Skip to content

Commit 4bd1f1d

Browse files
authored
EnumCaseAttribute (graphql-dotnet#2773)
1 parent c5f288f commit 4bd1f1d

File tree

5 files changed

+167
-11
lines changed

5 files changed

+167
-11
lines changed

docs2/site/docs/getting-started/schema-types.md

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,44 @@ The name of each member _is_ the value.
122122

123123
GraphQL.NET provides two methods of defining GraphQL enums.
124124

125-
You can use `EnumerationGraphType<TEnum>` to automatically generate values by providing a .NET
126-
`enum` for `TEnum`. The `Name` will default to the .NET Type name, which you can override in
127-
the constructor. The `Description` will default to any `System.ComponentModel.DescriptionAttribute`
128-
applied to the enum type. The `DeprecationReason` will default to any `System.ObsoleteAttribute`
129-
applied to the enum type. By default, the name of each enum member will be converted to CONSTANT_CASE.
130-
Override `ChangeEnumCase` to change this behavior. Apply a `DescriptionAttribute` to an enum member
131-
to set the GraphQL `Description`. Apply an `ObsoleteAttribute` to an enum member to set the GraphQL
132-
`DeprecationReason`.
125+
1. You can use `EnumerationGraphType<TEnum>` to automatically generate values by providing a .NET `enum` for `TEnum`.
126+
127+
- The `Name` will default to the .NET type name, which you can override in the constructor.
128+
- The `Description` will default to any `System.ComponentModel.DescriptionAttribute` applied to the enum type.
129+
- The `DeprecationReason` will default to any `System.ObsoleteAttribute` applied to the enum type.
130+
- Apply a `DescriptionAttribute` to an enum member to set the GraphQL `Description`.
131+
- Apply an `ObsoleteAttribute` to an enum member to set the GraphQL `DeprecationReason`.
132+
133+
By default, the name of each enum member will be converted to CONSTANT_CASE. If you want to change
134+
this behavior, you can make it in two ways.
135+
136+
a. Inherit from `EnumerationGraphType<TEnum>` and override `ChangeEnumCase` method.
137+
138+
```csharp
139+
public class CamelCaseEnumerationGraphType<T> : EnumerationGraphType<T> where T : Enum
140+
{
141+
protected override string ChangeEnumCase(string val) => val.ToCamelCase();
142+
}
143+
```
144+
145+
and then inheriting this class instead of `EnumerationGraphType`
146+
147+
```csharp
148+
public class MediaTypeEnum : CamelCaseEnumerationGraphType<MediaTypeViewModel>
149+
{
150+
}
151+
```
152+
153+
b. Mark your .NET enum with one of the `EnumCaseAttribute` descendants (`PascalCase`, `CamelCase`, `ConstantCase` or your own).
154+
155+
```csharp
156+
[CamelCase]
157+
public enum CamelCaseEnum
158+
{
159+
FirstValue,
160+
SecondValue
161+
}
162+
```
133163

134164
```csharp
135165
[Description("The Star Wars movies.")]
@@ -163,7 +193,7 @@ mapped via `Schema.RegisterTypeMapping`:
163193
Field(x => x.MyEnum);
164194
```
165195

166-
You can also manually create the `EnumerationGraphType`. Advantages of this method:
196+
2. You can also manually create the `EnumerationGraphType`. Advantages of this method:
167197

168198
- The GraphQL enum need not map to a specific .NET `enum`. You could, for instance, build the enum from one of the alternate methods of defining discrete sets of values in .NET, such as classes of constants or static properties.
169199
- You can manually add descriptions and deprecation reasons. This may be useful if you do not control the source code for the enum.

src/GraphQL.ApiTests/net6/GraphQL.approved.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,11 @@ namespace GraphQL.Types
18681868
public override object? ParseLiteral(GraphQL.Language.AST.IValue value) { }
18691869
public override object? ParseValue(object? value) { }
18701870
}
1871+
public class CamelCaseAttribute : GraphQL.Types.EnumCaseAttribute
1872+
{
1873+
public CamelCaseAttribute() { }
1874+
public override string ChangeEnumCase(string val) { }
1875+
}
18711876
public abstract class ComplexGraphType<TSourceType> : GraphQL.Types.GraphType, GraphQL.Types.IComplexGraphType, GraphQL.Types.IGraphType, GraphQL.Types.INamedType, GraphQL.Types.IProvideDeprecationReason, GraphQL.Types.IProvideDescription, GraphQL.Types.IProvideMetadata
18721877
{
18731878
protected ComplexGraphType() { }
@@ -1905,6 +1910,11 @@ namespace GraphQL.Types
19051910
public GraphQL.Types.FieldType? GetField(string name) { }
19061911
public bool HasField(string name) { }
19071912
}
1913+
public class ConstantCaseAttribute : GraphQL.Types.EnumCaseAttribute
1914+
{
1915+
public ConstantCaseAttribute() { }
1916+
public override string ChangeEnumCase(string val) { }
1917+
}
19081918
public class DateGraphType : GraphQL.Types.ScalarGraphType
19091919
{
19101920
public DateGraphType() { }
@@ -2003,6 +2013,12 @@ namespace GraphQL.Types
20032013
[System.ComponentModel.Description("Location adjacent to an input object field definition.")]
20042014
InputFieldDefinition = 17,
20052015
}
2016+
[System.AttributeUsage(System.AttributeTargets.Enum | System.AttributeTargets.All, AllowMultiple=false)]
2017+
public abstract class EnumCaseAttribute : System.Attribute
2018+
{
2019+
protected EnumCaseAttribute() { }
2020+
public abstract string ChangeEnumCase(string val);
2021+
}
20062022
public class EnumValueDefinition : GraphQL.Utilities.MetadataProvider, GraphQL.Types.IProvideDeprecationReason, GraphQL.Types.IProvideDescription
20072023
{
20082024
public EnumValueDefinition() { }
@@ -2304,6 +2320,11 @@ namespace GraphQL.Types
23042320
public void Interface<TInterface>()
23052321
where TInterface : GraphQL.Types.IInterfaceGraphType { }
23062322
}
2323+
public class PascalCaseAttribute : GraphQL.Types.EnumCaseAttribute
2324+
{
2325+
public PascalCaseAttribute() { }
2326+
public override string ChangeEnumCase(string val) { }
2327+
}
23072328
public class PossibleTypes : System.Collections.Generic.IEnumerable<GraphQL.Types.IObjectGraphType>, System.Collections.IEnumerable
23082329
{
23092330
public PossibleTypes() { }

src/GraphQL.ApiTests/netstandard20+netstandard21/GraphQL.approved.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,11 @@ namespace GraphQL.Types
18681868
public override object? ParseLiteral(GraphQL.Language.AST.IValue value) { }
18691869
public override object? ParseValue(object? value) { }
18701870
}
1871+
public class CamelCaseAttribute : GraphQL.Types.EnumCaseAttribute
1872+
{
1873+
public CamelCaseAttribute() { }
1874+
public override string ChangeEnumCase(string val) { }
1875+
}
18711876
public abstract class ComplexGraphType<TSourceType> : GraphQL.Types.GraphType, GraphQL.Types.IComplexGraphType, GraphQL.Types.IGraphType, GraphQL.Types.INamedType, GraphQL.Types.IProvideDeprecationReason, GraphQL.Types.IProvideDescription, GraphQL.Types.IProvideMetadata
18721877
{
18731878
protected ComplexGraphType() { }
@@ -1905,6 +1910,11 @@ namespace GraphQL.Types
19051910
public GraphQL.Types.FieldType? GetField(string name) { }
19061911
public bool HasField(string name) { }
19071912
}
1913+
public class ConstantCaseAttribute : GraphQL.Types.EnumCaseAttribute
1914+
{
1915+
public ConstantCaseAttribute() { }
1916+
public override string ChangeEnumCase(string val) { }
1917+
}
19081918
public class DateGraphType : GraphQL.Types.ScalarGraphType
19091919
{
19101920
public DateGraphType() { }
@@ -1996,6 +2006,12 @@ namespace GraphQL.Types
19962006
[System.ComponentModel.Description("Location adjacent to an input object field definition.")]
19972007
InputFieldDefinition = 17,
19982008
}
2009+
[System.AttributeUsage(System.AttributeTargets.Enum | System.AttributeTargets.All, AllowMultiple=false)]
2010+
public abstract class EnumCaseAttribute : System.Attribute
2011+
{
2012+
protected EnumCaseAttribute() { }
2013+
public abstract string ChangeEnumCase(string val);
2014+
}
19992015
public class EnumValueDefinition : GraphQL.Utilities.MetadataProvider, GraphQL.Types.IProvideDeprecationReason, GraphQL.Types.IProvideDescription
20002016
{
20012017
public EnumValueDefinition() { }
@@ -2297,6 +2313,11 @@ namespace GraphQL.Types
22972313
public void Interface<TInterface>()
22982314
where TInterface : GraphQL.Types.IInterfaceGraphType { }
22992315
}
2316+
public class PascalCaseAttribute : GraphQL.Types.EnumCaseAttribute
2317+
{
2318+
public PascalCaseAttribute() { }
2319+
public override string ChangeEnumCase(string val) { }
2320+
}
23002321
public class PossibleTypes : System.Collections.Generic.IEnumerable<GraphQL.Types.IObjectGraphType>, System.Collections.IEnumerable
23012322
{
23022323
public PossibleTypes() { }

src/GraphQL.Tests/Types/EnumGraphTypeTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,45 @@ private enum MyEnum
159159
TestHello,
160160
Hello1
161161
}
162+
163+
[Fact]
164+
public void enum_names_from_attribute_tests()
165+
{
166+
EnumerationGraphType e = new EnumerationGraphType<ConstantCaseEnum>();
167+
e.Values.Count.ShouldBe(2);
168+
e.Values.FindByValue(ConstantCaseEnum.OneOne).Name.ShouldBe("ONE_ONE");
169+
e.Values.FindByValue(ConstantCaseEnum.TwoTwo).Name.ShouldBe("TWO_TWO");
170+
171+
e = new EnumerationGraphType<CamelCaseEnum>();
172+
e.Values.Count.ShouldBe(2);
173+
e.Values.FindByValue(CamelCaseEnum.OneOne).Name.ShouldBe("oneOne");
174+
e.Values.FindByValue(CamelCaseEnum.TwoTwo).Name.ShouldBe("twoTwo");
175+
176+
e = new EnumerationGraphType<PascalCaseEnum>();
177+
e.Values.Count.ShouldBe(2);
178+
e.Values.FindByValue(PascalCaseEnum.OneOne).Name.ShouldBe("OneOne");
179+
e.Values.FindByValue(PascalCaseEnum.TwoTwo).Name.ShouldBe("TwoTwo");
180+
}
181+
182+
[ConstantCase]
183+
private enum ConstantCaseEnum
184+
{
185+
OneOne,
186+
TwoTwo
187+
}
188+
189+
[CamelCase]
190+
private enum CamelCaseEnum
191+
{
192+
OneOne,
193+
TwoTwo
194+
}
195+
196+
[PascalCase]
197+
private enum PascalCaseEnum
198+
{
199+
OneOne,
200+
TwoTwo
201+
}
162202
}
163203
}

