Skip to content

Commit 4d8ddbb

Browse files
authoredJun 3, 2021
Replicate changes of #3783 for v11 (#3787)
1 parent d88c81d commit 4d8ddbb

File tree

8 files changed

+137
-45
lines changed

8 files changed

+137
-45
lines changed
 

‎src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ private async Task HandleRequestAsync(HttpContext context)
7575
statusCode = HttpStatusCode.BadRequest;
7676
result = QueryResultBuilder.CreateError(errorHandler.Handle(ex.Errors));
7777
}
78+
catch (GraphQLException ex)
79+
{
80+
// This allows extensions to throw GraphQL exceptions in the GraphQL interceptor.
81+
statusCode = null; // we let the serializer determine the status code.
82+
result = QueryResultBuilder.CreateError(ex.Errors);
83+
}
7884
catch (Exception ex)
7985
{
8086
statusCode = HttpStatusCode.InternalServerError;

‎src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs

+4-6
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ private static HttpMultipartRequest ParseMultipartRequest(IFormCollection form)
106106

107107
try
108108
{
109-
map = JsonSerializer.Deserialize<Dictionary<string, string[]>>(mapString);
109+
map = JsonSerializer.Deserialize<Dictionary<string, string[]>>(mapString!);
110110
}
111111
catch
112112
{
@@ -176,7 +176,7 @@ private static void InsertFilesIntoRequest(
176176
{
177177
var path = VariablePath.Parse(objectPath);
178178

179-
if (!mutableVariables.TryGetValue(path.Key.Value, out object? value))
179+
if (!mutableVariables.TryGetValue(path.Key.Value, out var value))
180180
{
181181
throw ThrowHelper.HttpMultipartMiddleware_VariableNotFound(objectPath);
182182
}
@@ -206,8 +206,7 @@ private static IValueNode RewriteVariable(
206206
object value,
207207
FileValueNode file)
208208
{
209-
if (segment is KeyPathSegment key &&
210-
value is ObjectValueNode ov)
209+
if (segment is KeyPathSegment key && value is ObjectValueNode ov)
211210
{
212211
var pos = -1;
213212

@@ -234,8 +233,7 @@ key.Next is not null
234233
return ov.WithFields(fields);
235234
}
236235

237-
if (segment is IndexPathSegment index &&
238-
value is ListValueNode lv)
236+
if (segment is IndexPathSegment index && value is ListValueNode lv)
239237
{
240238
IValueNode[] items = lv.Items.ToArray();
241239
IValueNode item = items[index.Value];

‎src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ protected async Task HandleRequestAsync(
136136
statusCode = HttpStatusCode.BadRequest;
137137
result = QueryResultBuilder.CreateError(errorHandler.Handle(ex.Errors));
138138
}
139+
catch (GraphQLException ex)
140+
{
141+
// This allows extensions to throw GraphQL exceptions in the GraphQL interceptor.
142+
statusCode = null; // we let the serializer determine the status code.
143+
result = QueryResultBuilder.CreateError(ex.Errors);
144+
}
139145
catch (Exception ex)
140146
{
141147
statusCode = HttpStatusCode.InternalServerError;
@@ -145,7 +151,7 @@ protected async Task HandleRequestAsync(
145151

146152
// in any case we will have a valid GraphQL result at this point that can be written
147153
// to the HTTP response stream.
148-
Debug.Assert(result is not null, "No GraphQL result was created.");
154+
Debug.Assert(result is not null!, "No GraphQL result was created.");
149155
await WriteResultAsync(context.Response, result, statusCode, context.RequestAborted);
150156
}
151157

‎src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetMiddlewareTests.cs

+38
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using HotChocolate.AspNetCore.Utilities;
88
using HotChocolate.Execution;
99
using HotChocolate.Language;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.Extensions.DependencyInjection;
1012
using Snapshooter;
1113
using Snapshooter.Xunit;
1214
using Xunit;
@@ -697,5 +699,41 @@ await server.GetStoreActivePersistedQueryAsync(
697699
resultB
698700
}.MatchSnapshot();
699701
}
702+
703+
[Fact]
704+
public async Task Throw_Custom_GraphQL_Error()
705+
{
706+
// arrange
707+
TestServer server = CreateStarWarsServer(
708+
configureServices: s => s.AddGraphQLServer()
709+
.AddHttpRequestInterceptor<ErrorRequestInterceptor>());
710+
711+
// act
712+
ClientQueryResult result =
713+
await server.GetAsync(new ClientQueryRequest
714+
{
715+
Query = @"
716+
{
717+
hero {
718+
name
719+
}
720+
}"
721+
});
722+
723+
// assert
724+
result.MatchSnapshot();
725+
}
726+
727+
public class ErrorRequestInterceptor : DefaultHttpRequestInterceptor
728+
{
729+
public override ValueTask OnCreateAsync(
730+
HttpContext context,
731+
IRequestExecutor requestExecutor,
732+
IQueryRequestBuilder requestBuilder,
733+
CancellationToken cancellationToken)
734+
{
735+
throw new GraphQLException("MyCustomError");
736+
}
737+
}
700738
}
701739
}

‎src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpPostMiddlewareTests.cs

+50-38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System.Collections.Generic;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using Microsoft.AspNetCore.TestHost;
45
using HotChocolate.AspNetCore.Utilities;
6+
using HotChocolate.Execution;
7+
using Microsoft.AspNetCore.Http;
58
using Microsoft.Extensions.DependencyInjection;
69
using Snapshooter;
710
using Snapshooter.Xunit;
@@ -68,8 +71,8 @@ public async Task Simple_IsAlive_Test_On_Non_GraphQL_Path()
6871

6972
// act
7073
ClientQueryResult result = await server.PostAsync(
71-
new ClientQueryRequest { Query = "{ __typename }" },
72-
"/foo");
74+
new ClientQueryRequest { Query = "{ __typename }" },
75+
"/foo");
7376

7477
// assert
7578
result.MatchSnapshot();
@@ -135,10 +138,7 @@ await server.PostAsync(new ClientQueryRequest
135138
name
136139
}
137140
}",
138-
Variables = new Dictionary<string, object>
139-
{
140-
{ "episode", "NEW_HOPE" }
141-
}
141+
Variables = new Dictionary<string, object> { { "episode", "NEW_HOPE" } }
142142
});
143143

