Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,52 +24,47 @@ 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<Dictionary<string, object>>(token);
case JTokenType.Array:
return ReadArray(token);
case JTokenType.Integer:
return token.Value<int>();
case JTokenType.Float:
return token.Value<double>();
case JTokenType.Raw:
case JTokenType.String:
case JTokenType.Uri:
return token.Value<string>();
case JTokenType.Boolean:
return token.Value<bool>();
case JTokenType.Date:
return token.Value<DateTime>();
case JTokenType.Bytes:
return token.Value<byte[]>();
case JTokenType.Guid:
return token.Value<Guid>();
case JTokenType.TimeSpan:
return token.Value<TimeSpan>();
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<Dictionary<string, object>>(token),
JTokenType.Array => ReadArray(token),
JTokenType.Integer => token.Value<int>(),
JTokenType.Float => token.Value<double>(),
JTokenType.Raw => token.Value<string>(),
JTokenType.String => token.Value<string>(),
JTokenType.Uri => token.Value<string>(),
JTokenType.Boolean => token.Value<bool>(),
JTokenType.Date => token.Value<DateTime>(),
JTokenType.Bytes => token.Value<byte[]>(),
JTokenType.Guid => token.Value<Guid>(),
JTokenType.TimeSpan => token.Value<TimeSpan>(),
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<TDictionary>(JToken element) where TDictionary : Dictionary<string, object> {
var result = Activator.CreateInstance<TDictionary>();
foreach (var property in ((JObject)element).Properties()) {
if (IsUnsupportedJTokenType(property.Value.Type)) continue;
result[property.Name] = ReadToken(property.Value);
}
return result;
}

private IEnumerable<object> 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;
}
}
}
27 changes: 21 additions & 6 deletions src/GraphQL.Primitives/GraphQLResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ public bool Equals(GraphQLResponse<T>? other) {
if (other == null) { return false; }
if (ReferenceEquals(this, other)) { return true; }
if (!EqualityComparer<T>.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;
}

Expand All @@ -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<KeyValuePair<string,object>>.Default.GetHashCode(element);
}
}
else {
hashCode = (hashCode * 397) ^ 0;
}
}
return hashCode;
}
Expand Down
22 changes: 22 additions & 0 deletions tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,6 +43,26 @@ public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest re
json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace());
}

[Theory]
[ClassData(typeof(DeserializeResponseTestData))]
public async void DeserializeFromUtf8StreamTest(string json, GraphQLResponse<object> expectedResponse) {
var jsonBytes = Encoding.UTF8.GetBytes(json);
await using var ms = new MemoryStream(jsonBytes);
var response = await Serializer.DeserializeFromUtf8StreamAsync<GraphQLResponse<object>>(ms, CancellationToken.None);

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]
public async void CanDeserializeExtensions() {

Expand Down
Original file line number Diff line number Diff line change
@@ -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<object[]> {
public IEnumerator<object[]> 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<object> {
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<string, object> {
{"requestedQueryCost", 992},
{"actualQueryCost", null},
{"throttleStatus", new Dictionary<string, object> {
{"maximumAvailable", 1000},
{"currentlyAvailable", 632},
{"restoreRate", 50}
}}
}}
}
}
};
}

IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}

}
}