diff --git a/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs b/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs
index 4ae4dd9bc05..8de71c209b4 100644
--- a/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs
+++ b/src/HotChocolate/Core/src/Types.Mutations/MutationConventionTypeInterceptor.cs
@@ -1,3 +1,4 @@
+using System.Text;
using HotChocolate.Features;
using HotChocolate.Types.Helpers;
using static HotChocolate.Resolvers.FieldClassMiddlewareFactory;
@@ -207,7 +208,7 @@ private void TryApplyInputConvention(
TypeMemHelper.Return(argumentNameMap);
}
- var inputTypeName = options.FormatInputTypeName(mutation.Name);
+ var inputTypeName = options.FormatInputTypeName(mutation);
if (_typeRegistry.NameRefs.ContainsKey(inputTypeName))
{
@@ -278,7 +279,7 @@ private void TryApplyPayloadConvention(
Options options)
{
var typeRef = mutation.Type;
- var payloadTypeName = options.FormatPayloadTypeName(mutation.Name);
+ var payloadTypeName = options.FormatPayloadTypeName(mutation);
// we ensure that we can resolve the mutation result type.
if (!_typeLookup.TryNormalizeReference(typeRef!, out typeRef)
@@ -396,7 +397,7 @@ private void TryApplyPayloadConvention(
// now that everything is put in place we will create the error types and
// the error middleware.
- var errorTypeName = options.FormatErrorTypeName(mutation.Name);
+ var errorTypeName = options.FormatErrorTypeName(mutation);
RegisterErrorType(CreateErrorType(errorTypeName, errorDefinitions), mutation.Name);
var errorListTypeRef = Parse($"[{errorTypeName}!]");
payloadTypeDef.Fields.Add(
@@ -434,7 +435,7 @@ private void TryApplyPayloadConvention(
if (errorDefinitions.Count > 0)
{
// create error type
- var errorTypeName = options.FormatErrorTypeName(mutation.Name);
+ var errorTypeName = options.FormatErrorTypeName(mutation);
RegisterErrorType(CreateErrorType(errorTypeName, errorDefinitions), mutation.Name);
var errorListTypeRef = Parse($"[{errorTypeName}!]");
errorField = new FieldDef(options.PayloadErrorsFieldName, errorListTypeRef);
@@ -864,34 +865,40 @@ private readonly ref struct Options(
public bool Apply { get; } = apply ??
MutationConventionOptionDefaults.ApplyToAllMutations;
- public string FormatInputTypeName(string mutationName)
+ public string FormatInputTypeName(ObjectFieldConfiguration mutation)
=> InputTypeNamePattern.Replace(
$"{{{MutationConventionOptionDefaults.MutationName}}}",
- FormatMutationName(mutationName));
+ FormatMutationName(mutation));
- public string FormatPayloadTypeName(string mutationName)
+ public string FormatPayloadTypeName(ObjectFieldConfiguration mutation)
=> PayloadTypeNamePattern.Replace(
$"{{{MutationConventionOptionDefaults.MutationName}}}",
- FormatMutationName(mutationName));
+ FormatMutationName(mutation));
- public string FormatErrorTypeName(string mutationName)
+ public string FormatErrorTypeName(ObjectFieldConfiguration mutation)
=> PayloadErrorTypeNamePattern.Replace(
$"{{{MutationConventionOptionDefaults.MutationName}}}",
- FormatMutationName(mutationName));
+ FormatMutationName(mutation));
+
+ private static string FormatMutationName(ObjectFieldConfiguration mutation)
+ {
+ ArgumentNullException.ThrowIfNull(mutation);
+
+ return mutation.DisableMutationReformatting
+ ? mutation.Name
+ : FormatMutationName(mutation.Name);
+ }
private static string FormatMutationName(string mutationName)
{
- if (string.IsNullOrEmpty(mutationName))
- {
- return mutationName;
- }
+ ArgumentException.ThrowIfNullOrEmpty(mutationName);
- if (mutationName.IndexOf('_', StringComparison.Ordinal) < 0)
+ if (!mutationName.Contains('_'))
{
return char.ToUpperInvariant(mutationName[0]) + mutationName[1..];
}
- var builder = new System.Text.StringBuilder(mutationName.Length);
+ var builder = new StringBuilder(mutationName.Length);
var upperNext = true;
foreach (var c in mutationName)
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/CoreFieldFlags.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/CoreFieldFlags.cs
index c700eae18ec..a1b1a729216 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/CoreFieldFlags.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/CoreFieldFlags.cs
@@ -47,5 +47,6 @@ internal enum CoreFieldFlags : long
UsesProjections = 1L << 31,
ImplicitField = 1L << 32,
BatchResolver = 1L << 33,
- MemberReplacement = 1L << 34
+ MemberReplacement = 1L << 34,
+ DisableMutationReformatting = 1L << 35
}
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldConfiguration.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldConfiguration.cs
index d4b55ce5f8f..527ff3e2ef3 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldConfiguration.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldConfiguration.cs
@@ -208,6 +208,26 @@ public bool IsParallelExecutable
}
}
+ ///
+ /// If this field is a mutation to which mutation conventions are applied,
+ /// this flag indicates that the field name should not be reformatted.
+ ///
+ public bool DisableMutationReformatting
+ {
+ get => (Flags & CoreFieldFlags.DisableMutationReformatting) == CoreFieldFlags.DisableMutationReformatting;
+ set
+ {
+ if (value)
+ {
+ Flags |= CoreFieldFlags.DisableMutationReformatting;
+ }
+ else
+ {
+ Flags &= ~CoreFieldFlags.DisableMutationReformatting;
+ }
+ }
+ }
+
///
/// Defines in which DI scope this field is executed.
///
diff --git a/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs b/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs
index 8606b232d7b..9a354bc8b18 100644
--- a/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs
+++ b/src/HotChocolate/Core/test/Types.Mutations.Tests/AnnotationBasedMutations.cs
@@ -1319,6 +1319,32 @@ public async Task MutationConvention_With_SnakeCase_ObjectField_NamingConvention
Assert.DoesNotContain("userName: String!", schemaText);
}
+ [Fact]
+ public async Task MutationConvention_With_DisabledMutationReformatting_Uses_Field_Name()
+ {
+ var schema =
+ await new ServiceCollection()
+ .AddGraphQL()
+ .AddMutationType()
+ .AddMutationConventions(
+ new MutationConventionOptions
+ {
+ ApplyToAllMutations = true,
+ InputTypeNamePattern = "{MutationName}InputType",
+ PayloadTypeNamePattern = "{MutationName}PayloadType"
+ })
+ .ModifyOptions(o => o.StrictValidation = false)
+ .BuildSchemaAsync();
+
+ var schemaText = schema.ToString();
+
+ Assert.Contains("ch_myMutation(input: ch_myMutationInputType!): ch_myMutationPayloadType!", schemaText);
+ Assert.Contains("input ch_myMutationInputType {", schemaText);
+ Assert.Contains("type ch_myMutationPayloadType {", schemaText);
+ Assert.DoesNotContain("ChMyMutationInputType", schemaText);
+ Assert.DoesNotContain("ChMyMutationPayloadType", schemaText);
+ }
+
[Fact]
public async Task Mutation_With_ErrorAnnotatedAndCustomInterface_LateAndEarlyRegistration()
{
@@ -1575,6 +1601,12 @@ public User DoSomething(string name)
}
}
+ [PrefixMutationFields("ch_")]
+ public class MutationWithDisabledReformatting
+ {
+ public string MyMutation(string value) => value;
+ }
+
public class SimpleMutationWithSingleError
{
[Error(typeof(CustomException))]
@@ -2035,4 +2067,29 @@ private static string ToSnakeCase(string memberName)
[System.Text.RegularExpressions.GeneratedRegex(@"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+")]
private static partial System.Text.RegularExpressions.Regex SnakeCasePatternRegex();
}
+
+ public sealed class PrefixMutationFieldsAttribute(string prefix) : ObjectTypeDescriptorAttribute
+ {
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IObjectTypeDescriptor descriptor,
+ Type? type)
+ {
+ if (type is null)
+ {
+ return;
+ }
+
+ descriptor
+ .Extend()
+ .OnBeforeCreate((_, definition) =>
+ {
+ foreach (var field in definition.Fields)
+ {
+ field.Name = prefix + field.Name;
+ field.DisableMutationReformatting = true;
+ }
+ });
+ }
+ }
}