Skip to content

Commit

Permalink
Implemented Stream and Defer Spec Edits (#5405)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Sep 18, 2022
1 parent 5991f99 commit cc4c477
Show file tree
Hide file tree
Showing 229 changed files with 8,812 additions and 6,461 deletions.
10 changes: 10 additions & 0 deletions src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpMethod.cs
@@ -1,7 +1,17 @@
namespace HotChocolate.AspNetCore;

/// <summary>
/// The default HTTP fetch method for Banana Cake Pop.
/// </summary>
public enum DefaultHttpMethod
{
/// <summary>
/// Use a GraphQL HTTP GET request.
/// </summary>
Get,

/// <summary>
/// Use a GraphQL HTTP Post request.
/// </summary>
Post
}
Expand Up @@ -299,7 +299,7 @@ public class DefaultHttpResponseFormatter : IHttpResponseFormatter
var mediaType = acceptMediaTypes[0];

if (resultKind is ResultKind.Single &&
mediaType.Kind is ApplicationGraphQL or AllApplication or All)
mediaType.Kind is ApplicationGraphQL or AllApplication)
{
formatInfo = new FormatInfo(
ContentType.GraphQLResponse,
Expand All @@ -309,7 +309,7 @@ public class DefaultHttpResponseFormatter : IHttpResponseFormatter
}

if (resultKind is ResultKind.Single &&
mediaType.Kind is ApplicationJson)
mediaType.Kind is ApplicationJson or All)
{
formatInfo = new FormatInfo(
ContentType.Json,
Expand Down Expand Up @@ -350,7 +350,7 @@ public class DefaultHttpResponseFormatter : IHttpResponseFormatter
var mediaType = Unsafe.Add(ref searchSpace, i);

if (resultKind is ResultKind.Single &&
mediaType.Kind is ApplicationGraphQL or AllApplication or All)
mediaType.Kind is ApplicationGraphQL or AllApplication)
{
formatInfo = new FormatInfo(
ContentType.GraphQLResponse,
Expand All @@ -360,7 +360,7 @@ public class DefaultHttpResponseFormatter : IHttpResponseFormatter
}

if (resultKind is ResultKind.Single &&
mediaType.Kind is ApplicationJson)
mediaType.Kind is ApplicationJson or All)
{
// application/json is a legacy response content-type.
// We will create a formatInfo but keep on validating for
Expand Down
Expand Up @@ -16,4 +16,10 @@ public bool Evict([FromServices] IRequestExecutorResolver executorResolver, ISch
executorResolver.EvictRequestExecutor(schema.Name);
return true;
}

public async Task<bool> Wait(int m, CancellationToken ct)
{
await Task.Delay(m, ct);
return true;
}
}
Expand Up @@ -385,7 +385,7 @@ public async Task New_Query_No_Streams_4()

/// <summary>
/// This request specifies the */* accept header.
/// expected response content-type: application/graphql-response+json; charset=utf-8
/// expected response content-type: application/json; charset=utf-8
/// expected status code: 200
/// </summary>
[Fact]
Expand All @@ -412,14 +412,14 @@ public async Task New_Query_No_Streams_5()
using var response = await client.SendAsync(request);

// assert
// expected response content-type: application/graphql-response+json; charset=utf-8
// expected response content-type: application/json; charset=utf-8
// expected status code: 200
Snapshot
.Create()
.Add(response)
.MatchInline(
@"Headers:
Content-Type: application/graphql-response+json; charset=utf-8
Content-Type: application/json; charset=utf-8
-------------------------->
Status Code: OK
-------------------------->
Expand Down Expand Up @@ -862,7 +862,8 @@ public async Task New_Query_With_Streams_3()
Status Code: OK
-------------------------->
{""event"":""next"",""data"":{""data"":{},""hasNext"":true}}
{""event"":""next"",""data"":{""path"":[],""data"":{""__typename"":""Query""},""hasNext"":false}}
{""event"":""next"",""data"":{""incremental"":[{""data"":{" +
@"""__typename"":""Query""},""path"":[]}],""hasNext"":false}}
{""event"":""complete""}
");
}
Expand Down
Expand Up @@ -240,6 +240,9 @@ public async Task SingleRequest_Defer_Results()
{
Query = @"
{
... @defer {
wait(m: 300)
}
hero(episode: NEW_HOPE)
{
name
Expand Down Expand Up @@ -271,6 +274,9 @@ public async Task Single_Diagnostic_Listener_Is_Triggered()
{
Query = @"
{
... @defer {
wait(m: 300)
}
hero(episode: NEW_HOPE)
{
name
Expand Down Expand Up @@ -304,6 +310,9 @@ public async Task Aggregate_Diagnostic_All_Listeners_Are_Triggered()
{
Query = @"
{
... @defer {
wait(m: 300)
}
hero(episode: NEW_HOPE)
{
name
Expand Down Expand Up @@ -332,6 +341,9 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Defer()
{
Query = @"
{
... @defer {
wait(m: 300)
}
hero(episode: NEW_HOPE)
{
name
Expand Down Expand Up @@ -359,6 +371,9 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Defer_If_Condition_Tru
{
Query = @"
query ($if: Boolean!){
... @defer {
wait(m: 300)
}
hero(episode: NEW_HOPE)
{
name
Expand Down Expand Up @@ -421,6 +436,9 @@ public async Task Ensure_Multipart_Format_Is_Correct_With_Stream()
{
Query = @"
{
... @defer {
wait(m: 300)
}
hero(episode: NEW_HOPE)
{
name
Expand Down
Expand Up @@ -74,6 +74,7 @@ type Query {
droid(id: String): Droid
time: Long!
evict: Boolean!
wait(m: Int!): Boolean!
}

type Review {
Expand Down
Expand Up @@ -74,6 +74,7 @@ type Query {
droid(id: String): Droid
time: Long!
evict: Boolean!
wait(m: Int!): Boolean!
}

type Review {
Expand Down
Expand Up @@ -74,6 +74,7 @@ type Query {
droid(id: String): Droid
time: Long!
evict: Boolean!
wait(m: Int!): Boolean!
}

type Review {
Expand Down
Expand Up @@ -7,4 +7,5 @@ type Query {
droid(id: String): Droid
time: Long!
evict: Boolean!
wait(m: Int!): Boolean!
}
Expand Up @@ -15,4 +15,5 @@ type Query {
droid(id: String): Droid
time: Long!
evict: Boolean!
wait(m: Int!): Boolean!
}
Expand Up @@ -6,5 +6,9 @@ Content-Type: application/json; charset=utf-8
---
Content-Type: application/json; charset=utf-8

{"incremental":[{"data":{"id":"2001"},"label":"my_id","path":["hero"]}],"hasNext":false}
{"incremental":[{"data":{"id":"2001"},"label":"my_id","path":["hero"]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8

{"incremental":[{"data":{"wait":true},"path":[]}],"hasNext":false}
-----
Expand Up @@ -6,5 +6,9 @@ Content-Type: application/json; charset=utf-8
---
Content-Type: application/json; charset=utf-8

{"incremental":[{"data":{"id":"2001"},"label":"my_id","path":["hero"]}],"hasNext":false}
{"incremental":[{"data":{"id":"2001"},"label":"my_id","path":["hero"]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8

{"incremental":[{"data":{"wait":true},"path":[]}],"hasNext":false}
-----
Expand Up @@ -6,9 +6,9 @@ Content-Type: application/json; charset=utf-8
---
Content-Type: application/json; charset=utf-8

{"incremental":[{"data":{"name":"Han Solo"},"label":"foo","path":["hero","friends","nodes",1]},{"data":{"name":"Leia Organa"},"label":"foo","path":["hero","friends","nodes",2]}],"hasNext":true}
{"incremental":[{"items":[{"name":"Han Solo"}],"label":"foo","path":["hero","friends","nodes",1]},{"items":[{"name":"Leia Organa"}],"label":"foo","path":["hero","friends","nodes",2]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8

{"hasNext":false}
{"incremental":[{"data":{"wait":true},"path":[]}],"hasNext":false}
-----
@@ -1,5 +1,5 @@
{
"ContentType": "multipart/mixed; boundary=\"-\"; charset=utf-8",
"StatusCode": "OK",
"Content": "\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"data\":{\"hero\":{\"name\":\"R2-D2\"}},\"hasNext\":true}\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"incremental\":[{\"data\":{\"id\":\"2001\"},\"label\":\"my_id\",\"path\":[\"hero\"]}],\"hasNext\":false}\r\n-----\r\n"
"Content": "\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"data\":{\"hero\":{\"name\":\"R2-D2\"}},\"hasNext\":true}\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"incremental\":[{\"data\":{\"id\":\"2001\"},\"label\":\"my_id\",\"path\":[\"hero\"]}],\"hasNext\":true}\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"incremental\":[{\"data\":{\"wait\":true},\"path\":[]}],\"hasNext\":false}\r\n-----\r\n"
}
Expand Up @@ -29,6 +29,15 @@ public interface IQueryResult : IExecutionResult
/// <value></value>
IReadOnlyDictionary<string, object?>? Data { get; }

/// <summary>
/// The `items` entry in a stream payload is a list of results from the execution of
/// the associated @stream directive. This output will be a list of the same type of
/// the field with the associated `@stream` directive. If `items` is set to `null`,
/// it indicates that an error has caused a `null` to bubble up to a field higher
/// than the list field with the associated `@stream` directive.
/// </summary>
IReadOnlyList<object?>? Items { get; }

/// <summary>
/// Gets the GraphQL errors of the result.
/// </summary>
Expand Down
Expand Up @@ -10,6 +10,8 @@ public interface IQueryResultBuilder
{
IQueryResultBuilder SetData(IReadOnlyDictionary<string, object?>? data);

IQueryResultBuilder SetItems(IReadOnlyList<object?>? items);

IQueryResultBuilder AddError(IError error);

IQueryResultBuilder AddErrors(IEnumerable<IError> errors);
Expand Down
19 changes: 17 additions & 2 deletions src/HotChocolate/Core/src/Abstractions/Execution/QueryResult.cs
Expand Up @@ -17,21 +17,27 @@ public sealed class QueryResult : ExecutionResult, IQueryResult
IReadOnlyList<IError>? errors,
IReadOnlyDictionary<string, object?>? extension,
IReadOnlyDictionary<string, object?>? contextData,
IReadOnlyList<object?>? items,
IReadOnlyList<IQueryResult>? incremental,
string? label,
Path? path,
bool? hasNext,
Func<ValueTask>[] cleanupTasks)
: base(cleanupTasks)
{
if (data is null && errors is null && incremental is null && hasNext is not false)
if (data is null &&
items is null &&
errors is null &&
incremental is null &&
hasNext is not false)
{
throw new ArgumentException(
AbstractionResources.QueryResult_DataAndResultAreNull,
nameof(data));
}

Data = data;
Items = items;
Errors = errors;
Extensions = extension;
ContextData = contextData;
Expand All @@ -49,19 +55,25 @@ public sealed class QueryResult : ExecutionResult, IQueryResult
IReadOnlyList<IError>? errors = null,
IReadOnlyDictionary<string, object?>? extension = null,
IReadOnlyDictionary<string, object?>? contextData = null,
IReadOnlyList<object?>? items = null,
IReadOnlyList<IQueryResult>? incremental = null,
string? label = null,
Path? path = null,
bool? hasNext = null)
{
if (data is null && errors is null && incremental is null && hasNext is not false)
if (data is null &&
items is null &&
errors is null &&
incremental is null &&
hasNext is not false)
{
throw new ArgumentException(
AbstractionResources.QueryResult_DataAndResultAreNull,
nameof(data));
}

Data = data;
Items = items;
Errors = errors;
Extensions = extension;
ContextData = contextData;
Expand All @@ -83,6 +95,9 @@ public sealed class QueryResult : ExecutionResult, IQueryResult
/// <inheritdoc />
public IReadOnlyDictionary<string, object?>? Data { get; }

/// <inheritdoc />
public IReadOnlyList<object?>? Items { get; }

/// <inheritdoc />
public IReadOnlyList<IError>? Errors { get; }

Expand Down
Expand Up @@ -9,6 +9,7 @@ namespace HotChocolate.Execution;
public class QueryResultBuilder : IQueryResultBuilder
{
private IReadOnlyDictionary<string, object?>? _data;
private IReadOnlyList<object?>? _items;
private List<IError>? _errors;
private ExtensionData? _extensionData;
private ExtensionData? _contextData;
Expand All @@ -21,6 +22,19 @@ public class QueryResultBuilder : IQueryResultBuilder
public IQueryResultBuilder SetData(IReadOnlyDictionary<string, object?>? data)
{
_data = data;
_items = null;
return this;
}

public IQueryResultBuilder SetItems(IReadOnlyList<object?>? items)
{
_items = items;

if (items is not null)
{
_data = null;
}

return this;
}

Expand Down Expand Up @@ -159,6 +173,7 @@ public IQueryResult Create()
_errors?.Count > 0 ? _errors : null,
_extensionData?.Count > 0 ? _extensionData : null,
_contextData?.Count > 0 ? _contextData : null,
_items,
_incremental,
_label,
_path,
Expand Down

0 comments on commit cc4c477

Please sign in to comment.