From f491d2ff769695ad69ead1abac73e329ce7fdd91 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 27 Oct 2020 11:00:01 +0100 Subject: [PATCH] Fixed dictionary middleware. (#2492) --- .../src/Abstractions/Execution/FieldData.cs | 153 ---------------- .../src/Abstractions/Execution/FieldValue.cs | 62 ------- .../src/Abstractions/Execution/ResultValue.cs | 8 +- .../Delegation/DictionaryDeserializer.cs | 112 ++++++++++++ .../Delegation/DictionaryResultMiddleware.cs | 108 +----------- .../Delegation/DictionaryDeserializerTests.cs | 166 ++++++++++++++++++ .../FieldScopedVariableResolverTests.cs | 1 - .../src/Utilities/Base64Serializer.cs | 23 --- 8 files changed, 284 insertions(+), 349 deletions(-) delete mode 100644 src/HotChocolate/Core/src/Abstractions/Execution/FieldData.cs delete mode 100644 src/HotChocolate/Core/src/Abstractions/Execution/FieldValue.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs delete mode 100644 src/HotChocolate/Utilities/src/Utilities/Base64Serializer.cs diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/FieldData.cs b/src/HotChocolate/Core/src/Abstractions/Execution/FieldData.cs deleted file mode 100644 index 09f8a64619e..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/FieldData.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Buffers; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace HotChocolate.Execution -{ - public class FieldData - : IReadOnlyDictionary - , IEnumerable - , IDisposable - { - private static readonly ArrayPool _pool = - ArrayPool.Create(1024, 64); - private FieldValue[] _rented; - private int _capacity; - - public FieldData(int fields) - { - _capacity = fields; - _rented = _pool.Rent(fields); - } - - public object GetFieldValue(int index) - { - if (_capacity <= index) - { - throw new ArgumentException( - "The specified index does not exist.", - nameof(index)); - } - return _rented[index].Value; - } - - public void SetFieldValue(int index, string key, object value) - { - if (_capacity <= index) - { - throw new ArgumentException( - "The specified index does not exist.", - nameof(index)); - } - - _rented[index] = new FieldValue(key, value); - } - - public void Clear() - { - Dispose(); - _rented = Array.Empty(); - _capacity = 0; - } - - public void Dispose() - { - if (_rented.Length > 0) - { - for (var i = 0; i < _capacity; i++) - { - DisposeNested(_rented[i].Value); - } - _pool.Return(_rented, true); - } - } - - private void DisposeNested(object o) - { - if (o is FieldData data) - { - data.Dispose(); - } - else if (o is IList list) - { - for (var i = 0; i < list.Count; i++) - { - DisposeNested(list[i]); - } - } - } - - private bool TryGetFieldValue(string key, out FieldValue fieldValue) - { - fieldValue = Array.Find( - _rented, - t => string.Equals(t.Key, key, StringComparison.Ordinal)); - return fieldValue.HasValue; - } - - object IReadOnlyDictionary.this[string key] - { - get - { - if (TryGetFieldValue(key, out FieldValue value)) - { - return value.Value; - } - - throw new KeyNotFoundException($"The key {key} does not exist."); - } - } - - bool IReadOnlyDictionary.TryGetValue(string key, out object value) - { - if (TryGetFieldValue(key, out FieldValue fieldValue)) - { - value = fieldValue.Value; - return true; - } - - value = null; - return false; - } - - bool IReadOnlyDictionary.ContainsKey(string key) - { - for (int i = 0; i < _capacity; i++) - { - FieldValue value = _rented[i]; - if (value.HasValue && string.Equals(value.Key, key, StringComparison.Ordinal)) - { - return true; - } - } - return false; - } - - IEnumerable IReadOnlyDictionary.Keys => - _rented.Take(_capacity) - .Where(t => t.HasValue) - .Select(t => t.Key); - - IEnumerable IReadOnlyDictionary.Values => - _rented.Take(_capacity) - .Where(t => t.HasValue) - .Select(t => t.Value); - - int IReadOnlyCollection>.Count => _capacity; - - public IEnumerator GetEnumerator() => - _rented.Take(_capacity) - .Where(t => t.HasValue) - .GetEnumerator(); - - IEnumerator> IEnumerable>.GetEnumerator() => - _rented.Take(_capacity) - .Where(t => t.HasValue) - .Select(t => new KeyValuePair(t.Key, t.Value)) - .GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/FieldValue.cs b/src/HotChocolate/Core/src/Abstractions/Execution/FieldValue.cs deleted file mode 100644 index c79d754acde..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/Execution/FieldValue.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; - -#nullable enable - -namespace HotChocolate.Execution -{ - public readonly struct FieldValue - : IEquatable - { - internal FieldValue(string key, object value) - { - Key = key; - Value = value; - HasValue = true; - } - - public string Key { get; } - - public object Value { get; } - - public bool HasValue { get; } - - public override bool Equals(object? obj) - { - return obj is FieldValue value && - HasValue == value.HasValue && - Key == value.Key && - Value == value.Value; - } - - public bool Equals(FieldValue? other) - { - if (other is null) - { - return false; - } - - if (HasValue != other.Value.HasValue) - { - return false; - } - - if (HasValue == false) - { - return true; - } - - return Key == other.Value.Key && - Value == other.Value.Value; - } - - public override int GetHashCode() - { - unchecked - { - int hash = (Key?.GetHashCode() ?? 0) * 3; - hash = hash ^ ((Value?.GetHashCode() ?? 0) * 7); - return hash; - } - } - } -} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs b/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs index 12a2c66cdc5..6d6134afb38 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/ResultValue.cs @@ -24,9 +24,9 @@ public ResultValue(string name, object? value, bool isNullable) public override bool Equals(object? obj) { - return obj is FieldValue value && + return obj is ResultValue value && HasValue == value.HasValue && - Name == value.Key && + Name == value.Name && Value == value.Value; } @@ -55,8 +55,8 @@ public override int GetHashCode() { unchecked { - int hash = (Name?.GetHashCode() ?? 0) * 3; - hash = hash ^ ((Value?.GetHashCode() ?? 0) * 7); + var hash = (Name?.GetHashCode() ?? 0) * 3; + hash ^= (Value?.GetHashCode() ?? 0) * 7; return hash; } } diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs new file mode 100644 index 00000000000..bea0fffa783 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Delegation +{ + internal static class DictionaryDeserializer + { + public static object? DeserializeResult( + IType fieldType, + object? obj) + { + INamedType namedType = fieldType.NamedType(); + + if (namedType is IInputType && fieldType is IInputType inputType) + { + if (namedType.Kind == TypeKind.Enum) + { + return DeserializeEnumResult(inputType, obj); + } + + if (namedType.Kind == TypeKind.Scalar) + { + return DeserializeScalarResult(inputType, obj); + } + } + + return obj is NullValueNode ? null : obj; + } + + private static object? DeserializeEnumResult(IInputType inputType, object? value) + { + switch (value) + { + case IReadOnlyList list: + { + var elementType = (IInputType)inputType.ElementType(); + var deserializedList = (IList)Activator.CreateInstance(inputType.RuntimeType)!; + + foreach (object? item in list) + { + deserializedList.Add(DeserializeEnumResult(elementType, item)); + } + + return deserializedList; + } + + case ListValueNode listLiteral: + { + var elementType = (IInputType)inputType.ElementType(); + var list = new List(); + + foreach (IValueNode item in listLiteral.Items) + { + list.Add(DeserializeEnumResult(elementType, item)); + } + + return list; + } + + case StringValueNode stringLiteral: + return inputType.Deserialize(stringLiteral.Value); + + case IValueNode literal: + return inputType.ParseLiteral(literal); + + default: + return inputType.Deserialize(value); + } + } + + private static object? DeserializeScalarResult(IInputType inputType, object? value) + { + switch (value) + { + case IReadOnlyList list: + { + var elementType = (IInputType)inputType.ElementType(); + var deserializedList = (IList)Activator.CreateInstance(inputType.RuntimeType)!; + + foreach (object? item in list) + { + deserializedList.Add(DeserializeEnumResult(elementType, item)); + } + + return deserializedList; + } + + case ListValueNode listLiteral: + { + var elementType = (IInputType)inputType.ElementType(); + var list = new List(); + + foreach (IValueNode item in listLiteral.Items) + { + list.Add(DeserializeEnumResult(elementType, item)); + } + + return list; + } + + case IValueNode literal: + return inputType.ParseLiteral(literal); + + default: + return inputType.Deserialize(value); + } + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs index d650c332ed2..2a804d1080c 100644 --- a/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs @@ -1,10 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; -using HotChocolate.Language; using HotChocolate.Resolvers; -using HotChocolate.Types; namespace HotChocolate.Stitching.Delegation { @@ -23,7 +20,7 @@ public ValueTask InvokeAsync(IMiddlewareContext context) { context.Result = s.Data is IDictionary d ? d - : DeserializeResult(context.Field, s.Data); + : DictionaryDeserializer.DeserializeResult(context.Field.Type, s.Data); } else if (context.Result is null && !context.Field.Directives.Contains(DirectiveNames.Computed) && @@ -34,111 +31,10 @@ public ValueTask InvokeAsync(IMiddlewareContext context) : context.FieldSelection.Alias.Value; dict.TryGetValue(responseName, out object? obj); - context.Result = DeserializeResult(context.Field, obj); + context.Result = DictionaryDeserializer.DeserializeResult(context.Field.Type, obj); } return _next.Invoke(context); } - - private static object? DeserializeResult( - IOutputField field, - object? obj) - { - INamedType namedType = field.Type.NamedType(); - - if (field.Type is IInputType inputType) - { - if (namedType.Kind == TypeKind.Enum) - { - return DeserializeEnumResult(inputType, obj); - } - - if (namedType.Kind == TypeKind.Scalar) - { - return DeserializeScalarResult(inputType, obj); - } - } - - return obj; - } - - private static object? DeserializeEnumResult(IInputType inputType, object? value) - { - switch (value) - { - case IReadOnlyList list: - { - var elementType = (IInputType)inputType.ElementType(); - var deserializedList = (IList)Activator.CreateInstance(inputType.RuntimeType)!; - - foreach (object? item in list) - { - deserializedList.Add(DeserializeEnumResult(elementType, item)); - } - - return deserializedList; - } - - case ListValueNode listLiteral: - { - var elementType = (IInputType)inputType.ElementType(); - var list = new List(); - - foreach (IValueNode item in listLiteral.Items) - { - list.Add(DeserializeEnumResult(elementType, item)); - } - - return list; - } - - case StringValueNode stringLiteral: - return inputType.Deserialize(stringLiteral.Value); - - case IValueNode literal: - return inputType.ParseLiteral(literal); - - default: - return inputType.Deserialize(value); - } - } - - private static object? DeserializeScalarResult(IInputType inputType, object? value) - { - switch (value) - { - case IReadOnlyList list: - { - var elementType = (IInputType)inputType.ElementType(); - var deserializedList = (IList)Activator.CreateInstance(inputType.RuntimeType)!; - - foreach (object? item in list) - { - deserializedList.Add(DeserializeEnumResult(elementType, item)); - } - - return deserializedList; - } - - case ListValueNode listLiteral: - { - var elementType = (IInputType)inputType.ElementType(); - var list = new List(); - - foreach (IValueNode item in listLiteral.Items) - { - list.Add(DeserializeEnumResult(elementType, item)); - } - - return list; - } - - case IValueNode literal: - return inputType.ParseLiteral(literal); - - default: - return inputType.Deserialize(value); - } - } } } diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs new file mode 100644 index 00000000000..1cd998d8d7e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs @@ -0,0 +1,166 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace HotChocolate.Stitching.Delegation +{ + public class DictionaryDeserializerTests + { + [Fact] + public async Task Deserialize_NullValueNode() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType person = schema.GetType("Person"); + + // act + object value = DictionaryDeserializer.DeserializeResult(person, NullValueNode.Default); + + // assert + Assert.Null(value); + } + + [Fact] + public async Task Deserialize_Null() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType person = schema.GetType("Person"); + + // act + object value = DictionaryDeserializer.DeserializeResult(person, null); + + // assert + Assert.Null(value); + } + + [Fact] + public async Task Deserialize_Dictionary() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType person = schema.GetType("Person"); + + var dict = new Dictionary(); + + // act + object value = DictionaryDeserializer.DeserializeResult(person, dict); + + // assert + Assert.Same(dict, value); + } + + [Fact] + public async Task Deserialize_String() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringType = schema.GetType("String"); + + // act + object value = DictionaryDeserializer.DeserializeResult(stringType, "abc"); + + // assert + Assert.Equal("abc", value); + } + + [Fact] + public async Task Deserialize_StringValueNode() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringType = schema.GetType("String"); + + // act + object value = DictionaryDeserializer.DeserializeResult( + stringType, new StringValueNode("abc")); + + // assert + Assert.Equal("abc", value); + } + + [Fact] + public async Task Deserialize_StringList_StringList() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringListType = new ListType(schema.GetType("String")); + + // act + object value = DictionaryDeserializer.DeserializeResult( + stringListType, new List { "abc" }); + + // assert + Assert.Collection( + ((IEnumerable)value)!, + v => Assert.Equal("abc", v)); + } + + [Fact] + public async Task Deserialize_StringList_ListOfStringValueNode() + { + // arrange + ISchema schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringListType = new ListType(schema.GetType("String")); + + // act + object value = DictionaryDeserializer.DeserializeResult( + stringListType, + new List { new StringValueNode("abc") }); + + // assert + Assert.Collection( + ((IEnumerable)value)!, + v => Assert.Equal("abc", v)); + } + + public class Query + { + public Person GetPerson() => new Person(); + } + + public class Person + { + public string Name { get; } = "Jon Doe"; + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs index 9a51dde1707..baa80821cf1 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using HotChocolate.Execution; using HotChocolate.Language; using HotChocolate.Resolvers; using HotChocolate.Stitching.Delegation.ScopedVariables; diff --git a/src/HotChocolate/Utilities/src/Utilities/Base64Serializer.cs b/src/HotChocolate/Utilities/src/Utilities/Base64Serializer.cs deleted file mode 100644 index 0ea6ff6e65f..00000000000 --- a/src/HotChocolate/Utilities/src/Utilities/Base64Serializer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Text; -using Newtonsoft.Json; - -namespace HotChocolate.Utilities -{ - public static class Base64Serializer - { - public static string Serialize(T obj) - { - string json = JsonConvert.SerializeObject(obj); - byte[] buffer = Encoding.UTF8.GetBytes(json); - return Convert.ToBase64String(buffer); - } - - public static T Deserialize(string s) - { - byte[] buffer = Convert.FromBase64String(s); - string json = Encoding.UTF8.GetString(buffer); - return JsonConvert.DeserializeObject(json); - } - } -}