Skip to content

Commit

Permalink
Introduced Require Directive (#6281)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Jun 21, 2023
1 parent 5f47bb8 commit 563600b
Show file tree
Hide file tree
Showing 15 changed files with 128 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace HotChocolate.Fusion.Composition;
internal static class DirectivesHelper
{
public const string IsDirectiveName = "is";
public const string RequireDirectiveName = "require";
public const string RemoveDirectiveName = "remove";
public const string RenameDirectiveName = "rename";
public const string CoordinateArg = "coordinate";
Expand Down Expand Up @@ -38,4 +39,21 @@ public static IsDirective GetIsDirective(this IHasDirectives member)
throw new InvalidOperationException(
DirectivesHelper_GetIsDirective_NoFieldAndNoCoordinate);
}

public static bool ContainsRequireDirective(this IHasDirectives member)
=> member.Directives.ContainsName(RequireDirectiveName);

public static RequireDirective GetRequireDirective(this IHasDirectives member)
{
var directive = member.Directives[RequireDirectiveName].First();
var arg = directive.Arguments.FirstOrDefault(t => t.Name.EqualsOrdinal(FieldArg));

if (arg is { Value: StringValueNode field })
{
return new RequireDirective(Utf8GraphQLParser.Syntax.ParseField(field.Value));
}

throw new InvalidOperationException(
DirectivesHelper_GetRequireDirective_NoFieldArg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using HotChocolate.Language;

namespace HotChocolate.Fusion.Composition;

internal sealed class RequireDirective
{
public RequireDirective(FieldNode field)
{
Field = field;
}

public FieldNode Field { get; }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using HotChocolate.Skimmed;

namespace HotChocolate.Fusion.Composition;

/// <summary>
/// Describes the dependency a field has to another subgraph.
/// </summary>
internal sealed class FieldDependency
{
public FieldDependency(int id, string subgraphName)
Expand All @@ -10,15 +11,19 @@ public FieldDependency(int id, string subgraphName)
SubgraphName = subgraphName;
}

/// <summary>
/// Gets the internal ID of this dependency,
/// </summary>
public int Id { get; }

/// <summary>
/// Gets the name of the subgraph in which it depends on other subgraph data.
/// There might be multiple resolver overloads that have different dependencies.
/// </summary>
public string SubgraphName { get; }

/// <summary>
/// The arguments that represent dependencies.
/// </summary>
public Dictionary<string, MemberReference> Arguments { get; } = new();
}

internal sealed record MemberReference(IsDirective Reference, InputField Argument)
{
public bool IsRequired => Argument.Type.Kind is TypeKind.NonNull;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using HotChocolate.Language;
using HotChocolate.Skimmed;

namespace HotChocolate.Fusion.Composition;

internal sealed record MemberReference(InputField Argument, FieldNode Requirement);
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public static string CreateVariableName(
SchemaCoordinate coordinate)
=> $"{type.Name}_{coordinate.MemberName}";

private static string CreateVariableName(
ObjectType type,
public static string CreateVariableName(
this ObjectType type,
FieldNode field)
{
var context = new FieldVariableNameContext();
Expand Down
10 changes: 0 additions & 10 deletions src/HotChocolate/Fusion/src/Composition/LogEntryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,6 @@ public static LogEntry OutputFieldArgumentSetMismatch(
coordinate: coordinate,
member: field);

public static LogEntry CoordinateNotAllowedForRequirements(
SchemaCoordinate coordinate,
Schema schema)
=> new LogEntry(
LogEntryHelper_CoordinateNotAllowedForRequirements,
severity: LogSeverity.Error,
code: LogEntryCodes.CoordinateNotAllowedForRequirements,
coordinate: coordinate,
schema: schema);

public static LogEntry FieldDependencyCannotBeResolved(
SchemaCoordinate coordinate,
FieldNode dependency,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ public ValueTask EnrichAsync(

foreach (var argument in field.Arguments)
{
if (argument.ContainsIsDirective())
if (argument.ContainsRequireDirective())
{
var memberRef = new MemberReference(argument.GetIsDirective(), argument);
var memberRef = new MemberReference(
argument,
argument.GetRequireDirective().Field);
dependency ??= new FieldDependency(++nextId, schema.Name);
dependency.Arguments.Add(argument.Name, memberRef);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,10 @@ private static void ResolveDependencies(
supportedBy.Add(subgraph.Name);
}

if (memberRef.Reference.IsCoordinate)
{
context.Log.Write(
CoordinateNotAllowedForRequirements(
new SchemaCoordinate(
entityType.Name,
entityField.Name,
argumentName),
context.GetSubgraphSchema(dependency.SubgraphName)));
continue;
}

if (!CanResolve(
context,
entityType,
memberRef.Reference.Field,
memberRef.Requirement,
supportedBy))
{
context.Log.Write(
Expand All @@ -123,12 +111,12 @@ private static void ResolveDependencies(
entityType.Name,
entityField.Name,
argumentName),
memberRef.Reference.Field,
memberRef.Requirement,
context.GetSubgraphSchema(dependency.SubgraphName)));
continue;
}

var argumentRef = entityType.CreateVariableName(memberRef.Reference);
var argumentRef = entityType.CreateVariableName(memberRef.Requirement);
argumentRefLookup.Add(argumentName, argumentRef);
arguments.Add(argumentRef, memberRef.Argument.Type.ToTypeNode());

Expand All @@ -138,7 +126,7 @@ private static void ResolveDependencies(
context.FusionTypes.CreateVariableDirective(
subgraph,
argumentRef,
memberRef.Reference.Field));
memberRef.Requirement));
}
}
}
Expand Down

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

Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<value>The number of arguments in an output field does not match the number of arguments in the same field in of a subgraph schema.</value>
</data>
<data name="DirectivesHelper_GetIsDirective_NoFieldAndNoCoordinate" xml:space="preserve">
<value>The is argument must have a value for coordinate or field.</value>
<value>The is directive must have a value for coordinate or field.</value>
</data>
<data name="LogEntryHelper_OutputFieldArgumentSetMismatch" xml:space="preserve">
<value>An output field (`{0}`) that is being merged into another has a different set of arguments.
Expand All @@ -60,4 +60,7 @@ Source Arguments: {2}</value>
<data name="CannotFindCorrelatingSubgraphField" xml:space="preserve">
<value>Cannot find correlating subgraph field.</value>
</data>
<data name="DirectivesHelper_GetRequireDirective_NoFieldArg" xml:space="preserve">
<value>The require directive must have a value for the argument field.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -120,48 +120,4 @@ public async Task Accounts_And_Reviews_Products_AutoCompose_With_Node()
.FormatAsString(fusionConfig)
.MatchSnapshot(extension: ".graphql");
}

[Fact]
public async Task Accounts_And_Reviews_Products_Shipping_With_Is_Directive()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

var composer = new FusionGraphComposer(logFactory: _logFactory);

var fusionConfig = await composer.ComposeAsync(
new[]
{
demoProject.Accounts.ToConfiguration(),
demoProject.Reviews.ToConfiguration(),
demoProject.Products.ToConfiguration(ProductsExtensionSdl),
demoProject.Shipping.ToConfiguration(ShippingExtensionSdl),
});

SchemaFormatter
.FormatAsString(fusionConfig)
.MatchSnapshot(extension: ".graphql");
}

[Fact]
public async Task Accounts_And_Reviews_Products_Shipping_With_Map_Directive()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

var composer = new FusionGraphComposer(logFactory: _logFactory);

var fusionConfig = await composer.ComposeAsync(
new[]
{
demoProject.Accounts.ToConfiguration(),
demoProject.Reviews.ToConfiguration(),
demoProject.Products.ToConfiguration(ProductsExtensionSdl),
demoProject.Shipping.ToConfiguration(ShippingExtensionSdl2),
});

SchemaFormatter
.FormatAsString(fusionConfig)
.MatchSnapshot(extension: ".graphql");
}
}
56 changes: 56 additions & 0 deletions src/HotChocolate/Fusion/test/Composition.Tests/RequireTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using CookieCrumble;
using HotChocolate.Fusion.Shared;
using HotChocolate.Skimmed.Serialization;
using Xunit.Abstractions;
using static HotChocolate.Fusion.Shared.DemoProjectSchemaExtensions;