src/GraphQL/Types/Scalars/EnumerationGraphType.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public void AddValue(EnumValueDefinition value)
126126
/// <typeparam name="TEnum"> The enum to take values from. </typeparam>
127127
public class EnumerationGraphType<TEnum> : EnumerationGraphType where TEnum : Enum
128128
{
129+
private static readonly EnumCaseAttribute _caseAttr = typeof(TEnum).GetCustomAttribute<EnumCaseAttribute>();
130+
129131
/// <summary>
130132
/// Initializes a new instance of the <see cref="EnumerationGraphType"/> class.
131133
/// </summary>
@@ -154,9 +156,11 @@ public EnumerationGraphType()
154156
}
155157

156158
/// <summary>
157-
/// Changes the case of the specified enum name. By default changes it to constant case (uppercase, using underscores to separate words).
159+
/// Changes the case of the specified enum name.
160+
/// By default changes it to constant case (uppercase, using underscores to separate words).
158161
/// </summary>
159-
protected virtual string ChangeEnumCase(string val) => val.ToConstantCase();
162+
protected virtual string ChangeEnumCase(string val)
163+
=> _caseAttr == null ? val.ToConstantCase() : _caseAttr.ChangeEnumCase(val);
160164
}
161165

162166
/// <summary>
@@ -284,4 +288,44 @@ public object? Value
284288
/// </summary>
285289
internal object? UnderlyingValue { get; set; }
286290
}
291+
292+
/// <summary>
293+
/// Allows to change the case of the enum names for enum marked with that attribute.
294+
/// </summary>
295+
[AttributeUsage(AttributeTargets.Enum, AllowMultiple = false)]
296+
public abstract class EnumCaseAttribute : Attribute
297+
{
298+
/// <summary>
299+
/// Changes the case of the specified enum name.
300+
/// </summary>
301+
public abstract string ChangeEnumCase(string val);
302+
}
303+
304+
/// <summary>
305+
/// Returns a constant case version of enum names.
306+
/// For example, converts 'StringError' into 'STRING_ERROR'.
307+
/// </summary>
308+
public class ConstantCaseAttribute : EnumCaseAttribute
309+
{
310+
/// <inheritdoc />
311+
public override string ChangeEnumCase(string val) => val.ToConstantCase();
312+
}
313+
314+
/// <summary>
315+
/// Returns a camel case version of enum names.
316+
/// </summary>
317+
public class CamelCaseAttribute : EnumCaseAttribute
318+
{
319+
/// <inheritdoc />
320+
public override string ChangeEnumCase(string val) => val.ToCamelCase();
321+
}
322+
323+
/// <summary>
324+
/// Returns a pascal case version of enum names.
325+
/// </summary>
326+
public class PascalCaseAttribute : EnumCaseAttribute
327+
{
328+
/// <inheritdoc />
329+
public override string ChangeEnumCase(string val) => val.ToPascalCase();
330+
}
287331
}

0 commit comments

Comments
 (0)