Skip to content

Commit e9dda60

Browse files
authored
Fix #6605 mutation error type Int32 scalar resolution (#9252)
1 parent bb123ab commit e9dda60

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed

src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ private void TryApplyPayloadConvention(
307307
_typeRegistry.TryRegister(
308308
_context.TypeInspector.GetOutputTypeRef(errorDef.RuntimeType),
309309
errorTypeRef);
310+
if (!typeof(Exception).IsAssignableFrom(errorDef.RuntimeType))
311+
{
312+
EnsureMemberTypesRegistered(errorDef.RuntimeType);
313+
}
310314
((ObjectType)obj.Type).Configuration!.Interfaces.Add(errorInterfaceTypeRef);
311315
}
312316

@@ -623,6 +627,19 @@ private static Options CreateErrorOptions(
623627
_typeInitializer.CompleteTypeName(registeredType);
624628
_typeInitializer.CompileResolvers(registeredType);
625629

630+
// Late-registered types bypass the normal discovery pipeline; add a direct runtime
631+
// binding so output field references (e.g. Int32! on error objects) can normalize.
632+
_typeRegistry.TryRegister(
633+
_context.TypeInspector.GetOutputTypeRef(type),
634+
registeredType.TypeReference);
635+
636+
if (registeredType.RuntimeType != typeof(object))
637+
{
638+
_typeRegistry.TryRegister(
639+
_context.TypeInspector.GetOutputTypeRef(registeredType.RuntimeType),
640+
registeredType.TypeReference);
641+
}
642+
626643
if (registeredType.Type is ObjectType errorObject
627644
&& errorObject.RuntimeType != typeof(object))
628645
{
@@ -665,6 +682,81 @@ private static Options CreateErrorOptions(
665682
return registeredType;
666683
}
667684

685+
private void EnsureMemberTypesRegistered(Type runtimeType)
686+
{
687+
HashSet<Type> processed = [];
688+
EnsureMemberTypesRegistered(runtimeType, processed);
689+
}
690+
691+
private void EnsureMemberTypesRegistered(
692+
Type runtimeType,
693+
HashSet<Type> processed)
694+
{
695+
if (!processed.Add(runtimeType))
696+
{
697+
return;
698+
}
699+
700+
foreach (var member in _context.TypeInspector.GetMembers(runtimeType))
701+
{
702+
var memberTypeReference = _context.TypeInspector.GetReturnTypeRef(member, TypeContext.Output);
703+
EnsureTypeReferenceRegistered(memberTypeReference, processed);
704+
}
705+
}
706+
707+
private void EnsureTypeReferenceRegistered(
708+
TypeReference typeReference,
709+
HashSet<Type> processed)
710+
{
711+
if (typeReference is not ExtendedTypeReference runtimeTypeReference)
712+
{
713+
return;
714+
}
715+
716+
if (_typeRegistry.TryGetTypeRef(runtimeTypeReference, out var existingTypeReference)
717+
&& _typeRegistry.IsRegistered(existingTypeReference))
718+
{
719+
return;
720+
}
721+
722+
if (!_context.TryInferSchemaType(typeReference, out var schemaTypeReferences))
723+
{
724+
return;
725+
}
726+
727+
foreach (var schemaTypeReference in schemaTypeReferences)
728+
{
729+
_typeRegistry.TryRegister(
730+
runtimeTypeReference.WithContext(schemaTypeReference.Context),
731+
schemaTypeReference);
732+
733+
var schemaClrType = schemaTypeReference switch
734+
{
735+
SchemaTypeReference { Type: TypeSystemObject schemaType } => schemaType.GetType(),
736+
ExtendedTypeReference { Type: { IsSchemaType: true } schemaType } => schemaType.Type,
737+
_ => null
738+
};
739+
740+
if (schemaClrType is null)
741+
{
742+
continue;
743+
}
744+
745+
var dependencyType = TryRegisterType(schemaClrType);
746+
if (dependencyType is not { RuntimeType: { } dependencyRuntimeType })
747+
{
748+
continue;
749+
}
750+
751+
if (dependencyRuntimeType != typeof(object)
752+
&& dependencyRuntimeType != typeof(string)
753+
&& !dependencyRuntimeType.IsPrimitive)
754+
{
755+
EnsureMemberTypesRegistered(dependencyRuntimeType, processed);
756+
}
757+
}
758+
}
759+
668760
private void RegisterType(TypeSystemObject type)
669761
{
670762
var registeredType = _typeInitializer.InitializeType(type);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using HotChocolate.Execution;
2+
using HotChocolate.Tests;
3+
using HotChocolate.Types.Descriptors;
4+
using HotChocolate.Types.Descriptors.Configurations;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace HotChocolate.Types;
8+
9+
public class Issue6605ReproTests
10+
{
11+
[Fact]
12+
public async Task Mutation_Error_Type_With_Int_Field_Should_Not_Fail_Schema_Build()
13+
{
14+
var exception = await Record.ExceptionAsync(async () =>
15+
await new ServiceCollection()
16+
.AddGraphQL()
17+
.AddQueryType<Query>()
18+
.AddMutationType<Mutation>()
19+
.AddMutationConventions()
20+
.AddMutationErrorConfiguration<CustomErrorConfig>()
21+
.BuildSchemaAsync());
22+
23+
Assert.Null(exception);
24+
}
25+
26+
public class CustomErrorConfig : MutationErrorConfiguration
27+
{
28+
public override void OnConfigure(
29+
IDescriptorContext context,
30+
ObjectFieldConfiguration mutationField)
31+
{
32+
mutationField.AddErrorType(context, typeof(CustomError));
33+
mutationField.MiddlewareConfigurations.Add(
34+
new(next => async ctx =>
35+
{
36+
try
37+
{
38+
await next(ctx);
39+
}
40+
catch (Exception ex)
41+
{
42+
ctx.Result = new FieldError(new CustomError(1, ex.Message));
43+
}
44+
}));
45+
}
46+
}
47+
48+
public record CustomError(int ErrorCode, string Message);
49+
50+
public class Query
51+
{
52+
public Foo GetFoo() => new("Baz");
53+
}
54+
55+
public class Mutation
56+
{
57+
public Foo AddFoo() => throw new Exception("foo");
58+
}
59+
60+
public record Foo(string Bar);
61+
}

0 commit comments

Comments
 (0)