From e856fe512d2af1175db0ca8f375eaf5e9752a97b Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 15 Mar 2020 11:00:15 +0100 Subject: [PATCH 1/2] fix GraphQLExtensionsConverter for Newtonsoft --- .../GraphQLExtensionsConverter.cs | 59 +++++++++---------- .../BaseSerializerTest.cs | 18 ++++++ .../TestData/DeserializeResponseTestData.cs | 46 +++++++++++++++ 3 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQLExtensionsConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQLExtensionsConverter.cs index 0ff13a0f..4c603f0c 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQLExtensionsConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQLExtensionsConverter.cs @@ -24,43 +24,33 @@ public override GraphQLExtensionsType ReadJson(JsonReader reader, Type objectTyp } private object ReadToken(JToken? token) { - switch (token.Type) { - case JTokenType.Undefined: - case JTokenType.None: - return null; - case JTokenType.Object: - return ReadDictionary>(token); - case JTokenType.Array: - return ReadArray(token); - case JTokenType.Integer: - return token.Value(); - case JTokenType.Float: - return token.Value(); - case JTokenType.Raw: - case JTokenType.String: - case JTokenType.Uri: - return token.Value(); - case JTokenType.Boolean: - return token.Value(); - case JTokenType.Date: - return token.Value(); - case JTokenType.Bytes: - return token.Value(); - case JTokenType.Guid: - return token.Value(); - case JTokenType.TimeSpan: - return token.Value(); - case JTokenType.Constructor: - case JTokenType.Property: - case JTokenType.Comment: - default: - throw new ArgumentOutOfRangeException(); - } + return token.Type switch { + JTokenType.Undefined => null, + JTokenType.None => null, + JTokenType.Null => null, + JTokenType.Object => ReadDictionary>(token), + JTokenType.Array => ReadArray(token), + JTokenType.Integer => token.Value(), + JTokenType.Float => token.Value(), + JTokenType.Raw => token.Value(), + JTokenType.String => token.Value(), + JTokenType.Uri => token.Value(), + JTokenType.Boolean => token.Value(), + JTokenType.Date => token.Value(), + JTokenType.Bytes => token.Value(), + JTokenType.Guid => token.Value(), + JTokenType.TimeSpan => token.Value(), + JTokenType.Constructor => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON constructor"), + JTokenType.Property => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON property"), + JTokenType.Comment => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON comment"), + _ => throw new ArgumentOutOfRangeException(nameof(token.Type)) + }; } private TDictionary ReadDictionary(JToken element) where TDictionary : Dictionary { var result = Activator.CreateInstance(); foreach (var property in ((JObject)element).Properties()) { + if (IsUnsupportedJTokenType(property.Value.Type)) continue; result[property.Name] = ReadToken(property.Value); } return result; @@ -68,8 +58,13 @@ private TDictionary ReadDictionary(JToken element) where TDictionar private IEnumerable ReadArray(JToken element) { foreach (var item in element.Values()) { + if (IsUnsupportedJTokenType(item.Type)) continue; yield return ReadToken(item); } } + + private bool IsUnsupportedJTokenType(JTokenType type) { + return type == JTokenType.Constructor || type == JTokenType.Property || type == JTokenType.Comment; + } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 26d44472..ee818ff1 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; +using System.Threading; using FluentAssertions; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; @@ -41,6 +43,22 @@ public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest re json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); } + [Theory] + [ClassData(typeof(DeserializeResponseTestData))] + public async void DeserializeFromUtf8StreamTest(string json, GraphQLResponse expectedResponse) { + var jsonBytes = Encoding.UTF8.GetBytes(json); + await using var ms = new MemoryStream(jsonBytes); + var response = await Serializer.DeserializeFromUtf8StreamAsync>(ms, CancellationToken.None); + + response.Should().BeEquivalentTo(expectedResponse, options => { + options.ComparingByMembers(); + options.ComparingByMembers>(); + options.ComparingByMembers>(); + options.AllowingInfiniteRecursion(); + return options; + }); + } + [Fact] public async void CanDeserializeExtensions() { diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs new file mode 100644 index 00000000..61ebb0b0 --- /dev/null +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace GraphQL.Client.Serializer.Tests.TestData { + public class DeserializeResponseTestData : IEnumerable { + public IEnumerator GetEnumerator() { + // object array structure: + // [0]: input json + // [1]: expected deserialized response + + yield return new object[] { + "{\"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}}}}", + new GraphQLResponse { + Data = null, + Errors = new[] { + new GraphQLError { + Message = "Throttled", + Extensions = new GraphQLExtensionsType { + {"code", "THROTTLED" }, + {"documentation", "https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits" } + } + } + }, + Extensions = new GraphQLExtensionsType { + {"cost", new Dictionary { + {"requestedQueryCost", 992}, + {"actualQueryCost", null}, + {"throttleStatus", new Dictionary { + {"maximumAvailable", 1000}, + {"currentlyAvailable", 632}, + {"restoreRate", 50} + }} + }} + } + } + }; + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + } +} From 093621c2b69246108ce9eb0f3ccd7b29885394df Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 15 Mar 2020 11:08:21 +0100 Subject: [PATCH 2/2] fix test --- src/GraphQL.Primitives/GraphQLResponse.cs | 27 ++++++++++++++----- .../BaseSerializerTest.cs | 20 ++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/GraphQL.Primitives/GraphQLResponse.cs b/src/GraphQL.Primitives/GraphQLResponse.cs index 088be7bd..94a6ea21 100644 --- a/src/GraphQL.Primitives/GraphQLResponse.cs +++ b/src/GraphQL.Primitives/GraphQLResponse.cs @@ -22,13 +22,19 @@ public bool Equals(GraphQLResponse? other) { if (other == null) { return false; } if (ReferenceEquals(this, other)) { return true; } if (!EqualityComparer.Default.Equals(this.Data, other.Data)) { return false; } - { - if (this.Errors != null && other.Errors != null) { - if (!Enumerable.SequenceEqual(this.Errors, other.Errors)) { return false; } - } - else if (this.Errors != null && other.Errors == null) { return false; } - else if (this.Errors == null && other.Errors != null) { return false; } + + if (this.Errors != null && other.Errors != null) { + if (!Enumerable.SequenceEqual(this.Errors, other.Errors)) { return false; } + } + else if (this.Errors != null && other.Errors == null) { return false; } + else if (this.Errors == null && other.Errors != null) { return false; } + + if (this.Extensions!= null && other.Extensions != null) { + if (!Enumerable.SequenceEqual(this.Extensions, other.Extensions)) { return false; } } + else if (this.Extensions != null && other.Extensions == null) { return false; } + else if (this.Extensions == null && other.Extensions != null) { return false; } + return true; } @@ -44,6 +50,15 @@ public override int GetHashCode() { else { hashCode = (hashCode * 397) ^ 0; } + + if (this.Extensions != null) { + foreach (var element in this.Extensions) { + hashCode = (hashCode * 397) ^ EqualityComparer>.Default.GetHashCode(element); + } + } + else { + hashCode = (hashCode * 397) ^ 0; + } } return hashCode; } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index ee818ff1..1f3a19c2 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -49,14 +49,18 @@ public async void DeserializeFromUtf8StreamTest(string json, GraphQLResponse>(ms, CancellationToken.None); - - response.Should().BeEquivalentTo(expectedResponse, options => { - options.ComparingByMembers(); - options.ComparingByMembers>(); - options.ComparingByMembers>(); - options.AllowingInfiniteRecursion(); - return options; - }); + + response.Data.Should().BeEquivalentTo(expectedResponse.Data); + response.Errors.Should().Equal(expectedResponse.Errors); + + if (expectedResponse.Extensions == null) + response.Extensions.Should().BeNull(); + else { + foreach (var element in expectedResponse.Extensions) { + response.Extensions.Should().ContainKey(element.Key); + response.Extensions[element.Key].Should().BeEquivalentTo(element.Value); + } + } } [Fact]