src/Epam.GraphQL/Configuration/Implementations/Fields/FieldBase.cs (167 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.Diagnostics; using System.Linq; using Epam.GraphQL.Configuration.Implementations.Descriptors; using Epam.GraphQL.Diagnostics; using Epam.GraphQL.Loaders; using Epam.GraphQL.TaskBatcher; using GraphQL; using GraphQL.DataLoader; using GraphQL.Resolvers; using GraphQL.Types; namespace Epam.GraphQL.Configuration.Implementations.Fields { [DebuggerDisplay("{DebuggerDisplay,nq}")] internal class FieldBase<TEntity, TExecutionContext> : IField<TEntity, TExecutionContext>, IArgumentCollection { protected FieldBase(IChainConfigurationContext configurationContext, BaseObjectGraphTypeConfigurator<TEntity, TExecutionContext> parent, string name) : this(parent) { ConfigurationContext = configurationContext; Name = name.ToCamelCase(); } protected FieldBase( Func<IChainConfigurationContextOwner, IChainConfigurationContext> configurationContextFactory, BaseObjectGraphTypeConfigurator<TEntity, TExecutionContext> parent, Func<IChainConfigurationContext, string> nameFactory) : this(parent) { ConfigurationContext = configurationContextFactory(this); Name = nameFactory(ConfigurationContext); } protected FieldBase( Func<IChainConfigurationContextOwner, IChainConfigurationContext> configurationContextFactory, BaseObjectGraphTypeConfigurator<TEntity, TExecutionContext> parent, string name) : this(parent, name) { ConfigurationContext = configurationContextFactory(this); } private FieldBase(BaseObjectGraphTypeConfigurator<TEntity, TExecutionContext> parent) { Parent = parent; } private FieldBase(BaseObjectGraphTypeConfigurator<TEntity, TExecutionContext> parent, string name) : this(parent) { Name = name.ToCamelCase(); } public LazyQueryArguments? Arguments { get; set; } public IChainConfigurationContext ConfigurationContext { get; private set; } = null!; IChainConfigurationContext IChainConfigurationContextOwner.ConfigurationContext { get => ConfigurationContext; set => ConfigurationContext = value; } public virtual IGraphTypeDescriptor<TExecutionContext> GraphType => GraphTypeDescriptor<TExecutionContext>.NullInstance; public string Name { get; set; } = null!; public string? DeprecationReason { get; set; } public virtual IFieldResolver Resolver => throw new InvalidOperationException($"Field `{Name}` must have resolver."); public virtual Type FieldType => throw new NotImplementedException(); public IFieldEditSettings<TEntity, TExecutionContext>? EditSettings { get; protected set; } IObjectGraphTypeConfigurator<TExecutionContext> IField<TExecutionContext>.Parent => Parent; internal IRegistry<TExecutionContext> Registry => Parent.Registry; internal BaseObjectGraphTypeConfigurator<TEntity, TExecutionContext> Parent { get; } [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string DebuggerDisplay => $"{GetType().GetGenericTypeDefinition().Name}({FieldType.Name} {typeof(TEntity).Name}.{Name})"; public override string ToString() { return ConfigurationContext.ToString(); } public virtual FieldType AsFieldType() { var fieldType = new FieldType { Type = GraphType.Type, Arguments = Arguments?.ToQueryArguments(), Resolver = Resolver, Name = Name, ResolvedType = GraphType.GraphType, DeprecationReason = DeprecationReason, }; fieldType.Metadata.Add("CONFIGURATION_CONTEXT", ConfigurationContext); return fieldType; } public void Argument(string name, Type type, string? description = null) { Argument(name, () => new QueryArgument(Registry.GenerateInputGraphType(type)) { Name = name, Description = description, }); } public void Argument(string name, IGraphType graphType, string? description = null) { Argument(name, () => new QueryArgument(graphType) { Name = name, Description = description, }); } public void Argument(string name, Func<QueryArgument> factory) { if (Arguments == null) { Arguments = new LazyQueryArguments(); } var index = Arguments.TakeWhile(a => a.Name != name).Count(); if (index == Arguments.Count) { Arguments.Add(new LazyQueryArgument(name, factory)); } else { Arguments[index].Factory = factory; } } public IDataLoader<IFieldChange<TEntity, TExecutionContext>, (bool CanEdit, string DisableReason)> CanEdit(IResolveFieldContext context) { if (EditSettings != null) { if (EditSettings.IsReadOnly) { return BatchLoader.FromResult<IFieldChange<TEntity, TExecutionContext>, (bool CanEdit, string DisableReason)>(change => (false, "The field is read only.")); } if (EditSettings.CanEdit != null) { return EditSettings.CanEdit(context); } } return BatchLoader.FromResult<IFieldChange<TEntity, TExecutionContext>, (bool CanEdit, string DisableReason)>(change => (false, "The field is not editable. Consider to use `Editable()` or `EditableIf(...)` methods.")); } public virtual void Validate() { ConfigurationContext.AddErrorIf(string.IsNullOrEmpty(Name), "Field name cannot be null or empty.", ConfigurationContext); // Force resolver; FieldBase throws exception if Resolver field is not overriden. try { _ = Resolver; } catch (InvalidOperationException e) { ConfigurationContext.AddError(e.Message, ConfigurationContext); } GraphType.Validate(ConfigurationContext); } public TField ApplyField<TField>(TField field) where TField : FieldBase<TEntity, TExecutionContext> { return Parent.ReplaceField(this, field); } public override int GetHashCode() { var hash = default(HashCode); hash.Add(Name); hash.Add(typeof(TEntity)); hash.Add(typeof(TExecutionContext)); return hash.ToHashCode(); } public override bool Equals(object other) { if (other == null) { return false; } if (other is FieldBase<TEntity, TExecutionContext> field) { return field.Name == Name; // TODO check for actual field type equality } return false; } } }