Skip to content

Commit f1e4a26

Browse files
committed
- Fix issue with incorrect deserialization when using wrapper convenience class GraphQLEdge<T>
1 parent 9fa5954 commit f1e4a26

10 files changed

+196
-25
lines changed

FlurlGraphQL.Newtonsoft/FlurlGraphQL.Newtonsoft.csproj

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
(because it had big issues using netstandard prior to .NET Framework 4.7) -->
66
<TargetFrameworks>net461;netstandard2.0;net6.0</TargetFrameworks>
77
<ImplicitUsings>disable</ImplicitUsings>
8-
<Version>2.0.0</Version>
9-
<AssemblyVersion>2.0.0</AssemblyVersion>
10-
<FileVersion>2.0.0</FileVersion>
8+
<Version>2.0.1</Version>
9+
<AssemblyVersion>2.0.1</AssemblyVersion>
10+
<FileVersion>2.0.1</FileVersion>
1111
<Authors>BBernard / CajunCoding</Authors>
1212
<Company>CajunCoding</Company>
1313
<Description>Newtonsoft JSON Supportt for FlurlGraphQL v2.0+ -- A GraphQL client extensions for Flurl.Http - lightweight, simplified, asynchronous, fluent GraphQL client API extensions for the amazing Flurl Http library!</Description>
@@ -18,8 +18,10 @@
1818
<RepositoryUrl>https://github.com/cajuncoding/FlurlGraphQL</RepositoryUrl>
1919
<PackageReleaseNotes>
2020
Release Notes:
21-
- Newtonsoft JSON Compatibility implementation for FlurlGraphQL v2.0 using the all new Flurl v4.0+.
21+
- Fix issue with incorrect deserialization when using wrapper convenience class GraphQLEdge&lt;T&gt;.
2222

23+
Prior Release Notes:
24+
- Newtonsoft JSON Compatibility implementation for FlurlGraphQL v2.0 using the all new Flurl v4.0+.
2325
</PackageReleaseNotes>
2426
<PackageTags>graphql, graph-ql, graphql client, graphql query, flurl, newtonsoft, rest, http, http client, json, hotchocolate, paging, cursor</PackageTags>
2527
</PropertyGroup>

