Cannot add Errors with MutationConventions via ITypeModule
Is there an existing issue for this?
- [X] I have searched the existing issues
Product
Hot Chocolate
Describe the bug
When adding mutations via an ITypeModule you can register them using UseMutationConvention but if you call Error<>() afterwards it will throw in ObjectFieldDefinitionExtensions.cs on line 52 due to the MutationContextDataKeys.Options not being registered in the provided IDescriptorContext from the ITypeModule /or/ registered via a manual call to UseMutationConvention().
Steps to reproduce
- Create a ITypeModule
- Add a mutation method
ObjectFieldDefinition newField = new(<stuff>).ToDescriptor(context).UseMutationConvention().Error<AnError>().ToDefinition() - Fails
Relevant log output
No response
Additional Context?
This can be worked around by adding this to the top of the registration function (fixes the broken AddError lookup):
context.ContextData["HotChocolate.Types.Mutations.Options"] = new HotChocolate.Types.MutationConventionOptions
{
ApplyToAllMutations = true,
};
Version
13.7.0
Can you try with the latest 13.8 preview and report back.
Sorry I took so long to report back, we've been getting a new release of our product ready for release. So I upgraded to 13.8.1 (and just incase the fix was removed prior to release I have also checked 13.8.0-preview.8 & 7 & 6), and removed my work around and still received the same crash/issue. The full error is:
Unhandled exception. HotChocolate.SchemaException: For more details look at the `Errors` property.
1. Adding an error type `SaveMutationError` to field `api_accessGroups` failed as mutation conventions weren't enabled.
at HotChocolate.Types.ObjectFieldDefinitionExtensions.AddErrorType(ObjectFieldDefinition fieldDefinition, IDescriptorContext descriptorContext, Type errorType)
at HotChocolate.Types.MutationObjectFieldDescriptorExtensions.<>c__DisplayClass2_0.<Error>b__0(IDescriptorContext ctx, ObjectFieldDefinition d)
at HotChocolate.Types.Descriptors.DescriptorBase`1.<>c__DisplayClass19_0.<OnBeforeCreate>b__0(IDescriptorContext c, IDefinition d)
at HotChocolate.Types.Descriptors.Definitions.CreateConfiguration.Configure(IDescriptorContext context)
at HotChocolate.Types.Descriptors.DescriptorBase`1.CreateDefinition()
at HotChocolate.Types.Descriptors.DescriptorExtensions.ToDefinition[T](IDescriptor`1 descriptor)
at MCEverywhere.GraphQL.GraphQL_DataApi.CreateTypesAsync(IDescriptorContext context, CancellationToken cancellationToken) in D:\Programming\_gitLab_MCe\mceverywhere-e7-vnext\src\MCEverywhere\GraphQL\GraphQL_DataApi.cs:line 231
at HotChocolate.Execution.RequestExecutorResolver.TypeModuleChangeMonitor.TypeModuleEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext()
at HotChocolate.Execution.RequestExecutorResolver.TypeModuleChangeMonitor.TypeModuleEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaAsync(ConfigurationContext context, RequestExecutorSetup setup, RequestExecutorOptions executorOptions, IServiceProvider schemaServices, TypeModuleChangeMonitor typeModuleChangeMonitor, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaAsync(ConfigurationContext context, RequestExecutorSetup setup, RequestExecutorOptions executorOptions, IServiceProvider schemaServices, TypeModuleChangeMonitor typeModuleChangeMonitor, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaServicesAsync(ConfigurationContext context, RequestExecutorSetup setup, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.GetRequestExecutorNoLockAsync(String schemaName, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.GetRequestExecutorAsync(String schemaName, CancellationToken cancellationToken)
at HotChocolate.AspNetCore.Warmup.ExecutorWarmupService.ExecuteAsync(CancellationToken stoppingToken)
at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
The code triggering the error is:
ObjectFieldDefinition mutateField = new(
name: $"api_{dataConfig.ExposeGraphNamePlural}",
description: string.Format(
"Insert, update or delete one or multiple `{0}` entities.",
dataConfig.RootEntityName
),
type: TypeReference.Parse($"[{dataConfig.RootEntityName}!]!"),
resolver: async ctx =>
{
//throw new AggregateException()
try
{
return await MutateForData(useSourceArray: true, dataConfig, ctx);
}
catch (Exception ex) when (ex is not SaveMutationException && ex is not AggregateException)
{
throw new global::HotChocolate.Execution.QueryException(ErrorBuilder.New().SetMessage(ex.ToString()).Build());
}
}
);
// Reminder to future self, due to the Mutate Convention, everything needs to be optional or it won't be possible to
// effectively change how it behaves in the future.
// Also defaults don't work :(
mutateField.Arguments.Add(new(
"entities",
description: "Used to send multiple entities to the server in order to create, update or delete them. The actions performed can be different per entity included. The operation will succeed or fail as a combined batch preventing half changes.",
type: inputType.Value.IsT0 ? TypeReference.Parse($"[{dataConfig.RootEntityName}Input!]!") : inputType.Value.AsT1
));
mutateField = mutateField.ToDescriptor(context)
.Argument("changeFromEntities", a => a.Type<ListType<NonNullType<AnyType>>>()
.Description("")
)
.Argument("batchId", a => a.Type<UuidType>()
.Description("")
)
.Argument("application", a => a.Type<StringType>()
.Description("")
)
.Authorize("UserAndDatabase")
.UseMutationConvention(new MutationFieldOptions {
Disable = false,
PayloadFieldName = "data",
InputTypeName = $"API_{char.ToUpper(dataConfig.ExposeGraphNamePlural[0])}{dataConfig.ExposeGraphNamePlural[1..]}Input",
PayloadTypeName = $"API_{char.ToUpper(dataConfig.ExposeGraphNamePlural[0])}{dataConfig.ExposeGraphNamePlural[1..]}Payload",
PayloadErrorTypeName = $"API_{char.ToUpper(dataConfig.ExposeGraphNamePlural[0])}{dataConfig.ExposeGraphNamePlural[1..]}Error",
})
// Errors list has to be after turning on the mutate convention
.Error<SaveMutationError>()
.ToDefinition();
From what I can tell this extra detail likely doesn't change the core of the issue, but just incase...
And turning on my mitigation from the top... it does fix the issue (so somehow in this dynamic context the mutation convention isn't marking itself as "on" and me forcing it into believing its on corrects the issue).