src/Epam.GraphQL/Configuration/Implementations/Fields/ChildFields/RootQueryableFieldBase.cs (229 lines of code) (raw):

// Copyright © 2020 EPAM Systems, Inc. All Rights Reserved. All information contained herein is, and remains the // property of EPAM Systems, Inc. and/or its suppliers and is protected by international intellectual // property law. Dissemination of this information or reproduction of this material is strictly forbidden, // unless prior written permission is obtained from EPAM Systems, Inc using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Epam.GraphQL.Configuration.Implementations.FieldResolvers; using Epam.GraphQL.Diagnostics; using Epam.GraphQL.Extensions; using Epam.GraphQL.Filters; using Epam.GraphQL.Helpers; using Epam.GraphQL.Loaders; using Epam.GraphQL.Search; using Epam.GraphQL.Sorters; using Epam.GraphQL.Sorters.Implementations; using Epam.GraphQL.Types; using GraphQL; using GraphQL.Types; namespace Epam.GraphQL.Configuration.Implementations.Fields.ChildFields { internal abstract class RootQueryableFieldBase<TThis, TThisIntf, TReturnType, TExecutionContext> : RootEnumerableFieldBase<TThis, TReturnType, TExecutionContext> where TThis : RootQueryableFieldBase<TThis, TThisIntf, TReturnType, TExecutionContext>, TThisIntf { protected RootQueryableFieldBase( IChainConfigurationContext configurationContext, BaseObjectGraphTypeConfigurator<object, TExecutionContext> parent, string name, Func<IResolveFieldContext, IQueryable<TReturnType>> query, Func<IResolveFieldContext, IQueryable<TReturnType>, IQueryable<TReturnType>> transform, IGraphTypeDescriptor<TReturnType, TExecutionContext> elementGraphType, IObjectGraphTypeConfigurator<TReturnType, TExecutionContext>? configurator, LazyQueryArguments? arguments, ISearcher<TReturnType, TExecutionContext>? searcher, IEnumerable<(LambdaExpression SortExpression, SortDirection SortDirection)> naturalSorters) : this( configurationContext, parent, name, CreateResolver( query, transform, searcher, naturalSorters, configurator), elementGraphType, configurator, arguments, searcher, naturalSorters) { } protected RootQueryableFieldBase( IChainConfigurationContext configurationContext, BaseObjectGraphTypeConfigurator<object, TExecutionContext> parent, string name, IRootQueryableResolver<TReturnType, TExecutionContext> resolver, IGraphTypeDescriptor<TReturnType, TExecutionContext> elementGraphType, IObjectGraphTypeConfigurator<TReturnType, TExecutionContext>? configurator, LazyQueryArguments? arguments, ISearcher<TReturnType, TExecutionContext>? searcher, IEnumerable<(LambdaExpression SortExpression, SortDirection SortDirection)> naturalSorters) : base( configurationContext, parent, name, resolver, elementGraphType) { Arguments = arguments; ObjectGraphTypeConfigurator = configurator; NaturalSorters = naturalSorters; if (HasFilter) { Argument("filter", CreateFilterArgument); } var sortableFields = ObjectGraphTypeConfigurator?.Sorters.Select(f => f.Name).ToArray(); if (sortableFields != null && sortableFields.Any()) { Argument("sorting", new ListGraphType(new SortingOptionGraphType(ObjectGraphTypeConfigurator?.Name ?? typeof(TReturnType).GraphQLTypeName(false), sortableFields))); } Searcher = searcher; if (searcher != null) { Argument("search", typeof(string)); } QueryArgument CreateFilterArgument() { Guards.ThrowNotSupportedIf(ObjectGraphTypeConfigurator == null); return new QueryArgument(Registry.GenerateInputGraphType(ObjectGraphTypeConfigurator.CreateInlineFilters().FilterType)) { Name = "filter", }; } } public virtual bool HasFilter => ObjectGraphTypeConfigurator?.HasInlineFilters ?? false; protected IEnumerable<(LambdaExpression SortExpression, SortDirection SortDirection)> NaturalSorters { get; } protected ISearcher<TReturnType, TExecutionContext>? Searcher { get; private set; } protected IObjectGraphTypeConfigurator<TReturnType, TExecutionContext>? ObjectGraphTypeConfigurator { get; private set; } protected virtual IRootQueryableResolver<TReturnType, TExecutionContext> QueryableFieldResolver => QueryableFieldResolverBase.Reorder(ApplySort(ObjectGraphTypeConfigurator?.Sorters, Searcher, NaturalSorters)); protected IRootQueryableResolver<TReturnType, TExecutionContext> QueryableFieldResolverBase => (IRootQueryableResolver<TReturnType, TExecutionContext>)EnumerableFieldResolver; public void Argument<TArgumentType>(string name, string? description = null) => Argument(name, typeof(TArgumentType), description); public virtual TThisIntf WithFilter<TLoaderFilter, TFilter>() where TLoaderFilter : Filter<TReturnType, TFilter, TExecutionContext> where TFilter : Input { ConfigurationContext.AddErrorIf(HasFilter, "Cannot apply filter twice."); Registry.RegisterInputAutoObjectGraphType<TFilter>(ConfigurationContext.New()); var loaderFilterType = typeof(TLoaderFilter); var filter = Registry.ResolveFilter<TReturnType>(loaderFilterType); Argument("filter", filter.FilterType); var field = ReplaceResolver( ConfigurationContext.Chain<TLoaderFilter, TFilter>(nameof(WithFilter)), QueryableFieldResolverBase.Select(GetFilteredQuery(filter))); return ApplyField(field); } public TThisIntf WithSearch<TSearcher>() where TSearcher : ISearcher<TReturnType, TExecutionContext> { ConfigurationContext.AddErrorIf(Searcher != null, "Cannot apply search twice."); Searcher = Registry.ResolveSearcher<TSearcher, TReturnType>(); Argument("search", typeof(string)); var field = ReplaceResolver( ConfigurationContext.Chain<TSearcher>(nameof(WithSearch)), QueryableFieldResolverBase.Select(GetSearchQuery(Searcher))); return ApplyField(field); } public new TThisIntf Where(Expression<Func<TReturnType, bool>> predicate) { var enumerableField = CreateWhere( ConfigurationContext.Chain(nameof(Where)).Argument(predicate), predicate); return ApplyField(enumerableField); } protected abstract TThis ReplaceResolver(IChainConfigurationContext configurationContext, IRootQueryableResolver<TReturnType, TExecutionContext> resolver); protected override TThis CreateWhere(IChainConfigurationContext configurationContext, Expression<Func<TReturnType, bool>> predicate) { var queryableField = ReplaceResolver( configurationContext, QueryableFieldResolverBase.Where(predicate)); return queryableField; } protected override RootEnumerableFieldBase<TReturnType1, TExecutionContext> CreateSelect<TReturnType1>( IChainConfigurationContext configurationContext, Expression<Func<TReturnType, TReturnType1>> selector, IGraphTypeDescriptor<TReturnType1, TExecutionContext> graphType) { var queryableField = new RootQueryableField<TReturnType1, TExecutionContext>( configurationContext, Parent, Name, QueryableFieldResolver.Select(selector, graphType.Configurator?.ProxyAccessor), graphType, graphType.Configurator, Arguments, searcher: null, naturalSorters: SortingHelpers.Empty); return queryableField; } private static IRootQueryableResolver<TReturnType, TExecutionContext> CreateResolver( Func<IResolveFieldContext, IQueryable<TReturnType>> query, Func<IResolveFieldContext, IQueryable<TReturnType>, IQueryable<TReturnType>> transform, ISearcher<TReturnType, TExecutionContext>? searcher, IEnumerable<(LambdaExpression SortExpression, SortDirection SortDirection)> naturalSorters, IObjectGraphTypeConfigurator<TReturnType, TExecutionContext>? configurator) { var sorters = configurator?.Sorters; if (configurator != null) { return new QueryableFuncResolver<TReturnType, Proxy<TReturnType>, TExecutionContext>( configurator.ProxyAccessor, GetQuery(configurator, query), transform, ApplySort(sorters, searcher, naturalSorters)); } return new QueryableFuncResolver<TReturnType, TReturnType, TExecutionContext>( IdentityProxyAccessor<TReturnType, TExecutionContext>.Instance, GetQuery(null, query), transform, ApplySort(sorters, searcher, naturalSorters)); static Func<IResolveFieldContext, IQueryable<TReturnType>> GetQuery( IObjectGraphTypeConfigurator<TReturnType, TExecutionContext>? configurator, Func<IResolveFieldContext, IQueryable<TReturnType>> queryFactory) { return context => { var filter = configurator != null && configurator.HasInlineFilters ? configurator.CreateInlineFilters() : null; var query = queryFactory(context); if (filter != null) { var listener = context.GetListener(); var ctx = context.GetUserContext<TExecutionContext>(); query = filter.All(listener, query, ctx, context.GetFilterValue(filter.FilterType)); } return query; }; } } private static Func<IResolveFieldContext, IQueryable<TReturnType>, IQueryable<TReturnType>> GetFilteredQuery(IFilter<TReturnType, TExecutionContext> filter) { return (context, query) => { var listener = context.GetListener(); return filter.All(listener, query, context.GetUserContext<TExecutionContext>(), context.GetFilterValue(filter.FilterType)); }; } private static Func<IResolveFieldContext, IQueryable<TReturnType>, IQueryable<TReturnType>> GetSearchQuery(ISearcher<TReturnType, TExecutionContext> searcher) { return (context, query) => { var result = query; if (!string.IsNullOrEmpty(context.GetSearch())) { result = searcher.All(result, context.GetUserContext<TExecutionContext>(), context.GetSearch()); } return result; }; } private static Func<IResolveFieldContext, IEnumerable<(LambdaExpression SortExpression, SortDirection SortDirection)>> ApplySort( IReadOnlyList<ISorter<TExecutionContext>>? sorters, ISearcher<TReturnType, TExecutionContext>? searcher, IEnumerable<(LambdaExpression SortExpression, SortDirection SortDirection)> naturalSorters) { return context => SortingHelpers.GetSort( context, sorters, searcher as IOrderedSearcher<TReturnType, TExecutionContext>, naturalSorters); } } }