namespace HotChocolate.Fusion.Composition;

public class RequireTests
{
private readonly Func<ICompositionLog> _logFactory;

public RequireTests(ITestOutputHelper output)
{
_logFactory = () => new TestCompositionLog(output);
}

[Fact]
public async Task Require_Scalar_Arguments_No_Overloads()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

var composer = new FusionGraphComposer(logFactory: _logFactory);

var fusionConfig = await composer.ComposeAsync(
new[]
{
demoProject.Accounts.ToConfiguration(),
demoProject.Reviews.ToConfiguration(),
demoProject.Products.ToConfiguration(
"""
extend type Query {
productById(id: ID! @is(field: "id")): Product
}
"""),
demoProject.Shipping.ToConfiguration(
"""
extend type Query {
productById(id: ID! @is(field: "id")): Product
}
extend type Product {
deliveryEstimate(
size: Int! @require(field: "dimension { size }"),
weight: Int! @require(field: "dimension { weight }"),
zip: String!): DeliveryEstimate!
}
"""),
});

SchemaFormatter
.FormatAsString(fusionConfig)
.MatchSnapshot(extension: ".graphql");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ extend type Query {
extend type Product {
deliveryEstimate(
size: Int! @is(field: "dimension { size }"),
weight: Int! @is(field: "dimension { weight }"),
size: Int! @require(field: "dimension { size }"),
weight: Int! @require(field: "dimension { weight }"),
zip: String!): DeliveryEstimate!
}
""";
Expand Down

0 comments on commit 563600b

Please sign in to comment.