Skip to content

Commit

Permalink
Added more robust extensibility to extend mutation errors. (#6595)
Browse files Browse the repository at this point in the history
* Added more robust extensibility to extend mutation errors

* Still fails but adds initial tests
  • Loading branch information
michaelstaib authored Oct 12, 2023
1 parent 183638f commit 77d1c05
Show file tree
Hide file tree
Showing 18 changed files with 173 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal sealed class FederationTypeInterceptor : TypeInterceptor
private readonly List<ObjectType> _entityTypes = new();
private IDescriptorContext _context = default!;
private ITypeInspector _typeInspector = default!;
private ObjectType _queryType = default!;

internal override void InitializeContext(
IDescriptorContext context,
Expand Down Expand Up @@ -84,6 +85,17 @@ public override void OnTypesInitialized()
}
}

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
ObjectTypeDefinition definition,
OperationType operationType)
{
if (operationType is OperationType.Query)
{
_queryType = (ObjectType) completionContext.Type;
}
}

public override void OnBeforeCompleteType(
ITypeCompletionContext completionContext,
DefinitionBase definition)
Expand Down Expand Up @@ -159,7 +171,7 @@ private void AddServiceTypeToQueryType(
ITypeCompletionContext completionContext,
DefinitionBase? definition)
{
if (completionContext.IsQueryType == true &&
if (ReferenceEquals(completionContext.Type, _queryType) &&
definition is ObjectTypeDefinition objectTypeDefinition)
{
var serviceFieldDescriptor = ObjectFieldDescriptor.New(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
using HotChocolate.Configuration;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors.Definitions;
using IHasDirectives = HotChocolate.Types.IHasDirectives;

namespace HotChocolate.Caching;

internal sealed class CacheControlValidationTypeInterceptor : TypeInterceptor
{
public override void OnValidateType(ITypeSystemObjectContext validationContext,
private ITypeCompletionContext _queryContext = default!;

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
ObjectTypeDefinition definition,
OperationType operationType)
{
if (operationType is OperationType.Query)
{
_queryContext = completionContext;
}
}

public override void OnValidateType(
ITypeSystemObjectContext validationContext,
DefinitionBase definition)
{
if (validationContext.IsIntrospectionType)
Expand All @@ -18,7 +34,7 @@ public override void OnValidateType(ITypeSystemObjectContext validationContext,
{
case ObjectType objectType:
{
var isQueryType = validationContext is ITypeCompletionContext { IsQueryType: true };
var isQueryType = ReferenceEquals(validationContext, _queryContext);

ValidateCacheControlOnType(validationContext, objectType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal sealed partial class AuthorizationTypeInterceptor : TypeInterceptor
private TypeRegistry _typeRegistry = default!;
private TypeLookup _typeLookup = default!;
private ExtensionData _schemaContextData = default!;
private ITypeCompletionContext _queryContext = default!;

internal override uint Position => uint.MaxValue;

Expand Down Expand Up @@ -102,13 +103,24 @@ public override void OnBeforeCompleteTypes()
FindFieldsAndApplyAuthMiddleware(state);
}

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
ObjectTypeDefinition definition,
OperationType operationType)
{
if (operationType is OperationType.Query)
{
_queryContext = completionContext;
}
}

public override void OnBeforeCompleteType(
ITypeCompletionContext completionContext,
DefinitionBase definition)
{
// last in the initialization we need to intercept the query type and ensure that
// authorization configuration is applied to the special introspection and node fields.
if ((completionContext.IsQueryType ?? false) &&
if (ReferenceEquals(_queryContext, completionContext) &&
definition is ObjectTypeDefinition typeDef)
{
var state = _state ?? throw ThrowHelper.StateNotInitialized();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,17 @@ public override void OnBeforeRegisterDependencies(
public override void OnAfterCompleteTypeNames()
=> _mutations = _context.ContextData.GetMutationFields();

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase? definition,
OperationType operationType)
internal override void OnBeforeCompleteMutation(
ITypeCompletionContext completionContext,
ObjectTypeDefinition definition)
{
// if the type initialization resolved the root type we will capture its definition
// so we can use it after the type extensions are merged into this type.
if (operationType is OperationType.Mutation)
{
_mutationTypeDef = (ObjectTypeDefinition)definition!;
}

// we need to capture a completion context to resolve types.
// any context will do.
_completionContext ??= completionContext;
}

public override void OnAfterMergeTypeExtensions()
{
_mutationTypeDef = definition;
base.OnBeforeCompleteMutation(completionContext, definition);
// if we have found a mutation type we will start applying the mutation conventions
// on the mutations.
if (_mutationTypeDef is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ public override void OnAfterCompleteName(

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase definition,
ObjectTypeDefinition definition,
OperationType operationType)
{
ref var first = ref GetReference();
Expand Down Expand Up @@ -307,6 +307,19 @@ public override void OnAfterMergeTypeExtensions()
}
}

internal override void OnBeforeCompleteMutation(
ITypeCompletionContext completionContext,
ObjectTypeDefinition definition)
{
ref var first = ref GetReference();
var length = _typeInterceptors.Length;

for (var i = 0; i < length; i++)
{
Unsafe.Add(ref first, i).OnBeforeCompleteMutation(completionContext, definition);
}
}

public override void OnBeforeCompleteTypes()
{
ref var first = ref GetReference();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,6 @@ namespace HotChocolate.Configuration;
/// </summary>
public interface ITypeCompletionContext : ITypeSystemObjectContext
{
/// <summary>
/// Defines if the type that is being completed is the query type.
/// </summary>
bool? IsQueryType { get; }

/// <summary>
/// Defines if the type that is being completed is the mutation type.
/// </summary>
bool? IsMutationType { get; }

/// <summary>
/// Defines if the type that is being completed is the subscription type.
/// </summary>
bool? IsSubscriptionType { get; }

/// <summary>
/// Global middleware components.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/HotChocolate/Core/src/Types/Configuration/TypeInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,15 @@ t.Type is INamedType n &&
}

_interceptor.OnAfterMergeTypeExtensions();

var mutationType = _rootTypes.FirstOrDefault(t => t.Kind == OperationType.Mutation);

if (mutationType.IsInitialized)
{
_interceptor.OnBeforeCompleteMutation(
mutationType.Type,
((ObjectType)mutationType.Type.Type).Definition!);
}
}

private void MergeTypeExtension(
Expand Down Expand Up @@ -658,12 +667,15 @@ public RegisteredRootType(
Context = context;
Type = type;
Kind = kind;
IsInitialized = true;
}

public ITypeCompletionContext Context { get; }

public RegisteredType Type { get; }

public OperationType Kind { get; }

public bool IsInitialized { get; }
}
}
31 changes: 25 additions & 6 deletions src/HotChocolate/Core/src/Types/Configuration/TypeInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@

namespace HotChocolate.Configuration;

// note: this type is considered internal and should not be used by users.
/// <summary>
/// A type initialization interceptors can hook into the various initialization events
/// of type system members and change / rewrite them. This is useful in order to transform
/// specified types.
/// </summary>
public abstract class TypeInterceptor
{
private const uint _position = uint.MaxValue / 2;
private const uint _defaultPosition = uint.MaxValue / 2;

/// <summary>
/// A weight to order interceptors.
/// </summary>
internal virtual uint Position => _position;
internal virtual uint Position => _defaultPosition;

public virtual bool IsEnabled(IDescriptorContext context) => true;
internal virtual bool IsEnabled(IDescriptorContext context) => true;

/// <summary>
/// This hook is invoked before anything else any allows for additional modification
Expand Down Expand Up @@ -169,9 +170,10 @@ public virtual void OnAfterCompleteName(

internal virtual void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase definition,
OperationType operationType) { }

ObjectTypeDefinition definition,
OperationType operationType)
{ }

public virtual void OnTypesCompletedName() { }

/// <summary>
Expand All @@ -183,6 +185,23 @@ public virtual void OnBeforeMergeTypeExtensions() { }
/// This method is called after the type extensions are merged.
/// </summary>
public virtual void OnAfterMergeTypeExtensions() { }

internal virtual void OnBeforeCompleteMutation(
ITypeCompletionContext completionContext,
ObjectTypeDefinition definition)
{
foreach (var field in definition.Fields)
{
OnBeforeCompleteMutationField(completionContext, field);
}
}

public virtual void OnBeforeCompleteMutationField(
ITypeCompletionContext completionContext,
ObjectFieldDefinition mutationField)
{
}


/// <summary>
/// This method is called before the types are completed.
Expand Down
1 change: 1 addition & 0 deletions src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<InternalsVisibleTo Include="HotChocolate.Types.Filters" />
<InternalsVisibleTo Include="HotChocolate.Types.Sorting" />
<InternalsVisibleTo Include="HotChocolate.Types.Selections" />
<InternalsVisibleTo Include="HotChocolate.Stitching" />

<!--Tests-->
<InternalsVisibleTo Include="HotChocolate.Types.Filters.Tests" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void PrintOther()

internal sealed class EnableTrueNullabilityTypeInterceptor : TypeInterceptor
{
public override bool IsEnabled(IDescriptorContext context)
internal override bool IsEnabled(IDescriptorContext context)
=> context.Options.EnableTrueNullability;

public override void OnBeforeCreateSchema(IDescriptorContext context, ISchemaBuilder schemaBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public override void OnAfterCompleteName(

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase definition,
ObjectTypeDefinition definition,
OperationType operationType)
{
if (operationType is OperationType.Query && definition is ObjectTypeDefinition typeDef)
if (operationType is OperationType.Query)
{
_queryTypeDefinition = typeDef;
_queryTypeDefinition = definition;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected virtual FieldCollection<ObjectField> OnCompleteFields(
ITypeCompletionContext context,
ObjectTypeDefinition definition)
{
if (context.IsMutationType ?? false)
if (((RegisteredType)context).IsMutationType ?? false)
{
// if this type represents the mutation type we flag all fields as serially executable
// so that the operation compiler and execution engine will uphold the spec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ internal sealed class NodeFieldTypeInterceptor : TypeInterceptor

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase definition,
ObjectTypeDefinition definition,
OperationType operationType)
{
if (operationType is OperationType.Query && definition is ObjectTypeDefinition typeDef)
if (operationType is OperationType.Query)
{
_queryContext = completionContext;
_queryTypeDefinition = typeDef;
_queryTypeDefinition = definition;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,15 @@ TypeDef is not null &&

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase definition,
ObjectTypeDefinition definition,
OperationType operationType)
{
// we are only interested in the query type to infer node resolvers.
if (operationType is OperationType.Query &&
definition is ObjectTypeDefinition typeDef &&
completionContext.Type is ObjectType queryType)
{
CompletionContext = completionContext;
TypeDef = typeDef;
TypeDef = definition;
QueryType = queryType;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal sealed class QueryFieldTypeInterceptor : TypeInterceptor

internal override void OnAfterResolveRootType(
ITypeCompletionContext completionContext,
DefinitionBase definition,
ObjectTypeDefinition definition,
OperationType operationType)
{
_context ??= completionContext;
Expand Down
Loading

0 comments on commit 77d1c05

Please sign in to comment.