Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope the projection selection properly when using the mutation conventions. #6444

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ public static class WellKnownContextData
/// </summary>
public const string MutationQueryField = "HotChocolate.Relay.Mutations.QueryField";

/// <summary>
/// The key to the name of the data field when using the mutation convention.
/// </summary>
public const string MutationConventionDataField = "HotChocolate.Types.Mutations.Conventions.DataField";

/// <summary>
/// The key to get the Cache-Control header value from the context data.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ public SelectionVariants(int id)
public IEnumerable<IObjectType> GetPossibleTypes()
=> _map?.Keys ?? GetPossibleTypesLazy();

public bool IsPossibleType(IObjectType typeContext)
{
if(_map is not null)
{
return _map.ContainsKey(typeContext);
}

if (ReferenceEquals(_firstType, typeContext))
{
return true;
}

if (ReferenceEquals(_secondType, typeContext))
{
return true;
}

return false;
}

private IEnumerable<IObjectType> GetPossibleTypesLazy()
{
yield return _firstType!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ private static ObjectType CreatePayloadType(

return parent;
});
objectDef.ContextData.Add(MutationConventionDataField, dataFieldDef.Name);
objectDef.Fields.Add(dataFieldDef);

// if the mutation has domain errors we will add the errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,43 @@

namespace HotChocolate.Execution.Processing;

/// <summary>
/// Represents all the selection set variants of a field.
/// </summary>
public interface ISelectionVariants
{
/// <summary>
/// Gets the operation unique id for this variant.
/// </summary>
int Id { get; }

/// <summary>
/// Gets all the possible return types of the field to which this variant belongs to.
/// </summary>
IEnumerable<IObjectType> GetPossibleTypes();

/// <summary>
/// Evaluates if the specified type context is a possible type for this variant.
/// </summary>
/// <param name="typeContext">
/// The type context to evaluate.
/// </param>
/// <returns>
/// Returns <c>true</c> if the specified type context is a possible type for this variant;
/// </returns>
bool IsPossibleType(IObjectType typeContext);

/// <summary>
/// Gets the selection set for the specified field return type.
/// </summary>
/// <param name="typeContext">
/// The field return type.
/// </param>
/// <returns>
/// Returns the selection set for the specified field return type.
/// </returns>
/// <exception cref="System.ArgumentException">
/// Invalid field return type.
/// </exception>
ISelectionSet GetSelectionSet(IObjectType typeContext);
}
10 changes: 8 additions & 2 deletions src/HotChocolate/Data/src/Data/DataResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/HotChocolate/Data/src/Data/DataResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
<data name="FilterConvention_ProviderHasToBeInitializedByConvention" xml:space="preserve">
<value>The filter provider {0} {1}was not initialized by a convention. It is only valid to register a provider over a FilterConvention</value>
</data>
<data name="UseProjection_CannotHandleType_" xml:space="preserve">
<data name="UseProjection_CannotHandleType" xml:space="preserve">
<value>Cannot handle the specified type.</value>
</data>
<data name="PagingProjectionOptimizer_NotAPagingField" xml:space="preserve">
Expand Down Expand Up @@ -310,4 +310,7 @@
<data name="NameHelpers_UppercaseFirstLetter" xml:space="preserve">
<value>Provided string was empty.</value>
</data>
<data name="ProjectionObjectFieldDescriptorExtensions_UnwrapMutationPayloadSelect_Failed" xml:space="preserve">
<value>There is no selection that matches the specified field.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using System.Security.AccessControl;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Configuration;
Expand All @@ -13,6 +13,7 @@
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types.Descriptors.Definitions;
using static HotChocolate.Data.DataResources;
using static HotChocolate.Data.Projections.ProjectionProvider;
using static HotChocolate.Execution.Processing.OperationCompilerOptimizerHelper;
using static HotChocolate.WellKnownContextData;
Expand Down Expand Up @@ -48,8 +49,7 @@ public static IObjectFieldDescriptor IsProjected(
{
descriptor
.Extend()
.OnBeforeCreate(
x => x.ContextData[ProjectionConvention.IsProjectedKey] = isProjected);
.OnBeforeCreate(x => x.ContextData[ProjectionConvention.IsProjectedKey] = isProjected);

return descriptor;
}
Expand Down Expand Up @@ -153,7 +153,7 @@ public static IObjectFieldDescriptor UseProjection(
out var typeInfo))
{
throw new ArgumentException(
DataResources.UseProjection_CannotHandleType_,
UseProjection_CannotHandleType,
nameof(descriptor));
}

Expand Down Expand Up @@ -191,7 +191,7 @@ private static void CompileMiddleware(

private static FieldMiddleware CreateMiddleware<TEntity>(IProjectionConvention convention)
{
FieldMiddleware executor = convention.CreateExecutor<TEntity>();
var executor = convention.CreateExecutor<TEntity>();
return next => context =>
{
// in case we are being called from the node/nodes field we need to enrich
Expand All @@ -204,11 +204,40 @@ value is ObjectType objectType &&
var selection = CreateProxySelection(context.Selection, fieldProxy);
context = new MiddlewareContextProxy(context, selection, objectType);
}

//for use case when projection is used with Mutation Conventions
else if (context.Operation.Type is OperationType.Mutation &&
context.Selection.Type.NamedType() is ObjectType mutationPayloadType &&
mutationPayloadType.ContextData.GetValueOrDefault(MutationConventionDataField, null)
is string dataFieldName)
{
var dataField = mutationPayloadType.Fields[dataFieldName];
var selection = UnwrapMutationPayloadSelection(context, dataField);
context = new MiddlewareContextProxy(context, selection, dataField.DeclaringType);
}
return executor.Invoke(next).Invoke(context);
};
}

private static Selection UnwrapMutationPayloadSelection(IMiddlewareContext context, ObjectField field)
{
var selectionSet = Unsafe.As<SelectionSet>(context.Operation.RootSelectionSet);
ref var selection = ref selectionSet.GetSelectionsReference();
ref var end = ref Unsafe.Add(ref selection, selectionSet.Selections.Count);

while (Unsafe.IsAddressLessThan(ref selection, ref end))
{
if (ReferenceEquals(selection.Field, field))
{
return selection;
}

selection = ref Unsafe.Add(ref selection, 1)!;
}

throw new InvalidOperationException(
ProjectionObjectFieldDescriptorExtensions_UnwrapMutationPayloadSelect_Failed);
}

private sealed class MiddlewareContextProxy : IMiddlewareContext
{
private readonly IMiddlewareContext _context;
Expand Down
Loading