Skip to content

Commit

Permalink
Adds @defer transport layer. (#2377)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Sep 25, 2020
1 parent 91d6801 commit 689c52b
Show file tree
Hide file tree
Showing 67 changed files with 1,360 additions and 291 deletions.
11 changes: 9 additions & 2 deletions src/GreenDonut/test/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="coverlet.msbuild" Version="2.9.0" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="coverlet.collector" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="2.9.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
Expand Down
12 changes: 12 additions & 0 deletions src/HotChocolate/AspNetCore/sample/StarWars/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -17,6 +18,7 @@ public void ConfigureServices(IServiceCollection services)
.AddGraphQLServer()
.AddStarWarsTypes()
.AddStarWarsRepositories()
.AddTypeExtension<SlowTypeExtension>()
.AddGraphQLServer("hello_world")
.AddQueryType(d => d
.Name("Query")
Expand Down Expand Up @@ -46,4 +48,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
});
}
}

[ExtendObjectType("Droid")]
public class SlowTypeExtension
{
public async Task<string> SlowAsync()
{
await Task.Delay(3000);
return "hello";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,27 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
return builder.ConfigureSchemaServices(s =>
s.TryAddSingleton<IHttpRequestInterceptor, DefaultHttpRequestInterceptor>());
}

public static IServiceCollection AddHttpRequestSerializer(
this IServiceCollection services,
HttpResultSerialization batchSerialization = HttpResultSerialization.MultiPartChunked,
HttpResultSerialization deferSerialization = HttpResultSerialization.MultiPartChunked)
{
services.RemoveAll<IHttpResultSerializer>();
services.AddSingleton<IHttpResultSerializer>(
new DefaultHttpResultSerializer(
batchSerialization,
deferSerialization));
return services;
}

public static IServiceCollection AddHttpRequestSerializer<T>(
this IServiceCollection services)
where T : class, IHttpResultSerializer
{
services.RemoveAll<IHttpResultSerializer>();
services.AddSingleton<IHttpResultSerializer, T>();
return services;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
}

services.AddGraphQLCore();
services.TryAddSingleton<IHttpResultSerializer, DefaultHttpResultSerializer>();
services.TryAddSingleton<IHttpResultSerializer>(
new DefaultHttpResultSerializer());
services.TryAddSingleton<IHttpRequestParser>(
sp => new DefaultHttpRequestParser(
sp.GetRequiredService<IDocumentCache>(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,131 @@
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Execution;
using HotChocolate.Execution.Serialization;
using static HotChocolate.AspNetCore.Utilities.ErrorHelper;

namespace HotChocolate.AspNetCore.Utilities
{
public class DefaultHttpResultSerializer : IHttpResultSerializer
{
private readonly JsonQueryResultSerializer _queryResultSerializer =
private const string _multiPartContentType =
"multipart/mixed; boundary=\"-\"";
private const string _jsonContentType =
"application/json; charset=utf-8";

private readonly JsonQueryResultSerializer _jsonSerializer =
new JsonQueryResultSerializer();
private readonly JsonArrayResponseStreamSerializer _responseStreamSerializer =
private readonly JsonArrayResponseStreamSerializer _jsonArraySerializer =
new JsonArrayResponseStreamSerializer();
private readonly MultiPartResponseStreamSerializer _multiPartSerializer =
new MultiPartResponseStreamSerializer();

public string GetContentType(IExecutionResult result) =>
"application/json; charset=utf-8";
private readonly HttpResultSerialization _batchSerialization;
private readonly HttpResultSerialization _deferSerialization;

public HttpStatusCode GetStatusCode(IExecutionResult result)
public DefaultHttpResultSerializer(
HttpResultSerialization batchSerialization = HttpResultSerialization.MultiPartChunked,
HttpResultSerialization deferSerialization = HttpResultSerialization.MultiPartChunked)
{
if (result is IQueryResult q)
_batchSerialization = batchSerialization;
_deferSerialization = deferSerialization;
}

public virtual string GetContentType(IExecutionResult result)
{
if (result == null)
{
return q.Data is null
? q.ContextData is not null &&
q.ContextData.ContainsKey(ContextDataKeys.ValidationErrors)
? HttpStatusCode.BadRequest
: HttpStatusCode.InternalServerError
: HttpStatusCode.OK;
throw new ArgumentNullException(nameof(result));
}

switch (result)
{
case QueryResult:
return _jsonContentType;

case DeferredQueryResult:
return _deferSerialization == HttpResultSerialization.JsonArray
? _jsonContentType
: _multiPartContentType;

case BatchQueryResult:
return _batchSerialization == HttpResultSerialization.JsonArray
? _jsonContentType
: _multiPartContentType;

default:
return _jsonContentType;
}
return HttpStatusCode.OK;
}

public async ValueTask SerializeAsync(
public virtual HttpStatusCode GetStatusCode(IExecutionResult result)
{
switch (result)
{
case QueryResult queryResult:
return queryResult.Data is null
? queryResult.ContextData is not null &&
queryResult.ContextData.ContainsKey(ContextDataKeys.ValidationErrors)
? HttpStatusCode.BadRequest
: HttpStatusCode.InternalServerError
: HttpStatusCode.OK;

case DeferredQueryResult:
case BatchQueryResult:
return HttpStatusCode.OK;

default:
return HttpStatusCode.InternalServerError;
}
}

public virtual async ValueTask SerializeAsync(
IExecutionResult result,
Stream stream,
CancellationToken cancellationToken)
{
if (result is IReadOnlyQueryResult q)
switch (result)
{
await _queryResultSerializer.SerializeAsync(
q, stream, cancellationToken);
}
case IQueryResult queryResult:
await _jsonSerializer
.SerializeAsync(queryResult, stream, cancellationToken)
.ConfigureAwait(false);
break;

if (result is IResponseStream r)
{
await _responseStreamSerializer.SerializeAsync(
r, stream, cancellationToken);
case DeferredQueryResult deferredResult
when _deferSerialization == HttpResultSerialization.JsonArray:
await _jsonArraySerializer
.SerializeAsync(deferredResult, stream, cancellationToken)
.ConfigureAwait(false);
break;

case DeferredQueryResult deferredResult:
await _multiPartSerializer
.SerializeAsync(deferredResult, stream, cancellationToken)
.ConfigureAwait(false);
break;

case BatchQueryResult batchResult
when _batchSerialization == HttpResultSerialization.JsonArray:
await _jsonArraySerializer
.SerializeAsync(batchResult, stream, cancellationToken)
.ConfigureAwait(false);
break;

case BatchQueryResult batchResult:
await _multiPartSerializer
.SerializeAsync(batchResult, stream, cancellationToken)
.ConfigureAwait(false);
break;

default:
await _jsonSerializer
.SerializeAsync(ResponseTypeNotSupported(), stream, cancellationToken)
.ConfigureAwait(false);
break;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using HotChocolate.Execution;

namespace HotChocolate.AspNetCore.Utilities
{
internal static class ErrorHelper
Expand All @@ -13,5 +15,11 @@ internal static class ErrorHelper
.SetMessage("The GraphQL batch request has no elements.")
.SetCode(ErrorCodes.Server.RequestInvalid)
.Build();

public static IQueryResult ResponseTypeNotSupported() =>
QueryResultBuilder.CreateError(
ErrorBuilder.New()
.SetMessage("The response type is not supported.")
.Build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace HotChocolate.AspNetCore.Utilities
{
public enum HttpResultSerialization
{
JsonArray,
MultiPartChunked
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Linq;
using HotChocolate.AspNetCore.Utilities;
using HotChocolate.Execution.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Xunit;

namespace Microsoft.Extensions.DependencyInjection
{
public class ServiceCollectionExtensionTests
{
[Fact]
public static void AddHttpRequestSerializer_OfT()
{
// arrange
var serviceCollection = new ServiceCollection();

// act
HotChocolateAspNetCoreServiceCollectionExtensions
.AddHttpRequestSerializer<DefaultHttpResultSerializer>(serviceCollection);

// assert
Assert.Collection(
serviceCollection,
t =>
{
Assert.Equal(typeof(IHttpResultSerializer), t.ServiceType);
Assert.Equal(typeof(DefaultHttpResultSerializer), t.ImplementationType);
});
}
}
}

0 comments on commit 689c52b

Please sign in to comment.