FlurlGraphQL.Newtonsoft/FlurlGraphQLNewtonsoftJsonTransformer.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
5+
using System.Text.Json.Nodes;
56
using FlurlGraphQL.CustomExtensions;
67
using FlurlGraphQL.ValidationExtensions;
78
using Newtonsoft.Json;
@@ -49,7 +50,7 @@ protected FlurlGraphQLNewtonsoftJsonTransformer(FlurlGraphQLJsonTransformTypeInf
4950
//If our ROOT EntityType does not implement IGraphQLResults then we need to Flatten the Root Level first...
5051
// This ensures we are accessing the expected data within the [edges], [nodes], or [items] collection for all recursive processing...
5152
if(!this.JsonTransformTypeInfo.ImplementsIGraphQLQueryResults && json is JObject jsonObject)
52-
processedJson = TransformGraphQLJsonObjectAsNeeded(jsonObject, false);
53+
processedJson = TransformGraphQLJsonObjectAsNeeded(jsonObject, this.JsonTransformTypeInfo.ImplementsIGraphQLEdge);
5354

5455
//Secondly, after the Root is prepared we can recursively process the rest of the Json...
5556
var rewrittenJson = TransformGraphQLJsonAsNeededRecursively(processedJson);
@@ -157,7 +158,12 @@ private JToken TransformGraphQLJsonObjectAsNeeded(JObject json, bool isIGraphQLE
157158
// - finally use the (non-nested) array of results if not a Paginated result set of any kind above...
158159
if (json.Field(GraphQLFields.Nodes) is JArray nodesJson)
159160
{
160-
entityNodes = nodesJson.OfType<JObject>();
161+
entityNodes = isIGraphQLEdgeImplementedOnProp
162+
//Handle the edge case (pun intended) where the GraphQLEdge<T> is used though only nodes{} was queried in the GraphQL query;
163+
// in this case we must map the Node to an Edge structure (with null cursor) for proper de-serialization.
164+
? ConvertGraphQLNodesToEdgesJsonArray(nodesJson)
165+
: nodesJson.OfType<JObject>();
166+
161167
//NOTE: Clearing the Parent is not needed with Newtonsoft.Json so we save a little work here...
162168
//nodesJson.Clear();
163169
}
@@ -228,6 +234,19 @@ private IEnumerable<JObject> FlattenGraphQLEdgesToJsonArray(JArray edgesArray)
228234
.Where(i => i != null);
229235
}
230236

237+
private IEnumerable<JObject> ConvertGraphQLNodesToEdgesJsonArray(JArray nodesArray)
238+
{
239+
return nodesArray
240+
//NOW re-map the node objects into an Edge Structure for proper de-serialization (e.g. when GraphQLEdge<T> but only nodes {} are requested)...
241+
//When converting from a Node, we don't have a Cursor property so it is simply null, but we are building the structure for proper de-serialization to GraphQLEdge<T>...
242+
//NOTE: For performance we set the values directly as they are expected (vs serializing an anonymous object which would have a lot more overhead)...
243+
.Select(node => new JObject
244+
{
245+
[GraphQLFields.Cursor] = null,
246+
[GraphQLFields.Node] = node
247+
});
248+
}
249+
231250
private PaginationType? DeterminePaginationType(JToken json)
232251
{
233252
if (json is JObject jsonObject)

FlurlGraphQL.Tests/FlurlGraphQL.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<ImplicitUsings>disable</ImplicitUsings>
77
<Nullable>disable</Nullable>
88
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
910
</PropertyGroup>
1011

1112
<ItemGroup>

FlurlGraphQL.Tests/FlurlGraphQLConfigTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void TestSystemTextJsonSerializerFlurlRequestLevelConfig()
5858
Assert.AreEqual(JsonIgnoreCondition.WhenWritingNull, graphqlJsonSerializer.JsonSerializerOptions.DefaultIgnoreCondition);
5959
}
6060

61-
//TODO: Find out why Global Configuration isn't working and Fix Unit Test...
61+
//TODO: Update to execute Full Request so that clients are actually initialized to fix this Test!
6262
[Ignore]
6363
[TestMethod]
6464
public void TestSystemTextJsonSerializerFlurlGlobalConfig()
@@ -124,7 +124,7 @@ public void TestNewtonsoftJsonSerializerFlurlRequestLevelConfig()
124124
Assert.AreEqual(TypeNameHandling.None, graphqlJsonSerializer.JsonSerializerSettings.TypeNameHandling);
125125
}
126126

127-
//TODO: Find out why Global Configuration isn't working and Fix Unit Test...
127+
//TODO: Update to execute Full Request so that clients are actually initialized to fix this Test!
128128
[Ignore]
129129
[TestMethod]
130130
public void TestNewtonsoftJsonSerializerFlurlGlobalConfig()

FlurlGraphQL.Tests/FlurlGraphQLQueryingPaginatedTests.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,124 @@ public async Task TestSingleQueryCursorPagingNodeResultsAsync(IFlurlRequest grap
5858
TestContext.WriteLine(jsonText);
5959
}
6060

