From 83ff50f074ae5dd9df8c9d1df56c945105b88ba9 Mon Sep 17 00:00:00 2001 From: Daniel Reynolds Date: Sat, 4 Jan 2025 15:25:23 +0000 Subject: [PATCH 1/2] [Fusion] Added pre-merge validation rule InputFieldsTypesMergeableRule --- .../Logging/LogEntryCodes.cs | 1 + .../Logging/LogEntryHelper.cs | 21 +++ .../Rules/InputFieldTypesMergeableRule.cs | 46 +++++ .../CompositionResources.Designer.cs | 150 +++------------- .../Properties/CompositionResources.resx | 3 + .../Fusion.Composition/SourceSchemaMerger.cs | 1 + .../InputFieldTypesMergeableRuleTests.cs | 163 ++++++++++++++++++ 7 files changed, 261 insertions(+), 124 deletions(-) create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/InputFieldTypesMergeableRule.cs create mode 100644 src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.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 0d41e0883a3..db11ba87062 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs @@ -8,6 +8,7 @@ public static class LogEntryCodes public const string ExternalOnInterface = "EXTERNAL_ON_INTERFACE"; public const string ExternalUnused = "EXTERNAL_UNUSED"; public const string InputFieldDefaultMismatch = "INPUT_FIELD_DEFAULT_MISMATCH"; + public const string InputFieldTypesNotMergeable = "INPUT_FIELD_TYPES_NOT_MERGEABLE"; 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"; 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 618a169d442..b97f8f61b08 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs @@ -187,6 +187,27 @@ public static LogEntry InputFieldDefaultMismatch( schemaA); } + public static LogEntry InputFieldTypesNotMergeable( + InputFieldDefinition field, + string typeName, + SchemaDefinition schemaA, + SchemaDefinition schemaB) + { + var coordinate = new SchemaCoordinate(typeName, field.Name); + + return new LogEntry( + string.Format( + LogEntryHelper_InputFieldTypesNotMergeable, + coordinate, + schemaA.Name, + schemaB.Name), + LogEntryCodes.InputFieldTypesNotMergeable, + LogSeverity.Error, + coordinate, + field, + schemaA); + } + public static LogEntry KeyDirectiveInFieldsArgument( string entityTypeName, Directive keyDirective, diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/InputFieldTypesMergeableRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/InputFieldTypesMergeableRule.cs new file mode 100644 index 00000000000..d7e5b12d4f3 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/InputFieldTypesMergeableRule.cs @@ -0,0 +1,46 @@ +using HotChocolate.Fusion.Events; +using static HotChocolate.Fusion.Logging.LogEntryHelper; + +namespace HotChocolate.Fusion.PreMergeValidation.Rules; + +/// +/// +/// The input fields of input objects with the same name must be mergeable. This rule ensures that +/// input objects with the same name in different source schemas have fields that can be merged +/// consistently without conflicts. +/// +/// +/// Input fields are considered mergeable when they share the same name and have compatible types. +/// The compatibility of types is determined by their structure (e.g., lists), excluding +/// nullability. Mergeable input fields with different nullability are considered mergeable, +/// and the resulting merged field will be the most permissive of the two. +/// +/// +/// +/// Specification +/// +internal sealed class InputFieldTypesMergeableRule : IEventHandler +{ + public void Handle(InputFieldGroupEvent @event, CompositionContext context) + { + var (_, fieldGroup, typeName) = @event; + + for (var i = 0; i < fieldGroup.Length - 1; i++) + { + var fieldInfoA = fieldGroup[i]; + var fieldInfoB = fieldGroup[i + 1]; + var typeA = fieldInfoA.Field.Type; + var typeB = fieldInfoB.Field.Type; + + if (!ValidationHelper.SameTypeShape(typeA, typeB)) + { + context.Log.Write( + InputFieldTypesNotMergeable( + fieldInfoA.Field, + typeName, + fieldInfoA.Schema, + fieldInfoB.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 cd4eba83903..83b635f20ed 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 @@ -11,46 +11,32 @@ namespace HotChocolate.Fusion.Properties { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class CompositionResources { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal CompositionResources() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HotChocolate.Fusion.Properties.CompositionResources", typeof(CompositionResources).Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.Fusion.Properties.CompositionResources", typeof(CompositionResources).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -59,270 +45,186 @@ internal CompositionResources() { } } - /// - /// Looks up a localized string similar to Pre-merge validation failed. View the composition log for details.. - /// internal static string ErrorHelper_PreMergeValidationFailed { get { return ResourceManager.GetString("ErrorHelper_PreMergeValidationFailed", resourceCulture); } } - /// - /// Looks up a localized string similar to The built-in scalar type '{0}' in schema '{1}' is not accessible.. - /// internal static string LogEntryHelper_DisallowedInaccessibleBuiltInScalar { get { return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleBuiltInScalar", resourceCulture); } } - /// - /// Looks up a localized string similar to The built-in directive argument '{0}' in schema '{1}' is not accessible.. - /// - internal static string LogEntryHelper_DisallowedInaccessibleDirectiveArgument { + internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionType { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleDirectiveArgument", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionType", resourceCulture); } } - /// - /// Looks up a localized string similar to The introspection argument '{0}' in schema '{1}' is not accessible.. - /// - internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionArgument { + internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionField { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionArgument", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionField", resourceCulture); } } - /// - /// Looks up a localized string similar to The introspection field '{0}' in schema '{1}' is not accessible.. - /// - internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionField { + internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionArgument { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionField", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionArgument", resourceCulture); } } - /// - /// Looks up a localized string similar to The introspection type '{0}' in schema '{1}' is not accessible.. - /// - internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionType { + internal static string LogEntryHelper_DisallowedInaccessibleDirectiveArgument { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionType", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleDirectiveArgument", resourceCulture); } } - /// - /// Looks up a localized string similar to The argument with schema coordinate '{0}' has inconsistent default values.. - /// internal static string LogEntryHelper_ExternalArgumentDefaultMismatch { get { return ResourceManager.GetString("LogEntryHelper_ExternalArgumentDefaultMismatch", resourceCulture); } } - /// - /// Looks up a localized string similar to External field '{0}' in schema '{1}' is not defined (non-external) in any other schema.. - /// internal static string LogEntryHelper_ExternalMissingOnBase { get { return ResourceManager.GetString("LogEntryHelper_ExternalMissingOnBase", resourceCulture); } } - /// - /// Looks up a localized string similar to Interface field '{0}' in schema '{1}' must not be marked as external.. - /// internal static string LogEntryHelper_ExternalOnInterface { get { return ResourceManager.GetString("LogEntryHelper_ExternalOnInterface", resourceCulture); } } - /// - /// Looks up a localized string similar to External field '{0}' in schema '{1}' is not referenced by a @provides directive in the schema.. - /// internal static string LogEntryHelper_ExternalUnused { get { return ResourceManager.GetString("LogEntryHelper_ExternalUnused", resourceCulture); } } - /// - /// Looks up a localized string similar to The default value '{0}' of input field '{1}' in schema '{2}' differs from the default value of '{3}' in schema '{4}'.. - /// internal static string LogEntryHelper_InputFieldDefaultMismatch { get { return ResourceManager.GetString("LogEntryHelper_InputFieldDefaultMismatch", resourceCulture); } } - /// - /// Looks up a localized string similar to A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications.. - /// + internal static string LogEntryHelper_InputFieldTypesNotMergeable { + get { + return ResourceManager.GetString("LogEntryHelper_InputFieldTypesNotMergeable", resourceCulture); + } + } + internal static string LogEntryHelper_KeyDirectiveInFieldsArgument { get { return ResourceManager.GetString("LogEntryHelper_KeyDirectiveInFieldsArgument", resourceCulture); } } - /// - /// Looks up a localized string similar to A @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 A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not be a list, interface, or union type.. - /// internal static string LogEntryHelper_KeyFieldsSelectInvalidType { get { return ResourceManager.GetString("LogEntryHelper_KeyFieldsSelectInvalidType", resourceCulture); } } - /// - /// Looks up a localized string similar to A @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 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 The lookup field '{0}' in schema '{1}' must not return a list.. - /// internal static string LogEntryHelper_LookupMustNotReturnList { get { return ResourceManager.GetString("LogEntryHelper_LookupMustNotReturnList", resourceCulture); } } - /// - /// Looks up a localized string similar to The lookup field '{0}' in schema '{1}' should return a nullable type.. - /// internal static string LogEntryHelper_LookupShouldHaveNullableReturnType { get { return ResourceManager.GetString("LogEntryHelper_LookupShouldHaveNullableReturnType", resourceCulture); } } - /// - /// Looks up a localized string similar to Field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'.. - /// internal static string LogEntryHelper_OutputFieldTypesNotMergeable { get { return ResourceManager.GetString("LogEntryHelper_OutputFieldTypesNotMergeable", resourceCulture); } } - /// - /// Looks up a localized string similar to The @provides directive on field '{0}' in schema '{1}' references field '{2}', which must not include directive applications.. - /// internal static string LogEntryHelper_ProvidesDirectiveInFieldsArgument { get { return ResourceManager.GetString("LogEntryHelper_ProvidesDirectiveInFieldsArgument", resourceCulture); } } - /// - /// Looks up a localized string similar to The @provides directive on field '{0}' in schema '{1}' references field '{2}', which must not have arguments.. - /// internal static string LogEntryHelper_ProvidesFieldsHasArguments { get { return ResourceManager.GetString("LogEntryHelper_ProvidesFieldsHasArguments", resourceCulture); } } - /// - /// Looks up a localized string similar to The @provides directive on field '{0}' in schema '{1}' references field '{2}', which must be marked as external.. - /// internal static string LogEntryHelper_ProvidesFieldsMissingExternal { get { return ResourceManager.GetString("LogEntryHelper_ProvidesFieldsMissingExternal", resourceCulture); } } - /// - /// Looks up a localized string similar to The field '{0}' in schema '{1}' includes a @provides directive, but does not return a composite type.. - /// internal static string LogEntryHelper_ProvidesOnNonCompositeField { get { return ResourceManager.GetString("LogEntryHelper_ProvidesOnNonCompositeField", resourceCulture); } } - /// - /// Looks up a localized string similar to The root query type in schema '{0}' must be accessible.. - /// internal static string LogEntryHelper_QueryRootTypeInaccessible { get { return ResourceManager.GetString("LogEntryHelper_QueryRootTypeInaccessible", resourceCulture); } } - /// - /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' references field '{2}', which must not include directive applications.. - /// internal static string LogEntryHelper_RequireDirectiveInFieldsArgument { get { return ResourceManager.GetString("LogEntryHelper_RequireDirectiveInFieldsArgument", resourceCulture); } } - /// - /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' must specify a string value for the 'fields' argument.. - /// internal static string LogEntryHelper_RequireInvalidFieldsType { get { return ResourceManager.GetString("LogEntryHelper_RequireInvalidFieldsType", resourceCulture); } } - /// - /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' contains invalid syntax in the 'fields' argument.. - /// internal static string LogEntryHelper_RequireInvalidSyntax { get { return ResourceManager.GetString("LogEntryHelper_RequireInvalidSyntax", resourceCulture); } } - /// - /// Looks up a localized string similar to The root mutation type in schema '{0}' must be named 'Mutation'.. - /// internal static string LogEntryHelper_RootMutationUsed { get { return ResourceManager.GetString("LogEntryHelper_RootMutationUsed", resourceCulture); } } - /// - /// Looks up a localized string similar to The root query type in schema '{0}' must be named 'Query'.. - /// internal static string LogEntryHelper_RootQueryUsed { get { return ResourceManager.GetString("LogEntryHelper_RootQueryUsed", resourceCulture); } } - /// - /// Looks up a localized string similar to The root subscription type in schema '{0}' must be named 'Subscription'.. - /// internal static string LogEntryHelper_RootSubscriptionUsed { get { return ResourceManager.GetString("LogEntryHelper_RootSubscriptionUsed", resourceCulture); 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 f41a27f757f..bcea05d02c3 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -51,6 +51,9 @@ The default value '{0}' of input field '{1}' in schema '{2}' differs from the default value of '{3}' in schema '{4}'. + + Input field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'. + A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications. diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs index 1b4d745c70e..0079a5f0c20 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs @@ -52,6 +52,7 @@ private CompositionResult MergeSchemaDefinitions(CompositionCo new ExternalOnInterfaceRule(), new ExternalUnusedRule(), new InputFieldDefaultMismatchRule(), + new InputFieldTypesMergeableRule(), new KeyDirectiveInFieldsArgumentRule(), new KeyFieldsHasArgumentsRule(), new KeyFieldsSelectInvalidTypeRule(), diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs new file mode 100644 index 00000000000..f168b22a8fb --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs @@ -0,0 +1,163 @@ +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.PreMergeValidation; +using HotChocolate.Fusion.PreMergeValidation.Rules; + +namespace HotChocolate.Composition.PreMergeValidation.Rules; + +public sealed class InputFieldTypesMergeableRuleTests : CompositionTestBase +{ + private readonly PreMergeValidator _preMergeValidator = + new([new InputFieldTypesMergeableRule()]); + + [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 == "INPUT_FIELD_TYPES_NOT_MERGEABLE")); + Assert.True(context.Log.All(e => e.Severity == LogSeverity.Error)); + } + + public static TheoryData ValidExamplesData() + { + return new TheoryData + { + // In the following example, the field "name" in "AuthorInput" has compatible types + // across source schemas, making them mergeable. + { + [ + """ + # Schema A + input AuthorInput { + name: String! + } + """, + """ + # Schema B + input AuthorInput { + name: String! + } + """ + ] + }, + // The following example shows that fields are mergeable if they have different + // nullability but the named type is the same and the list structure is the same. + { + [ + """ + # Schema A + input AuthorInput { + tags: [String!] + } + """, + """ + # Schema B + input AuthorInput { + tags: [String]! + } + """, + """ + # Schema C + input AuthorInput { + tags: [String] + } + """ + ] + }, + // Multiple input fields. + { + [ + """ + # Schema A + input AuthorInput { + name: String! + tags: [String!] + birthdate: DateTime + } + """, + """ + # Schema B + input AuthorInput { + name: String! + tags: [String]! + birthdate: DateTime + } + """ + ] + }, + }; + } + + public static TheoryData InvalidExamplesData() + { + return new TheoryData + { + // In this example, the field "birthdate" on "AuthorInput" is not mergeable as the + // field has different named types ("String" and "DateTime") across source schemas. + { + [ + """ + # Schema A + input AuthorInput { + birthdate: String! + } + """, + """ + # Schema B + input AuthorInput { + birthdate: DateTime! + } + """ + ], + [ + "Input field 'AuthorInput.birthdate' has a different type shape in schema " + + "'A' than it does in schema 'B'." + ] + }, + // List versus non-list + { + [ + """ + # Schema A + input AuthorInput { + birthdate: String! + } + """, + """ + # Schema B + input AuthorInput { + birthdate: [String!] + } + """ + ], + [ + "Input field 'AuthorInput.birthdate' has a different type shape in schema " + + "'A' than it does in schema 'B'." + ] + } + }; + } +} From 31e140a20808b1c647596d2d603934da07e592d1 Mon Sep 17 00:00:00 2001 From: Glen Date: Mon, 6 Jan 2025 14:56:37 +0200 Subject: [PATCH 2/2] Minor adjustments --- .../CompositionResources.Designer.cs | 147 +++++++++++++++--- .../Properties/CompositionResources.resx | 2 +- .../InputFieldTypesMergeableRuleTests.cs | 27 +--- 3 files changed, 136 insertions(+), 40 deletions(-) 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 83b635f20ed..a4300a30716 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 @@ -11,32 +11,46 @@ namespace HotChocolate.Fusion.Properties { using System; - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class CompositionResources { - private static System.Resources.ResourceManager resourceMan; + private static global::System.Resources.ResourceManager resourceMan; - private static System.Globalization.CultureInfo resourceCulture; + private static global::System.Globalization.CultureInfo resourceCulture; - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal CompositionResources() { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.Fusion.Properties.CompositionResources", typeof(CompositionResources).Assembly); + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HotChocolate.Fusion.Properties.CompositionResources", typeof(CompositionResources).Assembly); resourceMan = temp; } return resourceMan; } } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -45,186 +59,279 @@ internal static System.Globalization.CultureInfo Culture { } } + /// + /// Looks up a localized string similar to Pre-merge validation failed. View the composition log for details.. + /// internal static string ErrorHelper_PreMergeValidationFailed { get { return ResourceManager.GetString("ErrorHelper_PreMergeValidationFailed", resourceCulture); } } + /// + /// Looks up a localized string similar to The built-in scalar type '{0}' in schema '{1}' is not accessible.. + /// internal static string LogEntryHelper_DisallowedInaccessibleBuiltInScalar { get { return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleBuiltInScalar", resourceCulture); } } - internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionType { + /// + /// Looks up a localized string similar to The built-in directive argument '{0}' in schema '{1}' is not accessible.. + /// + internal static string LogEntryHelper_DisallowedInaccessibleDirectiveArgument { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionType", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleDirectiveArgument", resourceCulture); } } - internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionField { + /// + /// Looks up a localized string similar to The introspection argument '{0}' in schema '{1}' is not accessible.. + /// + internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionArgument { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionField", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionArgument", resourceCulture); } } - internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionArgument { + /// + /// Looks up a localized string similar to The introspection field '{0}' in schema '{1}' is not accessible.. + /// + internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionField { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionArgument", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionField", resourceCulture); } } - internal static string LogEntryHelper_DisallowedInaccessibleDirectiveArgument { + /// + /// Looks up a localized string similar to The introspection type '{0}' in schema '{1}' is not accessible.. + /// + internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionType { get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleDirectiveArgument", resourceCulture); + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleIntrospectionType", resourceCulture); } } + /// + /// Looks up a localized string similar to The argument with schema coordinate '{0}' has inconsistent default values.. + /// internal static string LogEntryHelper_ExternalArgumentDefaultMismatch { get { return ResourceManager.GetString("LogEntryHelper_ExternalArgumentDefaultMismatch", resourceCulture); } } + /// + /// Looks up a localized string similar to External field '{0}' in schema '{1}' is not defined (non-external) in any other schema.. + /// internal static string LogEntryHelper_ExternalMissingOnBase { get { return ResourceManager.GetString("LogEntryHelper_ExternalMissingOnBase", resourceCulture); } } + /// + /// Looks up a localized string similar to Interface field '{0}' in schema '{1}' must not be marked as external.. + /// internal static string LogEntryHelper_ExternalOnInterface { get { return ResourceManager.GetString("LogEntryHelper_ExternalOnInterface", resourceCulture); } } + /// + /// Looks up a localized string similar to External field '{0}' in schema '{1}' is not referenced by a @provides directive in the schema.. + /// internal static string LogEntryHelper_ExternalUnused { get { return ResourceManager.GetString("LogEntryHelper_ExternalUnused", resourceCulture); } } + /// + /// Looks up a localized string similar to The default value '{0}' of input field '{1}' in schema '{2}' differs from the default value of '{3}' in schema '{4}'.. + /// internal static string LogEntryHelper_InputFieldDefaultMismatch { get { return ResourceManager.GetString("LogEntryHelper_InputFieldDefaultMismatch", resourceCulture); } } + /// + /// Looks up a localized string similar to The input field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'.. + /// internal static string LogEntryHelper_InputFieldTypesNotMergeable { get { return ResourceManager.GetString("LogEntryHelper_InputFieldTypesNotMergeable", resourceCulture); } } + /// + /// Looks up a localized string similar to A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications.. + /// internal static string LogEntryHelper_KeyDirectiveInFieldsArgument { get { return ResourceManager.GetString("LogEntryHelper_KeyDirectiveInFieldsArgument", resourceCulture); } } + /// + /// Looks up a localized string similar to A @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 A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not be a list, interface, or union type.. + /// internal static string LogEntryHelper_KeyFieldsSelectInvalidType { get { return ResourceManager.GetString("LogEntryHelper_KeyFieldsSelectInvalidType", resourceCulture); } } + /// + /// Looks up a localized string similar to A @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 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 The lookup field '{0}' in schema '{1}' must not return a list.. + /// internal static string LogEntryHelper_LookupMustNotReturnList { get { return ResourceManager.GetString("LogEntryHelper_LookupMustNotReturnList", resourceCulture); } } + /// + /// Looks up a localized string similar to The lookup field '{0}' in schema '{1}' should return a nullable type.. + /// internal static string LogEntryHelper_LookupShouldHaveNullableReturnType { get { return ResourceManager.GetString("LogEntryHelper_LookupShouldHaveNullableReturnType", resourceCulture); } } + /// + /// Looks up a localized string similar to Field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'.. + /// internal static string LogEntryHelper_OutputFieldTypesNotMergeable { get { return ResourceManager.GetString("LogEntryHelper_OutputFieldTypesNotMergeable", resourceCulture); } } + /// + /// Looks up a localized string similar to The @provides directive on field '{0}' in schema '{1}' references field '{2}', which must not include directive applications.. + /// internal static string LogEntryHelper_ProvidesDirectiveInFieldsArgument { get { return ResourceManager.GetString("LogEntryHelper_ProvidesDirectiveInFieldsArgument", resourceCulture); } } + /// + /// Looks up a localized string similar to The @provides directive on field '{0}' in schema '{1}' references field '{2}', which must not have arguments.. + /// internal static string LogEntryHelper_ProvidesFieldsHasArguments { get { return ResourceManager.GetString("LogEntryHelper_ProvidesFieldsHasArguments", resourceCulture); } } + /// + /// Looks up a localized string similar to The @provides directive on field '{0}' in schema '{1}' references field '{2}', which must be marked as external.. + /// internal static string LogEntryHelper_ProvidesFieldsMissingExternal { get { return ResourceManager.GetString("LogEntryHelper_ProvidesFieldsMissingExternal", resourceCulture); } } + /// + /// Looks up a localized string similar to The field '{0}' in schema '{1}' includes a @provides directive, but does not return a composite type.. + /// internal static string LogEntryHelper_ProvidesOnNonCompositeField { get { return ResourceManager.GetString("LogEntryHelper_ProvidesOnNonCompositeField", resourceCulture); } } + /// + /// Looks up a localized string similar to The root query type in schema '{0}' must be accessible.. + /// internal static string LogEntryHelper_QueryRootTypeInaccessible { get { return ResourceManager.GetString("LogEntryHelper_QueryRootTypeInaccessible", resourceCulture); } } + /// + /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' references field '{2}', which must not include directive applications.. + /// internal static string LogEntryHelper_RequireDirectiveInFieldsArgument { get { return ResourceManager.GetString("LogEntryHelper_RequireDirectiveInFieldsArgument", resourceCulture); } } + /// + /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' must specify a string value for the 'fields' argument.. + /// internal static string LogEntryHelper_RequireInvalidFieldsType { get { return ResourceManager.GetString("LogEntryHelper_RequireInvalidFieldsType", resourceCulture); } } + /// + /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' contains invalid syntax in the 'fields' argument.. + /// internal static string LogEntryHelper_RequireInvalidSyntax { get { return ResourceManager.GetString("LogEntryHelper_RequireInvalidSyntax", resourceCulture); } } + /// + /// Looks up a localized string similar to The root mutation type in schema '{0}' must be named 'Mutation'.. + /// internal static string LogEntryHelper_RootMutationUsed { get { return ResourceManager.GetString("LogEntryHelper_RootMutationUsed", resourceCulture); } } + /// + /// Looks up a localized string similar to The root query type in schema '{0}' must be named 'Query'.. + /// internal static string LogEntryHelper_RootQueryUsed { get { return ResourceManager.GetString("LogEntryHelper_RootQueryUsed", resourceCulture); } } + /// + /// Looks up a localized string similar to The root subscription type in schema '{0}' must be named 'Subscription'.. + /// internal static string LogEntryHelper_RootSubscriptionUsed { get { return ResourceManager.GetString("LogEntryHelper_RootSubscriptionUsed", resourceCulture); 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 bcea05d02c3..5daa07cb788 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -52,7 +52,7 @@ The default value '{0}' of input field '{1}' in schema '{2}' differs from the default value of '{3}' in schema '{4}'. - Input field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'. + The input field '{0}' has a different type shape in schema '{1}' than it does in schema '{2}'. A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications. diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs index f168b22a8fb..0321689f437 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/InputFieldTypesMergeableRuleTests.cs @@ -45,20 +45,18 @@ public static TheoryData ValidExamplesData() { return new TheoryData { - // In the following example, the field "name" in "AuthorInput" has compatible types - // across source schemas, making them mergeable. + // In this example, the field "name" in "AuthorInput" has compatible types across source + // schemas, making them mergeable. { [ """ - # Schema A input AuthorInput { name: String! } """, """ - # Schema B input AuthorInput { - name: String! + name: String } """ ] @@ -68,19 +66,16 @@ input AuthorInput { { [ """ - # Schema A input AuthorInput { tags: [String!] } """, """ - # Schema B input AuthorInput { tags: [String]! } """, """ - # Schema C input AuthorInput { tags: [String] } @@ -91,7 +86,6 @@ input AuthorInput { { [ """ - # Schema A input AuthorInput { name: String! tags: [String!] @@ -99,11 +93,10 @@ input AuthorInput { } """, """ - # Schema B input AuthorInput { - name: String! + name: String tags: [String]! - birthdate: DateTime + birthdate: DateTime! } """ ] @@ -115,18 +108,16 @@ public static TheoryData InvalidExamplesData() { return new TheoryData { - // In this example, the field "birthdate" on "AuthorInput" is not mergeable as the - // field has different named types ("String" and "DateTime") across source schemas. + // In this example, the field "birthdate" on "AuthorInput" is not mergeable as the field + // has different named types ("String" and "DateTime") across source schemas. { [ """ - # Schema A input AuthorInput { birthdate: String! } """, """ - # Schema B input AuthorInput { birthdate: DateTime! } @@ -137,17 +128,15 @@ input AuthorInput { "'A' than it does in schema 'B'." ] }, - // List versus non-list + // List versus non-list. { [ """ - # Schema A input AuthorInput { birthdate: String! } """, """ - # Schema B input AuthorInput { birthdate: [String!] }