From 9e692d24ddf14b3015346174154050ecc0df9df7 Mon Sep 17 00:00:00 2001 From: Glen Date: Mon, 30 Dec 2024 14:37:40 +0200 Subject: [PATCH] [Fusion] Added pre-merge validation rule "KeyInvalidSyntaxRule" --- .../Logging/LogEntryCodes.cs | 1 + .../Logging/LogEntryHelper.cs | 17 ++++ .../PreMergeValidation/Events.cs | 5 + .../PreMergeValidation/PreMergeValidator.cs | 4 +- .../Rules/KeyInvalidSyntaxRule.cs | 24 +++++ .../CompositionResources.Designer.cs | 9 ++ .../Properties/CompositionResources.resx | 3 + .../Fusion.Composition/SourceSchemaMerger.cs | 1 + .../Rules/KeyInvalidSyntaxRuleTests.cs | 92 +++++++++++++++++++ 9 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidSyntaxRule.cs create mode 100644 src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidSyntaxRuleTests.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 fda5165c795..2bfd1ffa98b 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs @@ -10,6 +10,7 @@ public static class LogEntryCodes 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 KeyInvalidSyntax = "KEY_INVALID_SYNTAX"; 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 a1973c5af0e..2b1a921a7db 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs @@ -224,6 +224,23 @@ public static LogEntry KeyInvalidFields( schema); } + public static LogEntry KeyInvalidSyntax( + string entityTypeName, + Directive keyDirective, + SchemaDefinition schema) + { + return new LogEntry( + string.Format( + LogEntryHelper_KeyInvalidSyntax, + entityTypeName, + schema.Name), + LogEntryCodes.KeyInvalidSyntax, + 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 44de3a394b6..ca3599ec58e 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs @@ -47,6 +47,11 @@ internal record KeyFieldsInvalidReferenceEvent( ComplexTypeDefinition Type, SchemaDefinition Schema) : IEvent; +internal record KeyFieldsInvalidSyntaxEvent( + ComplexTypeDefinition EntityType, + Directive KeyDirective, + 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 3758fedb82f..c0bba5c2190 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs @@ -149,7 +149,9 @@ private void PublishEntityEvents( } catch (SyntaxException) { - // Ignore. + PublishEvent( + new KeyFieldsInvalidSyntaxEvent(entityType, keyDirective, schema), + context); } } } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidSyntaxRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidSyntaxRule.cs new file mode 100644 index 00000000000..0f24b527fd0 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/KeyInvalidSyntaxRule.cs @@ -0,0 +1,24 @@ +using HotChocolate.Fusion.Events; +using static HotChocolate.Fusion.Logging.LogEntryHelper; + +namespace HotChocolate.Fusion.PreMergeValidation.Rules; + +/// +/// Each @key directive must specify the fields that uniquely identify an entity using a +/// valid GraphQL selection set in its fields argument. If the fields argument string +/// is syntactically incorrect—missing closing braces, containing invalid tokens, or otherwise +/// malformed—it cannot be composed into a valid schema and triggers the KEY_INVALID_SYNTAX +/// error. +/// +/// +/// Specification +/// +internal sealed class KeyInvalidSyntaxRule : IEventHandler +{ + public void Handle(KeyFieldsInvalidSyntaxEvent @event, CompositionContext context) + { + var (entityType, keyDirective, schema) = @event; + + context.Log.Write(KeyInvalidSyntax(entityType.Name, keyDirective, 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 6735200b2f5..8a4e8b6eb20 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 @@ -176,6 +176,15 @@ internal static string LogEntryHelper_KeyInvalidFields { } } + /// + /// Looks up a localized string similar to A @key directive on type '{0}' in schema '{1}' contains invalid syntax in the 'fields' argument.. + /// + internal static string LogEntryHelper_KeyInvalidSyntax { + get { + return ResourceManager.GetString("LogEntryHelper_KeyInvalidSyntax", 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 d6e6794cbea..a9a350ad135 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -57,6 +57,9 @@ A @key directive on type '{0}' in schema '{1}' references field '{2}', which does not exist. + + A @key directive on type '{0}' in schema '{1}' contains invalid syntax in the 'fields' argument. + 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 e8322a0785f..db9a56c3a61 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs @@ -54,6 +54,7 @@ private CompositionResult MergeSchemaDefinitions(CompositionCo new KeyFieldsHasArgumentsRule(), new KeyFieldsSelectInvalidTypeRule(), new KeyInvalidFieldsRule(), + new KeyInvalidSyntaxRule(), new OutputFieldTypesMergeableRule(), new RootMutationUsedRule(), new RootQueryUsedRule(), diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidSyntaxRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidSyntaxRuleTests.cs new file mode 100644 index 00000000000..f77f920b11e --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/KeyInvalidSyntaxRuleTests.cs @@ -0,0 +1,92 @@ +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.PreMergeValidation; +using HotChocolate.Fusion.PreMergeValidation.Rules; + +namespace HotChocolate.Composition.PreMergeValidation.Rules; + +public sealed class KeyInvalidSyntaxRuleTests : CompositionTestBase +{ + private readonly PreMergeValidator _preMergeValidator = new([new KeyInvalidSyntaxRule()]); + + [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_SYNTAX")); + Assert.True(context.Log.All(e => e.Severity == LogSeverity.Error)); + } + + public static TheoryData ValidExamplesData() + { + return new TheoryData + { + // In this example, the "fields" argument is a correctly formed selection set: + // "sku featuredItem { id }" is properly balanced and contains no syntax errors. + { + [ + """ + type Product @key(fields: "sku featuredItem { id }") { + sku: String! + featuredItem: Node! + } + + interface Node { + id: ID! + } + """ + ] + } + }; + } + + public static TheoryData InvalidExamplesData() + { + return new TheoryData + { + // Here, the selection set "featuredItem { id" is missing the closing brace "}". It is + // thus invalid syntax, causing a "KEY_INVALID_SYNTAX" error. + { + [ + """ + type Product @key(fields: "featuredItem { id") { + featuredItem: Node! + sku: String! + } + + interface Node { + id: ID! + } + """ + ], + [ + "A @key directive on type 'Product' in schema 'A' contains invalid syntax in " + + "the 'fields' argument." + ] + } + }; + } +}