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;
}
}
}