144144
// assert
@@ -161,10 +161,7 @@ query h($id: String!) {
161161
name
162162
}
163163
}",
164-
Variables = new Dictionary<string, object>
165-
{
166-
{ "id", "1000" }
167-
}
164+
Variables = new Dictionary<string, object> { { "id", "1000" } }
168165
});
169166

170167
// assert
@@ -224,8 +221,7 @@ mutation CreateReviewForEpisode(
224221
"review",
225222
new Dictionary<string, object>
226223
{
227-
{ "stars", 5 },
228-
{ "commentary", "This is a great movie!" },
224+
{ "stars", 5 }, { "commentary", "This is a great movie!" },
229225
}
230226
}
231227
}
@@ -260,8 +256,7 @@ mutation CreateReviewForEpisode(
260256
"review",
261257
new Dictionary<string, object>
262258
{
263-
{ "stars", 5 },
264-
{ "commentary", "This is a great movie!" },
259+
{ "stars", 5 }, { "commentary", "This is a great movie!" },
265260
}
266261
}
267262
}
@@ -390,10 +385,7 @@ await server.PostAsync(new ClientQueryRequest
390385
name
391386
}
392387
}",
393-
Variables = new Dictionary<string, object>
394-
{
395-
{ "episode", "NEW_HOPE" }
396-
}
388+
Variables = new Dictionary<string, object> { { "episode", "NEW_HOPE" } }
397389
});
398390

399391
// assert
@@ -463,11 +455,7 @@ await server.PostAsync(new ClientQueryRequest
463455
"/arguments");
464456

