Skip to content

Commit 61f46a0

Browse files
authored
Support persisted documents (#1147)
1 parent cbafeed commit 61f46a0

File tree

8 files changed

+176
-55
lines changed

8 files changed

+176
-55
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<VersionPrefix>8.0.0-preview</VersionPrefix>
4+
<VersionPrefix>8.0.1-preview</VersionPrefix>
55
<NextVersion>9.0.0</NextVersion>
66
<LangVersion>latest</LangVersion>
77
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@@ -32,7 +32,7 @@
3232
<RootNamespace>GraphQL.Server.$(MSBuildProjectName)</RootNamespace>
3333
<PackageId>GraphQL.Server.$(MSBuildProjectName)</PackageId>
3434

35-
<GraphQLVersion>[8.0.0,9.0.0)</GraphQLVersion>
35+
<GraphQLVersion>[8.0.1,9.0.0)</GraphQLVersion>
3636

3737
<SignAssembly>true</SignAssembly>
3838
<_FriendAssembliesPublicKey>PublicKey=0024000004800000940000000602000000240000525341310004000001000100352162dbf27be78fc45136884b8f324aa9f1dfc928c96c24704bf1df1a8779b2f26c760ed8321eca5b95ea6bd9bb60cd025b300f73bd1f4ae1ee6e281f85c527fa013ab5cb2c3fc7a1cbef7f9bf0c9014152e6a21f6e0ac6a371f8b45c6d7139c9119df9eeecf1cf59063545bb7c07437b1bc12be2c57d108d72d6c27176fbb8</_FriendAssembliesPublicKey>

src/Transports.AspNetCore/GraphQLHttpMiddleware.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public class GraphQLHttpMiddleware : IUserContextBuilder
6868
private const string VARIABLES_KEY = "variables";
6969
private const string EXTENSIONS_KEY = "extensions";
7070
private const string OPERATION_NAME_KEY = "operationName";
71+
private const string DOCUMENT_ID_KEY = "documentId";
7172
private const string OPERATIONS_KEY = "operations"; // used for multipart/form-data requests per https://github.com/jaydenseric/graphql-multipart-request-spec
7273
private const string MAP_KEY = "map"; // used for multipart/form-data requests per https://github.com/jaydenseric/graphql-multipart-request-spec
7374
private const string MEDIATYPE_GRAPHQLJSON = "application/graphql+json"; // deprecated
@@ -199,7 +200,8 @@ protected virtual async Task InvokeAsync(HttpContext context, RequestDelegate ne
199200
Query = urlGQLRequest?.Query ?? bodyGQLRequest?.Query,
200201
Variables = urlGQLRequest?.Variables ?? bodyGQLRequest?.Variables,
201202
Extensions = urlGQLRequest?.Extensions ?? bodyGQLRequest?.Extensions,
202-
OperationName = urlGQLRequest?.OperationName ?? bodyGQLRequest?.OperationName
203+
OperationName = urlGQLRequest?.OperationName ?? bodyGQLRequest?.OperationName,
204+
DocumentId = urlGQLRequest?.DocumentId ?? bodyGQLRequest?.DocumentId,
203205
};
204206

205207
await HandleRequestAsync(context, next, gqlRequest);
@@ -343,8 +345,8 @@ void ApplyMapToRequests(Dictionary<string, string?[]> map, IFormCollection form,
343345
foreach (var entry in map)
344346
{
345347
// validate entry key
346-
if (entry.Key == "" || entry.Key == "query" || entry.Key == "operationName" || entry.Key == "variables" || entry.Key == "extensions" || entry.Key == "operations" || entry.Key == "map")
347-
throw new InvalidMapError("Map key cannot be query, operationName, variables, extensions, operations or map.");
348+
if (entry.Key == "" || entry.Key == QUERY_KEY || entry.Key == OPERATION_NAME_KEY || entry.Key == VARIABLES_KEY || entry.Key == EXTENSIONS_KEY || entry.Key == DOCUMENT_ID_KEY || entry.Key == OPERATIONS_KEY || entry.Key == MAP_KEY)
349+
throw new InvalidMapError("Map key cannot be query, operationName, variables, extensions, documentId, operations or map.");
348350
// locate file
349351
var file = form.Files[entry.Key]
350352
?? throw new InvalidMapError("Map key does not refer to an uploaded file.");
@@ -683,6 +685,7 @@ protected virtual async Task<ExecutionResult> ExecuteRequestAsync(HttpContext co
683685
Query = request?.Query,
684686
Variables = request?.Variables,
685687
Extensions = request?.Extensions,
688+
DocumentId = request?.DocumentId,
686689
CancellationToken = context.RequestAborted,
687690
OperationName = request?.OperationName,
688691
RequestServices = serviceProvider,
@@ -1212,6 +1215,7 @@ protected virtual Task WriteErrorResponseAsync(HttpContext context, HttpStatusCo
12121215
Variables = _options.ReadVariablesFromQueryString && queryCollection.TryGetValue(VARIABLES_KEY, out var variablesValues) ? _serializer.Deserialize<Inputs>(variablesValues[0]) : null,
12131216
Extensions = _options.ReadExtensionsFromQueryString && queryCollection.TryGetValue(EXTENSIONS_KEY, out var extensionsValues) ? _serializer.Deserialize<Inputs>(extensionsValues[0]) : null,
12141217
OperationName = queryCollection.TryGetValue(OPERATION_NAME_KEY, out var operationNameValues) ? operationNameValues[0] : null,
1218+
DocumentId = queryCollection.TryGetValue(DOCUMENT_ID_KEY, out var documentIdValues) ? documentIdValues[0] : null,
12151219
};
12161220

12171221
private GraphQLRequest DeserializeFromFormBody(IFormCollection formCollection) => new()
@@ -1220,6 +1224,7 @@ protected virtual Task WriteErrorResponseAsync(HttpContext context, HttpStatusCo
12201224
Variables = formCollection.TryGetValue(VARIABLES_KEY, out var variablesValues) ? _serializer.Deserialize<Inputs>(variablesValues[0]) : null,
12211225
Extensions = formCollection.TryGetValue(EXTENSIONS_KEY, out var extensionsValues) ? _serializer.Deserialize<Inputs>(extensionsValues[0]) : null,
12221226
OperationName = formCollection.TryGetValue(OPERATION_NAME_KEY, out var operationNameValues) ? operationNameValues[0] : null,
1227+
DocumentId = formCollection.TryGetValue(DOCUMENT_ID_KEY, out var documentIdValues) ? documentIdValues[0] : null,
12231228
};
12241229

12251230
/// <summary>

src/Transports.AspNetCore/WebSockets/GraphQLWs/SubscriptionServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ protected override async Task<ExecutionResult> ExecuteRequestAsync(OperationMess
208208
Query = request.Query,
209209
Variables = request.Variables,
210210
Extensions = request.Extensions,
211+
DocumentId = request.DocumentId,
211212
OperationName = request.OperationName,
212213
RequestServices = scope.ServiceProvider,
213214
CancellationToken = CancellationToken,

src/Transports.AspNetCore/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ protected override async Task<ExecutionResult> ExecuteRequestAsync(OperationMess
186186
Query = request.Query,
187187
Variables = request.Variables,
188188
Extensions = request.Extensions,
189+
DocumentId = request.DocumentId,
189190
OperationName = request.OperationName,
190191
RequestServices = scope.ServiceProvider,
191192
CancellationToken = CancellationToken,

tests/Transports.AspNetCore.Tests/Middleware/GetTests.cs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Net;
22
using System.Net.Http.Headers;
3+
using GraphQL.PersistedDocuments;
34
using GraphQL.Server.Transports.AspNetCore.Errors;
45
using GraphQL.Validation;
56

@@ -10,6 +11,7 @@ public class GetTests : IDisposable
1011
private GraphQLHttpMiddlewareOptions _options = null!;
1112
private GraphQLHttpMiddlewareOptions _options2 = null!;
1213
private Action<ExecutionOptions> _configureExecution = _ => { };
14+
private bool _enablePersistedDocuments = true;
1315
private readonly TestServer _server;
1416

1517
public GetTests()
@@ -18,13 +20,35 @@ public GetTests()
1820
hostBuilder.ConfigureServices(services =>
1921
{
2022
services.AddSingleton<Chat.IChat, Chat.Chat>();
21-
services.AddGraphQL(b => b
22-
.AddAutoSchema<Chat.Query>(s => s
23-
.WithMutation<Chat.Mutation>()
24-
.WithSubscription<Chat.Subscription>())
25-
.AddSchema<Schema2>()
26-
.AddSystemTextJson()
27-
.ConfigureExecutionOptions(o => _configureExecution(o)));
23+
services.AddGraphQL(b =>
24+
{
25+
b
26+
.AddAutoSchema<Chat.Query>(s => s
27+
.WithMutation<Chat.Mutation>()
28+
.WithSubscription<Chat.Subscription>())
29+
.AddSchema<Schema2>()
30+
.AddSystemTextJson()
31+
.ConfigureExecution((options, next) =>
32+
{
33+
if (_enablePersistedDocuments)
34+
{
35+
var handler = options.RequestServices!.GetRequiredService<PersistedDocumentHandler>();
36+
return handler.ExecuteAsync(options, next);
37+
}
38+
return next(options);
39+
})
40+
.ConfigureExecutionOptions(o => _configureExecution(o));
41+
b.Services.Configure<PersistedDocumentOptions>(o =>
42+
{
43+
o.AllowOnlyPersistedDocuments = false;
44+
o.AllowedPrefixes.Add("test");
45+
o.GetQueryDelegate = (options, prefix, payload) =>
46+
prefix == "test" && payload == "abc" ? new("{count}") :
47+
prefix == "test" && payload == "form" ? new("query op1{ext} query op2($test:String!){ext var(test:$test)}") :
48+
default;
49+
});
50+
});
51+
services.AddSingleton<PersistedDocumentHandler>();
2852
#if NETCOREAPP2_1 || NET48
2953
services.AddHostApplicationLifetime();
3054
#endif
@@ -78,6 +102,14 @@ public async Task BasicTest()
78102
await response.ShouldBeAsync("""{"data":{"count":0}}""");
79103
}
80104

105+
[Fact]
106+
public async Task PersistedDocumentTest()
107+
{
108+
var client = _server.CreateClient();
109+
using var response = await client.GetAsync("/graphql?documentId=test:abc");
110+
await response.ShouldBeAsync("""{"data":{"count":0}}""");
111+
}
112+
81113
[Theory]
82114
[InlineData(true, true)]
83115
[InlineData(true, false)]
@@ -346,14 +378,19 @@ public async Task QueryParseError(bool badRequest)
346378
}
347379

348380
[Theory]
349-
[InlineData(false)]
350-
[InlineData(true)]
351-
public async Task NoQuery(bool badRequest)
381+
[InlineData(false, false)]
382+
[InlineData(true, false)]
383+
[InlineData(false, true)]
384+
[InlineData(true, true)]
385+
public async Task NoQuery(bool badRequest, bool usePersistedDocumentHandler)
352386
{
387+
_enablePersistedDocuments = usePersistedDocumentHandler;
353388
_options.ValidationErrorsReturnBadRequest = badRequest;
354389
var client = _server.CreateClient();
355390
using var response = await client.GetAsync("/graphql");
356-
await response.ShouldBeAsync(badRequest, """{"errors":[{"message":"GraphQL query is missing.","extensions":{"code":"QUERY_MISSING","codes":["QUERY_MISSING"]}}]}""");
391+
await response.ShouldBeAsync(badRequest, usePersistedDocumentHandler
392+
? """{"errors":[{"message":"The request must have a documentId parameter.","extensions":{"code":"DOCUMENT_ID_MISSING","codes":["DOCUMENT_ID_MISSING"]}}]}"""
393+
: """{"errors":[{"message":"GraphQL query is missing.","extensions":{"code":"QUERY_MISSING","codes":["QUERY_MISSING"]}}]}""");
357394
}
358395

359396
[Theory]

0 commit comments

Comments
 (0)