Skip to content

Commit ccb8eb7

Browse files
authored
Merge pull request #190 from graphql-dotnet/187-fix-extensions-deserialization
Fix extensions deserialization
2 parents a727000 + 093621c commit ccb8eb7

File tree

4 files changed

+116
-38
lines changed

4 files changed

+116
-38
lines changed

src/GraphQL.Client.Serializer.Newtonsoft/GraphQLExtensionsConverter.cs

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,52 +24,47 @@ public override GraphQLExtensionsType ReadJson(JsonReader reader, Type objectTyp
2424
}
2525

2626
private object ReadToken(JToken? token) {
27-
switch (token.Type) {
28-
case JTokenType.Undefined:
29-
case JTokenType.None:
30-
return null;
31-
case JTokenType.Object:
32-
return ReadDictionary<Dictionary<string, object>>(token);
33-
case JTokenType.Array:
34-
return ReadArray(token);
35-
case JTokenType.Integer:
36-
return token.Value<int>();
37-
case JTokenType.Float:
38-
return token.Value<double>();
39-
case JTokenType.Raw:
40-
case JTokenType.String:
41-
case JTokenType.Uri:
42-
return token.Value<string>();
43-
case JTokenType.Boolean:
44-
return token.Value<bool>();
45-
case JTokenType.Date:
46-
return token.Value<DateTime>();
47-
case JTokenType.Bytes:
48-
return token.Value<byte[]>();
49-
case JTokenType.Guid:
50-
return token.Value<Guid>();
51-
case JTokenType.TimeSpan:
52-
return token.Value<TimeSpan>();
53-
case JTokenType.Constructor:
54-
case JTokenType.Property:
55-
case JTokenType.Comment:
56-
default:
57-
throw new ArgumentOutOfRangeException();
58-
}
27+
return token.Type switch {
28+
JTokenType.Undefined => null,
29+
JTokenType.None => null,
30+
JTokenType.Null => null,
31+
JTokenType.Object => ReadDictionary<Dictionary<string, object>>(token),
32+
JTokenType.Array => ReadArray(token),
33+
JTokenType.Integer => token.Value<int>(),
34+
JTokenType.Float => token.Value<double>(),
35+
JTokenType.Raw => token.Value<string>(),
36+
JTokenType.String => token.Value<string>(),
37+
JTokenType.Uri => token.Value<string>(),
38+
JTokenType.Boolean => token.Value<bool>(),
39+
JTokenType.Date => token.Value<DateTime>(),
40+
JTokenType.Bytes => token.Value<byte[]>(),
41+
JTokenType.Guid => token.Value<Guid>(),
42+
JTokenType.TimeSpan => token.Value<TimeSpan>(),
43+
JTokenType.Constructor => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON constructor"),
44+
JTokenType.Property => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON property"),
45+
JTokenType.Comment => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON comment"),
46+
_ => throw new ArgumentOutOfRangeException(nameof(token.Type))
47+
};
5948
}
6049

6150
private TDictionary ReadDictionary<TDictionary>(JToken element) where TDictionary : Dictionary<string, object> {
6251
var result = Activator.CreateInstance<TDictionary>();
6352
foreach (var property in ((JObject)element).Properties()) {
53+
if (IsUnsupportedJTokenType(property.Value.Type)) continue;
6454
result[property.Name] = ReadToken(property.Value);
6555
}
6656
return result;
6757
}
6858

6959
private IEnumerable<object> ReadArray(JToken element) {
7060
foreach (var item in element.Values()) {
61+
if (IsUnsupportedJTokenType(item.Type)) continue;
7162
yield return ReadToken(item);
7263
}
7364
}
65+
66+
private bool IsUnsupportedJTokenType(JTokenType type) {
67+
return type == JTokenType.Constructor || type == JTokenType.Property || type == JTokenType.Comment;
68+
}
7469
}
7570
}

