public static Expression <Func <T, T> > Project <T>(this QueryableProjectionContext context)
        {
            if (context.TryGetQueryableScope(out QueryableProjectionScope? scope))
            {
                return(scope.Project <T>());
            }

            throw ProjectionConvention_CouldNotProject();
        }
        public static QueryableProjectionScope AddScope(
            this QueryableProjectionContext context,
            Type runtimeType)
        {
            var parameterName = "p" + context.Scopes.Count;
            var closure       =
                new QueryableProjectionScope(runtimeType, parameterName);

            context.Scopes.Push(closure);
            return(closure);
        }
        public static bool TryGetQueryableScope(
            this QueryableProjectionContext ctx,
            [NotNullWhen(true)] out QueryableProjectionScope?scope)
        {
            if (ctx.Scopes.Count > 0 &&
                ctx.Scopes.Peek() is QueryableProjectionScope queryableScope)
            {
                scope = queryableScope;
                return(true);
            }

            scope = null;
            return(false);
        }
        private static ApplyProjection CreateApplicatorAsync <TEntityType>()
        {
            return((context, input) =>
            {
                if (input is null)
                {
                    return input;
                }

                // if projections are already applied we can skip
                var skipProjection =
                    context.LocalContextData.TryGetValue(SkipProjectionKey, out object?skip) &&
                    skip is true;

                // ensure sorting is only applied once
                context.LocalContextData =
                    context.LocalContextData.SetItem(SkipProjectionKey, true);

                if (skipProjection)
                {
                    return input;
                }

                var visitorContext =
                    new QueryableProjectionContext(
                        context,
                        context.ObjectType,
                        context.Field.Type.UnwrapRuntimeType());
                var visitor = new QueryableProjectionVisitor();
                visitor.Visit(visitorContext);

                Expression <Func <TEntityType, TEntityType> > projection =
                    visitorContext.Project <TEntityType>();

                input = input switch
                {
                    IQueryable <TEntityType> q => q.Select(projection),
                    IEnumerable <TEntityType> e => e.AsQueryable().Select(projection),
                    QueryableExecutable <TEntityType> ex =>
                    ex.WithSource(ex.Source.Select(projection)),
                    _ => input
                };

                return input;
            });
        }
        public override FieldMiddleware CreateExecutor <TEntityType>()
        {
            return(next => context => ExecuteAsync(next, context));

            async ValueTask ExecuteAsync(
                FieldDelegate next,
                IMiddlewareContext context)
            {
                // first we let the pipeline run and produce a result.
                await next(context).ConfigureAwait(false);


                IQueryable <TEntityType>?source = null;

                if (context.Result is IQueryable <TEntityType> q)
                {
                    source = q;
                }
                else if (context.Result is IEnumerable <TEntityType> e)
                {
                    source = e.AsQueryable();
                }

                if (source is not null)
                {
                    var visitorContext =
                        new QueryableProjectionContext(
                            context,
                            context.ObjectType,
                            context.Field.Type.UnwrapRuntimeType());
                    var visitor = new QueryableProjectionVisitor();
                    visitor.Visit(visitorContext);
                    context.Result = source.Select(visitorContext.Project <TEntityType>());
                }
            }
        }
        public override FieldMiddleware CreateExecutor <TEntityType>()
        {
            return(next => context => ExecuteAsync(next, context));

            async ValueTask ExecuteAsync(
                FieldDelegate next,
                IMiddlewareContext context)
            {
                // first we let the pipeline run and produce a result.
                await next(context).ConfigureAwait(false);

                if (context.Result is not null)
                {
                    var visitorContext =
                        new QueryableProjectionContext(
                            context,
                            context.ObjectType,
                            context.Field.Type.UnwrapRuntimeType());
                    var visitor = new QueryableProjectionVisitor();
                    visitor.Visit(visitorContext);

                    Expression <Func <TEntityType, TEntityType> > projection =
                        visitorContext.Project <TEntityType>();

                    context.Result = context.Result switch
                    {
                        IQueryable <TEntityType> q => q.Select(projection),
                        IEnumerable <TEntityType> e => e.AsQueryable().Select(projection),
                        QueryableExecutable <TEntityType> ex =>
                        ex.WithSource(ex.Source.Select(projection)),
                        _ => context.Result
                    };
                }
            }
        }
    }