diff --git a/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs b/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs index 7c50b3e7f42..b9023858d19 100644 --- a/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs @@ -453,7 +453,7 @@ private void TryApplyPayloadConvention( var type = CreatePayloadType( payloadTypeName, - new(payloadFieldName, EnsureNullable(mutation.Type!)), + new(payloadFieldName, EnsureNullable(NormalizeTypeRef(mutation.Type!))), errorField); RegisterType(type); @@ -826,7 +826,7 @@ private TypeReference EnsureNonNull(TypeReference typeRef) return Create(CreateTypeNode(new NonNullType(type))); } - private ITypeNode CreateTypeNode(IType type) + private static ITypeNode CreateTypeNode(IType type) => type switch { NonNullType nnt => new NonNullTypeNode((INullableTypeNode) CreateTypeNode(nnt.Type)), @@ -835,41 +835,42 @@ private ITypeNode CreateTypeNode(IType type) _ => throw new NotSupportedException("Type is not supported."), }; - private readonly ref struct Options + private static TypeReference NormalizeTypeRef(TypeReference typeRef) { - public Options( - string? inputTypeNamePattern, - string? inputArgumentName, - string? payloadTypeNamePattern, - string? payloadErrorTypeNamePattern, - string? payloadErrorsFieldName, - bool? apply) + if (typeRef is ExtendedTypeReference { Type.IsGeneric: true } extendedTypeRef && + typeof(IMutationResult).IsAssignableFrom(extendedTypeRef.Type.Type)) { - InputTypeNamePattern = inputTypeNamePattern ?? - MutationConventionOptionDefaults.InputTypeNamePattern; - InputArgumentName = inputArgumentName ?? - MutationConventionOptionDefaults.InputArgumentName; - PayloadTypeNamePattern = payloadTypeNamePattern ?? - MutationConventionOptionDefaults.PayloadTypeNamePattern; - PayloadErrorsFieldName = payloadErrorsFieldName ?? - MutationConventionOptionDefaults.PayloadErrorsFieldName; - PayloadErrorTypeNamePattern = payloadErrorTypeNamePattern ?? - MutationConventionOptionDefaults.ErrorTypeNamePattern; - Apply = apply ?? - MutationConventionOptionDefaults.ApplyToAllMutations; + return extendedTypeRef.WithType(extendedTypeRef.Type.TypeArguments[0]); } - public string InputTypeNamePattern { get; } + return typeRef; + } + + private readonly ref struct Options( + string? inputTypeNamePattern, + string? inputArgumentName, + string? payloadTypeNamePattern, + string? payloadErrorTypeNamePattern, + string? payloadErrorsFieldName, + bool? apply) + { + public string InputTypeNamePattern { get; } = inputTypeNamePattern ?? + MutationConventionOptionDefaults.InputTypeNamePattern; - public string InputArgumentName { get; } + public string InputArgumentName { get; } = inputArgumentName ?? + MutationConventionOptionDefaults.InputArgumentName; - public string PayloadTypeNamePattern { get; } + public string PayloadTypeNamePattern { get; } = payloadTypeNamePattern ?? + MutationConventionOptionDefaults.PayloadTypeNamePattern; - public string PayloadErrorTypeNamePattern { get; } + public string PayloadErrorTypeNamePattern { get; } = payloadErrorTypeNamePattern ?? + MutationConventionOptionDefaults.ErrorTypeNamePattern; - public string PayloadErrorsFieldName { get; } + public string PayloadErrorsFieldName { get; } = payloadErrorsFieldName ?? + MutationConventionOptionDefaults.PayloadErrorsFieldName; - public bool Apply { get; } + public bool Apply { get; } = apply ?? + MutationConventionOptionDefaults.ApplyToAllMutations; public string FormatInputTypeName(string mutationName) => InputTypeNamePattern.Replace( diff --git a/src/HotChocolate/Core/src/Types.Mutations/MutationResultTypeDiscoveryHandler.cs b/src/HotChocolate/Core/src/Types.Mutations/MutationResultTypeDiscoveryHandler.cs index ad8e3636501..f11d50a5a1d 100644 --- a/src/HotChocolate/Core/src/Types.Mutations/MutationResultTypeDiscoveryHandler.cs +++ b/src/HotChocolate/Core/src/Types.Mutations/MutationResultTypeDiscoveryHandler.cs @@ -4,9 +4,6 @@ namespace HotChocolate.Types; internal sealed class MutationResultTypeDiscoveryHandler(ITypeInspector typeInspector) : TypeDiscoveryHandler { - private readonly ITypeInspector _typeInspector = typeInspector ?? - throw new ArgumentNullException(nameof(typeInspector)); - public override bool TryInferType( TypeReference typeReference, TypeDiscoveryInfo typeInfo, @@ -18,7 +15,7 @@ public override bool TryInferType( typeof(IMutationResult).IsAssignableFrom(runtimeType) && typeReference is ExtendedTypeReference typeRef) { - var type = _typeInspector.GetType(runtimeType.GenericTypeArguments[0]); + var type = GetNamedType(typeInspector.GetType(runtimeType.GenericTypeArguments[0])); schemaTypeRefs = new TypeReference[runtimeType.GenericTypeArguments.Length]; schemaTypeRefs[0] = typeRef.WithType(type); @@ -26,7 +23,7 @@ public override bool TryInferType( { var errorType = runtimeType.GenericTypeArguments[i]; - type = _typeInspector.GetType( + type = typeInspector.GetType( typeof(Exception).IsAssignableFrom(errorType) ? typeof(ExceptionObjectType<>).MakeGenericType(errorType) : typeof(ErrorObjectType<>).MakeGenericType(errorType)); @@ -40,4 +37,10 @@ public override bool TryInferType( schemaTypeRefs = null; return false; } + + private IExtendedType GetNamedType(IExtendedType extendedType) + { + var typeInfo = typeInspector.CreateTypeInfo(extendedType); + return typeInspector.GetType(typeInfo.NamedType); + } } diff --git a/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs b/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs index 98932309754..bc0fab4d321 100644 --- a/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs +++ b/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs @@ -1114,6 +1114,20 @@ public async Task Query_Filed_Stays_NonNull() schema.MatchSnapshot(); } + [Fact] + public async Task List_Return_Type() + { + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddQueryType(d => d.Field("abc").Resolve("def")) + .AddMutationType() + .AddMutationConventions() + .BuildSchemaAsync(); + + schema.MatchSnapshot(); + } + [Fact] public async Task Mutation_Aggregate_Error_Not_Mapped() { @@ -1641,6 +1655,27 @@ public string DoSomething(string something) public record DoSomething2Payload(int? UserId); + public class ListReturnMutation + { + public MutationResult> AddItem(AddItemInput input) + => new List + { + new(), + new(), + new(), + }; + + public class AddItemInput + { + public int Count { get; set; } + } + + public class ResultItem + { + public string Name { get; set; } = "Test"; + } + } + public record SomeNewError(string Message); public class CustomErrorConfig : MutationErrorConfiguration diff --git a/src/HotChocolate/Core/test/Types.Mutations.Tests/__snapshots__/AnnotationBasedMutations.List_Return_Type.graphql b/src/HotChocolate/Core/test/Types.Mutations.Tests/__snapshots__/AnnotationBasedMutations.List_Return_Type.graphql new file mode 100644 index 00000000000..6f3cfb88731 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Mutations.Tests/__snapshots__/AnnotationBasedMutations.List_Return_Type.graphql @@ -0,0 +1,24 @@ +schema { + query: Query + mutation: ListReturnMutation +} + +type AddItemPayload { + resultItem: [ResultItem!] +} + +type ListReturnMutation { + addItem(input: AddItemInput!): AddItemPayload! +} + +type Query { + abc: String +} + +type ResultItem { + name: String! +} + +input AddItemInput { + count: Int! +}