Skip to content

Commit 574fc66

Browse files
committed
Improve String Enum handling for additional use cases to ensure resulting Enums are properly formed (e.g. no unnecessary underscores being inserted in various cases). And some code naming improvements, and cleanup. Added greater Unit Test coverage for Enum parsing (serialization & deserialization).
1 parent 685814a commit 574fc66

File tree

9 files changed

+122
-15
lines changed

9 files changed

+122
-15
lines changed

FlurlGraphQL.Newtonsoft/FlurlGraphQLNewtonsoftJsonScreamingCaseNamingStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44
public class FlurlGraphQLNewtonsoftJsonScreamingCaseNamingStrategy : NamingStrategy
55
{
6-
protected override string ResolvePropertyName(string name) => name.ToScreamingCase();
6+
protected override string ResolvePropertyName(string name) => name.ToScreamingSnakeCase();
77
}

FlurlGraphQL.Tests/FlurlGraphQLMutationTests.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ public async Task TestMutationWithQueryResultsAsync(IFlurlRequest graphqlApiRequ
2727
}
2828
}
2929
")
30-
.SetGraphQLVariable("reviewInput", new {
30+
.SetGraphQLVariable("reviewInput", new {
3131
episode = "EMPIRE",
3232
stars = 5,
33+
StarsEnum = StarsEnum.FiveStars,
3334
commentary = "I love this Movie!"
3435
})
3536
.PostGraphQLQueryAsync()
@@ -48,6 +49,15 @@ public async Task TestMutationWithQueryResultsAsync(IFlurlRequest graphqlApiRequ
4849
}
4950
}
5051

52+
public enum StarsEnum
53+
{
54+
OneStar = 1,
55+
TwoStars = 2,
56+
ThreeStars = 3,
57+
FourStars = 4,
58+
FiveStars = 5
59+
};
60+
5161
public class CreateReviewPayload {
5262
public string Episode { get; set; }
5363
public ReviewResult Review {get; set;}

FlurlGraphQL.Tests/FlurlGraphQLParsingTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using System.Text.Json.Serialization;
@@ -10,6 +11,9 @@
1011
using Microsoft.VisualStudio.TestTools.UnitTesting;
1112
using Newtonsoft.Json;
1213
using Newtonsoft.Json.Linq;
14+
using System.Reflection;
15+
using Bogus.DataSets;
16+
using System.Runtime.Serialization;
1317

1418
namespace FlurlGraphQL.Tests
1519
{
@@ -156,6 +160,55 @@ public void TestSimpleSystemTextJsonParsingOfPreFlattenedJsonWithStringEnumDirec
156160
Assert.IsTrue(isEqual, "The Test Json and the Round Trip Serialized Json do not match!");
157161
}
158162

163+
[TestMethod]
164+
[TestDataExecuteWithAllFlurlSerializerRequests]
165+
public void TestJsonParsingForAllStringEnumTestCases(IFlurlRequest flurlRequest)
166+
{
167+
//The DEFAULT options for FlurlGraphQL System.Text.Json Serializer should already have the String Enum Converter added...
168+
//var flurlGraphQLSystemTextJsonSerializer = new FlurlGraphQLSystemTextJsonSerializer(FlurlGraphQLSystemTextJsonSerializer.CreateDefaultSerializerOptions());
169+
var graphqlApiRequest = flurlRequest.ToGraphQLRequest();
170+
171+
var graphqlJsonSerializer = graphqlApiRequest.GraphQLJsonSerializer;
172+
173+
TestContext.WriteLine($"[{graphqlJsonSerializer.GetType().Name}] Enum Test Cases Parsing ...");
174+
bool isUsingSystemTextJson = graphqlJsonSerializer is FlurlGraphQLSystemTextJsonSerializer;
175+
176+
foreach (int enumIntValue in Enum.GetValues(typeof(EnumTestCase)))
177+
{
178+
var enumValue = (EnumTestCase)enumIntValue;
179+
var enumField = enumValue.GetType().GetField(enumValue.ToString());
180+
181+
var expectedEnumStringValue =
182+
enumField.GetCustomAttribute<EnumMemberAttribute>(true)?.Value
183+
//JsonPropertyName is ONLY supported by System.Text.Json; Newtonsoft did not support this attribute for Enums...
184+
?? (isUsingSystemTextJson ? enumField.GetCustomAttribute<JsonPropertyNameAttribute>(true)?.Name : null)
185+
?? enumValue.ToString().ToScreamingSnakeCase();
186+
187+
//Serialize the enum test case...
188+
var serializedEnumValue = graphqlJsonSerializer.Serialize(enumValue);
189+
190+
//The serialized value should be a quoted string output from Json Serialization...
191+
Assert.AreEqual($"\"{expectedEnumStringValue}\"", serializedEnumValue, $"The Enum Value [{enumValue}] did not serialize to the expected string value [{expectedEnumStringValue}]!");
192+
193+
//Deserialize back to the Enum Value...
194+
var deserializedEnumValue = graphqlJsonSerializer.Deserialize<EnumTestCase>(serializedEnumValue);
195+
Assert.AreEqual(enumValue, deserializedEnumValue, $"The Enum Value [{enumValue}] did not deserialize back correctly from the serialized value [{serializedEnumValue}]!");
196+
197+
//Valdate actual values for the OVERRIDE cases using Attributes...
198+
switch (enumValue)
199+
{
200+
case EnumTestCase.TestPacalCaseEnumMember:
201+
Assert.AreEqual("TEST_PASCAL_CASE_ENUM_MEMBER_OVERRIDE", expectedEnumStringValue);
202+
break;
203+
case EnumTestCase.TestJsonPropertyNameSupportedBySystemTextJson when isUsingSystemTextJson:
204+
Assert.AreEqual("TEST.JsonPropertyName.Supported.By.System.Text.Json", expectedEnumStringValue);
205+
break;
206+
}
207+
208+
TestContext.WriteLine($"Success: [{enumValue}] <==> [{serializedEnumValue}]");
209+
}
210+
}
211+
159212
[TestMethod]
160213
public void TestSystemTextJsonParsingOfNestedPaginatedStarWarsGraphQLResults()
161214
{

FlurlGraphQL.Tests/Models/StarWarsCharacter.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.ComponentModel;
23
using System.Runtime.Serialization;
34
using System.Text.Json.Serialization;
45
using Newtonsoft.Json;
@@ -35,6 +36,31 @@ internal enum TheForceWithEnumMembers
3536
DarkSideForEvil = -1
3637
}
3738

39+
internal enum EnumTestCase
40+
{
41+
//Use Description to Override Behavior
42+
[EnumMember(Value = "TEST_PASCAL_CASE_ENUM_MEMBER_OVERRIDE")]
43+
TestPacalCaseEnumMember = 0,
44+
//Use JsonPropertyName to Override Behavior
45+
[JsonPropertyName("TEST.JsonPropertyName.Supported.By.System.Text.Json")]
46+
TestJsonPropertyNameSupportedBySystemTextJson = 8,
47+
//Auto-Coversion tests with no attribute...
48+
TestPascalCaseAutoConversion = 2,
49+
//Auto-Coversion tests with no attribute...
50+
TestV2 = 3,
51+
//Auto-Coversion tests with no attribute...
52+
Test__Strange__Pascal__Snake__Case__2 = 4,
53+
//Auto-Coversion tests with no attribute...
54+
testCamelCase = 5,
55+
//Auto-Coversion tests with no attribute...
56+
test_snake_case = 6,
57+
//Auto-Coversion tests with no attribute...
58+
TEST_SCREAMING_SNAKE_CASE = 7,
59+
//JsonPropertyAttribute Test...
60+
[Description("TEST.Description.Attribute")]
61+
TestDescriptionAttribute = 9
62+
}
63+
3864
internal class StarWarsCharacterWithSimpleEnum
3965
{
4066
public int PersonalIdentifier { get; set; }

FlurlGraphQL/CustomExtensions/StringExtensions.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ internal static class StringExtensions
99
{
1010
public const string SPACE = " ";
1111
public const char UNDERSCORE = '_';
12+
//This is a character literal representing the Unicode character with code point 0, also known as
13+
// the null terminator or NUL character. And often used to initialize a char variable to a known default or empty state.
14+
public const char NULL_CHAR = '\0';
15+
1216
private static readonly char[] _sentencePunctuationChars = { ' ', '.', ';', '?', '!' };
1317

14-
public static string ToScreamingCase(this string input)
18+
public static string ToScreamingSnakeCase(this string input)
1519
{
1620
if (string.IsNullOrEmpty(input)) return input;
1721

@@ -22,13 +26,23 @@ public static string ToScreamingCase(this string input)
2226
int index = 0;
2327
for (int i = 0; i < inputSpan.Length; i++)
2428
{
25-
char c = inputSpan[i];
26-
27-
//Insert an underscore before uppercase letters (except first letter)
28-
if (i > 0 && char.IsUpper(c))
29+
char currentChar = inputSpan[i];
30+
char previousChar = i > 0 ? inputSpan[i - 1] : NULL_CHAR;
31+
char nextChar = i < inputSpan.Length - 1 ? inputSpan[i + 1] : NULL_CHAR;
32+
33+
//Insert an underscore before uppercase letters except when:
34+
// - Currently on the first letter
35+
// - When current, previous, or next character is already an Underscore
36+
// - When already contiguous capital letters
37+
// - When contingous lower-case (non-capital) letters; implicitly (by processing only Capital Letters)
38+
if (i > 0
39+
&& currentChar != UNDERSCORE && previousChar != UNDERSCORE && nextChar != UNDERSCORE
40+
&& char.IsUpper(currentChar) && !char.IsUpper(previousChar)
41+
) {
2942
outputSpan[index++] = UNDERSCORE;
43+
}
3044

31-
outputSpan[index++] = char.ToUpper(c, CultureInfo.InvariantCulture);
45+
outputSpan[index++] = char.ToUpper(currentChar, CultureInfo.InvariantCulture);
3246
}
3347

3448
//return new string(outputSpan[..index]); // Convert the span into a string

FlurlGraphQL/FlurlGraphQL.csproj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
<!--NOTE: Just as with the base Flurl.Http.Newtonsoft library and Microsofts recommendation we now support net6 for the latest projects, but also netstandard2.1 which has proper Async streaming support,
55
in addition to netstandard2.0 + net461 for legacy support (because netstandard2.0 it had API compatibility issues prior to .NET Framework 4.7.x -->
66
<TargetFrameworks>net461;netstandard2.0;netstandard2.1;net6.0;</TargetFrameworks>
7-
<Version>2.0.5</Version>
8-
<AssemblyVersion>2.0.5</AssemblyVersion>
9-
<FileVersion>2.0.5</FileVersion>
7+
<Version>2.0.6</Version>
8+
<AssemblyVersion>2.0.6</AssemblyVersion>
9+
<FileVersion>2.0.6</FileVersion>
1010
<Authors>BBernard / CajunCoding</Authors>
1111
<Company>CajunCoding</Company>
1212
<Description>GraphQL client extensions for Flurl.Http -- lightweight, simplified, asynchronous, fluent GraphQL client API extensions for the amazing Flurl Http library!</Description>
@@ -17,9 +17,10 @@
1717
<RepositoryUrl>https://github.com/cajuncoding/FlurlGraphQL</RepositoryUrl>
1818
<PackageReleaseNotes>
1919
Release Notes:
20-
- Improve configuration support for Defeault Json Processing now with Enum Flags to make enabling/disabling the GraphQL Json Defaults much easier.
20+
- Improve String Enum hanlding for additional use cases to ensure resulting Enums are properly formed (e.g. no unnecessary underscores being inserted in various cases).
2121

2222
Prior Release Notes:
23+
- Improve configuration support for Default Json Processing now with Enum Flags to make enabling/disabling the GraphQL Json Defaults much easier.
2324
- Improve handling of Enums so that automatic processing as SCREAMING_CASE is handled now without the need to have [EnumMember("")] attributes on every enum value when the names match.
2425
- Improve handling of GraphQL Serialization to now automatically use CamelCase for System.Text.Json &amp; Newtonsoft.Json; it was already being handled when de-serializing but not when serializing.
2526
- Added new methods to help streamline the configuration of Json Serializer options/settings; a simple action/lambda can now be used to set Json Serialization Options/Settings.

FlurlGraphQL/FlurlGraphQL/JsonProcessing/FlurlGraphQLSystemTextJsonScreamingCaseNamingPolicy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ namespace FlurlGraphQL.JsonProcessing
44
{
55
public class FlurlGraphQLSystemTextJsonScreamingCaseNamingPolicy : JsonNamingPolicy
66
{
7-
public override string ConvertName(string name) => name.ToScreamingCase();
7+
public override string ConvertName(string name) => name.ToScreamingSnakeCase();
88
}
99
}

FlurlGraphQL/FlurlGraphQL/JsonProcessing/FlurlGraphQLSystemTextJsonSerializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ public static JsonSerializerOptions CreateDefaultSerializerOptions(JsonSerialize
4949
graphqlJsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
5050

5151
//For compatibility with FlurlGraphQL v1 behavior (using Newtonsoft.Json) we need to provide support for String to Enum conversion along with support for enum annotations
52-
// via [EnumMember(Value ="CustomName")] annotation (compatible with Newtonsoft.Json). In addition, we now also support [Description("CustomName")] annotation for
53-
// easier syntax that is arguably more intuitive to use.
52+
// via [EnumMember(Value ="CustomName")] annotation (compatible with Newtonsoft.Json). In addition, we now also inherently support [JsonPropertyName("CustomName")] annotation for
53+
// easier syntax that is arguably more intuitive to use as this is provided natively by System.Text.Json.
5454
if (defaultJsonConfig.IsJsonProcessingFlagEnabled(JsonDefaults.EnableStringEnumHandling))
5555
{
5656
//NOTE: For performance we KNOW we need to add this if the original options were not provided (e.g. null)...

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ To use this in your project, add the [FlurlGraphQL](https://www.nuget.org/packag
120120
To use this in your project with `Newtonsoft.Json` processing then add the add the [FlurlGraphQL.Newtonsoft](https://www.nuget.org/packages/FlurlGraphQL.Newtonsoft/) NuGet package to your project.
121121

122122
## Release Notes:
123+
### v2.0.6
124+
- Improve String Enum hanlding for additional use cases to ensure resulting Enums are properly formed (e.g. no unnecessary underscores being inserted in various cases).
125+
123126
### v2.0.5
124127
- Improve configuration support for Defeault Json Processing now with Enum Flags to make enabling/disabling the GraphQL Json Defaults much easier.
125128

0 commit comments

Comments
 (0)