465457
// assert
466-
new
467-
{
468-
double.MaxValue,
469-
result
470-
}.MatchSnapshot();
458+
new { double.MaxValue, result }.MatchSnapshot();
471459
}
472460

473461
[Fact]
@@ -489,11 +477,7 @@ await server.PostAsync(new ClientQueryRequest
489477
"/arguments");
490478

491479
// assert
492-
new
493-
{
494-
double.MinValue,
495-
result
496-
}.MatchSnapshot();
480+
new { double.MinValue, result }.MatchSnapshot();
497481
}
498482

499483
[Fact]
@@ -515,11 +499,7 @@ await server.PostAsync(new ClientQueryRequest
515499
"/arguments");
516500

517501
// assert
518-
new
519-
{
520-
decimal.MaxValue,
521-
result
522-
}.MatchSnapshot();
502+
new { decimal.MaxValue, result }.MatchSnapshot();
523503
}
524504

525505
[Fact]
@@ -541,11 +521,7 @@ await server.PostAsync(new ClientQueryRequest
541521
"/arguments");
542522

543523
// assert
544-
new
545-
{
546-
decimal.MinValue,
547-
result
548-
}.MatchSnapshot();
524+
new { decimal.MinValue, result }.MatchSnapshot();
549525
}
550526

551527
[Fact]
@@ -778,5 +754,41 @@ query getHuman {
778754
// assert
779755
result.MatchSnapshot();
780756
}
757+
758+
[Fact]
759+
public async Task Throw_Custom_GraphQL_Error()
760+
{
761+
// arrange
762+
TestServer server = CreateStarWarsServer(
763+
configureServices: s => s.AddGraphQLServer()
764+
.AddHttpRequestInterceptor<ErrorRequestInterceptor>());
765+
766+
// act
767+
ClientQueryResult result =
768+
await server.PostAsync(new ClientQueryRequest
769+
{
770+
Query = @"
771+
{
772+
hero {
773+
name
774+
}
775+
}"
776+
});
777+
778+
// assert
779+
result.MatchSnapshot();
780+
}
781+
782+
public class ErrorRequestInterceptor : DefaultHttpRequestInterceptor
783+
{
784+
public override ValueTask OnCreateAsync(
785+
HttpContext context,
786+
IRequestExecutor requestExecutor,
787+
IQueryRequestBuilder requestBuilder,
788+
CancellationToken cancellationToken)
789+
{
790+
throw new GraphQLException("MyCustomError");
791+
}
792+
}
781793
}
782794
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"ContentType": "application/json; charset=utf-8",
3+
"StatusCode": "InternalServerError",
4+
"Data": null,
5+
"Errors": [
6+
{
7+
"message": "MyCustomError"
8+
}
9+
],
10+
"Extensions": null
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"ContentType": "application/json; charset=utf-8",
3+
"StatusCode": "InternalServerError",
4+
"Data": null,
5+
"Errors": [
6+
{
7+
"message": "MyCustomError"
8+
}
9+
],
10+
"Extensions": null
11+
}

‎website/src/docs/hotchocolate/api-reference/aspnetcore.md

+10
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,16 @@ public string MyResolver([HttpContext] HttpContext context)
488488
}
489489
```
490490

491+
A custom http request interceptor can be registered like the following:
492+
493+
```csharp
494+
services.AddSocketSessionInterceptor<MyCustomHttpRequestInterceptor>();
495+
```
496+
497+
## Request Errors
498+
499+
The interceptor can be used to do general request validation. This essentially allows to fail the request before the GraphQL context is created. In order to create a GraphQL error response simply throw a `GraphQLException` in the `OnCreateAsync` method. The middleware will translate these to a proper GraphQL error response for the client. You also can customize the status code behavior by using the HTTP result serializer mentioned above.
500+
491501
# Subscription session handling
492502

493503
The Hot Chocolate GraphQL server allows you to interact with the server's socket session handling by implementing `ISocketSessionInterceptor`. For convenience reasons, we provide a default implementation (`DefaultSocketSessionInterceptor`) that can be extended.

0 commit comments

Comments
 (0)
Please sign in to comment.