Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -13,6 +13,7 @@ internal sealed class FusionRequiresMutableDirectiveDefinition : MutableDirectiv
{
public FusionRequiresMutableDirectiveDefinition(
MutableEnumTypeDefinition schemaMutableEnumType,
MutableScalarTypeDefinition fieldSelectionSetType,
MutableScalarTypeDefinition fieldDefinitionType,
MutableScalarTypeDefinition fieldSelectionMapType)
: base(FusionRequires)
Expand All @@ -25,6 +26,12 @@ public FusionRequiresMutableDirectiveDefinition(
Description = FusionRequiresMutableDirectiveDefinition_Schema_Description
});

Arguments.Add(
new MutableInputFieldDefinition(Requirements, new NonNullType(fieldSelectionSetType))
{
Description = FusionRequiresMutableDirectiveDefinition_Requirements_Description
});

Arguments.Add(
new MutableInputFieldDefinition(Field, new NonNullType(fieldDefinitionType))
{
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 @@ -96,6 +96,9 @@
<data name="FusionRequiresMutableDirectiveDefinition_Map_Description" xml:space="preserve">
<value>The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type.</value>
</data>
<data name="FusionRequiresMutableDirectiveDefinition_Requirements_Description" xml:space="preserve">
<value>A selection set on the annotated field that describes its requirements.</value>
</data>
<data name="FusionRequiresMutableDirectiveDefinition_Schema_Description" xml:space="preserve">
<value>The name of the source schema where this field has requirements to data on other source schemas.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ private MutableInterfaceTypeDefinition MergeInterfaceTypes(
.SelectMany(
i => ((MutableInterfaceTypeDefinition)i.Type).Fields.AsEnumerable(),
(i, f) => new OutputFieldInfo(f, (MutableComplexTypeDefinition)i.Type, i.Schema))
.GroupBy(i => i.Field.Name);
.GroupBy(i => i.Field.Name)
.ToImmutableArray();

foreach (var fieldGroup in fieldGroupByName)
{
Expand All @@ -417,6 +418,14 @@ private MutableInterfaceTypeDefinition MergeInterfaceTypes(
}
}

foreach (var (fieldName, fieldGroup) in fieldGroupByName)
{
if (interfaceType.Fields.TryGetField(fieldName, out var outputField))
{
AddFusionRequiresDirectives(outputField, interfaceType, [.. fieldGroup]);
}
}

return interfaceType;
}

Expand Down Expand Up @@ -485,7 +494,8 @@ .. typeGroup.Where(
.SelectMany(
i => ((MutableObjectTypeDefinition)i.Type).Fields.AsEnumerable(),
(i, f) => new OutputFieldInfo(f, (MutableComplexTypeDefinition)i.Type, i.Schema))
.GroupBy(i => i.Field.Name);
.GroupBy(i => i.Field.Name)
.ToImmutableArray();

foreach (var fieldGroup in fieldGroupByName)
{
Expand All @@ -497,6 +507,14 @@ .. typeGroup.Where(
}
}

foreach (var (fieldName, fieldGroup) in fieldGroupByName)
{
if (objectType.Fields.TryGetField(fieldName, out var outputField))
{
AddFusionRequiresDirectives(outputField, objectType, [.. fieldGroup]);
}
}

return objectType;
}

Expand Down Expand Up @@ -560,7 +578,6 @@ .. typeGroup.Where(
}

AddFusionFieldDirectives(outputField, fieldGroup);
AddFusionRequiresDirectives(outputField, fieldGroup);

if (fieldGroup.Any(i => i.Field.HasInaccessibleDirective()))
{
Expand Down Expand Up @@ -1002,6 +1019,7 @@ private static List<string> GetFusionLookupMap(MutableOutputFieldDefinition fiel

private void AddFusionRequiresDirectives(
MutableOutputFieldDefinition field,
MutableComplexTypeDefinition complexType,
ImmutableArray<OutputFieldInfo> fieldGroup)
{
foreach (var (sourceField, _, sourceSchema) in fieldGroup)
Expand All @@ -1025,6 +1043,22 @@ private void AddFusionRequiresDirectives(
if (map.Any(v => v is not null))
{
var schemaArgument = new EnumValueNode(_schemaConstantNames[sourceSchema]);
var requiresMap = map.Where(f => f is not null);
var selectedValues =
requiresMap.Select(a => new FieldSelectionMapParser(a).Parse());
var selectedValueToSelectionSetRewriter =
GetSelectedValueToSelectionSetRewriter(sourceSchema);
var selectionSets = selectedValues
.Select(
s =>
selectedValueToSelectionSetRewriter
.SelectedValueToSelectionSet(s, complexType))
.ToImmutableArray();
var mergedSelectionSet = selectionSets.Length == 1
? selectionSets[0]
: GetMergeSelectionSetRewriter(sourceSchema).Merge(selectionSets, complexType);
var requirementsArgument =
mergedSelectionSet.ToString(indented: false).AsSpan()[2 .. ^2].ToString();

var fieldArgument =
_removeDirectivesRewriter
Expand All @@ -1039,6 +1073,7 @@ private void AddFusionRequiresDirectives(
new Directive(
_fusionDirectiveDefinitions[DirectiveNames.FusionRequires],
new ArgumentAssignment(ArgumentNames.Schema, schemaArgument),
new ArgumentAssignment(ArgumentNames.Requirements, requirementsArgument),
new ArgumentAssignment(ArgumentNames.Field, fieldArgument),
new ArgumentAssignment(ArgumentNames.Map, mapArgument)));
}
Expand Down Expand Up @@ -1155,6 +1190,7 @@ private FrozenDictionary<string, MutableDirectiveDefinition> CreateFusionDirecti
DirectiveNames.FusionRequires,
new FusionRequiresMutableDirectiveDefinition(
schemaEnumType,
fieldSelectionSetType,
fieldDefinitionType,
fieldSelectionMapType)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal static class WellKnownArgumentNames
public const string Partial = "partial";
public const string Path = "path";
public const string Provides = "provides";
public const string Requirements = "requirements";
public const string Schema = "schema";
public const string SourceType = "sourceType";
public const string Value = "value";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ namespace HotChocolate.Fusion.Types.Directives;
/*
directive @fusion__requires(
schema: fusion__Schema!
requirements: fusion__FieldSelectionSet!
field: fusion__FieldDefinition!
map: [fusion__FieldSelectionMap]!
) repeatable on FIELD_DEFINITION
*/
internal class RequireDirective(
string schemaName,
SelectionSetNode requirements,
FieldDefinitionNode field,
ImmutableArray<string?> map)
{
Expand All @@ -20,6 +22,9 @@ internal class RequireDirective(
/// </summary>
public string SchemaName { get; } = schemaName;

/// <summary>Gets the requirements for a field.</summary>
public SelectionSetNode Requirements { get; } = requirements;

/// <summary>
/// Gets the arguments that represent field requirements.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static bool CanParse(DirectiveNode directiveNode)
public static RequireDirective Parse(DirectiveNode directiveNode)
{
string? schemaName = null;
SelectionSetNode? requirements = null;
FieldDefinitionNode? field = null;
ImmutableArray<string?>? map = null;

Expand All @@ -23,6 +24,17 @@ public static RequireDirective Parse(DirectiveNode directiveNode)
schemaName = ((EnumValueNode)argument.Value).Value;
break;

case "requirements":
var requirementsSourceText = ((StringValueNode)argument.Value).Value.Trim();

if (!requirementsSourceText.StartsWith('{'))
{
requirementsSourceText = $"{{ {requirementsSourceText} }}";
}

requirements = Utf8GraphQLParser.Syntax.ParseSelectionSet(requirementsSourceText);
break;

case "field":
field = Utf8GraphQLParser.Syntax.ParseFieldDefinition(((StringValueNode)argument.Value).Value);
break;
Expand All @@ -43,6 +55,12 @@ public static RequireDirective Parse(DirectiveNode directiveNode)
"The `schema` argument is required on the @require directive.");
}

if (requirements is null)
{
throw new DirectiveParserException(
"The `requirements` argument is required on the @require directive.");
}

if (field is null)
{
throw new DirectiveParserException(
Expand All @@ -55,7 +73,7 @@ public static RequireDirective Parse(DirectiveNode directiveNode)
"The `map` argument is required on the @require directive.");
}

return new RequireDirective(schemaName, field, map.Value);
return new RequireDirective(schemaName, requirements, field, map.Value);
}

private static ImmutableArray<string?> ParseMap(IValueNode value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ directive @fusion__inputField("The name of the source schema that originally pro
directive @fusion__lookup("The GraphQL field definition in the source schema that can be used to look up the entity." field: fusion__FieldDefinition! "A selection set on the annotated entity type that describes the stable key for the lookup." key: fusion__FieldSelectionSet! "The map describes how the key values are resolved from the annotated entity type." map: [fusion__FieldSelectionMap!]! "The path to the lookup field relative to the Query type." path: fusion__FieldSelectionPath "The name of the source schema where the annotated entity type can be looked up from." schema: fusion__Schema!) repeatable on OBJECT | INTERFACE | UNION

"The @fusion__requires directive specifies if a field has requirements on a source schema."
directive @fusion__requires("The GraphQL field definition in the source schema that this field depends on." field: fusion__FieldDefinition! "The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type." map: [fusion__FieldSelectionMap]! "The name of the source schema where this field has requirements to data on other source schemas." schema: fusion__Schema!) repeatable on FIELD_DEFINITION
directive @fusion__requires("The GraphQL field definition in the source schema that this field depends on." field: fusion__FieldDefinition! "The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type." map: [fusion__FieldSelectionMap]! "A selection set on the annotated field that describes its requirements." requirements: fusion__FieldSelectionSet! "The name of the source schema where this field has requirements to data on other source schemas." schema: fusion__Schema!) repeatable on FIELD_DEFINITION

"The @fusion__type directive specifies which source schemas provide parts of a composite type."
directive @fusion__type("The name of the source schema that originally provided part of the annotated type." schema: fusion__Schema!) repeatable on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ directive @fusion__inputField("The name of the source schema that originally pro
directive @fusion__lookup("The GraphQL field definition in the source schema that can be used to look up the entity." field: fusion__FieldDefinition! "A selection set on the annotated entity type that describes the stable key for the lookup." key: fusion__FieldSelectionSet! "The map describes how the key values are resolved from the annotated entity type." map: [fusion__FieldSelectionMap!]! "The path to the lookup field relative to the Query type." path: fusion__FieldSelectionPath "The name of the source schema where the annotated entity type can be looked up from." schema: fusion__Schema!) repeatable on OBJECT | INTERFACE | UNION

"The @fusion__requires directive specifies if a field has requirements on a source schema."
directive @fusion__requires("The GraphQL field definition in the source schema that this field depends on." field: fusion__FieldDefinition! "The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type." map: [fusion__FieldSelectionMap]! "The name of the source schema where this field has requirements to data on other source schemas." schema: fusion__Schema!) repeatable on FIELD_DEFINITION
directive @fusion__requires("The GraphQL field definition in the source schema that this field depends on." field: fusion__FieldDefinition! "The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type." map: [fusion__FieldSelectionMap]! "A selection set on the annotated field that describes its requirements." requirements: fusion__FieldSelectionSet! "The name of the source schema where this field has requirements to data on other source schemas." schema: fusion__Schema!) repeatable on FIELD_DEFINITION

"The @fusion__type directive specifies which source schemas provide parts of a composite type."
directive @fusion__type("The name of the source schema that originally provided part of the annotated type." schema: fusion__Schema!) repeatable on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,18 @@ type Query
"""
type Product {
id: ID!
dimension: ProductDimension!
delivery(
zip: String!
size: Int! @require(field: "dimension.size")
weight: Int! @require(field: "dimension.weight")
): DeliveryEstimates
}

type ProductDimension {
size: Int!
weight: Int!
}
"""
],
"""
Expand All @@ -175,11 +181,21 @@ type Product
delivery(zip: String!
@fusion__inputField(schema: A)): DeliveryEstimates
@fusion__field(schema: A)
@fusion__requires(schema: A, field: "delivery(zip: String! size: Int! weight: Int!): DeliveryEstimates", map: [ null, "dimension.size", "dimension.weight" ])
@fusion__requires(schema: A, requirements: "dimension { size weight }", field: "delivery(zip: String! size: Int! weight: Int!): DeliveryEstimates", map: [ null, "dimension.size", "dimension.weight" ])
dimension: ProductDimension!
@fusion__field(schema: A)
id: ID!
@fusion__field(schema: A)
}

type ProductDimension
@fusion__type(schema: A) {
size: Int!
@fusion__field(schema: A)
weight: Int!
@fusion__field(schema: A)
}

scalar DeliveryEstimates
@fusion__type(schema: A)
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ type Product
"""
# Schema A
type Product {
percent: Int
discountPercentage(percent: Int): Int
}
""",
Expand All @@ -146,7 +147,9 @@ type Product
discountPercentage: Int
@fusion__field(schema: A)
@fusion__field(schema: B)
@fusion__requires(schema: B, field: "discountPercentage(percent: Int): Int", map: [ "percent" ])
@fusion__requires(schema: B, requirements: "percent", field: "discountPercentage(percent: Int): Int", map: [ "percent" ])
percent: Int
@fusion__field(schema: A)
}
"""
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ directive @fusion__inputField("The name of the source schema that originally pro
directive @fusion__lookup("The GraphQL field definition in the source schema that can be used to look up the entity." field: fusion__FieldDefinition! "A selection set on the annotated entity type that describes the stable key for the lookup." key: fusion__FieldSelectionSet! "The map describes how the key values are resolved from the annotated entity type." map: [fusion__FieldSelectionMap!]! "The path to the lookup field relative to the Query type." path: fusion__FieldSelectionPath "The name of the source schema where the annotated entity type can be looked up from." schema: fusion__Schema!) repeatable on OBJECT | INTERFACE | UNION

"The @fusion__requires directive specifies if a field has requirements on a source schema."
directive @fusion__requires("The GraphQL field definition in the source schema that this field depends on." field: fusion__FieldDefinition! "The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type." map: [fusion__FieldSelectionMap]! "The name of the source schema where this field has requirements to data on other source schemas." schema: fusion__Schema!) repeatable on FIELD_DEFINITION
directive @fusion__requires("The GraphQL field definition in the source schema that this field depends on." field: fusion__FieldDefinition! "The map describes how the argument values for the source schema are resolved from the arguments of the field exposed in the client-facing composite schema and from required data relative to the current type." map: [fusion__FieldSelectionMap]! "A selection set on the annotated field that describes its requirements." requirements: fusion__FieldSelectionSet! "The name of the source schema where this field has requirements to data on other source schemas." schema: fusion__Schema!) repeatable on FIELD_DEFINITION

"The @fusion__type directive specifies which source schemas provide parts of a composite type."
directive @fusion__type("The name of the source schema that originally provided part of the annotated type." schema: fusion__Schema!) repeatable on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT
Expand Down
Loading
Loading