From a0a2e3ad37b0681b65fd6f61964e117f95ddf078 Mon Sep 17 00:00:00 2001 From: NOlbert <45466413+N-Olbert@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:26:40 +0200 Subject: [PATCH 1/7] Honor GraphQLName`s and fluent definition type names for ID --- .../Types/Relay/Attributes/IDAttribute.cs | 29 +--- .../Extensions/RelayIdFieldExtensions.cs | 17 +- .../Relay/Extensions/RelayIdFieldHelpers.cs | 155 ++++++++++++++---- .../Relay/NodeResolverTypeInterceptor.cs | 3 +- .../Types/Relay/IdDescriptorTests.cs | 136 ++++++++++++++- ...ptorTests.Id_Honors_CustomGraphQLName.snap | 42 +++++ ..._Honors_CustomGraphQLName_InvalidArgs.snap | 53 ++++++ 7 files changed, 361 insertions(+), 74 deletions(-) create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Attributes/IDAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Attributes/IDAttribute.cs index 3b3ac1f43b5..153be064c82 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Attributes/IDAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Attributes/IDAttribute.cs @@ -170,31 +170,8 @@ public class IDAttribute : DescriptorAttribute /// public IDAttribute() { - TypeName = typeof(T).Name; } - /// - /// With the property you can override the type name - /// of the ID. This is useful to rewrite a parameter of a mutation or query, to a specific - /// id. - /// - /// - /// - /// A field can be rewritten to an ID by adding [ID] to the resolver. - /// - /// - /// public class UserQuery - /// { - /// public User GetUserById([ID("User")] int id) => //.... - /// } - /// - /// - /// The argument is rewritten to ID and expect an ID of type User. - /// Assuming `User.id` has the value 1. The following string is base64 encoded - /// - /// - public string? TypeName { get; } - /// protected internal override void TryConfigure( IDescriptorContext context, @@ -204,13 +181,13 @@ protected internal override void TryConfigure( switch (descriptor) { case IInputFieldDescriptor d when element is PropertyInfo: - d.ID(TypeName); + d.ID(); break; case IArgumentDescriptor d when element is ParameterInfo: - d.ID(TypeName); + d.ID(); break; case IObjectFieldDescriptor d when element is MemberInfo: - d.ID(TypeName); + d.ID(); break; case IInterfaceFieldDescriptor d when element is MemberInfo: d.ID(); diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs index 63d13e5dfdd..a679851db2c 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs @@ -76,8 +76,7 @@ public static IInputFieldDescriptor ID( return descriptor; } - /// - /// the descriptor + /// /// /// the type from which the type name is derived /// @@ -85,7 +84,7 @@ public static IInputFieldDescriptor ID(this IInputFieldDescriptor descriptor) { ArgumentNullException.ThrowIfNull(descriptor); - RelayIdFieldHelpers.ApplyIdToField(descriptor, typeof(T).Name); + RelayIdFieldHelpers.ApplyIdToField(descriptor); return descriptor; } @@ -106,8 +105,7 @@ public static IArgumentDescriptor ID( return descriptor; } - /// - /// the descriptor + /// /// /// the type from which the type name is derived /// @@ -115,7 +113,7 @@ public static IArgumentDescriptor ID(this IArgumentDescriptor descriptor) { ArgumentNullException.ThrowIfNull(descriptor); - RelayIdFieldHelpers.ApplyIdToField(descriptor, typeof(T).Name); + RelayIdFieldHelpers.ApplyIdToField(descriptor); return descriptor; } @@ -136,16 +134,15 @@ public static IObjectFieldDescriptor ID( return descriptor; } - /// - /// the descriptor + /// /// /// the type from which the type name is derived /// public static IObjectFieldDescriptor ID(this IObjectFieldDescriptor descriptor) { ArgumentNullException.ThrowIfNull(descriptor); - - RelayIdFieldHelpers.ApplyIdToField(descriptor, typeof(T).Name); + + RelayIdFieldHelpers.ApplyIdToField(descriptor); return descriptor; } diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs index a23d670e26a..d9cbf4f1720 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs @@ -25,21 +25,16 @@ internal static class RelayIdFieldHelpers /// public static void ApplyIdToField( IDescriptor descriptor, - string? typeName = null) - { - ArgumentNullException.ThrowIfNull(descriptor); - - var extend = descriptor.Extend(); + string? typeName = null) => + ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create(typeName)); - // rewrite type - extend.OnBeforeCreate(RewriteConfiguration); - - // add serializer if globalID support is enabled. - if (extend.Context.Features.Get()?.IsEnabled == true) - { - extend.OnBeforeCompletion((c, d) => AddSerializerToInputField(c, d, typeName)); - } - } + /// + /// + /// the type from which the type name is derived + /// + public static void ApplyIdToField( + IDescriptor descriptor) => + ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create()); /// /// Applies the .ID() to an argument @@ -50,24 +45,16 @@ public static void ApplyIdToField( /// public static void ApplyIdToField( IDescriptor descriptor, - string? typeName = null) - { - ArgumentNullException.ThrowIfNull(descriptor); - - // rewrite type - descriptor.Extend().OnBeforeCreate(RewriteConfiguration); - - if (descriptor is IDescriptor objectFieldDescriptor) - { - var extend = objectFieldDescriptor.Extend(); + string? typeName = null) => + ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create(typeName)); - // add serializer if globalID support is enabled. - if (extend.Context.Features.Get()?.IsEnabled == true) - { - ApplyIdToField(extend.Configuration, typeName); - } - } - } + /// + /// + /// the type from which the type name is derived + /// + public static void ApplyIdToField( + IDescriptor descriptor) => + ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create()); /// /// Applies the .ID() to an argument @@ -78,7 +65,8 @@ public static void ApplyIdToField( /// internal static void ApplyIdToField( ObjectFieldConfiguration configuration, - string? typeName = null) + NodeIdNameDefinitionUnion? nameDefinition = null, + TypeReference? dependsOn = null) { var placeholder = new ResultFormatterConfiguration( (_, r) => r, @@ -91,12 +79,75 @@ internal static void ApplyIdToField( ctx, (ObjectFieldConfiguration)def, placeholder, - typeName), + nameDefinition), configuration, - ApplyConfigurationOn.BeforeCompletion); + ApplyConfigurationOn.BeforeCompletion, + typeReference: dependsOn); configuration.Tasks.Add(configurationTask); } + internal static void ApplyIdToFieldCore( + IDescriptor descriptor, + NodeIdNameDefinitionUnion? nameDefinition) + { + ArgumentNullException.ThrowIfNull(descriptor); + + // rewrite type + descriptor.Extend().OnBeforeCreate(RewriteConfiguration); + + if (descriptor is IDescriptor objectFieldDescriptor) + { + var extend = objectFieldDescriptor.Extend(); + + // add serializer if globalID support is enabled. + if (extend.Context.Features.Get()?.IsEnabled == true) + { + if (nameDefinition?.Type != null) + { + var dependsOn = extend.Context.TypeInspector.GetTypeRef(nameDefinition.Type); + ApplyIdToField(extend.Configuration, nameDefinition, dependsOn); + } + else + { + ApplyIdToField(extend.Configuration, nameDefinition); + } + } + } + } + + public static void ApplyIdToFieldCore( + IDescriptor descriptor, + NodeIdNameDefinitionUnion? nameDefinition) + { + ArgumentNullException.ThrowIfNull(descriptor); + + var extend = descriptor.Extend(); + + // rewrite type + extend.OnBeforeCreate(RewriteConfiguration); + + // add serializer if globalID support is enabled. + if (extend.Context.Features.Get()?.IsEnabled == true) + { + if (nameDefinition?.Type == null) + { + extend.OnBeforeCompletion((c, d) => + AddSerializerToInputField(c, d, nameDefinition)); + } + else + { + var dependsOn = extend.Context.TypeInspector.GetTypeRef(nameDefinition.Type); + + var configurationTask = new OnCompleteTypeSystemConfigurationTask( + (ctx, def) => AddSerializerToInputField(ctx, (ArgumentConfiguration)def, nameDefinition), + extend.Configuration, + ApplyConfigurationOn.BeforeCompletion, + typeReference: dependsOn); + + extend.Configuration.Tasks.Add(configurationTask); + } + } + } private static void RewriteConfiguration( IDescriptorContext context, @@ -139,7 +190,7 @@ private static IExtendedType RewriteType(ITypeInspector typeInspector, ITypeInfo internal static void AddSerializerToInputField( ITypeCompletionContext completionContext, ArgumentConfiguration configuration, - string? typeName) + NodeIdNameDefinitionUnion? nameDefinition) { var typeInspector = completionContext.TypeInspector; IExtendedType? resultType; @@ -167,6 +218,8 @@ internal static void AddSerializerToInputField( completionContext.Type); } + var typeName = GetIdTypeName(completionContext, nameDefinition, typeInspector); + var validateType = typeName is not null; typeName ??= completionContext.Type.Name; SetSerializerInfos(completionContext.DescriptorContext, typeName, resultType); @@ -178,7 +231,7 @@ private static void AddSerializerToObjectField( ITypeCompletionContext completionContext, ObjectFieldConfiguration configuration, ResultFormatterConfiguration placeholder, - string? typeName) + NodeIdNameDefinitionUnion? nameDefinition) { var typeInspector = completionContext.TypeInspector; IExtendedType? resultType; @@ -201,6 +254,8 @@ private static void AddSerializerToObjectField( var serializerAccessor = completionContext.DescriptorContext.NodeIdSerializerAccessor; var index = configuration.FormatterConfigurations.IndexOf(placeholder); + var typeName = GetIdTypeName(completionContext, nameDefinition, typeInspector); + typeName ??= completionContext.Type.Name; SetSerializerInfos(completionContext.DescriptorContext, typeName, resultType); @@ -281,4 +336,32 @@ internal static void SetSerializerInfos(IDescriptorContext context, string typeN var feature = context.Features.GetOrSet(); feature.NodeIdTypes.TryAdd(typeName, runtimeTypeInfo.NamedType); } + + private static string? GetIdTypeName(ITypeCompletionContext completionContext, + NodeIdNameDefinitionUnion? nameDefinition, + ITypeInspector typeInspector) + { + var typeName = nameDefinition?.Literal; + if (nameDefinition?.Type is { } t) + { + var referencedType = typeInspector.GetType(t); + var foo = completionContext.GetType(TypeReference.Create(referencedType)); + typeName = foo.NamedType().Name; + } + + return typeName; + } +} + +/// +/// A discriminated union, containing either a literal or a type that defines +/// the name of the node identifier. +/// +internal class NodeIdNameDefinitionUnion(string? Literal, Type? Type) +{ + public static NodeIdNameDefinitionUnion? Create(string? literal) => + literal == null ? null : new NodeIdNameDefinitionUnion(literal, null); + + public static NodeIdNameDefinitionUnion Create() => + new NodeIdNameDefinitionUnion(null, typeof(T)); } diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs index e6a7b0310da..8c2c47da9c1 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Reflection.Metadata; using HotChocolate.Configuration; using HotChocolate.Features; using HotChocolate.Language; @@ -157,7 +158,7 @@ public override void OnAfterMergeTypeExtensions() RelayIdFieldHelpers.AddSerializerToInputField( CompletionContext, argument, - fieldTypeDef.Name); + NodeIdNameDefinitionUnion.Create(fieldTypeDef.Name)); // As with the id argument, we also want to make sure that the ID field of // the field result type is a non-null ID type. diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index 8d7f1ece92a..be692961f02 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -93,6 +93,70 @@ public void Id_Type_Is_Correctly_Inferred() .MatchSnapshot(); } + [Fact] + public async Task Id_Honors_CustomGraphQLName() + { + var services = new ServiceCollection() + .AddGraphQL() + .AddMutationType() + .AddMutationConventions() + .ModifyOptions(o => o.StrictValidation = false) + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .AddErrorFilter(x => new Error { Message = x.Message }) + .AddGlobalObjectIdentification(); + + const string userId = "UmVuYW1lZFVzZXI6MQ=="; // "RenamedUser:1" + const string fooId = "Rm9vRm9vOjM="; // "FooFoo:3" + const string fluentFooId = "Rm9vRm9vRmx1ZW50OjQ="; // "FooFooFluent:4" + const string singleTypeFluentFooId = "Rm9vRm9vRmx1ZW50U2luZ2xlOjQ="; // "FooFooFluentSingle:4" + var result = await services + .ExecuteRequestAsync( + $$""" + mutation { + out: doSomethingElse { + renamedUser { + userId + explicitUserId + fooId + fluentFooId + singleTypeFluentFooId + userIdMethod + explicitUserIdMethod + fooIdMethod + fluentFooIdMethod + singleTypeFluentFooIdMethod + } + } + + validAnyIdInput1: acceptsAnyId(input: { id:"{{userId}}"}) { int } + validAnyIdInput2: acceptsAnyId(input: { id:"{{fooId}}"}) { int } + validAnyIdInput3: acceptsAnyId(input: { id:"{{fluentFooId}}"}) { int } + validAnyIdInput4: acceptsAnyId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + + validUserIdInput: acceptsUserId(input: { id:"{{userId}}"}) { int } + validFooIdInput: acceptsFooId(input: { id:"{{fooId}}"}) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{fluentFooId}}"}) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + } + """); + + result.MatchSnapshot(); + + // verify throw for incorrect id + result = await services + .ExecuteRequestAsync( + $$""" + mutation { + validUserIdInput: acceptsUserId(input: { id:"{{fooId}}"}) { int } + validFooIdInput: acceptsFooId(input: { id:"{{fluentFooId}}"}) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{userId}}"}) { int } + } + """); + + result.MatchSnapshot(postFix: "InvalidArgs"); + } + private static byte[] Combine(ReadOnlySpan s1, ReadOnlySpan s2) { var buffer = new byte[s1.Length + s2.Length]; @@ -195,5 +259,75 @@ public interface IFooPayload string AnotherId { get; set; } } - private class Another; + public class Another + { + public string Id { get; set; } + } + + public class MutationWithRenamedIds + { + [GraphQLName("doSomethingElse")] + public IdContainer DoSomething() + { + return new IdContainer(); + } + + public int? AcceptsAnyId([ID] int? id = 0) => id; + public int? AcceptsUserId([ID] int? id = 0) => id; + public int? AcceptsFooId([ID] int? id = 0) => id; + public int? AcceptsFluentFooId([ID] int? id = 0) => id; + public int? AcceptsSingleTypeFluentFooId([ID] int? id = 0) => id; + } + + [GraphQLName("RenamedUser")] + public class IdContainer + { + [ID] + public int UserId { get; set; } = 1; + + [ID] + public int ExplicitUserId { get; set; } = 2; + + [ID] + public int FooId { get; set; } = 3; + + [ID] + public int FluentFooId { get; set; } = 4; + + [ID] + public int SingleTypeFluentFooId { get; set; } = 4; + + [ID] + public int UserIdMethod() => 1; + + [ID] + public int ExplicitUserIdMethod() => 2; + + [ID] + public int FooIdMethod() => 3; + + [ID] + public int FluentFooIdMethod() => 4; + + [ID] + public int SingleTypeFluentFooIdMethod() => 4; + } + + [GraphQLName("FooFoo")] + public class RenamedFoo; + + public class FluentRenamedFoo; + + public class FluentRenamedFooType : ObjectType + { + protected override void Configure(IObjectTypeDescriptor descriptor) => + descriptor.Name("FooFooFluent"); + } + + public class SingleTypeFluentRenamedFooType : ObjectType + { + protected override void Configure(IObjectTypeDescriptor descriptor) => + descriptor.Name("FooFooFluentSingle") + .BindFieldsExplicitly(); + } } diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap new file mode 100644 index 00000000000..2d22e5d59cb --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap @@ -0,0 +1,42 @@ +{ + "data": { + "out": { + "renamedUser": { + "userId": "UmVuYW1lZFVzZXI6MQ==", + "explicitUserId": "UmVuYW1lZFVzZXI6Mg==", + "fooId": "Rm9vRm9vOjM=", + "fluentFooId": "Rm9vRm9vRmx1ZW50OjQ=", + "singleTypeFluentFooId": "Rm9vRm9vRmx1ZW50U2luZ2xlOjQ=", + "userIdMethod": "UmVuYW1lZFVzZXI6MQ==", + "explicitUserIdMethod": "UmVuYW1lZFVzZXI6Mg==", + "fooIdMethod": "Rm9vRm9vOjM=", + "fluentFooIdMethod": "Rm9vRm9vRmx1ZW50OjQ=", + "singleTypeFluentFooIdMethod": "Rm9vRm9vRmx1ZW50U2luZ2xlOjQ=" + } + }, + "validAnyIdInput1": { + "int": 1 + }, + "validAnyIdInput2": { + "int": 3 + }, + "validAnyIdInput3": { + "int": 4 + }, + "validAnyIdInput4": { + "int": 4 + }, + "validUserIdInput": { + "int": 1 + }, + "validFooIdInput": { + "int": 3 + }, + "validFluentFooIdInput": { + "int": 4 + }, + "validSingleTypeFluentFooIdInput": { + "int": 4 + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap new file mode 100644 index 00000000000..dd746ccc21a --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap @@ -0,0 +1,53 @@ +{ + "errors": [ + { + "message": "The node id type name `FooFoo` does not match the expected type name `RenamedUser`.", + "locations": [ + { + "line": 2, + "column": 5 + } + ], + "path": [ + "validUserIdInput" + ] + }, + { + "message": "The node id type name `FooFooFluent` does not match the expected type name `FooFoo`.", + "locations": [ + { + "line": 3, + "column": 5 + } + ], + "path": [ + "validFooIdInput" + ] + }, + { + "message": "The node id type name `FooFooFluentSingle` does not match the expected type name `FooFooFluent`.", + "locations": [ + { + "line": 4, + "column": 5 + } + ], + "path": [ + "validFluentFooIdInput" + ] + }, + { + "message": "The node id type name `RenamedUser` does not match the expected type name `FooFooFluentSingle`.", + "locations": [ + { + "line": 5, + "column": 5 + } + ], + "path": [ + "validSingleTypeFluentFooIdInput" + ] + } + ], + "data": null +} From b6f3795b2be8a2b0ef05cb9d0046cb6b3a27f6e3 Mon Sep 17 00:00:00 2001 From: NOlbert <45466413+N-Olbert@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:13:09 +0200 Subject: [PATCH 2/7] Reworked tests --- .../Extensions/NodeIdNameDefinitionUnion.cs | 15 ++ .../Relay/Extensions/RelayIdFieldHelpers.cs | 13 -- .../Types/Relay/IdDescriptorTests.cs | 172 ++++++++++++------ ...ptorTests.Id_Honors_CustomGraphQLName.snap | 42 ----- ..._Throws_On_InvalidInputs_InvalidArgs.snap} | 0 ...d_Honors_CustomTypeNaming_ValidInputs.snap | 28 +++ 6 files changed, 159 insertions(+), 111 deletions(-) create mode 100644 src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs delete mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap rename src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/{IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap => IdDescriptorTests.Id_Honors_CustomTypeNaming_Throws_On_InvalidInputs_InvalidArgs.snap} (100%) create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomTypeNaming_ValidInputs.snap diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs new file mode 100644 index 00000000000..10da17849c4 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs @@ -0,0 +1,15 @@ +#nullable enable +namespace HotChocolate.Types.Relay; + +/// +/// A discriminated union, containing either a literal or a type that defines +/// the name of the node identifier. +/// +internal record NodeIdNameDefinitionUnion(string? Literal, Type? Type) +{ + public static NodeIdNameDefinitionUnion? Create(string? literal) => + literal == null ? null : new NodeIdNameDefinitionUnion(literal, null); + + public static NodeIdNameDefinitionUnion Create() => + new NodeIdNameDefinitionUnion(null, typeof(T)); +} diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs index d9cbf4f1720..02ddd893102 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs @@ -352,16 +352,3 @@ internal static void SetSerializerInfos(IDescriptorContext context, string typeN return typeName; } } - -/// -/// A discriminated union, containing either a literal or a type that defines -/// the name of the node identifier. -/// -internal class NodeIdNameDefinitionUnion(string? Literal, Type? Type) -{ - public static NodeIdNameDefinitionUnion? Create(string? literal) => - literal == null ? null : new NodeIdNameDefinitionUnion(literal, null); - - public static NodeIdNameDefinitionUnion Create() => - new NodeIdNameDefinitionUnion(null, typeof(T)); -} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index be692961f02..28a763e6d95 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -94,66 +94,126 @@ public void Id_Type_Is_Correctly_Inferred() } [Fact] - public async Task Id_Honors_CustomGraphQLName() + public async Task Id_Honors_CustomTypeNaming_OutputFields() { + // arrange var services = new ServiceCollection() .AddGraphQL() .AddMutationType() .AddMutationConventions() .ModifyOptions(o => o.StrictValidation = false) - .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) - .AddErrorFilter(x => new Error { Message = x.Message }) .AddGlobalObjectIdentification(); - const string userId = "UmVuYW1lZFVzZXI6MQ=="; // "RenamedUser:1" - const string fooId = "Rm9vRm9vOjM="; // "FooFoo:3" - const string fluentFooId = "Rm9vRm9vRmx1ZW50OjQ="; // "FooFooFluent:4" - const string singleTypeFluentFooId = "Rm9vRm9vRmx1ZW50U2luZ2xlOjQ="; // "FooFooFluentSingle:4" - var result = await services - .ExecuteRequestAsync( - $$""" - mutation { - out: doSomethingElse { - renamedUser { - userId - explicitUserId - fooId - fluentFooId - singleTypeFluentFooId - userIdMethod - explicitUserIdMethod - fooIdMethod - fluentFooIdMethod - singleTypeFluentFooIdMethod - } - } - - validAnyIdInput1: acceptsAnyId(input: { id:"{{userId}}"}) { int } - validAnyIdInput2: acceptsAnyId(input: { id:"{{fooId}}"}) { int } - validAnyIdInput3: acceptsAnyId(input: { id:"{{fluentFooId}}"}) { int } - validAnyIdInput4: acceptsAnyId(input: { id:"{{singleTypeFluentFooId}}"}) { int } - - validUserIdInput: acceptsUserId(input: { id:"{{userId}}"}) { int } - validFooIdInput: acceptsFooId(input: { id:"{{fooId}}"}) { int } - validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{fluentFooId}}"}) { int } - validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + // act + var result = await services.ExecuteRequestAsync( + """ + mutation { + out: doSomethingElse { + renamedUser { + userId + explicitUserId + fooId + fluentFooId + singleTypeFluentFooId + userIdMethod + explicitUserIdMethod + fooIdMethod + fluentFooIdMethod + singleTypeFluentFooIdMethod + } + } + } + """); + + // assert + result.MatchInlineSnapshot( + $$""" + { + "data": { + "out": { + "renamedUser": { + "userId": "{{Convert.ToBase64String("RenamedUser:1"u8)}}", + "explicitUserId": "{{Convert.ToBase64String("RenamedUser:1"u8)}}", + "fooId": "{{Convert.ToBase64String("FooFoo:1"u8)}}", + "fluentFooId": "{{Convert.ToBase64String("FooFooFluent:1"u8)}}", + "singleTypeFluentFooId": "{{Convert.ToBase64String("FooFooFluentSingle:1"u8)}}", + "userIdMethod": "{{Convert.ToBase64String("RenamedUser:1"u8)}}", + "explicitUserIdMethod": "{{Convert.ToBase64String("RenamedUser:1"u8)}}", + "fooIdMethod": "{{Convert.ToBase64String("FooFoo:1"u8)}}", + "fluentFooIdMethod": "{{Convert.ToBase64String("FooFooFluent:1"u8)}}", + "singleTypeFluentFooIdMethod": "{{Convert.ToBase64String("FooFooFluentSingle:1"u8)}}" } - """); + } + } + } + """ + ); + } + [Fact] + public async Task Id_Honors_CustomTypeNaming_ValidInputs() + { + // arrange + var services = new ServiceCollection() + .AddGraphQL() + .AddMutationType() + .AddMutationConventions() + .ModifyOptions(o => o.StrictValidation = false) + .AddGlobalObjectIdentification(); + + var userId = Convert.ToBase64String("RenamedUser:100"u8); + var fooId = Convert.ToBase64String("FooFoo:300"u8); + var fluentFooId = Convert.ToBase64String("FooFooFluent:500"u8); + var singleTypeFluentFooId = Convert.ToBase64String("FooFooFluentSingle:600"u8); + + // act + var result = + await services.ExecuteRequestAsync($$""" + mutation { + validAnyIdInput1: acceptsAnyId(input: { id:"{{userId}}"}) { int } + validAnyIdInput2: acceptsAnyId(input: { id:"{{fooId}}"}) { int } + validAnyIdInput3: acceptsAnyId(input: { id:"{{fluentFooId}}"}) { int } + validAnyIdInput4: acceptsAnyId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + + validUserIdInput: acceptsUserId(input: { id:"{{userId}}"}) { int } + validFooIdInput: acceptsFooId(input: { id:"{{fooId}}"}) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{fluentFooId}}"}) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + } + """); + // assert result.MatchSnapshot(); + } - // verify throw for incorrect id - result = await services - .ExecuteRequestAsync( - $$""" - mutation { - validUserIdInput: acceptsUserId(input: { id:"{{fooId}}"}) { int } - validFooIdInput: acceptsFooId(input: { id:"{{fluentFooId}}"}) { int } - validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } - validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{userId}}"}) { int } - } - """); + [Fact] + public async Task Id_Honors_CustomTypeNaming_Throws_On_InvalidInputs() + { + // arrange + var services = new ServiceCollection() + .AddGraphQL() + .AddMutationType() + .AddMutationConventions() + .ModifyOptions(o => o.StrictValidation = false) + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .AddErrorFilter(x => new Error { Message = x.Message }) + .AddGlobalObjectIdentification(); + + var userId = Convert.ToBase64String("RenamedUser:100"u8); + var fooId = Convert.ToBase64String("FooFoo:300"u8); + var fluentFooId = Convert.ToBase64String("FooFooFluent:500"u8); + var singleTypeFluentFooId = Convert.ToBase64String("FooFooFluentSingle:600"u8); + // act + var result = await services.ExecuteRequestAsync($$""" + mutation { + validUserIdInput: acceptsUserId(input: { id:"{{fooId}}"}) { int } + validFooIdInput: acceptsFooId(input: { id:"{{fluentFooId}}"}) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{userId}}"}) { int } + } + """); + + // assert result.MatchSnapshot(postFix: "InvalidArgs"); } @@ -286,31 +346,31 @@ public class IdContainer public int UserId { get; set; } = 1; [ID] - public int ExplicitUserId { get; set; } = 2; + public int ExplicitUserId { get; set; } = 1; [ID] - public int FooId { get; set; } = 3; + public int FooId { get; set; } = 1; [ID] - public int FluentFooId { get; set; } = 4; + public int FluentFooId { get; set; } = 1; [ID] - public int SingleTypeFluentFooId { get; set; } = 4; + public int SingleTypeFluentFooId { get; set; } = 1; [ID] public int UserIdMethod() => 1; [ID] - public int ExplicitUserIdMethod() => 2; + public int ExplicitUserIdMethod() => 1; [ID] - public int FooIdMethod() => 3; + public int FooIdMethod() => 1; [ID] - public int FluentFooIdMethod() => 4; + public int FluentFooIdMethod() => 1; [ID] - public int SingleTypeFluentFooIdMethod() => 4; + public int SingleTypeFluentFooIdMethod() => 1; } [GraphQLName("FooFoo")] @@ -323,7 +383,7 @@ public class FluentRenamedFooType : ObjectType protected override void Configure(IObjectTypeDescriptor descriptor) => descriptor.Name("FooFooFluent"); } - + public class SingleTypeFluentRenamedFooType : ObjectType { protected override void Configure(IObjectTypeDescriptor descriptor) => diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap deleted file mode 100644 index 2d22e5d59cb..00000000000 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName.snap +++ /dev/null @@ -1,42 +0,0 @@ -{ - "data": { - "out": { - "renamedUser": { - "userId": "UmVuYW1lZFVzZXI6MQ==", - "explicitUserId": "UmVuYW1lZFVzZXI6Mg==", - "fooId": "Rm9vRm9vOjM=", - "fluentFooId": "Rm9vRm9vRmx1ZW50OjQ=", - "singleTypeFluentFooId": "Rm9vRm9vRmx1ZW50U2luZ2xlOjQ=", - "userIdMethod": "UmVuYW1lZFVzZXI6MQ==", - "explicitUserIdMethod": "UmVuYW1lZFVzZXI6Mg==", - "fooIdMethod": "Rm9vRm9vOjM=", - "fluentFooIdMethod": "Rm9vRm9vRmx1ZW50OjQ=", - "singleTypeFluentFooIdMethod": "Rm9vRm9vRmx1ZW50U2luZ2xlOjQ=" - } - }, - "validAnyIdInput1": { - "int": 1 - }, - "validAnyIdInput2": { - "int": 3 - }, - "validAnyIdInput3": { - "int": 4 - }, - "validAnyIdInput4": { - "int": 4 - }, - "validUserIdInput": { - "int": 1 - }, - "validFooIdInput": { - "int": 3 - }, - "validFluentFooIdInput": { - "int": 4 - }, - "validSingleTypeFluentFooIdInput": { - "int": 4 - } - } -} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomTypeNaming_Throws_On_InvalidInputs_InvalidArgs.snap similarity index 100% rename from src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomGraphQLName_InvalidArgs.snap rename to src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomTypeNaming_Throws_On_InvalidInputs_InvalidArgs.snap diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomTypeNaming_ValidInputs.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomTypeNaming_ValidInputs.snap new file mode 100644 index 00000000000..860067cd9f2 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/__snapshots__/IdDescriptorTests.Id_Honors_CustomTypeNaming_ValidInputs.snap @@ -0,0 +1,28 @@ +{ + "data": { + "validAnyIdInput1": { + "int": 100 + }, + "validAnyIdInput2": { + "int": 300 + }, + "validAnyIdInput3": { + "int": 500 + }, + "validAnyIdInput4": { + "int": 600 + }, + "validUserIdInput": { + "int": 100 + }, + "validFooIdInput": { + "int": 300 + }, + "validFluentFooIdInput": { + "int": 500 + }, + "validSingleTypeFluentFooIdInput": { + "int": 600 + } + } +} From 6c755c8a5c33edc247341054c8e0972fce14b53c Mon Sep 17 00:00:00 2001 From: NOlbert <45466413+N-Olbert@users.noreply.github.com> Date: Mon, 1 Sep 2025 19:45:52 +0200 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Glen --- .../Relay/Extensions/RelayIdFieldHelpers.cs | 1 + .../Relay/NodeResolverTypeInterceptor.cs | 1 - .../Types/Relay/IdDescriptorTests.cs | 47 ++++++++++--------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs index b606d0131e9..44977a55cb9 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs @@ -84,6 +84,7 @@ internal static void ApplyIdToField( configuration.Tasks.Add(configurationTask); } + internal static void ApplyIdToFieldCore( IDescriptor descriptor, NodeIdNameDefinitionUnion? nameDefinition) diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs index 77fb2ea1e9b..17a15a95b28 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Reflection.Metadata; using HotChocolate.Configuration; using HotChocolate.Features; using HotChocolate.Language; diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index 28a763e6d95..5f9a83e9232 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -146,8 +146,7 @@ public async Task Id_Honors_CustomTypeNaming_OutputFields() } } } - """ - ); + """); } [Fact] public async Task Id_Honors_CustomTypeNaming_ValidInputs() @@ -167,19 +166,20 @@ public async Task Id_Honors_CustomTypeNaming_ValidInputs() // act var result = - await services.ExecuteRequestAsync($$""" - mutation { - validAnyIdInput1: acceptsAnyId(input: { id:"{{userId}}"}) { int } - validAnyIdInput2: acceptsAnyId(input: { id:"{{fooId}}"}) { int } - validAnyIdInput3: acceptsAnyId(input: { id:"{{fluentFooId}}"}) { int } - validAnyIdInput4: acceptsAnyId(input: { id:"{{singleTypeFluentFooId}}"}) { int } - - validUserIdInput: acceptsUserId(input: { id:"{{userId}}"}) { int } - validFooIdInput: acceptsFooId(input: { id:"{{fooId}}"}) { int } - validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{fluentFooId}}"}) { int } - validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } - } - """); + await services.ExecuteRequestAsync( + $$""" + mutation { + validAnyIdInput1: acceptsAnyId(input: { id: "{{userId}}" }) { int } + validAnyIdInput2: acceptsAnyId(input: { id: "{{fooId}}" }) { int } + validAnyIdInput3: acceptsAnyId(input: { id: "{{fluentFooId}}" }) { int } + validAnyIdInput4: acceptsAnyId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + + validUserIdInput: acceptsUserId(input: { id: "{{userId}}" }) { int } + validFooIdInput: acceptsFooId(input: { id: "{{fooId}}" }) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id: "{{fluentFooId}}" }) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + } + """); // assert result.MatchSnapshot(); @@ -204,14 +204,15 @@ public async Task Id_Honors_CustomTypeNaming_Throws_On_InvalidInputs() var singleTypeFluentFooId = Convert.ToBase64String("FooFooFluentSingle:600"u8); // act - var result = await services.ExecuteRequestAsync($$""" - mutation { - validUserIdInput: acceptsUserId(input: { id:"{{fooId}}"}) { int } - validFooIdInput: acceptsFooId(input: { id:"{{fluentFooId}}"}) { int } - validFluentFooIdInput: acceptsFluentFooId(input: { id:"{{singleTypeFluentFooId}}"}) { int } - validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id:"{{userId}}"}) { int } - } - """); + var result = await services.ExecuteRequestAsync( + $$""" + mutation { + validUserIdInput: acceptsUserId(input: { id: "{{fooId}}" }) { int } + validFooIdInput: acceptsFooId(input: { id: "{{fluentFooId}}" }) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id: "{{userId}}" }) { int } + } + """); // assert result.MatchSnapshot(postFix: "InvalidArgs"); From 09b2c83af58d2e05cd04132aaf5daa3c507f5efe Mon Sep 17 00:00:00 2001 From: NOlbert <45466413+N-Olbert@users.noreply.github.com> Date: Mon, 1 Sep 2025 20:00:22 +0200 Subject: [PATCH 4/7] Adjustments from review --- .../src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs | 2 +- .../Types/Relay/{Extensions => }/NodeIdNameDefinitionUnion.cs | 1 - .../Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) rename src/HotChocolate/Core/src/Types/Types/Relay/{Extensions => }/NodeIdNameDefinitionUnion.cs (96%) diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs index 78c7e1e730f..c7319f40361 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldExtensions.cs @@ -139,7 +139,7 @@ public static IObjectFieldDescriptor ID( public static IObjectFieldDescriptor ID(this IObjectFieldDescriptor descriptor) { ArgumentNullException.ThrowIfNull(descriptor); - + RelayIdFieldHelpers.ApplyIdToField(descriptor); return descriptor; diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeIdNameDefinitionUnion.cs similarity index 96% rename from src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs rename to src/HotChocolate/Core/src/Types/Types/Relay/NodeIdNameDefinitionUnion.cs index 10da17849c4..cbe8c131e8b 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/NodeIdNameDefinitionUnion.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeIdNameDefinitionUnion.cs @@ -1,4 +1,3 @@ -#nullable enable namespace HotChocolate.Types.Relay; /// diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index 5f9a83e9232..97383a8137b 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -168,7 +168,7 @@ public async Task Id_Honors_CustomTypeNaming_ValidInputs() var result = await services.ExecuteRequestAsync( $$""" - mutation { + mutation { validAnyIdInput1: acceptsAnyId(input: { id: "{{userId}}" }) { int } validAnyIdInput2: acceptsAnyId(input: { id: "{{fooId}}" }) { int } validAnyIdInput3: acceptsAnyId(input: { id: "{{fluentFooId}}" }) { int } From e61fdd5572e3432d483baec669d941adad50fc6b Mon Sep 17 00:00:00 2001 From: NOlbert <45466413+N-Olbert@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:08:05 +0200 Subject: [PATCH 5/7] Reformat multiline string in `IdDescriptorTests` for consistent indentation --- .../Types/Relay/IdDescriptorTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index 97383a8137b..27152117cb2 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -168,18 +168,18 @@ public async Task Id_Honors_CustomTypeNaming_ValidInputs() var result = await services.ExecuteRequestAsync( $$""" - mutation { - validAnyIdInput1: acceptsAnyId(input: { id: "{{userId}}" }) { int } - validAnyIdInput2: acceptsAnyId(input: { id: "{{fooId}}" }) { int } - validAnyIdInput3: acceptsAnyId(input: { id: "{{fluentFooId}}" }) { int } - validAnyIdInput4: acceptsAnyId(input: { id: "{{singleTypeFluentFooId}}" }) { int } - - validUserIdInput: acceptsUserId(input: { id: "{{userId}}" }) { int } - validFooIdInput: acceptsFooId(input: { id: "{{fooId}}" }) { int } - validFluentFooIdInput: acceptsFluentFooId(input: { id: "{{fluentFooId}}" }) { int } - validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id: "{{singleTypeFluentFooId}}" }) { int } - } - """); + mutation { + validAnyIdInput1: acceptsAnyId(input: { id: "{{userId}}" }) { int } + validAnyIdInput2: acceptsAnyId(input: { id: "{{fooId}}" }) { int } + validAnyIdInput3: acceptsAnyId(input: { id: "{{fluentFooId}}" }) { int } + validAnyIdInput4: acceptsAnyId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + + validUserIdInput: acceptsUserId(input: { id: "{{userId}}" }) { int } + validFooIdInput: acceptsFooId(input: { id: "{{fooId}}" }) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id: "{{fluentFooId}}" }) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + } + """); // assert result.MatchSnapshot(); From 15619708d5ffb5578576d633b722ff5c633db673 Mon Sep 17 00:00:00 2001 From: NOlbert <45466413+N-Olbert@users.noreply.github.com> Date: Wed, 3 Sep 2025 22:29:09 +0200 Subject: [PATCH 6/7] Second attempt on indentation --- .../Types/Relay/IdDescriptorTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index 27152117cb2..c820974ac35 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -169,15 +169,15 @@ public async Task Id_Honors_CustomTypeNaming_ValidInputs() await services.ExecuteRequestAsync( $$""" mutation { - validAnyIdInput1: acceptsAnyId(input: { id: "{{userId}}" }) { int } - validAnyIdInput2: acceptsAnyId(input: { id: "{{fooId}}" }) { int } - validAnyIdInput3: acceptsAnyId(input: { id: "{{fluentFooId}}" }) { int } - validAnyIdInput4: acceptsAnyId(input: { id: "{{singleTypeFluentFooId}}" }) { int } - - validUserIdInput: acceptsUserId(input: { id: "{{userId}}" }) { int } - validFooIdInput: acceptsFooId(input: { id: "{{fooId}}" }) { int } - validFluentFooIdInput: acceptsFluentFooId(input: { id: "{{fluentFooId}}" }) { int } - validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + validAnyIdInput1: acceptsAnyId(input: { id: "{{userId}}" }) { int } + validAnyIdInput2: acceptsAnyId(input: { id: "{{fooId}}" }) { int } + validAnyIdInput3: acceptsAnyId(input: { id: "{{fluentFooId}}" }) { int } + validAnyIdInput4: acceptsAnyId(input: { id: "{{singleTypeFluentFooId}}" }) { int } + + validUserIdInput: acceptsUserId(input: { id: "{{userId}}" }) { int } + validFooIdInput: acceptsFooId(input: { id: "{{fooId}}" }) { int } + validFluentFooIdInput: acceptsFluentFooId(input: { id: "{{fluentFooId}}" }) { int } + validSingleTypeFluentFooIdInput: acceptsSingleTypeFluentFooId(input: { id: "{{singleTypeFluentFooId}}" }) { int } } """); From cc0f9d92a005beb03f4f33d648a23a835c4d34f2 Mon Sep 17 00:00:00 2001 From: Glen Date: Fri, 19 Sep 2025 14:10:24 +0200 Subject: [PATCH 7/7] Fixed CS8618 error --- .../Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs index edfbd08621d..7aab7cd2281 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdDescriptorTests.cs @@ -322,7 +322,7 @@ public interface IFooPayload public class Another { - public string Id { get; set; } + public required string Id { get; set; } } public class MutationWithRenamedIds