diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/ArgumentDescriptorBase~1.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/ArgumentDescriptorBase~1.cs index 7712256736a..ee4b8b40131 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/ArgumentDescriptorBase~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/ArgumentDescriptorBase~1.cs @@ -39,21 +39,15 @@ protected void Deprecated(string? reason) /// protected void Deprecated() - { - Configuration.DeprecationReason = DirectiveNames.Deprecated.Arguments.DefaultReason; - } + => Configuration.DeprecationReason = DirectiveNames.Deprecated.Arguments.DefaultReason; - /// - protected void Description(string value) - { - Configuration.Description = value; - } + /// + protected void Description(string? value) + => Configuration.Description = value; /// public void Type() where TInputType : IInputType - { - Type(typeof(TInputType)); - } + => Type(typeof(TInputType)); /// /// Sets the type of the argument @@ -71,6 +65,8 @@ public void Type() where TInputType : IInputType /// public void Type(Type type) { + ArgumentNullException.ThrowIfNull(type); + var typeInfo = Context.TypeInspector.CreateTypeInfo(type); if (typeInfo.IsSchemaType && !typeInfo.IsInputType()) @@ -133,7 +129,7 @@ public void Type(ITypeNode typeNode) Configuration.SetMoreSpecificType(typeNode, TypeContext.Input); } - /// + /// public void DefaultValue(IValueNode? value) { Configuration.DefaultValue = value ?? NullValueNode.Default; @@ -150,6 +146,11 @@ public void DefaultValue(object? value) } else { + if (TryCoerceEnumUnderlyingValue(value, out var enumValue)) + { + value = enumValue; + } + var type = Context.TypeInspector.GetType(value.GetType()); Configuration.SetMoreSpecificType(type, TypeContext.Input); Configuration.RuntimeDefaultValue = value; @@ -168,4 +169,32 @@ public void Directive(TDirective directiveInstance) where TDirective /// public void Directive(string name, params ArgumentNode[] arguments) => Configuration.AddDirective(name, arguments); + + private bool TryCoerceEnumUnderlyingValue(object value, out object enumValue) + { + enumValue = default!; + + if (Configuration.Type is not ExtendedTypeReference typeReference) + { + return false; + } + + var clrType = Nullable.GetUnderlyingType(typeReference.Type.Source) + ?? typeReference.Type.Source; + + if (!clrType.IsEnum) + { + return false; + } + + var underlyingType = Enum.GetUnderlyingType(clrType); + + if (value.GetType() != underlyingType) + { + return false; + } + + enumValue = Enum.ToObject(clrType, value); + return true; + } } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs index 9ecf394194e..e2a7a64a814 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs @@ -1,5 +1,3 @@ -#nullable disable - using HotChocolate.Language; using HotChocolate.Types.Descriptors.Configurations; @@ -26,7 +24,7 @@ public interface IArgumentDescriptor /// /// /// - IArgumentDescriptor Deprecated(string reason); + IArgumentDescriptor Deprecated(string? reason); /// /// Marks the argument as deprecated @@ -65,7 +63,7 @@ public interface IArgumentDescriptor /// /// /// The description - IArgumentDescriptor Description(string value); + IArgumentDescriptor Description(string? value); /// /// Sets the type of the argument @@ -149,7 +147,7 @@ IArgumentDescriptor Type(TInputType inputType) /// /// /// - IArgumentDescriptor DefaultValue(IValueNode value); + IArgumentDescriptor DefaultValue(IValueNode? value); /// /// Sets the default value of this argument @@ -166,7 +164,7 @@ IArgumentDescriptor Type(TInputType inputType) /// /// /// - IArgumentDescriptor DefaultValue(object value); + IArgumentDescriptor DefaultValue(object? value); /// /// Sets a directive on the argument diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Issue7040ReproTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Issue7040ReproTests.cs new file mode 100644 index 00000000000..1fe6d11aab1 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Issue7040ReproTests.cs @@ -0,0 +1,30 @@ +namespace HotChocolate.Types; + +public class Issue7040ReproTests +{ + [Fact] + public void Enum_DefaultValueAttribute_With_Integer_Default_Does_Not_Downgrade_To_Int() + { + SchemaBuilder.New() + .AddInputObjectType() + .ModifyOptions(o => o.StrictValidation = false) + .Create() + .MatchSnapshot(); + } + + public class InputWithEnumIntDefault + { + // The F# compiler boxes enum values as their underlying type when passed + // to attribute constructors that accept object. So [] + // in F# arrives at runtime as [DefaultValue(0)] rather than [DefaultValue(MyEnum.Value1)]. + // We use the integer literal here to reproduce that behavior in C#. + [DefaultValue(0)] + public Issue7040Enum Enum { get; set; } + } + + public enum Issue7040Enum + { + Value1 = 0, + Value2 = 1 + } +}