src/GraphQL.Primitives/GraphQLResponse.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ public bool Equals(GraphQLResponse<T>? other) {
2222
if (other == null) { return false; }
2323
if (ReferenceEquals(this, other)) { return true; }
2424
if (!EqualityComparer<T>.Default.Equals(this.Data, other.Data)) { return false; }
25-
{
26-
if (this.Errors != null && other.Errors != null) {
27-
if (!Enumerable.SequenceEqual(this.Errors, other.Errors)) { return false; }
28-
}
29-
else if (this.Errors != null && other.Errors == null) { return false; }
30-
else if (this.Errors == null && other.Errors != null) { return false; }
25+
26+
if (this.Errors != null && other.Errors != null) {
27+
if (!Enumerable.SequenceEqual(this.Errors, other.Errors)) { return false; }
28+
}
29+
else if (this.Errors != null && other.Errors == null) { return false; }
30+
else if (this.Errors == null && other.Errors != null) { return false; }
31+
32+
if (this.Extensions!= null && other.Extensions != null) {
33+
if (!Enumerable.SequenceEqual(this.Extensions, other.Extensions)) { return false; }
3134
}
35+
else if (this.Extensions != null && other.Extensions == null) { return false; }
36+
else if (this.Extensions == null && other.Extensions != null) { return false; }
37+
3238
return true;
3339
}
3440

@@ -44,6 +50,15 @@ public override int GetHashCode() {
4450
else {
4551
hashCode = (hashCode * 397) ^ 0;
4652
}
53+
54+
if (this.Extensions != null) {
55+
foreach (var element in this.Extensions) {
56+
hashCode = (hashCode * 397) ^ EqualityComparer<KeyValuePair<string,object>>.Default.GetHashCode(element);
57+
}
58+
}
59+
else {
60+
hashCode = (hashCode * 397) ^ 0;
61+
}
4762
}
4863
return hashCode;
4964
}

tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Collections.Generic;
2+
using System.IO;
23
using System.Linq;
34
using System.Text;
5+
using System.Threading;
46
using FluentAssertions;
57
using GraphQL.Client.Abstractions;
68
using GraphQL.Client.Abstractions.Websocket;
@@ -41,6 +43,26 @@ public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest re
4143
json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace());
4244
}
4345

46+
[Theory]
47+
[ClassData(typeof(DeserializeResponseTestData))]
48+
public async void DeserializeFromUtf8StreamTest(string json, GraphQLResponse<object> expectedResponse) {
49+
var jsonBytes = Encoding.UTF8.GetBytes(json);
50+
await using var ms = new MemoryStream(jsonBytes);
51+
var response = await Serializer.DeserializeFromUtf8StreamAsync<GraphQLResponse<object>>(ms, CancellationToken.None);
52+
53+
response.Data.Should().BeEquivalentTo(expectedResponse.Data);
54+
response.Errors.Should().Equal(expectedResponse.Errors);
55+
56+
if (expectedResponse.Extensions == null)
57+
response.Extensions.Should().BeNull();
58+
else {
59+
foreach (var element in expectedResponse.Extensions) {
60+
response.Extensions.Should().ContainKey(element.Key);
61+
response.Extensions[element.Key].Should().BeEquivalentTo(element.Value);
62+
}
63+
}
64+
}
65+
4466
[Fact]
4567
public async void CanDeserializeExtensions() {
4668

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
6+
namespace GraphQL.Client.Serializer.Tests.TestData {
7+
public class DeserializeResponseTestData : IEnumerable<object[]> {
8+
public IEnumerator<object[]> GetEnumerator() {
9+
// object array structure:
10+
// [0]: input json
11+
// [1]: expected deserialized response
12+
13+
yield return new object[] {
14+
"{\"errors\":[{\"message\":\"Throttled\",\"extensions\":{\"code\":\"THROTTLED\",\"documentation\":\"https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits\"}}],\"extensions\":{\"cost\":{\"requestedQueryCost\":992,\"actualQueryCost\":null,\"throttleStatus\":{\"maximumAvailable\":1000,\"currentlyAvailable\":632,\"restoreRate\":50}}}}",
15+
new GraphQLResponse<object> {
16+
Data = null,
17+
Errors = new[] {
18+
new GraphQLError {
19+
Message = "Throttled",
20+
Extensions = new GraphQLExtensionsType {
21+
{"code", "THROTTLED" },
22+
{"documentation", "https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits" }
23+
}
24+
}
25+
},
26+
Extensions = new GraphQLExtensionsType {
27+
{"cost", new Dictionary<string, object> {
28+
{"requestedQueryCost", 992},
29+
{"actualQueryCost", null},
30+
{"throttleStatus", new Dictionary<string, object> {
31+
{"maximumAvailable", 1000},
32+
{"currentlyAvailable", 632},
33+
{"restoreRate", 50}
34+
}}
35+
}}
36+
}
37+
}
38+
};
39+
}
40+
41+
IEnumerator IEnumerable.GetEnumerator() {
42+
return GetEnumerator();
43+
}
44+
45+
}
46+
}

0 commit comments

Comments
 (0)