diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs index 36711cf8aeb..d1db7111f30 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs @@ -3,7 +3,6 @@ using HotChocolate.AspNetCore; using HotChocolate.AspNetCore.Serialization; using HotChocolate.Execution.Configuration; -using HotChocolate.Execution.Serialization; using HotChocolate.Utilities; // ReSharper disable once CheckNamespace @@ -114,14 +113,14 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions /// The . /// /// - /// The JSON result formatter options + /// The HTTP response formatter options /// /// /// Returns the so that configuration can be chained. /// public static IServiceCollection AddHttpResponseFormatter( this IServiceCollection services, - JsonResultFormatterOptions options) + HttpResponseFormatterOptions options) { services.RemoveAll(); services.AddSingleton(new DefaultHttpResponseFormatter(options)); diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpTransportVersion.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpTransportVersion.cs new file mode 100644 index 00000000000..ee38bf3aebe --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpTransportVersion.cs @@ -0,0 +1,22 @@ +namespace HotChocolate.AspNetCore; + +/// +/// Represents the GraphQL over HTTP transport version. +/// +public enum HttpTransportVersion +{ + /// + /// Represents the latest released transport specification. + /// + Latest = 0, + + /// + /// Represents the legacy specification version which will be cut off at 2025-01-01T00:00:00Z. + /// + Legacy = 1, + + /// + /// Represents the GraphQL over HTTP spec version with the commit on 2023-01-27. + /// + Draft20230127 = 2 +} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs index 0188ad4dbc1..52c371f389a 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Encodings.Web; @@ -22,9 +21,12 @@ namespace HotChocolate.AspNetCore.Serialization; /// public class DefaultHttpResponseFormatter : IHttpResponseFormatter { - private readonly JsonResultFormatter _jsonFormatter; - private readonly MultiPartResultFormatter _multiPartFormatter; - private readonly EventStreamResultFormatter _eventStreamResultFormatter; + private readonly FormatInfo _defaultFormat; + private readonly FormatInfo _graphqlResponseFormat; + private readonly FormatInfo _multiPartFormat; + private readonly FormatInfo _eventStreamFormat; + private readonly FormatInfo _legacyFormat; + /// /// Creates a new instance of . @@ -42,9 +44,16 @@ public class DefaultHttpResponseFormatter : IHttpResponseFormatter public DefaultHttpResponseFormatter( bool indented = false, JavaScriptEncoder? encoder = null) - : this(new JsonResultFormatterOptions { Indented = indented, Encoder = encoder }) - { - } + : this( + new HttpResponseFormatterOptions + { + Json = new JsonResultFormatterOptions + { + Indented = indented, + Encoder = encoder + } + }) + { } /// /// Creates a new instance of . @@ -52,11 +61,31 @@ public class DefaultHttpResponseFormatter : IHttpResponseFormatter /// /// The JSON result formatter options /// - public DefaultHttpResponseFormatter(JsonResultFormatterOptions options) + public DefaultHttpResponseFormatter(HttpResponseFormatterOptions options) { - _jsonFormatter = new JsonResultFormatter(options); - _multiPartFormatter = new MultiPartResultFormatter(_jsonFormatter); - _eventStreamResultFormatter = new EventStreamResultFormatter(options); + var jsonFormatter = new JsonResultFormatter(options.Json); + var multiPartFormatter = new MultiPartResultFormatter(jsonFormatter); + var eventStreamResultFormatter = new EventStreamResultFormatter(options.Json); + + _graphqlResponseFormat = new FormatInfo( + ContentType.GraphQLResponse, + ResponseContentType.GraphQLResponse, + jsonFormatter); + _legacyFormat = new FormatInfo( + ContentType.Json, + ResponseContentType.Json, + jsonFormatter); + _multiPartFormat = new FormatInfo( + ContentType.MultiPartMixed, + ResponseContentType.MultiPartMixed, + multiPartFormatter); + _eventStreamFormat = new FormatInfo( + ContentType.EventStream, + ResponseContentType.EventStream, + eventStreamResultFormatter); + _defaultFormat = options.HttpTransportVersion is HttpTransportVersion.Legacy + ? _legacyFormat + : _graphqlResponseFormat; } public GraphQLRequestFlags CreateRequestFlags( @@ -119,7 +148,9 @@ public DefaultHttpResponseFormatter(JsonResultFormatterOptions options) HttpStatusCode? proposedStatusCode, CancellationToken cancellationToken) { - if (!TryGetFormatter(result, acceptMediaTypes, out var format)) + var format = TryGetFormatter(result, acceptMediaTypes); + + if (format is null) { // we should not hit this point except if a middleware did not validate the // GraphQL request flags which would indicate that there is no way to execute @@ -276,46 +307,33 @@ public DefaultHttpResponseFormatter(JsonResultFormatterOptions options) throw ThrowHelper.Formatter_ResponseContentTypeNotSupported(format.ContentType); } - private bool TryGetFormatter( + private FormatInfo? TryGetFormatter( IExecutionResult result, - AcceptMediaType[] acceptMediaTypes, - out FormatInfo formatInfo) + AcceptMediaType[] acceptMediaTypes) { - formatInfo = default; + var length = acceptMediaTypes.Length; // if the request does not specify the accept header then we will // use the `application/graphql-response+json` response content-type, // which is the new response content-type. - if (acceptMediaTypes.Length == 0) + if (length == 0) { if (result.Kind is SingleResult) { - formatInfo = new FormatInfo( - ContentType.GraphQLResponse, - ResponseContentType.GraphQLResponse, - _jsonFormatter); - return true; + return _defaultFormat; } if (result.Kind is DeferredResult or BatchResult) { - formatInfo = new FormatInfo( - ContentType.MultiPartMixed, - ResponseContentType.MultiPartMixed, - _multiPartFormatter); - return true; + return _multiPartFormat; } if (result.Kind is SubscriptionResult) { - formatInfo = new FormatInfo( - ContentType.EventStream, - ResponseContentType.EventStream, - _eventStreamResultFormatter); - return true; + return _eventStreamFormat; } - return false; + return null; } // if the request specifies at least one accept media-type we will @@ -328,150 +346,112 @@ public DefaultHttpResponseFormatter(JsonResultFormatterOptions options) _ => ResultKind.Stream }; + ref var start = ref MemoryMarshal.GetArrayDataReference(acceptMediaTypes); + // if we just have one accept header we will try to determine which formatter to take. // we should only be unable to find a match if there was a previous validation skipped. - if (acceptMediaTypes.Length == 1) + if (length == 1) { - var mediaType = acceptMediaTypes[0]; + var mediaType = start; if (resultKind is ResultKind.Single && mediaType.Kind is ApplicationGraphQL or AllApplication or All) { - formatInfo = new FormatInfo( - ContentType.GraphQLResponse, - ResponseContentType.GraphQLResponse, - _jsonFormatter); - return true; + return _graphqlResponseFormat; } if (resultKind is ResultKind.Single && mediaType.Kind is ApplicationJson) { - formatInfo = new FormatInfo( - ContentType.Json, - ResponseContentType.Json, - _jsonFormatter); - return true; + return _legacyFormat; } if (resultKind is ResultKind.Stream or ResultKind.Single && mediaType.Kind is MultiPartMixed or AllMultiPart or All) { - formatInfo = new FormatInfo( - ContentType.MultiPartMixed, - ResponseContentType.MultiPartMixed, - _multiPartFormatter); - return true; + return _multiPartFormat; } if (mediaType.Kind is EventStream) { - formatInfo = new FormatInfo( - ContentType.EventStream, - ResponseContentType.EventStream, - _eventStreamResultFormatter); - return true; + return _eventStreamFormat; } - return false; + return null; } // if we have more than one specified accept media-type we will try to find the best for // our GraphQL result. - ref var searchSpace = ref MemoryMarshal.GetReference(acceptMediaTypes.AsSpan()); - var success = false; + ref var end = ref Unsafe.Add(ref start, length); + FormatInfo? possibleFormat = null; - for (var i = 0; i < acceptMediaTypes.Length; i++) + while (Unsafe.IsAddressLessThan(ref start, ref end)) { - var mediaType = Unsafe.Add(ref searchSpace, i); - if (resultKind is ResultKind.Single && - mediaType.Kind is ApplicationGraphQL or AllApplication or All) + start.Kind is ApplicationGraphQL or AllApplication or All) { - formatInfo = new FormatInfo( - ContentType.GraphQLResponse, - ResponseContentType.GraphQLResponse, - _jsonFormatter); - return true; + return _graphqlResponseFormat; } if (resultKind is ResultKind.Single && - mediaType.Kind is ApplicationJson) + start.Kind is ApplicationJson) { // application/json is a legacy response content-type. // We will create a formatInfo but keep on validating for // a better suited format. - formatInfo = new FormatInfo( - ContentType.Json, - ResponseContentType.Json, - _jsonFormatter); - success = true; + possibleFormat = _legacyFormat; } if (resultKind is ResultKind.Stream or ResultKind.Single && - mediaType.Kind is MultiPartMixed or AllMultiPart or All) + start.Kind is MultiPartMixed or AllMultiPart or All) { // if the result is a stream we consider this a perfect match and // will use this format. if (resultKind is ResultKind.Stream) { - formatInfo = new FormatInfo( - ContentType.MultiPartMixed, - ResponseContentType.MultiPartMixed, - _multiPartFormatter); - return true; + possibleFormat = _multiPartFormat; } // if the format is a event-stream or not set we will create a // multipart/mixed formatInfo for the current result but also keep // on validating for a better suited format. - if (formatInfo.Kind is not ResponseContentType.Json) + if (possibleFormat?.Kind is not ResponseContentType.Json) { - formatInfo = new FormatInfo( - ContentType.MultiPartMixed, - ResponseContentType.MultiPartMixed, - _multiPartFormatter); - success = true; + possibleFormat = _multiPartFormat; } } - if (mediaType.Kind is EventStream or All) + if (start.Kind is EventStream or All) { // if the result is a subscription we consider this a perfect match and // will use this format. if (resultKind is ResultKind.Stream) { - formatInfo = new FormatInfo( - ContentType.EventStream, - ResponseContentType.EventStream, - _eventStreamResultFormatter); - return true; + possibleFormat = _eventStreamFormat; } - // if the result is stream it means that we did not yet validated a + // if the result is stream it means that we did not yet validate a // multipart content-type and thus will create a format for the case that it // is not specified; // or we have a single result but there is no format yet specified // we will create a text/event-stream formatInfo for the current result // but also keep on validating for a better suited format. - if (formatInfo.Kind is ResponseContentType.Unknown) + if (possibleFormat?.Kind is ResponseContentType.Unknown) { - formatInfo = new FormatInfo( - ContentType.MultiPartMixed, - ResponseContentType.MultiPartMixed, - _multiPartFormatter); - success = true; + possibleFormat = _multiPartFormat; } } + + start = ref Unsafe.Add(ref start, 1); } - return success; + return possibleFormat; } /// /// Representation of a resolver format, containing the formatter and the content type. /// - protected readonly struct FormatInfo + protected sealed class FormatInfo { /// /// Initializes a new instance of . diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResponseFormatterOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResponseFormatterOptions.cs new file mode 100644 index 00000000000..4e3190fa79b --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResponseFormatterOptions.cs @@ -0,0 +1,19 @@ +using HotChocolate.Execution.Serialization; + +namespace HotChocolate.AspNetCore.Serialization; + +/// +/// Represents the GraphQL over HTTP formatter options. +/// +public struct HttpResponseFormatterOptions +{ + /// + /// Gets or sets the GraphQL over HTTP transport version. + /// + public HttpTransportVersion HttpTransportVersion { get; set; } + + /// + /// Gets or sets the JSON result formatter options. + /// + public JsonResultFormatterOptions Json { get; set; } +} diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs index 1b5bbba27c4..f7200d7655b 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs @@ -1,7 +1,9 @@ #if NET6_0_OR_GREATER using System.Net.Http.Json; using CookieCrumble; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.AspNetCore.Tests.Utilities; +using Microsoft.Extensions.DependencyInjection; using static System.Net.Http.HttpCompletionOption; namespace HotChocolate.AspNetCore; @@ -11,13 +13,11 @@ public class GraphQLOverHttpSpecTests : ServerTestBase private static readonly Uri _url = new("http://localhost:5000/graphql"); public GraphQLOverHttpSpecTests(TestServerFactory serverFactory) - : base(serverFactory) - { - } + : base(serverFactory) { } /// /// This request does not specify a accept header. - /// expected response content-type: application/json + /// expected response content-type: graphql-response+json /// expected status code: 200 /// [Fact] @@ -31,10 +31,7 @@ public async Task Legacy_Query_No_Streams_1() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }) + new ClientQueryRequest { Query = "{ __typename }" }) }; using var response = await client.SendAsync(request); @@ -54,42 +51,83 @@ public async Task Legacy_Query_No_Streams_1() {""data"":{""__typename"":""Query""}}"); } - /// - /// This request does not specify a accept header. - /// expected response content-type: application/json - /// expected status code: 200 - /// - [Fact] - public async Task Query_No_Body() + /// + /// This request does not specify a accept header and spec is set to legacy. + /// expected response content-type: graphql-response+json + /// expected status code: 200 + /// + [Fact] + public async Task Legacy_Query_No_Streams_Legacy_Response() + { + // arrange + var server = CreateStarWarsServer( + configureServices: s => s.AddHttpResponseFormatter( + new HttpResponseFormatterOptions + { + HttpTransportVersion = HttpTransportVersion.Legacy + })); + var client = server.CreateClient(); + + // act + using var request = new HttpRequestMessage(HttpMethod.Post, _url) { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); + Content = JsonContent.Create( + new ClientQueryRequest { Query = "{ __typename }" }) + }; + + using var response = await client.SendAsync(request); + + // assert + // expected response content-type: application/json + // expected status code: 200 + Snapshot + .Create() + .Add(response) + .MatchInline( + @"Headers: + Content-Type: application/json; charset=utf-8 + --------------------------> + Status Code: OK + --------------------------> + {""data"":{""__typename"":""Query""}}"); + } + + /// + /// This request does not specify a accept header. + /// expected response content-type: application/json + /// expected status code: 200 + /// + [Fact] + public async Task Query_No_Body() + { + // arrange + var server = CreateStarWarsServer(); + var client = server.CreateClient(); - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) + // act + using var request = new HttpRequestMessage(HttpMethod.Post, _url) + { + Content = new ByteArrayContent(Array.Empty()) { - Content = new ByteArrayContent(Array.Empty()) - { - Headers = { ContentType = new("application/json") { CharSet = "utf-8" } } - } - }; - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: + Headers = { ContentType = new("application/json") { CharSet = "utf-8" } } + } + }; + using var response = await client.SendAsync(request); + + // assert + // expected response content-type: application/json + // expected status code: 200 + Snapshot + .Create() + .Add(response) + .MatchInline( + @"Headers: Content-Type: application/graphql-response+json; charset=utf-8 --------------------------> Status Code: BadRequest --------------------------> {""errors"":[{""message"":""The GraphQL request is empty."",""extensions"":{""code"":""HC0012""}}]}"); - } + } /// /// This request does not specify a accept header and has a syntax error. @@ -107,10 +145,7 @@ public async Task Legacy_Query_No_Streams_2() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typ$ename }" - }) + new ClientQueryRequest { Query = "{ __typ$ename }" }) }; using var response = await client.SendAsync(request); @@ -148,10 +183,7 @@ public async Task Legacy_Query_No_Streams_3() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __type name }" - }) + new ClientQueryRequest { Query = "{ __type name }" }) }; using var response = await client.SendAsync(request); @@ -202,10 +234,7 @@ public async Task Legacy_With_Stream_1() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ ... @defer { __typename } }" - }) + new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }) }; using var response = await client.SendAsync(request); @@ -253,14 +282,8 @@ public async Task New_Query_No_Streams_1() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), - Headers = - { - { "Accept", ContentType.GraphQLResponse } - } + new ClientQueryRequest { Query = "{ __typename }" }), + Headers = { { "Accept", ContentType.GraphQLResponse } } }; using var response = await client.SendAsync(request); @@ -296,14 +319,8 @@ public async Task New_Query_No_Streams_2() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), - Headers = - { - { "Accept", ContentType.Json } - } + new ClientQueryRequest { Query = "{ __typename }" }), + Headers = { { "Accept", ContentType.Json } } }; using var response = await client.SendAsync(request); @@ -339,10 +356,7 @@ public async Task New_Query_No_Streams_3() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), + new ClientQueryRequest { Query = "{ __typename }" }), Headers = { { "Accept", $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}" } @@ -389,13 +403,12 @@ public async Task New_Query_No_Streams_4() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), + new ClientQueryRequest { Query = "{ __typename }" }), Headers = { - { "Accept", new[] + { + "Accept", + new[] { ContentType.GraphQLResponse, $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}" @@ -437,14 +450,8 @@ public async Task New_Query_No_Streams_5() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), - Headers = - { - { "Accept", "*/*" } - } + new ClientQueryRequest { Query = "{ __typename }" }), + Headers = { { "Accept", "*/*" } } }; using var response = await client.SendAsync(request); @@ -480,14 +487,8 @@ public async Task New_Query_No_Streams_6() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), - Headers = - { - { "Accept", "application/*" } - } + new ClientQueryRequest { Query = "{ __typename }" }), + Headers = { { "Accept", "application/*" } } }; using var response = await client.SendAsync(request); @@ -524,14 +525,8 @@ public async Task New_Query_No_Streams_7() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typ$ename }" - }), - Headers = - { - { "Accept", ContentType.GraphQLResponse } - } + new ClientQueryRequest { Query = "{ __typ$ename }" }), + Headers = { { "Accept", ContentType.GraphQLResponse } } }; using var response = await client.SendAsync(request); @@ -570,14 +565,8 @@ public async Task New_Query_No_Streams_8() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __type name }" - }), - Headers = - { - { "Accept", ContentType.GraphQLResponse } - } + new ClientQueryRequest { Query = "{ __type name }" }), + Headers = { { "Accept", ContentType.GraphQLResponse } } }; using var response = await client.SendAsync(request); @@ -629,18 +618,16 @@ public async Task New_Query_No_Streams_9() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }), + new ClientQueryRequest { Query = "{ __typename }" }), Headers = { - { "Accept", new[] + { + "Accept", + new[] { ContentType.EventStream, $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}", - ContentType.Json, - ContentType.GraphQLResponse, + ContentType.Json, ContentType.GraphQLResponse, } } } @@ -679,10 +666,7 @@ public async Task New_Query_No_Streams_10() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }) + new ClientQueryRequest { Query = "{ __typename }" }) }; request.Headers.TryAddWithoutValidation("Accept", "unsupported"); @@ -722,10 +706,7 @@ public async Task New_Query_No_Streams_12() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }) + new ClientQueryRequest { Query = "{ __typename }" }) }; request.Headers.TryAddWithoutValidation("Accept", "application/unsupported"); @@ -765,10 +746,7 @@ public async Task New_Query_No_Streams_13() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __typename }" - }) + new ClientQueryRequest { Query = "{ __typename }" }) }; request.Headers.TryAddWithoutValidation( @@ -809,13 +787,12 @@ public async Task New_Query_With_Streams_1() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ ... @defer { __typename } }" - }), + new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }), Headers = { - { "Accept", new[] + { + "Accept", + new[] { ContentType.GraphQLResponse, $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}" @@ -870,14 +847,8 @@ public async Task New_Query_With_Streams_2() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ ... @defer { __typename } }" - }), - Headers = - { - { "Accept", new[] { ContentType.GraphQLResponse } } - } + new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }), + Headers = { { "Accept", new[] { ContentType.GraphQLResponse } } } }; using var response = await client.SendAsync(request, ResponseHeadersRead); @@ -915,18 +886,8 @@ public async Task New_Query_With_Streams_3() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ ... @defer { __typename } }" - }), - Headers = - { - { "Accept", new[] - { - ContentType.EventStream - } - } - } + new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }), + Headers = { { "Accept", new[] { ContentType.EventStream } } } }; using var response = await client.SendAsync(request, ResponseHeadersRead); diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpPostMiddlewareTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpPostMiddlewareTests.cs index df9a750ecbb..2663b193ee2 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpPostMiddlewareTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpPostMiddlewareTests.cs @@ -19,9 +19,7 @@ public class HttpPostMiddlewareTests : ServerTestBase private static readonly Uri _url = new("http://localhost:5000/graphql"); public HttpPostMiddlewareTests(TestServerFactory serverFactory) - : base(serverFactory) - { - } + : base(serverFactory) { } [Fact] public async Task Simple_IsAlive_Test() @@ -153,15 +151,16 @@ public async Task SingleRequest_GetHeroName() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" { hero { name } }" - }); + }); // assert result.MatchSnapshot(); @@ -175,15 +174,16 @@ public async Task SingleRequest_GetHeroName_Casing_Is_Preserved() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" { HERO: hero { name } }" - }); + }); // assert result.MatchSnapshot(); @@ -194,23 +194,25 @@ public async Task Complexity_Exceeded() { // arrange var server = CreateStarWarsServer( - configureServices: c => c.AddGraphQLServer().ModifyRequestOptions(o => - { - o.Complexity.Enable = true; - o.Complexity.MaximumAllowed = 1; - })); + configureServices: c => c.AddGraphQLServer().ModifyRequestOptions( + o => + { + o.Complexity.Enable = true; + o.Complexity.MaximumAllowed = 1; + })); // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" { HERO: hero { name } }" - }); + }); // assert result.MatchSnapshot(); @@ -224,19 +226,17 @@ public async Task SingleRequest_GetHeroName_With_EnumVariable() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" query ($episode: Episode!) { hero(episode: $episode) { name } }", - Variables = new Dictionary - { - { "episode", "NEW_HOPE" } - } - }); + Variables = new Dictionary { { "episode", "NEW_HOPE" } } + }); // assert result.MatchSnapshot(); @@ -250,19 +250,17 @@ public async Task SingleRequest_GetHumanName_With_StringVariable() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" query h($id: String!) { human(id: $id) { name } }", - Variables = new Dictionary - { - { "id", "1000" } - } - }); + Variables = new Dictionary { { "id", "1000" } } + }); // assert result.MatchSnapshot(); @@ -276,9 +274,10 @@ public async Task SingleRequest_Defer_Results() // act var result = - await server.PostRawAsync(new ClientQueryRequest - { - Query = @" + await server.PostRawAsync( + new ClientQueryRequest + { + Query = @" { ... @defer { wait(m: 300) @@ -292,7 +291,7 @@ public async Task SingleRequest_Defer_Results() } } }" - }); + }); // assert result.MatchSnapshot(); @@ -307,12 +306,13 @@ public async Task Single_Diagnostic_Listener_Is_Triggered() var server = CreateStarWarsServer( configureServices: s => s .AddGraphQLServer() - .AddDiagnosticEventListener(_ => listenerA)); + .AddDiagnosticEventListener(_ => listenerA)); // act - await server.PostRawAsync(new ClientQueryRequest - { - Query = @" + await server.PostRawAsync( + new ClientQueryRequest + { + Query = @" { ... @defer { wait(m: 300) @@ -326,7 +326,7 @@ public async Task Single_Diagnostic_Listener_Is_Triggered() } } }" - }); + }); // assert Assert.True(listenerA.Triggered); @@ -342,13 +342,14 @@ public async Task Aggregate_Diagnostic_All_Listeners_Are_Triggered() var server = CreateStarWarsServer( configureServices: s => s .AddGraphQLServer() - .AddDiagnosticEventListener(_ => listenerA) - .AddDiagnosticEventListener(_ => listenerB)); + .AddDiagnosticEventListener(_ => listenerA) + .AddDiagnosticEventListener(_ => listenerB)); // act - await server.PostRawAsync(new ClientQueryRequest - { - Query = @" + await server.PostRawAsync( + new ClientQueryRequest + { + Query = @" { ... @defer { wait(m: 300) @@ -362,7 +363,7 @@ public async Task Aggregate_Diagnostic_All_Listeners_Are_Triggered() } } }" - }); + }); // assert Assert.True(listenerA.Triggered); @@ -389,7 +390,6 @@ public async Task Apollo_Tracing_Invalid_Field() name } }" - }, enableApolloTracing: true); @@ -405,9 +405,10 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Defer() // act var result = - await server.PostRawAsync(new ClientQueryRequest - { - Query = @" + await server.PostRawAsync( + new ClientQueryRequest + { + Query = @" { ... @defer { wait(m: 300) @@ -421,7 +422,7 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Defer() } } }" - }); + }); // assert result.Content.MatchSnapshot(); @@ -435,9 +436,10 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Defer_If_Condition_Tru // act var result = - await server.PostRawAsync(new ClientQueryRequest - { - Query = @" + await server.PostRawAsync( + new ClientQueryRequest + { + Query = @" query ($if: Boolean!){ ... @defer { wait(m: 300) @@ -451,11 +453,8 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Defer_If_Condition_Tru } } }", - Variables = new Dictionary - { - ["if"] = true - } - }); + Variables = new Dictionary { ["if"] = true } + }); // assert result.Content.MatchSnapshot(); @@ -469,9 +468,10 @@ public async Task Ensure_JSON_Format_Is_Correct_With_Defer_If_Condition_False() // act var result = - await server.PostRawAsync(new ClientQueryRequest - { - Query = @" + await server.PostRawAsync( + new ClientQueryRequest + { + Query = @" query ($if: Boolean!){ hero(episode: NEW_HOPE) { @@ -482,11 +482,8 @@ public async Task Ensure_JSON_Format_Is_Correct_With_Defer_If_Condition_False() } } }", - Variables = new Dictionary - { - ["if"] = false - } - }); + Variables = new Dictionary { ["if"] = false } + }); // assert result.Content.MatchSnapshot(); @@ -531,9 +528,10 @@ public async Task SingleRequest_CreateReviewForEpisode_With_ObjectVariable() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" mutation CreateReviewForEpisode( $ep: Episode! $review: ReviewInput!) { @@ -542,18 +540,18 @@ public async Task SingleRequest_CreateReviewForEpisode_With_ObjectVariable() commentary } }", - Variables = new Dictionary - { - { "ep", "EMPIRE" }, + Variables = new Dictionary { - "review", - new Dictionary + { "ep", "EMPIRE" }, { - { "stars", 5 }, { "commentary", "This is a great movie!" }, + "review", + new Dictionary + { + { "stars", 5 }, { "commentary", "This is a great movie!" }, + } } } - } - }); + }); // assert result.MatchSnapshot(); @@ -567,9 +565,10 @@ public async Task SingleRequest_CreateReviewForEpisode_Omit_NonNull_Variable() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" mutation CreateReviewForEpisode( $ep: Episode! $review: ReviewInput!) { @@ -578,18 +577,17 @@ public async Task SingleRequest_CreateReviewForEpisode_Omit_NonNull_Variable() commentary } }", - Variables = new Dictionary - { + Variables = new Dictionary { - "review", - new Dictionary { - { "stars", 5 }, - { "commentary", "This is a great movie!" }, + "review", + new Dictionary + { + { "stars", 5 }, { "commentary", "This is a great movie!" }, + } } } - } - }); + }); // assert result.MatchSnapshot(); @@ -603,9 +601,10 @@ public async Task SingleRequest_CreateReviewForEpisode_Variables_In_ObjectValue( // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" mutation CreateReviewForEpisode( $ep: Episode! $stars: Int! @@ -618,13 +617,13 @@ public async Task SingleRequest_CreateReviewForEpisode_Variables_In_ObjectValue( commentary } }", - Variables = new Dictionary - { - { "ep", "EMPIRE" }, - { "stars", 5 }, - { "commentary", "This is a great movie!" } - } - }); + Variables = new Dictionary + { + { "ep", "EMPIRE" }, + { "stars", 5 }, + { "commentary", "This is a great movie!" } + } + }); // assert result.MatchSnapshot(); @@ -638,9 +637,10 @@ public async Task SingleRequest_CreateReviewForEpisode_Variables_Unused() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" mutation CreateReviewForEpisode( $ep: Episode! $stars: Int! @@ -654,13 +654,13 @@ public async Task SingleRequest_CreateReviewForEpisode_Variables_Unused() commentary } }", - Variables = new Dictionary - { - { "ep", "EMPIRE" }, - { "stars", 5 }, - { "commentary", "This is a great movie!" } - } - }); + Variables = new Dictionary + { + { "ep", "EMPIRE" }, + { "stars", 5 }, + { "commentary", "This is a great movie!" } + } + }); // assert result.MatchSnapshot(); @@ -677,9 +677,10 @@ public async Task SingleRequest_CreateReviewForEpisode_Variables_Unused() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" query a { a: hero { name @@ -691,8 +692,8 @@ public async Task SingleRequest_CreateReviewForEpisode_Variables_Unused() name } }", - OperationName = operationName - }); + OperationName = operationName + }); // assert result.MatchSnapshot(operationName); @@ -706,19 +707,17 @@ public async Task SingleRequest_ValidationError() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" { hero(episode: $episode) { name } }", - Variables = new Dictionary - { - { "episode", "NEW_HOPE" } - } - }); + Variables = new Dictionary { { "episode", "NEW_HOPE" } } + }); // assert result.MatchSnapshot(); @@ -732,15 +731,16 @@ public async Task SingleRequest_SyntaxError() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" { ähero { name } }" - }); + }); // assert result.MatchSnapshot(); @@ -761,10 +761,7 @@ public async Task SingleRequest_Double_Variable() query ($d: Float) { double_arg(d: $d) }", - Variables = new Dictionary - { - { "d", 1.539 } - } + Variables = new Dictionary { { "d", 1.539 } } }, "/arguments"); @@ -787,10 +784,7 @@ public async Task SingleRequest_Double_Max_Variable() query ($d: Float) { double_arg(d: $d) }", - Variables = new Dictionary - { - { "d", double.MaxValue } - } + Variables = new Dictionary { { "d", double.MaxValue } } }, "/arguments"); @@ -806,17 +800,15 @@ public async Task SingleRequest_Double_Min_Variable() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" query ($d: Float) { double_arg(d: $d) }", - Variables = new Dictionary - { - { "d", double.MinValue } - } - }, + Variables = new Dictionary { { "d", double.MinValue } } + }, "/arguments"); // assert @@ -838,10 +830,7 @@ public async Task SingleRequest_Decimal_Max_Variable() query ($d: Decimal) { decimal_arg(d: $d) }", - Variables = new Dictionary - { - { "d", decimal.MaxValue } - } + Variables = new Dictionary { { "d", decimal.MaxValue } } }, "/arguments"); @@ -864,10 +853,7 @@ public async Task SingleRequest_Decimal_Min_Variable() query ($d: Decimal) { decimal_arg(d: $d) }", - Variables = new Dictionary - { - { "d", decimal.MinValue } - } + Variables = new Dictionary { { "d", decimal.MinValue } } }, "/arguments"); @@ -993,27 +979,28 @@ public async Task BatchRequest_GetHero_And_GetHuman_MultiPart() // act var response = await server.SendPostRequestAsync( - JsonConvert.SerializeObject(new List - { - new ClientQueryRequest + JsonConvert.SerializeObject( + new List { - Query = @" + new ClientQueryRequest + { + Query = @" query getHero { hero(episode: EMPIRE) { id @export } }" - }, - new ClientQueryRequest - { - Query = @" + }, + new ClientQueryRequest + { + Query = @" query getHuman { human(id: $id) { name } }" - } - }), + } + }), "/graphql"); if (response.StatusCode == HttpStatusCode.NotFound) @@ -1161,15 +1148,16 @@ public async Task Throw_Custom_GraphQL_Error() // act var result = - await server.PostAsync(new ClientQueryRequest - { - Query = @" + await server.PostAsync( + new ClientQueryRequest + { + Query = @" { hero { name } }" - }); + }); // assert result.MatchSnapshot(); @@ -1181,17 +1169,15 @@ public async Task Strip_Null_Values_Variant_1() // arrange var server = CreateStarWarsServer( configureServices: s => s.AddHttpResponseFormatter( - _ => new DefaultHttpResponseFormatter(new() { NullIgnoreCondition = Fields }))); + _ => new DefaultHttpResponseFormatter( + new() { Json = new() { NullIgnoreCondition = Fields } }))); var client = server.CreateClient(); // act using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __schema { description } }" - }) + new ClientQueryRequest { Query = "{ __schema { description } }" }) }; using var response = await client.SendAsync(request); @@ -1217,17 +1203,17 @@ public async Task Strip_Null_Values_Variant_2() // arrange var server = CreateStarWarsServer( configureServices: s => s.AddHttpResponseFormatter( - new JsonResultFormatterOptions { NullIgnoreCondition = Fields })); + new HttpResponseFormatterOptions + { + Json = new JsonResultFormatterOptions { NullIgnoreCondition = Fields } + })); var client = server.CreateClient(); // act using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ __schema { description } }" - }) + new ClientQueryRequest { Query = "{ __schema { description } }" }) }; using var response = await client.SendAsync(request); @@ -1258,20 +1244,18 @@ public async Task Strip_Null_Elements() .AddGraphQLServer("test") .AddQueryType() .Services - .AddHttpResponseFormatter(new JsonResultFormatterOptions - { - NullIgnoreCondition = Lists - })); + .AddHttpResponseFormatter( + new HttpResponseFormatterOptions + { + Json = new JsonResultFormatterOptions { NullIgnoreCondition = Lists } + })); var client = server.CreateClient(); // act using var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent.Create( - new ClientQueryRequest - { - Query = "{ nullValues }" - }) + new ClientQueryRequest { Query = "{ nullValues }" }) }; using var response = await client.SendAsync(request);