61+
[DataTestMethod]
62+
[TestDataExecuteWithAllFlurlSerializerRequests]
63+
public async Task TestSingleQueryCursorPagingGraphQLEdgeWrapperAsync(IFlurlRequest graphQLApiRequest)
64+
{
65+
var results = await graphQLApiRequest
66+
.WithGraphQLQuery(@"
67+
query($first:Int) {
68+
characters (first:$first) {
69+
totalCount
70+
pageInfo {
71+
hasNextPage
72+
hasPreviousPage
73+
startCursor
74+
endCursor
75+
}
76+
edges {
77+
cursor
78+
node {
79+
personalIdentifier
80+
name
81+
height
82+
}
83+
}
84+
}
85+
}
86+
")
87+
.SetGraphQLVariables(new { first = 2 })
88+
.PostGraphQLQueryAsync()
89+
.ReceiveGraphQLConnectionResults<GraphQLEdge<StarWarsCharacter>>()
90+
.ConfigureAwait(false);
91+
92+
Assert.IsNotNull(results);
93+
Assert.IsTrue(results is IGraphQLConnectionResults<IGraphQLEdge<StarWarsCharacter>>);
94+
Assert.AreEqual(2, results.Count);
95+
96+
Assert.IsNotNull(results.TotalCount);
97+
Assert.IsTrue(results.TotalCount > results.Count);
98+
Assert.IsNotNull(results.PageInfo);
99+
Assert.IsTrue(results.PageInfo.HasNextPage);
100+
Assert.IsFalse(results.PageInfo.HasPreviousPage);
101+
Assert.IsFalse(string.IsNullOrWhiteSpace(results.PageInfo.StartCursor));
102+
Assert.IsFalse(string.IsNullOrWhiteSpace(results.PageInfo.EndCursor));
103+
104+
foreach (var edgeResult in results)
105+
{
106+
Assert.IsNotNull(edgeResult);
107+
Assert.IsNotNull(edgeResult.Cursor);
108+
Assert.IsNotNull(edgeResult.Node);
109+
110+
var charNode = edgeResult.Node;
111+
Assert.IsTrue(charNode.PersonalIdentifier > 0);
112+
Assert.IsFalse(string.IsNullOrWhiteSpace(charNode.Name));
113+
Assert.IsTrue(charNode.Height > 0);
114+
}
115+
116+
var jsonText = JsonConvert.SerializeObject(results, Formatting.Indented);
117+
TestContext.WriteLine(jsonText);
118+
}
119+
120+
[DataTestMethod]
121+
[TestDataExecuteWithAllFlurlSerializerRequests]
122+
public async Task TestSingleQueryCursorPagingGraphQLEdgeWrapperEdgeCaseWhenOnlyNodesAreQueriedAsync(IFlurlRequest graphQLApiRequest)
123+
{
124+
var results = await graphQLApiRequest
125+
.WithGraphQLQuery(@"
126+
query($first:Int) {
127+
characters (first:$first) {
128+
totalCount
129+
pageInfo {
130+
hasNextPage
131+
hasPreviousPage
132+
startCursor
133+
endCursor
134+
}
135+
nodes {
136+
personalIdentifier
137+
name
138+
height
139+
}
140+
}
141+
}
142+
")
143+
.SetGraphQLVariables(new { first = 2 })
144+
.PostGraphQLQueryAsync()
145+
.ReceiveGraphQLConnectionResults<GraphQLEdge<StarWarsCharacter>>()
146+
.ConfigureAwait(false);
147+
148+
Assert.IsNotNull(results);
149+
Assert.IsTrue(results is IGraphQLConnectionResults<IGraphQLEdge<StarWarsCharacter>>);
150+
Assert.AreEqual(2, results.Count);
151+
152+
Assert.IsNotNull(results.TotalCount);
153+
Assert.IsTrue(results.TotalCount > results.Count);
154+
Assert.IsNotNull(results.PageInfo);
155+
Assert.IsTrue(results.PageInfo.HasNextPage);
156+
Assert.IsFalse(results.PageInfo.HasPreviousPage);
157+
Assert.IsFalse(string.IsNullOrWhiteSpace(results.PageInfo.StartCursor));
158+
Assert.IsFalse(string.IsNullOrWhiteSpace(results.PageInfo.EndCursor));
159+
160+
foreach (var edgeResult in results)
161+
{
162+
Assert.IsNotNull(edgeResult);
163+
//*****************************************************************************************
164+
//NOTE: Cursor MUST be NULL because it is NOT available when ONLY nodes{} is queried!
165+
//*****************************************************************************************
166+
Assert.IsNull(edgeResult.Cursor);
167+
Assert.IsNotNull(edgeResult.Node);
168+
169+
var charNode = edgeResult.Node;
170+
Assert.IsTrue(charNode.PersonalIdentifier > 0);
171+
Assert.IsFalse(string.IsNullOrWhiteSpace(charNode.Name));
172+
Assert.IsTrue(charNode.Height > 0);
173+
}
174+
175+
var jsonText = JsonConvert.SerializeObject(results, Formatting.Indented);
176+
TestContext.WriteLine(jsonText);
177+
}
178+
61179
[TestMethod]
62180
[TestDataExecuteWithAllFlurlSerializerRequests]
63181
public async Task TestSingleQueryCursorPagingEdgeResultsAndNestedEdgeResultsAsync(IFlurlRequest graphqlApiRequest)

FlurlGraphQL/FlurlGraphQL.csproj

Lines changed: 6 additions & 5 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.0</Version>
8-
<AssemblyVersion>2.0.0</AssemblyVersion>
9-
<FileVersion>2.0.0</FileVersion>
7+
<Version>2.0.1</Version>
8+
<AssemblyVersion>2.0.1</AssemblyVersion>
9+
<FileVersion>2.0.1</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,13 +17,14 @@
1717
<RepositoryUrl>https://github.com/cajuncoding/FlurlGraphQL</RepositoryUrl>
1818
<PackageReleaseNotes>
1919
Release Notes:
20+
- Fix issue with incorrect deserialization when using wrapper convenience class GraphQLEdge&lt;T&gt;.
21+
22+
Prior Release Notes:
2023
- Implement full support for Flurl v4.0+
2124
- Completely rewritten Json processing engine to now support both System.Text.Json &amp; Newtonsoft.Json.
2225
- System.Text.Json processing with Json transformation strategy is now ~10X faster than the original Newtonsoft.Json processing.
2326
- Optimized Newtonsoft.Json processing with new Json transformation strategy (vs Converter) which now benchmarks at ~2X faster.
2427
- Namespace, Project/Library, and NuGet name has now been simplified to `FlurlGraphQL` (vs `FlurlGraphQL.Querying` in v1.x).
25-
26-
Prior Release Notes:
2728
- Fix null reference issue in GraphQL/Request Error handling of HttpStatusCode.
2829
- Added better support for Mutation handling so that single payload (per Mutation convention best practices) can be returned easily via ReceiveGraphQLMutationResult;
2930
this eliminates the need to use ReceiveGraphQLRawJsonResponse for dynamic Mutation response handling.

FlurlGraphQL/FlurlGraphQL/FlurlGraphQLResponseExtensions.Internal.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Linq;
33
using System.Net;
4-
using System.Text.Json.Nodes;
54
using System.Threading;
65
using System.Threading.Tasks;
76

FlurlGraphQL/FlurlGraphQL/JsonProcessing/FlurlGraphQLJsonTransformTypeInfo.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@ protected FlurlGraphQLJsonTransformTypeInfo(Type targetType)
3838

3939
TypeName = targetType.Name;
4040
EntityTypeName = (entityType ?? targetType).Name;
41-
ImplementsIGraphQLQueryResults = entityType.IsDerivedFromGenericParent(GraphQLTypeCache.IGraphQLQueryResultsType);
41+
42+
ImplementsIGraphQLQueryResults = targetType.IsDerivedFromGenericParent(GraphQLTypeCache.IGraphQLQueryResultsType);
43+
ImplementsIGraphQLEdge = targetType.IsDerivedFromGenericParent(GraphQLTypeCache.IGraphQLEdgeEntityType);
44+
4245
AllChildProperties = GetPropInfosToTransformForType(targetType);
4346
ChildPropertiesToTransform = AllChildProperties.Where(p => p.ShouldBeFlattened).ToArray();
4447
}
4548

4649
public string TypeName { get; }
4750
public string EntityTypeName { get; }
4851
public bool ImplementsIGraphQLQueryResults { get; }
52+
public bool ImplementsIGraphQLEdge { get; }
4953

5054
public IList<FlurlGraphQLJsonTransformPropInfo> AllChildProperties { get; }
5155
public IList<FlurlGraphQLJsonTransformPropInfo> ChildPropertiesToTransform { get; }

0 commit comments

Comments
 (0)