Skip to content

Commit 78c42cb

Browse files
authored
Separate errors within http middleware to protected methods (#719)
1 parent 4cfc36d commit 78c42cb

File tree

3 files changed

+44
-35
lines changed

3 files changed

+44
-35
lines changed

src/Transports.AspNetCore/GraphQLHttpMiddleware.cs

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,7 @@ public async Task InvokeAsync(HttpContext context)
6262
if (!isGet && !isPost)
6363
{
6464
httpResponse.Headers["Allow"] = "GET, POST";
65-
await WriteErrorResponseAsync(
66-
httpResponse,
67-
serializer,
68-
cancellationToken,
69-
$"Invalid HTTP method. Only GET and POST are supported. {DOCS_URL}",
70-
HttpStatusCode.MethodNotAllowed
71-
);
65+
await HandleInvalidHttpMethodErrorAsync(context);
7266
return;
7367
}
7468

@@ -79,12 +73,7 @@ await WriteErrorResponseAsync(
7973
{
8074
if (!MediaTypeHeaderValue.TryParse(httpRequest.ContentType, out var mediaTypeHeader))
8175
{
82-
await WriteErrorResponseAsync(
83-
httpResponse,
84-
serializer,
85-
cancellationToken,
86-
$"Invalid 'Content-Type' header: value '{httpRequest.ContentType}' could not be parsed.",
87-
HttpStatusCode.BadRequest);
76+
await HandleContentTypeCouldNotBeParsedErrorAsync(context);
8877
return;
8978
}
9079

@@ -98,8 +87,8 @@ await WriteErrorResponseAsync(
9887
}
9988
catch (Exception ex)
10089
{
101-
var message = $"JSON body text could not be parsed. {ex.Message}";
102-
await WriteErrorResponseAsync(httpResponse, serializer, cancellationToken, message, HttpStatusCode.BadRequest);
90+
if (!await HandleDeserializationErrorAsync(context, ex))
91+
throw;
10392
return;
10493
}
10594
if (deserializationResult?.Length == 1)
@@ -114,16 +103,19 @@ await WriteErrorResponseAsync(
114103

115104
case MediaType.FORM:
116105
var formCollection = await httpRequest.ReadFormAsync();
117-
bodyGQLRequest = DeserializeFromFormBody(formCollection);
106+
try
107+
{
108+
bodyGQLRequest = DeserializeFromFormBody(formCollection);
109+
}
110+
catch (Exception ex)
111+
{
112+
if (!await HandleDeserializationErrorAsync(context, ex))
113+
throw;
114+
}
118115
break;
119116

120117
default:
121-
await WriteErrorResponseAsync(
122-
httpResponse,
123-
serializer,
124-
cancellationToken,
125-
$"Invalid 'Content-Type' header: non-supported media type. Must be of '{MediaType.JSON}', '{MediaType.GRAPH_QL}' or '{MediaType.FORM}'. {DOCS_URL}",
126-
HttpStatusCode.UnsupportedMediaType);
118+
await HandleInvalidContentTypeErrorAsync(context);
127119
return;
128120
}
129121
}
@@ -145,13 +137,7 @@ await WriteErrorResponseAsync(
145137

146138
if (string.IsNullOrWhiteSpace(gqlRequest.Query))
147139
{
148-
await WriteErrorResponseAsync(
149-
httpResponse,
150-
serializer,
151-
cancellationToken,
152-
"GraphQL query is missing.",
153-
HttpStatusCode.BadRequest
154-
);
140+
await HandleNoQueryErrorAsync(context);
155141
return;
156142
}
157143
}
@@ -196,6 +182,24 @@ await WriteErrorResponseAsync(
196182
}
197183
}
198184

185+
protected virtual async ValueTask<bool> HandleDeserializationErrorAsync(HttpContext context, Exception ex)
186+
{
187+
await WriteErrorResponseAsync(context, $"JSON body text could not be parsed. {ex.Message}", HttpStatusCode.BadRequest);
188+
return true;
189+
}
190+
191+
protected virtual Task HandleNoQueryErrorAsync(HttpContext context)
192+
=> WriteErrorResponseAsync(context, "GraphQL query is missing.", HttpStatusCode.BadRequest);
193+
194+
protected virtual Task HandleContentTypeCouldNotBeParsedErrorAsync(HttpContext context)
195+
=> WriteErrorResponseAsync(context, $"Invalid 'Content-Type' header: value '{context.Request.ContentType}' could not be parsed.", HttpStatusCode.UnsupportedMediaType);
196+
197+
protected virtual Task HandleInvalidContentTypeErrorAsync(HttpContext context)
198+
=> WriteErrorResponseAsync(context, $"Invalid 'Content-Type' header: non-supported media type '{context.Request.ContentType}'. Must be of '{MediaType.JSON}', '{MediaType.GRAPH_QL}' or '{MediaType.FORM}'. {DOCS_URL}", HttpStatusCode.UnsupportedMediaType);
199+
200+
protected virtual Task HandleInvalidHttpMethodErrorAsync(HttpContext context)
201+
=> WriteErrorResponseAsync(context, $"Invalid HTTP method. Only GET and POST are supported. {DOCS_URL}", HttpStatusCode.MethodNotAllowed);
202+
199203
private static Task<ExecutionResult> ExecuteRequestAsync(GraphQLRequest gqlRequest, IDictionary<string, object> userContext, IGraphQLExecuter<TSchema> executer, IServiceProvider requestServices, CancellationToken token)
200204
=> executer.ExecuteAsync(
201205
gqlRequest.OperationName,
@@ -219,8 +223,7 @@ protected virtual Task RequestExecutedAsync(in GraphQLRequestExecutionResult req
219223
return Task.CompletedTask;
220224
}
221225

222-
private Task WriteErrorResponseAsync(HttpResponse httpResponse, IGraphQLSerializer serializer, CancellationToken cancellationToken,
223-
string errorMessage, HttpStatusCode httpStatusCode)
226+
protected virtual Task WriteErrorResponseAsync(HttpContext context, string errorMessage, HttpStatusCode httpStatusCode)
224227
{
225228
var result = new ExecutionResult
226229
{
@@ -230,10 +233,10 @@ private Task WriteErrorResponseAsync(HttpResponse httpResponse, IGraphQLSerializ
230233
}
231234
};
232235

233-
httpResponse.ContentType = "application/json";
234-
httpResponse.StatusCode = (int)httpStatusCode;
236+
context.Response.ContentType = "application/json";
237+
context.Response.StatusCode = (int)httpStatusCode;
235238

236-
return serializer.WriteAsync(httpResponse.Body, result, cancellationToken);
239+
return _serializer.WriteAsync(context.Response.Body, result, GetCancellationToken(context));
237240
}
238241

239242
private Task WriteResponseAsync<TResult>(HttpResponse httpResponse, IGraphQLSerializer serializer, CancellationToken cancellationToken, TResult result)

tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.approved.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ namespace GraphQL.Server.Transports.AspNetCore
1818
{
1919
public GraphQLHttpMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.IGraphQLTextSerializer serializer) { }
2020
protected virtual System.Threading.CancellationToken GetCancellationToken(Microsoft.AspNetCore.Http.HttpContext context) { }
21+
protected virtual System.Threading.Tasks.Task HandleContentTypeCouldNotBeParsedErrorAsync(Microsoft.AspNetCore.Http.HttpContext context) { }
22+
protected virtual System.Threading.Tasks.ValueTask<bool> HandleDeserializationErrorAsync(Microsoft.AspNetCore.Http.HttpContext context, System.Exception ex) { }
23+
protected virtual System.Threading.Tasks.Task HandleInvalidContentTypeErrorAsync(Microsoft.AspNetCore.Http.HttpContext context) { }
24+
protected virtual System.Threading.Tasks.Task HandleInvalidHttpMethodErrorAsync(Microsoft.AspNetCore.Http.HttpContext context) { }
25+
protected virtual System.Threading.Tasks.Task HandleNoQueryErrorAsync(Microsoft.AspNetCore.Http.HttpContext context) { }
2126
public System.Threading.Tasks.Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context) { }
2227
protected virtual System.Threading.Tasks.Task RequestExecutedAsync(in GraphQL.Server.Transports.AspNetCore.GraphQLRequestExecutionResult requestExecutionResult) { }
2328
protected virtual System.Threading.Tasks.Task RequestExecutingAsync(GraphQL.Transport.GraphQLRequest request, int? indexInBatch = default) { }
29+
protected virtual System.Threading.Tasks.Task WriteErrorResponseAsync(Microsoft.AspNetCore.Http.HttpContext context, string errorMessage, System.Net.HttpStatusCode httpStatusCode) { }
2430
}
2531
public readonly struct GraphQLRequestExecutionResult
2632
{

tests/Samples.Server.Tests/ResponseTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public async Task Wrong_Query_Should_Return_Error(HttpMethod httpMethod, HttpCon
9595
HttpMethod.Post,
9696
new StringContent(Serializer.ToJson(new GraphQLRequest { Query = "query { __schema { queryType { name } } }" }), Encoding.UTF8, "something/unknown"),
9797
HttpStatusCode.UnsupportedMediaType,
98-
"Invalid 'Content-Type' header: non-supported media type. Must be of 'application/json', 'application/graphql' or 'application/x-www-form-urlencoded'. See: http://graphql.org/learn/serving-over-http/."
98+
"Invalid 'Content-Type' header: non-supported media type 'something/unknown; charset=utf-8'. Must be of 'application/json', 'application/graphql' or 'application/x-www-form-urlencoded'. See: http://graphql.org/learn/serving-over-http/."
9999
},
100100

101101
// POST with JSON mime type that doesn't start with an object or array token should be a bad request

0 commit comments

Comments
 (0)