diff --git a/src/Core/Core.Tests/Execution/Utilities/VariableValueBuilderTests.cs b/src/Core/Core.Tests/Execution/Utilities/VariableValueBuilderTests.cs index 467ca7ea38b..2306441aca5 100644 --- a/src/Core/Core.Tests/Execution/Utilities/VariableValueBuilderTests.cs +++ b/src/Core/Core.Tests/Execution/Utilities/VariableValueBuilderTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using HotChocolate.Language; using HotChocolate.Types; using Snapshooter.Xunit; @@ -483,6 +484,37 @@ public void Variable_List_NonNullListItemHasValue() variables.GetVariable>("test").MatchSnapshot(); } + [Fact] + public async Task EnsureThatDateTimeIsCoercedTheSameInAllCases() + { + // arrange + IQueryExecutor executor = Schema.Create( + "type Query { a(a: DateTime) : DateTime }", + c => + { + c.RegisterExtendedScalarTypes(); + c.Use(next => context => + { + context.Result = context.Argument("a"); + return Task.CompletedTask; + }); + }).MakeExecutable(); + + // act + IExecutionResult result = await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery(@" + query a($d: DateTime!) { + a: a(a: ""2018-01-01T01:00:00.000Z"") + b: a(a: $d) + }") + .AddVariableValue("d", "2018-01-01T01:00:00.000Z") + .Create()); + + // assert + result.MatchSnapshot(); + } + private Schema CreateSchema() { return Schema.Create( diff --git a/src/Server/AspNetCore/GetQueryMiddleware.cs b/src/Server/AspNetCore/GetQueryMiddleware.cs index ecfe2f3079d..49137e3e3fc 100644 --- a/src/Server/AspNetCore/GetQueryMiddleware.cs +++ b/src/Server/AspNetCore/GetQueryMiddleware.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using HotChocolate.Execution; using Newtonsoft.Json.Linq; +using Newtonsoft.Json; #if ASPNETCLASSIC using Microsoft.Owin; @@ -81,7 +82,8 @@ private static QueryRequestDto ReadRequest(HttpContext context) NamedQuery = requestQuery[_namedQueryIdentifier], OperationName = requestQuery[_operationNameIdentifier], Variables = (variables != null && variables.Any()) - ? JObject.Parse(variables) + ? JsonConvert.DeserializeObject( + variables, QueryMiddlewareUtilities.JsonSettings) : null }; } diff --git a/src/Server/AspNetCore/PostQueryMiddleware.cs b/src/Server/AspNetCore/PostQueryMiddleware.cs index 64859213da8..36e5570748a 100644 --- a/src/Server/AspNetCore/PostQueryMiddleware.cs +++ b/src/Server/AspNetCore/PostQueryMiddleware.cs @@ -69,8 +69,8 @@ private static async Task ReadRequestAsync( switch (context.Request.ContentType.Split(';')[0]) { case ContentType.Json: - return JsonConvert - .DeserializeObject(content); + return JsonConvert.DeserializeObject( + content, QueryMiddlewareUtilities.JsonSettings); case ContentType.GraphQL: return new QueryRequestDto { Query = content }; diff --git a/src/Server/AspNetCore/QueryMiddlewareUtilities.cs b/src/Server/AspNetCore/QueryMiddlewareUtilities.cs index 94cbe3e89b5..c46c203bb84 100644 --- a/src/Server/AspNetCore/QueryMiddlewareUtilities.cs +++ b/src/Server/AspNetCore/QueryMiddlewareUtilities.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; #if ASPNETCLASSIC @@ -11,6 +12,12 @@ namespace HotChocolate.AspNetCore { internal static class QueryMiddlewareUtilities { + public static JsonSerializerSettings JsonSettings { get; } = + new JsonSerializerSettings + { + DateParseHandling = DateParseHandling.None + }; + public static Dictionary ToDictionary( this JObject input) { diff --git a/src/Stitching/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.cs b/src/Stitching/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.cs index 3d6f9ecfb15..0308461cb3b 100644 --- a/src/Stitching/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.cs +++ b/src/Stitching/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.cs @@ -529,7 +529,7 @@ query a($id: ID!) { Snapshot.Match(result); } - [Fact(Skip = "Fix this issue")] + [Fact] public async Task ExtendedScalarAsInAndOutputType() { // arrange @@ -613,7 +613,57 @@ query a($d: DateTime!) { }"); request.VariableValues = new Dictionary { - {"d", "2019-01-01T01:00"} + {"d", "2019-01-01T01:00:00.000Z"} + }; + request.Services = scope.ServiceProvider; + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task DateTimeIsHandledCorrectly() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddExtensionsFromString( + "directive @custom(d: DateTime) on FIELD") + .AddSchemaConfiguration(c => + { + c.RegisterExtendedScalarTypes(); + }) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + var request = new QueryRequest(@" + query a($d: DateTime!) { + a: extendedScalar(d: ""2018-01-01T01:00:00.000Z"") + b: extendedScalar(d: $d) + c: extendedScalar(d: $d) + @custom(d: ""2020-09-01T01:00:00.000Z"") + }"); + request.VariableValues = new Dictionary + { + {"d", "2019-01-01T01:00:00.000Z"} }; request.Services = scope.ServiceProvider; diff --git a/src/Stitching/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap b/src/Stitching/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap new file mode 100644 index 00000000000..f4f99dbe988 --- /dev/null +++ b/src/Stitching/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap @@ -0,0 +1,9 @@ +{ + "Data": { + "a": "2018-01-01T01:00:00.000Z", + "b": "2019-01-01T01:00:00.000Z", + "c": "2020-09-01T01:00:00.000Z" + }, + "Extensions": {}, + "Errors": [] +} diff --git a/src/Stitching/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap b/src/Stitching/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap new file mode 100644 index 00000000000..bcf184374b0 --- /dev/null +++ b/src/Stitching/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "a": "2018-01-01T01:00:00.000Z", + "b": "2019-01-01T01:00:00.000Z" + }, + "Extensions": {}, + "Errors": [] +} diff --git a/src/Stitching/Stitching/ErrorCodes.cs b/src/Stitching/Stitching/ErrorCodes.cs index f46febb9034..27e6c7dd5ce 100644 --- a/src/Stitching/Stitching/ErrorCodes.cs +++ b/src/Stitching/Stitching/ErrorCodes.cs @@ -6,5 +6,6 @@ internal static class ErrorCodes public const string FieldNotDefined = "STITCHING_FLD_NOT_DEFINED"; public const string VariableNotDefined = "STITCHING_VAR_NOT_DEFINED"; public const string ScopeNotDefined = "STITCHING_SCOPE_NOT_DEFINED"; + public const string TypeNotDefined = "STITCHING_TYPE_NOT_DEFINED"; } } diff --git a/src/Stitching/Stitching/Middleware/DelegateToRemoteSchemaMiddleware.cs b/src/Stitching/Stitching/Middleware/DelegateToRemoteSchemaMiddleware.cs index 9312d080c3f..dc0f613af6a 100644 --- a/src/Stitching/Stitching/Middleware/DelegateToRemoteSchemaMiddleware.cs +++ b/src/Stitching/Stitching/Middleware/DelegateToRemoteSchemaMiddleware.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using HotChocolate.Execution; using HotChocolate.Language; using HotChocolate.Resolvers; @@ -11,6 +13,7 @@ using HotChocolate.Stitching.Properties; using HotChocolate.Stitching.Utilities; using HotChocolate.Types; +using HotChocolate.Utilities; namespace HotChocolate.Stitching { @@ -90,7 +93,7 @@ private static IRemoteQueryRequest CreateQuery( var requestBuilder = new RemoteQueryRequestBuilder(); - AddVariables(context.Schema, schemaName, + AddVariables(context, schemaName, requestBuilder, query, variableValues); requestBuilder.SetQuery(query); @@ -299,8 +302,22 @@ private static void ResolveScopedVariableArguments( if (argument.Value is ScopedVariableNode sv) { - variables.Add(_resolvers.Resolve( - context, sv, arg.Type.ToTypeNode())); + VariableValue variable = + _resolvers.Resolve(context, sv, arg.Type.ToTypeNode()); + + if (arg.Type.IsLeafType() + && arg.Type.NamedType() is ISerializableType s) + { + variable = new VariableValue + ( + variable.Name, + variable.Type, + s.Serialize(variable.Value), + variable.DefaultValue + ); + } + + variables.Add(variable); } } } @@ -326,7 +343,7 @@ private static IEnumerable ResolveUsedRequestVariables( } private static void AddVariables( - ISchema schema, + IResolverContext context, NameString schemaName, IRemoteQueryRequestBuilder builder, DocumentNode query, @@ -344,15 +361,13 @@ private static void AddVariables( { object value = variableValue.Value; - if (schema.TryGetType( + if (context.Schema.TryGetType( variableValue.Type.NamedType().Name.Value, out InputObjectType inputType)) { + var wrapped = WrapType(inputType, variableValue.Type); value = ObjectVariableRewriter.RewriteVariable( - schemaName, - WrapType(inputType, - variableValue.Type), - value); + schemaName, wrapped, value); } builder.AddVariableValue(variableValue.Name, value); diff --git a/src/Stitching/Stitching/Utilities/ObjectVariableRewriter.cs b/src/Stitching/Stitching/Utilities/ObjectVariableRewriter.cs index d438007902e..94849fb89b5 100644 --- a/src/Stitching/Stitching/Utilities/ObjectVariableRewriter.cs +++ b/src/Stitching/Stitching/Utilities/ObjectVariableRewriter.cs @@ -68,7 +68,5 @@ private static object RewriteVariableValue( return value; } } - - } }