diff --git a/src/HotChocolate/Core/src/Abstractions/NameString.cs b/src/HotChocolate/Core/src/Abstractions/NameString.cs index 6fd5a9047e5..5788ad501c4 100644 --- a/src/HotChocolate/Core/src/Abstractions/NameString.cs +++ b/src/HotChocolate/Core/src/Abstractions/NameString.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using HotChocolate.Language; @@ -234,3 +236,33 @@ internal static NameString ConvertFromString(string s) ? new NameString() : new NameString(s); } + +/// +/// Provides comparers for . +/// +public static class NameStringComparer +{ + public static IEqualityComparer Ordinal { get; } = + new OrdinalComparer(); + + public static IEqualityComparer OrdinalIgnoreCase { get; } = + new OrdinalIgnoreCaseComparer(); + + private sealed class OrdinalIgnoreCaseComparer : IEqualityComparer + { + public bool Equals(NameString x, NameString y) + => x.Equals(y, StringComparison.OrdinalIgnoreCase); + + public int GetHashCode(NameString obj) + => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Value); + } + + private sealed class OrdinalComparer : IEqualityComparer + { + public bool Equals(NameString x, NameString y) + => x.Equals(y, StringComparison.Ordinal); + + public int GetHashCode(NameString obj) + => obj.Value.GetHashCode(); + } +} diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor.cs index d6c483c5aff..de43a92bda2 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; using HotChocolate.Language; using HotChocolate.Types.Descriptors.Definitions; namespace HotChocolate.Types; +/// +/// A fluent configuration API for GraphQL enum types. +/// public interface IEnumTypeDescriptor : IDescriptor , IFluent @@ -34,6 +38,12 @@ IEnumTypeDescriptor SyntaxNode( /// IEnumTypeDescriptor Description(string value); + /// + /// Defines a value that should be included on the enum type. + /// + /// + /// The value to include. + /// [Obsolete("Use `Value`.")] IEnumValueDescriptor Item(T value); @@ -45,9 +55,21 @@ IEnumTypeDescriptor SyntaxNode( /// IEnumValueDescriptor Value(T value); + /// + /// Specifies if the enum values shall be inferred or explicitly specfied. + /// + /// + /// The binding behavior. + /// [Obsolete("Use `BindValues`.")] IEnumTypeDescriptor BindItems(BindingBehavior behavior); + /// + /// Specifies if the enum values shall be inferred or explicitly specfied. + /// + /// + /// The binding behavior. + /// IEnumTypeDescriptor BindValues(BindingBehavior behavior); /// @@ -61,13 +83,55 @@ IEnumTypeDescriptor SyntaxNode( /// IEnumTypeDescriptor BindValuesImplicitly(); + /// + /// Specifies the enum name comparer that will be used to validate + /// if an enum name represents an enum value of this type. + /// + /// + /// The equality comparer for enum names. + /// + IEnumTypeDescriptor NameComparer(IEqualityComparer comparer); + + /// + /// Specifies the runtime value comparer that will be used to validate + /// if a runtime value represents a GraphQL enum value of this type. + /// + /// + /// The equality comparer for enum names. + /// + IEnumTypeDescriptor ValueComparer(IEqualityComparer comparer); + + /// + /// Annotates a directive to this type. + /// + /// + /// The directive that shall be annotated to this type. + /// + /// + /// The type of the directive instance. + /// IEnumTypeDescriptor Directive( T directiveInstance) where T : class; + /// + /// Annotates a directive to this type. + /// + /// + /// The type of the directive instance. + /// IEnumTypeDescriptor Directive() where T : class, new(); + /// + /// Annotates a directive to this type. + /// + /// + /// The name of the directive. + /// + /// + /// The argument values that the directive instance shall have. + /// IEnumTypeDescriptor Directive( NameString name, params ArgumentNode[] arguments); diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor~1.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor~1.cs index 435736edb44..a438ae6e331 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IEnumTypeDescriptor~1.cs @@ -1,9 +1,16 @@ using System; +using System.Collections.Generic; using HotChocolate.Language; using HotChocolate.Types.Descriptors.Definitions; namespace HotChocolate.Types; +/// +/// A fluent configuration API for GraphQL enum types. +/// +/// +/// The runtime type. +/// " public interface IEnumTypeDescriptor : IDescriptor , IFluent @@ -34,6 +41,12 @@ IEnumTypeDescriptor SyntaxNode( /// IEnumTypeDescriptor Description(string value); + /// + /// Defines a value that should be included on the enum type. + /// + /// + /// The value to include. + /// [Obsolete("Use `Value`.")] IEnumValueDescriptor Item(T value); @@ -45,9 +58,21 @@ IEnumTypeDescriptor SyntaxNode( /// IEnumValueDescriptor Value(T value); + /// + /// Specifies if the enum values shall be inferred or explicitly specfied. + /// + /// + /// The binding behavior. + /// [Obsolete("Use `BindValues`.")] IEnumTypeDescriptor BindItems(BindingBehavior behavior); + /// + /// Specifies if the enum values shall be inferred or explicitly specfied. + /// + /// + /// The binding behavior. + /// IEnumTypeDescriptor BindValues(BindingBehavior behavior); /// @@ -61,13 +86,55 @@ IEnumTypeDescriptor SyntaxNode( /// IEnumTypeDescriptor BindValuesImplicitly(); + /// + /// Specifies the enum name comparer that will be used to validate + /// if an enum name represents an enum value of this type. + /// + /// + /// The equality comparer for enum names. + /// + IEnumTypeDescriptor NameComparer(IEqualityComparer comparer); + + /// + /// Specifies the runtime value comparer that will be used to validate + /// if a runtime value represents a GraphQL enum value of this type. + /// + /// + /// The equality comparer for enum names. + /// + IEnumTypeDescriptor ValueComparer(IEqualityComparer comparer); + + /// + /// Annotates a directive to this type. + /// + /// + /// The directive that shall be annotated to this type. + /// + /// + /// The type of the directive instance. + /// IEnumTypeDescriptor Directive( TDirective directiveInstance) where TDirective : class; + /// + /// Annotates a directive to this type. + /// + /// + /// The type of the directive instance. + /// IEnumTypeDescriptor Directive() where TDirective : class, new(); + /// + /// Annotates a directive to this type. + /// + /// + /// The name of the directive. + /// + /// + /// The argument values that the directive instance shall have. + /// IEnumTypeDescriptor Directive( NameString name, params ArgumentNode[] arguments); diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/EnumTypeDefinition.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/EnumTypeDefinition.cs index 9bf98d9338a..f9c114f69da 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/EnumTypeDefinition.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/EnumTypeDefinition.cs @@ -30,6 +30,20 @@ public EnumTypeDefinition( Description = description; } + /// + /// Gets or sets the enum name comparer that will be used to validate + /// if an enum name represents an enum value of this type. + /// + public IEqualityComparer NameComparer { get; set; } = + NameStringComparer.Ordinal; + + /// + /// Gets or sets the runtime value comparer that will be used to validate + /// if a runtime value represents a GraphQL enum value of this type. + /// + public IEqualityComparer ValueComparer { get; set; } = + DefaultValueComparer.Instance; + /// /// Gets the enum values. /// @@ -57,4 +71,15 @@ public override IEnumerable GetConfigurations() return configs ?? Enumerable.Empty(); } + + private sealed class DefaultValueComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(object? x, object? y) + => Equals(x, y); + + int IEqualityComparer.GetHashCode(object obj) + => obj.GetHashCode(); + + public static DefaultValueComparer Instance { get; } = new(); + } } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/EnumTypeDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/EnumTypeDescriptor.cs index cc4b77e9755..e9f2433094b 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/EnumTypeDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/EnumTypeDescriptor.cs @@ -121,6 +121,18 @@ public IEnumTypeDescriptor BindValuesExplicitly() => public IEnumTypeDescriptor BindValuesImplicitly() => BindValues(BindingBehavior.Implicit); + public IEnumTypeDescriptor NameComparer(IEqualityComparer comparer) + { + Definition.NameComparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + return this; + } + + public IEnumTypeDescriptor ValueComparer(IEqualityComparer comparer) + { + Definition.ValueComparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + return this; + } + [Obsolete("Use `Value`.")] public IEnumValueDescriptor Item(T value) => Value(value); diff --git a/src/HotChocolate/Core/src/Types/Types/EnumType.Initialization.cs b/src/HotChocolate/Core/src/Types/Types/EnumType.Initialization.cs index fd00370085d..bb1ff89a42f 100644 --- a/src/HotChocolate/Core/src/Types/Types/EnumType.Initialization.cs +++ b/src/HotChocolate/Core/src/Types/Types/EnumType.Initialization.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using HotChocolate.Configuration; using HotChocolate.Internal; using HotChocolate.Properties; @@ -14,8 +13,8 @@ namespace HotChocolate.Types; public partial class EnumType { - private readonly Dictionary _enumValues = new(); - private readonly Dictionary _valueLookup = new(); + private Dictionary _enumValues = default!; + private Dictionary _valueLookup = default!; private Action? _configure; private INamingConventions _naming = default!; @@ -97,6 +96,9 @@ protected override void OnCompleteType( { base.OnCompleteType(context, definition); + _enumValues = new Dictionary(definition.NameComparer); + _valueLookup = new Dictionary(definition.ValueComparer); + _naming = context.DescriptorContext.Naming; SyntaxNode = definition.SyntaxNode; @@ -112,7 +114,7 @@ protected override void OnCompleteType( } } - if (!Values.Any()) + if (Values.Count == 0) { context.ReportError( SchemaErrorBuilder.New() diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/EnumTypeTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/EnumTypeTests.cs index 986d07706f9..84301e0898a 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/EnumTypeTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/EnumTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using HotChocolate.Configuration; using HotChocolate.Language; @@ -583,9 +584,9 @@ public void ValueContainingUnderline_Should_NotResultInDoubleUnderline() [Fact] public void Generic_Ignore_Descriptor_Is_Null() { - void Fail() + void Fail() => EnumTypeDescriptorExtensions.Ignore(default(IEnumTypeDescriptor)!, 1); - + Assert.Throws(Fail); } @@ -594,18 +595,18 @@ public void Generic_Ignore_Value_Is_Null() { var descriptor = new Mock>(); - void Fail() + void Fail() => EnumTypeDescriptorExtensions.Ignore(descriptor.Object, null); - + Assert.Throws(Fail); } [Fact] public void Ignore_Descriptor_Is_Null() { - void Fail() + void Fail() => EnumTypeDescriptorExtensions.Ignore(default(IEnumTypeDescriptor)!, 1); - + Assert.Throws(Fail); } @@ -614,12 +615,56 @@ public void Ignore_Value_Is_Null() { var descriptor = new Mock(); - void Fail() + void Fail() => EnumTypeDescriptorExtensions.Ignore(descriptor.Object, null); - + Assert.Throws(Fail); } + [Fact] + public void EnumName_Set_Name_Comparer() + { + // act + var schema = SchemaBuilder + .New() + .AddDirectiveType(new DirectiveType(d => d + .Name("bar") + .Location(DirectiveLocation.EnumValue))) + .AddEnumType(d => d + .Name("Foo") + .NameComparer(NameStringComparer.OrdinalIgnoreCase) + .Value("baz") + .Name("BAZ")) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + // assert + var type = schema.GetType("Foo"); + Assert.True(type.IsInstanceOfType(new EnumValueNode("baz"))); + } + + [Fact] + public void EnumName_Set_Value_Comparer() + { + // act + var schema = SchemaBuilder + .New() + .AddDirectiveType(new DirectiveType(d => d + .Name("bar") + .Location(DirectiveLocation.EnumValue))) + .AddEnumType(d => d + .Name("Foo") + .ValueComparer(new ValueComparer()) + .Value("baz") + .Name("BAZ")) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + // assert + var type = schema.GetType("Foo"); + Assert.True(type.IsInstanceOfType("ANYTHING WILL DO")); + } + public enum Foo { Bar1, @@ -705,5 +750,19 @@ public enum DescriptionTestEnum Foo, Bar } + + + public class ValueComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(object x, object y) + { + return true; + } + + int IEqualityComparer.GetHashCode(object obj) + { + return 1; + } + } } }