From ecf9247f6481e326f4821d3a51788d94deda8d7c Mon Sep 17 00:00:00 2001 From: Glen Date: Mon, 30 Dec 2024 11:33:31 +0200 Subject: [PATCH 1/2] [Fusion] Added pre-merge validation rule "KeyFieldsHasArgumentsRule" (#7873) --- .../Logging/LogEntryCodes.cs | 1 + .../Logging/LogEntryHelper.cs | 20 +++ .../Rules/KeyFieldsHasArgumentsRule.cs | 33 +++++ .../CompositionResources.Designer.cs | 9 ++ .../Properties/CompositionResources.resx | 3 + .../Fusion.Composition/SourceSchemaMerger.cs | 1 + .../Rules/KeyFieldsHasArgumentsRuleTests.cs | 122 ++++++++++++++++++ 7 files changed, 189 insertions(+) create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyFieldsHasArgumentsRule.cs create mode 100644 src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyFieldsHasArgumentsRuleTests.cs diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs index 0689461b9ab..bdad96c0d58 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs @@ -7,6 +7,7 @@ public static class LogEntryCodes public const string ExternalMissingOnBase = "EXTERNAL_MISSING_ON_BASE"; public const string ExternalUnused = "EXTERNAL_UNUSED"; public const string KeyDirectiveInFieldsArg = "KEY_DIRECTIVE_IN_FIELDS_ARG"; + public const string KeyFieldsHasArgs = "KEY_FIELDS_HAS_ARGS"; public const string KeyFieldsSelectInvalidType = "KEY_FIELDS_SELECT_INVALID_TYPE"; public const string OutputFieldTypesNotMergeable = "OUTPUT_FIELD_TYPES_NOT_MERGEABLE"; public const string RootMutationUsed = "ROOT_MUTATION_USED"; diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs index 9afc2e56d65..ff88a2aca4e 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs @@ -164,6 +164,26 @@ public static LogEntry KeyDirectiveInFieldsArgument( schema); } + public static LogEntry KeyFieldsHasArguments( + string entityTypeName, + Directive keyDirective, + string fieldName, + string typeName, + SchemaDefinition schema) + { + return new LogEntry( + string.Format( + LogEntryHelper_KeyFieldsHasArguments, + entityTypeName, + schema.Name, + new SchemaCoordinate(typeName, fieldName)), + LogEntryCodes.KeyFieldsHasArgs, + LogSeverity.Error, + new SchemaCoordinate(entityTypeName), + keyDirective, + schema); + } + public static LogEntry KeyFieldsSelectInvalidType( string entityTypeName, Directive keyDirective, diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyFieldsHasArgumentsRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyFieldsHasArgumentsRule.cs new file mode 100644 index 00000000000..fbf5d124a0d --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyFieldsHasArgumentsRule.cs @@ -0,0 +1,33 @@ +using HotChocolate.Fusion.Events; +using static HotChocolate.Fusion.Logging.LogEntryHelper; + +namespace HotChocolate.Fusion.PreMergeValidation.Rules; + +/// +/// The @key directive is used to define the set of fields that uniquely identify an entity. +/// These fields must not include any field that is defined with arguments, as arguments introduce +/// variability that prevents consistent and valid entity resolution across subgraphs. Fields +/// included in the fields argument of the @key directive must be static and +/// consistently resolvable. +/// +/// +/// Specification +/// +internal sealed class KeyFieldsHasArgumentsRule : IEventHandler +{ + public void Handle(KeyFieldEvent @event, CompositionContext context) + { + var (entityType, keyDirective, field, type, schema) = @event; + + if (field.Arguments.Count != 0) + { + context.Log.Write( + KeyFieldsHasArguments( + entityType.Name, + keyDirective, + field.Name, + type.Name, + schema)); + } + } +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs index 155cfbcf01a..f0d13b497eb 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs @@ -149,6 +149,15 @@ internal static string LogEntryHelper_KeyDirectiveInFieldsArgument { } } + /// + /// Looks up a localized string similar to An @key directive on type '{0}' in schema '{1}' references field '{2}', which must not have arguments.. + /// + internal static string LogEntryHelper_KeyFieldsHasArguments { + get { + return ResourceManager.GetString("LogEntryHelper_KeyFieldsHasArguments", resourceCulture); + } + } + /// /// Looks up a localized string similar to An @key directive on type '{0}' in schema '{1}' references field '{2}', which must not be a list, interface, or union type.. /// diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx index 3f2e94e9587..ee2e0335dba 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -48,6 +48,9 @@ An @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications. + + An @key directive on type '{0}' in schema '{1}' references field '{2}', which must not have arguments. + An @key directive on type '{0}' in schema '{1}' references field '{2}', which must not be a list, interface, or union type. diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs index 6b4d2db05f6..913ce4e5c5e 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs @@ -51,6 +51,7 @@ private CompositionResult MergeSchemaDefinitions(CompositionCo new ExternalMissingOnBaseRule(), new ExternalUnusedRule(), new KeyDirectiveInFieldsArgumentRule(), + new KeyFieldsHasArgumentsRule(), new KeyFieldsSelectInvalidTypeRule(), new OutputFieldTypesMergeableRule(), new RootMutationUsedRule(), diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyFieldsHasArgumentsRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyFieldsHasArgumentsRuleTests.cs new file mode 100644 index 00000000000..67d3f5f521b --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyFieldsHasArgumentsRuleTests.cs @@ -0,0 +1,122 @@ +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.PreMergeValidation; +using HotChocolate.Fusion.PreMergeValidation.Rules; + +namespace HotChocolate.Composition.PreMergeValidation.Rules; + +public sealed class KeyFieldsHasArgumentsRuleTests : CompositionTestBase +{ + private readonly PreMergeValidator _preMergeValidator = new([new KeyFieldsHasArgumentsRule()]); + + [Theory] + [MemberData(nameof(ValidExamplesData))] + public void Examples_Valid(string[] sdl) + { + // arrange + var context = CreateCompositionContext(sdl); + + // act + var result = _preMergeValidator.Validate(context); + + // assert + Assert.True(result.IsSuccess); + Assert.True(context.Log.IsEmpty); + } + + [Theory] + [MemberData(nameof(InvalidExamplesData))] + public void Examples_Invalid(string[] sdl, string[] errorMessages) + { + // arrange + var context = CreateCompositionContext(sdl); + + // act + var result = _preMergeValidator.Validate(context); + + // assert + Assert.True(result.IsFailure); + Assert.Equal(errorMessages, context.Log.Select(e => e.Message).ToArray()); + Assert.True(context.Log.All(e => e.Code == "KEY_FIELDS_HAS_ARGS")); + Assert.True(context.Log.All(e => e.Severity == LogSeverity.Error)); + } + + public static TheoryData ValidExamplesData() + { + return new TheoryData + { + // In this example, the `User` type has a valid `@key` directive that references the + // argument-free fields `id` and `name`. + { + [ + """ + type User @key(fields: "id name") { + id: ID! + name: String + tags: [String] + } + """ + ] + } + }; + } + + public static TheoryData InvalidExamplesData() + { + return new TheoryData + { + // In this example, the `@key` directive references a field (`tags`) that is defined + // with arguments (`limit`), which is not allowed. + { + [ + """ + type User @key(fields: "id tags") { + id: ID! + tags(limit: Int = 10): [String] + } + """ + ], + [ + "An @key directive on type 'User' in schema 'A' references field " + + "'User.tags', which must not have arguments." + ] + }, + // Nested field. + { + [ + """ + type User @key(fields: "id info { tags }") { + id: ID! + info: UserInfo + } + + type UserInfo { + tags(limit: Int = 10): [String] + } + """ + ], + [ + "An @key directive on type 'User' in schema 'A' references field " + + "'UserInfo.tags', which must not have arguments." + ] + }, + // Multiple keys. + { + [ + """ + type User @key(fields: "id") @key(fields: "tags") { + id(global: Boolean = true): ID! + tags(limit: Int = 10): [String] + } + """ + ], + [ + "An @key directive on type 'User' in schema 'A' references field " + + "'User.id', which must not have arguments.", + + "An @key directive on type 'User' in schema 'A' references field " + + "'User.tags', which must not have arguments." + ] + } + }; + } +} From 10c79aeb1b21f59c91fd7021cfaf49ade208172c Mon Sep 17 00:00:00 2001 From: Glen Date: Mon, 30 Dec 2024 11:48:46 +0200 Subject: [PATCH 2/2] [Fusion] Added pre-merge validation rule "KeyInvalidFieldsRule" (#7874) --- .../Logging/LogEntryCodes.cs | 1 + .../Logging/LogEntryHelper.cs | 20 +++ .../PreMergeValidation/Events.cs | 7 + .../PreMergeValidation/PreMergeValidator.cs | 46 ++++-- .../Rules/KeyInvalidFieldsRule.cs | 30 ++++ .../CompositionResources.Designer.cs | 9 + .../Properties/CompositionResources.resx | 3 + .../Fusion.Composition/SourceSchemaMerger.cs | 1 + .../Rules/KeyInvalidFieldsRuleTests.cs | 154 ++++++++++++++++++ 9 files changed, 257 insertions(+), 14 deletions(-) create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidFieldsRule.cs create mode 100644 src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidFieldsRuleTests.cs diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs index bdad96c0d58..fda5165c795 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs @@ -9,6 +9,7 @@ public static class LogEntryCodes public const string KeyDirectiveInFieldsArg = "KEY_DIRECTIVE_IN_FIELDS_ARG"; public const string KeyFieldsHasArgs = "KEY_FIELDS_HAS_ARGS"; public const string KeyFieldsSelectInvalidType = "KEY_FIELDS_SELECT_INVALID_TYPE"; + public const string KeyInvalidFields = "KEY_INVALID_FIELDS"; public const string OutputFieldTypesNotMergeable = "OUTPUT_FIELD_TYPES_NOT_MERGEABLE"; public const string RootMutationUsed = "ROOT_MUTATION_USED"; public const string RootQueryUsed = "ROOT_QUERY_USED"; diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs index ff88a2aca4e..a1973c5af0e 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs @@ -204,6 +204,26 @@ public static LogEntry KeyFieldsSelectInvalidType( schema); } + public static LogEntry KeyInvalidFields( + string entityTypeName, + Directive keyDirective, + string fieldName, + string typeName, + SchemaDefinition schema) + { + return new LogEntry( + string.Format( + LogEntryHelper_KeyInvalidFields, + entityTypeName, + schema.Name, + new SchemaCoordinate(typeName, fieldName)), + LogEntryCodes.KeyInvalidFields, + LogSeverity.Error, + new SchemaCoordinate(entityTypeName), + keyDirective, + schema); + } + public static LogEntry OutputFieldTypesNotMergeable( OutputFieldDefinition field, string typeName, diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs index 1ce5bdc790a..44de3a394b6 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs @@ -40,6 +40,13 @@ internal record KeyFieldNodeEvent( ImmutableArray FieldNamePath, SchemaDefinition Schema) : IEvent; +internal record KeyFieldsInvalidReferenceEvent( + ComplexTypeDefinition EntityType, + Directive KeyDirective, + FieldNode FieldNode, + ComplexTypeDefinition Type, + SchemaDefinition Schema) : IEvent; + internal record OutputFieldEvent( OutputFieldDefinition Field, INamedTypeDefinition Type, diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs index c80d77f81a4..3758fedb82f 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs @@ -159,10 +159,12 @@ private void PublishKeyFieldEvents( ComplexTypeDefinition entityType, Directive keyDirective, List fieldNamePath, - ComplexTypeDefinition parentType, + ComplexTypeDefinition? parentType, SchemaDefinition schema, CompositionContext context) { + ComplexTypeDefinition? nextParentType = null; + foreach (var selection in selectionSet.Selections) { if (selection is FieldNode fieldNode) @@ -178,20 +180,36 @@ private void PublishKeyFieldEvents( schema), context); - if (parentType.Fields.TryGetField(fieldNode.Name.Value, out var field)) + if (parentType is not null) { - PublishEvent( - new KeyFieldEvent( - entityType, - keyDirective, - field, - parentType, - schema), - context); - - if (field.Type.NullableType() is ComplexTypeDefinition fieldType) + if (parentType.Fields.TryGetField(fieldNode.Name.Value, out var field)) + { + PublishEvent( + new KeyFieldEvent( + entityType, + keyDirective, + field, + parentType, + schema), + context); + + if (field.Type.NullableType() is ComplexTypeDefinition fieldType) + { + nextParentType = fieldType; + } + } + else { - parentType = fieldType; + PublishEvent( + new KeyFieldsInvalidReferenceEvent( + entityType, + keyDirective, + fieldNode, + parentType, + schema), + context); + + nextParentType = null; } } @@ -202,7 +220,7 @@ private void PublishKeyFieldEvents( entityType, keyDirective, fieldNamePath, - parentType, + nextParentType, schema, context); } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidFieldsRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidFieldsRule.cs new file mode 100644 index 00000000000..9257ad82574 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidFieldsRule.cs @@ -0,0 +1,30 @@ +using HotChocolate.Fusion.Events; +using static HotChocolate.Fusion.Logging.LogEntryHelper; + +namespace HotChocolate.Fusion.PreMergeValidation.Rules; + +/// +/// Even if the selection set for @key(fields: "…") is syntactically valid, field references +/// within that selection set must also refer to actual fields on the annotated type. This +/// includes nested selections, which must appear on the corresponding return type. If any +/// referenced field is missing or incorrectly named, composition fails with a +/// KEY_INVALID_FIELDS error because the entity key cannot be resolved correctly. +/// +/// +/// Specification +/// +internal sealed class KeyInvalidFieldsRule : IEventHandler +{ + public void Handle(KeyFieldsInvalidReferenceEvent @event, CompositionContext context) + { + var (entityType, keyDirective, fieldNode, type, schema) = @event; + + context.Log.Write( + KeyInvalidFields( + entityType.Name, + keyDirective, + fieldNode.Name.Value, + type.Name, + schema)); + } +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs index f0d13b497eb..a47f72a0365 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs @@ -167,6 +167,15 @@ internal static string LogEntryHelper_KeyFieldsSelectInvalidType { } } + /// + /// Looks up a localized string similar to An @key directive on type '{0}' in schema '{1}' references field '{2}', which does not exist.. + /// + internal static string LogEntryHelper_KeyInvalidFields { + get { + return ResourceManager.GetString("LogEntryHelper_KeyInvalidFields", resourceCulture); + } + } + /// /// Looks up a localized string similar to Field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'.. /// diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx index ee2e0335dba..5a854552056 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -54,6 +54,9 @@ An @key directive on type '{0}' in schema '{1}' references field '{2}', which must not be a list, interface, or union type. + + An @key directive on type '{0}' in schema '{1}' references field '{2}', which does not exist. + Field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'. diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs index 913ce4e5c5e..e8322a0785f 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs @@ -53,6 +53,7 @@ private CompositionResult MergeSchemaDefinitions(CompositionCo new KeyDirectiveInFieldsArgumentRule(), new KeyFieldsHasArgumentsRule(), new KeyFieldsSelectInvalidTypeRule(), + new KeyInvalidFieldsRule(), new OutputFieldTypesMergeableRule(), new RootMutationUsedRule(), new RootQueryUsedRule(), diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidFieldsRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidFieldsRuleTests.cs new file mode 100644 index 00000000000..d2cb0c79a15 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidFieldsRuleTests.cs @@ -0,0 +1,154 @@ +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.PreMergeValidation; +using HotChocolate.Fusion.PreMergeValidation.Rules; + +namespace HotChocolate.Composition.PreMergeValidation.Rules; + +public sealed class KeyInvalidFieldsRuleTests : CompositionTestBase +{ + private readonly PreMergeValidator _preMergeValidator = new([new KeyInvalidFieldsRule()]); + + [Theory] + [MemberData(nameof(ValidExamplesData))] + public void Examples_Valid(string[] sdl) + { + // arrange + var context = CreateCompositionContext(sdl); + + // act + var result = _preMergeValidator.Validate(context); + + // assert + Assert.True(result.IsSuccess); + Assert.True(context.Log.IsEmpty); + } + + [Theory] + [MemberData(nameof(InvalidExamplesData))] + public void Examples_Invalid(string[] sdl, string[] errorMessages) + { + // arrange + var context = CreateCompositionContext(sdl); + + // act + var result = _preMergeValidator.Validate(context); + + // assert + Assert.True(result.IsFailure); + Assert.Equal(errorMessages, context.Log.Select(e => e.Message).ToArray()); + Assert.True(context.Log.All(e => e.Code == "KEY_INVALID_FIELDS")); + Assert.True(context.Log.All(e => e.Severity == LogSeverity.Error)); + } + + public static TheoryData ValidExamplesData() + { + return new TheoryData + { + // In this example, the `fields` argument of the `@key` directive is properly defined + // with valid syntax and references existing fields. + { + [ + """ + type Product @key(fields: "sku featuredItem { id }") { + sku: String! + featuredItem: Node! + } + + interface Node { + id: ID! + } + """ + ] + } + }; + } + + public static TheoryData InvalidExamplesData() + { + return new TheoryData + { + // In this example, the `fields` argument of the `@key` directive references a field + // `id`, which does not exist on the `Product` type. + { + [ + """ + type Product @key(fields: "id") { + sku: String! + } + """ + ], + [ + "An @key directive on type 'Product' in schema 'A' references field " + + "'Product.id', which does not exist." + ] + }, + // Nested field. + { + [ + """ + type Product @key(fields: "info { category { id } }") { + info: ProductInfo! + } + + type ProductInfo { + subcategory: Category + } + + type Category { + name: String! + } + """ + ], + [ + "An @key directive on type 'Product' in schema 'A' references field " + + "'ProductInfo.category', which does not exist." + ] + }, + // Multiple nested fields. + { + [ + """ + type Product @key(fields: "category { id name } info { id }") { + category: ProductCategory! + } + + type ProductCategory { + description: String + } + + type ProductInfo { + updatedAt: DateTime! + } + """ + ], + [ + "An @key directive on type 'Product' in schema 'A' references field " + + "'ProductCategory.id', which does not exist.", + + "An @key directive on type 'Product' in schema 'A' references field " + + "'ProductCategory.name', which does not exist.", + + "An @key directive on type 'Product' in schema 'A' references field " + + "'Product.info', which does not exist." + ] + }, + // Multiple keys. + { + [ + """ + type Product @key(fields: "id") @key(fields: "name") { + sku: String! + } + """ + ], + [ + "An @key directive on type 'Product' in schema 'A' references field " + + "'Product.id', which does not exist.", + + "An @key directive on type 'Product' in schema 'A' references field " + + "'Product.name', which does not exist." + ] + } + }; + } +}