Skip to content

Commit

Permalink
Add support for Mutations to Fusion. (#5953)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Mar 14, 2023
1 parent c92759f commit 064fb82
Show file tree
Hide file tree
Showing 118 changed files with 2,975 additions and 3,363 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Json;
using CookieCrumble;
using HotChocolate.AspNetCore;
using HotChocolate.AspNetCore.Subscriptions;
Expand Down Expand Up @@ -174,9 +175,9 @@ public Task Send_Subscribe_ValidationError()
// assert
await foreach (var result in socketResult.ReadResultsAsync().WithCancellation(ct))
{
Assert.Null(result.Data);
Assert.NotNull(result.Errors);
Assert.Null(result.Extensions);
Assert.Equal(JsonValueKind.Undefined, result.Data.ValueKind);
Assert.Equal(JsonValueKind.Array, result.Errors.ValueKind);
Assert.Equal(JsonValueKind.Undefined, result.Extensions.ValueKind);
snapshot.Add(result.Errors);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Result:
---------------
{
"errors": [
{
"message": "Unexpected Execution Error",
"locations": [
{
"line": 2,
"column": 25
}
],
"path": [
"rootExecutable"
]
}
],
"data": {
"rootExecutable": null
}
}
---------------

SQL:
---------------
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[HotChocolate.Data.Projections.QueryableFirstOrDefaultTests+Bar]
---------------
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Result:
---------------
{
"errors": [
{
"message": "Unexpected Execution Error",
"locations": [
{
"line": 2,
"column": 25
}
],
"path": [
"rootExecutable"
]
}
],
"data": {
"rootExecutable": null
}
}
---------------

SQL:
---------------
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[HotChocolate.Data.Projections.QueryableFirstOrDefaultTests+Bar]
---------------
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal FusionGraphComposer(
.Use<PrepareFusionSchemaMiddleware>()
.Use<MergeEntityMiddleware>()
.Use(() => new MergeTypeMiddleware(mergeHandlers))
.Use<MergeQueryTypeMiddleware>()
.Use<MergeQueryAndMutationTypeMiddleware>()
.Use<MergeSubscriptionTypeMiddleware>()
.Use<RemoveFusionTypesMiddleware>()
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace HotChocolate.Fusion.Composition.Pipeline;

internal sealed class MergeQueryTypeMiddleware : IMergeMiddleware
internal sealed class MergeQueryAndMutationTypeMiddleware : IMergeMiddleware
{
public async ValueTask InvokeAsync(CompositionContext context, MergeDelegate next)
{
Expand All @@ -19,39 +19,20 @@ public async ValueTask InvokeAsync(CompositionContext context, MergeDelegate nex
context.FusionGraph.Types.Add(queryType);
}

foreach (var field in schema.QueryType.Fields)
MergeRootFields(context, schema, schema.QueryType, queryType);
}

if (schema.MutationType is not null)
{
var queryType = context.FusionGraph.MutationType!;

if (context.FusionGraph.MutationType is null)
{
if (queryType.Fields.TryGetField(field.Name, out var targetField))
{
context.MergeField(field, targetField, queryType.Name);
}
else
{
targetField = context.CreateField(field, context.FusionGraph);
queryType.Fields.Add(targetField);
}

var arguments = new List<ArgumentNode>();

var selection = new FieldNode(
null,
new NameNode(field.GetOriginalName()),
null,
null,
Array.Empty<DirectiveNode>(),
arguments,
null);

var selectionSet = new SelectionSetNode(new[] { selection });

foreach (var arg in field.Arguments)
{
arguments.Add(new ArgumentNode(arg.Name, new VariableNode(arg.Name)));
context.ApplyVariable(targetField, arg, schema.Name);
}

context.ApplyResolvers(targetField, selectionSet, schema.Name);
queryType = context.FusionGraph.MutationType = new ObjectType("Mutation");
context.FusionGraph.Types.Add(queryType);
}

MergeRootFields(context, schema, schema.MutationType, queryType);
}
}

Expand All @@ -60,6 +41,47 @@ public async ValueTask InvokeAsync(CompositionContext context, MergeDelegate nex
await next(context).ConfigureAwait(false);
}
}

private static void MergeRootFields(
CompositionContext context,
Schema sourceSchema,
ObjectType sourceRootType,
ObjectType targetRootType)
{
foreach (var field in sourceRootType.Fields)
{
if (targetRootType.Fields.TryGetField(field.Name, out var targetField))
{
context.MergeField(field, targetField, targetRootType.Name);
}
else
{
targetField = context.CreateField(field, context.FusionGraph);
targetRootType.Fields.Add(targetField);
}

var arguments = new List<ArgumentNode>();

var selection = new FieldNode(
null,
new NameNode(field.GetOriginalName()),
null,
null,
Array.Empty<DirectiveNode>(),
arguments,
null);

var selectionSet = new SelectionSetNode(new[] { selection });

foreach (var arg in field.Arguments)
{
arguments.Add(new ArgumentNode(arg.Name, new VariableNode(arg.Name)));
context.ApplyVariable(targetField, arg, sourceSchema.Name);
}

context.ApplyResolvers(targetField, selectionSet, sourceSchema.Name);
}
}
}

static file class MergeQueryTypeMiddlewareExtensions
Expand Down
6 changes: 6 additions & 0 deletions src/HotChocolate/Fusion/src/Core/FusionResources.Designer.cs

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

3 changes: 3 additions & 0 deletions src/HotChocolate/Fusion/src/Core/FusionResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@
<data name="FusionRequestExecutorBuilderExtensions_AddFusionGatewayServer_NoSchema" xml:space="preserve">
<value>A valid service configuration must always produce a schema document.</value>
</data>
<data name="ThrowHelper_Requirement_Is_Missing" xml:space="preserve">
<value>The variable value `{0}` was not provided but is required.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public FusionGraphConfiguration(
{
if (!binding.Name.EqualsOrdinal(type.Name))
{
_typeNameLookup.Add((binding.SchemaName, binding.Name), type.Name);
_typeNameLookup.Add((binding.SubgraphName, binding.Name), type.Name);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ private FusionGraphConfiguration ReadServiceDefinition(DocumentNode document)

var types = new List<IType>();
var typeNames = FusionTypeNames.From(document);
var typeNameBindings = new Dictionary<string, MemberBinding>();
var httpClientConfigs = ReadHttpClientConfigs(typeNames, schemaDef.Directives);
var webSocketClientConfigs = ReadWebSocketClientConfigs(typeNames, schemaDef.Directives);
var typeNameField = CreateTypeNameField(_subgraphNames);
var typeNameField = CreateTypeNameField(typeNameBindings);

foreach (var definition in document.Definitions)
{
Expand All @@ -52,6 +53,11 @@ private FusionGraphConfiguration ReadServiceDefinition(DocumentNode document)
}
}

foreach (var subgraphName in _subgraphNames)
{
typeNameBindings.Add(subgraphName, new MemberBinding(subgraphName, typeNameField.Name));
}

if (httpClientConfigs is not { Count: > 0 })
{
throw ServiceConfNoClientsSpecified();
Expand Down Expand Up @@ -102,13 +108,15 @@ private ObjectFieldCollection ReadObjectFields(
return new ObjectFieldCollection(collection);
}

private static ObjectField CreateTypeNameField(IEnumerable<string> subgraphNames)
=> new ObjectField(
IntrospectionFields.TypeName,
new MemberBindingCollection(
subgraphNames.Select(t => new MemberBinding(t, IntrospectionFields.TypeName))),
FieldVariableDefinitionCollection.Empty,
ResolverDefinitionCollection.Empty);
private static ObjectField CreateTypeNameField(
Dictionary<string, MemberBinding> bindings)
{
return new ObjectField(
IntrospectionFields.TypeName,
new MemberBindingCollection(bindings),
FieldVariableDefinitionCollection.Empty,
ResolverDefinitionCollection.Empty);
}

private IReadOnlyList<HttpClientConfiguration> ReadHttpClientConfigs(
FusionTypeNames typeNames,
Expand Down Expand Up @@ -521,7 +529,7 @@ private MemberBindingCollection ReadMemberBindings(

foreach (var binding in definitions)
{
_assert.Add(binding.SchemaName);
_assert.Add(binding.SubgraphName);
}

foreach (var resolver in resolvers)
Expand Down
31 changes: 23 additions & 8 deletions src/HotChocolate/Fusion/src/Core/Metadata/MemberBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,40 @@ internal class MemberBinding
/// <summary>
/// Initializes a new instance of <see cref="MemberBinding"/>.
/// </summary>
/// <param name="schemaName">
/// The schema to which the type system member is bound to.
/// <param name="subgraphName">
/// The name of the subgraph to which the type system member is bound to.
/// </param>
/// <param name="name">
/// The name which the type system member has in the <see cref="SchemaName"/>.
/// The name which the type system member has in the <see cref="SubgraphName"/>.
/// </param>
public MemberBinding(string schemaName, string name)
public MemberBinding(string subgraphName, string name)
{
SchemaName = schemaName;
if (string.IsNullOrWhiteSpace(subgraphName))
{
throw new ArgumentException(
$"'{nameof(subgraphName)}' cannot be null or whitespace.",
nameof(subgraphName));
}

if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException(
$"'{nameof(name)}' cannot be null or whitespace.",
nameof(name));
}

SubgraphName = subgraphName;
Name = name;
}

/// <summary>
/// Gets the schema to which the type system member is bound to.
/// Gets the name of the subgraph to which the type system member is bound to.
/// </summary>
public string SchemaName { get; }
public string SubgraphName { get; }

/// <summary>
/// Gets the name which the type system member has in the <see cref="SchemaName"/>.
/// Gets the name which the type system member has on a certain the subgraph
/// represented by the <see cref="SubgraphName" />.
/// </summary>
public string Name { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,22 @@ internal sealed class MemberBindingCollection : IEnumerable<MemberBinding>

public MemberBindingCollection(IEnumerable<MemberBinding> bindings)
{
_bindings = bindings.ToDictionary(t => t.SchemaName, StringComparer.Ordinal);
if (bindings is null)
{
throw new ArgumentNullException(nameof(bindings));
}

_bindings = bindings.ToDictionary(t => t.SubgraphName, StringComparer.Ordinal);
}

public MemberBindingCollection(Dictionary<string, MemberBinding> bindings)
{
if (bindings is null)
{
throw new ArgumentNullException(nameof(bindings));
}

_bindings = bindings;
}

public int Count => _bindings.Count;
Expand Down
15 changes: 12 additions & 3 deletions src/HotChocolate/Fusion/src/Core/Planning/ExecutionPlanBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Diagnostics;
using System.Runtime.InteropServices.JavaScript;
using HotChocolate.Execution.Processing;
using HotChocolate.Fusion.Metadata;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types.Introspection;
using HotChocolate.Utilities;
using static HotChocolate.Fusion.Metadata.ResolverKind;

Expand Down Expand Up @@ -97,7 +97,9 @@ private QueryPlanNode BuildQueryTree(QueryPlanContext context)

while (current.Length > 0)
{
if (current.Length is 1)
if (current.Length is 1 ||
(_schema.MutationType is not null &&
_schema.MutationType.Name.EqualsOrdinal(current[0].Key.SelectionSetType.Name)))
{
var node = current[0];
var selectionSet = ResolveSelectionSet(context, node.Key);
Expand Down Expand Up @@ -382,7 +384,8 @@ private SelectionSetNode CreateSelectionSetNode(

foreach (var selection in selectionSet.Selections)
{
if (executionStep.AllSelections.Contains(selection))
if (executionStep.AllSelections.Contains(selection) ||
selection.Field.Name.EqualsOrdinal(IntrospectionFields.TypeName))
{
var field = typeContext.Fields[selection.Field.Name];
var selectionNode = CreateSelectionNode(
Expand All @@ -400,6 +403,12 @@ private SelectionSetNode CreateSelectionSetNode(
{
selectionNodes.Add(selection);
}

if(selectionNodes.Count == 0)
{
// TODO : ThrowHelper
throw new InvalidOperationException("A selection set must not be empty.");
}
}

return new SelectionSetNode(selectionNodes);
Expand Down
Loading

0 comments on commit 064fb82

Please sign in to comment.