Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,28 @@ public Task<GraphQLRequestDeserializationResult> DeserializeFromJsonBodyAsync(Ht

cancellationToken.ThrowIfCancellationRequested();

switch (firstChar)
try
{
case '{':
result.Single = ToGraphQLRequest(_serializer.Deserialize<InternalGraphQLRequest>(jsonReader));
break;
case '[':
result.Batch = _serializer.Deserialize<InternalGraphQLRequest[]>(jsonReader)
.Select(ToGraphQLRequest)
.ToArray();
break;
default:
result.IsSuccessful = false;
break;
switch (firstChar)
{
case '{':
result.Single = ToGraphQLRequest(_serializer.Deserialize<InternalGraphQLRequest>(jsonReader));
break;
case '[':
result.Batch = _serializer.Deserialize<InternalGraphQLRequest[]>(jsonReader)
.Select(ToGraphQLRequest)
.ToArray();
break;
default:
result.IsSuccessful = false;
result.Exception = GraphQLRequestDeserializationException.InvalidFirstChar();
break;
}
}
catch (JsonException e)
{
result.IsSuccessful = false;
result.Exception = new GraphQLRequestDeserializationException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,30 @@ public async Task<GraphQLRequestDeserializationResult> DeserializeFromJsonBodyAs
cancellationToken.ThrowIfCancellationRequested();

var result = new GraphQLRequestDeserializationResult { IsSuccessful = true };
switch (jsonTokenType)
try
{
switch (jsonTokenType)
{
case JsonTokenType.StartObject:
result.Single = ToGraphQLRequest(
await JsonSerializer.DeserializeAsync<InternalGraphQLRequest>(bodyReader.AsStream(), _serializerOptions, cancellationToken));
return result;
case JsonTokenType.StartArray:
result.Batch = (await JsonSerializer.DeserializeAsync<InternalGraphQLRequest[]>(bodyReader.AsStream(), _serializerOptions, cancellationToken))
.Select(ToGraphQLRequest)
.ToArray();
return result;
default:
result.IsSuccessful = false;
result.Exception = GraphQLRequestDeserializationException.InvalidFirstChar();
return result;
}
}
catch (JsonException e)
{
case JsonTokenType.StartObject:
result.Single = ToGraphQLRequest(
await JsonSerializer.DeserializeAsync<InternalGraphQLRequest>(bodyReader.AsStream(), _serializerOptions, cancellationToken));
return result;
case JsonTokenType.StartArray:
result.Batch = (await JsonSerializer.DeserializeAsync<InternalGraphQLRequest[]>(bodyReader.AsStream(), _serializerOptions, cancellationToken))
.Select(ToGraphQLRequest)
.ToArray();
return result;
default:
result.IsSuccessful = false;
return result;
result.IsSuccessful = false;
result.Exception = new GraphQLRequestDeserializationException(e);
return result;
}
}

Expand Down
21 changes: 15 additions & 6 deletions src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -62,7 +63,7 @@ public async Task InvokeAsync(HttpContext context)
httpResponse.Headers["Allow"] = "GET, POST";
await WriteErrorResponseAsync(httpResponse, writer, cancellationToken,
$"Invalid HTTP method. Only GET and POST are supported. {DOCS_URL}",
httpStatusCode: 405 // Method Not Allowed
httpStatusCode: HttpStatusCode.MethodNotAllowed
);
return;
}
Expand All @@ -84,7 +85,10 @@ await WriteErrorResponseAsync(httpResponse, writer, cancellationToken,
var deserializationResult = await _deserializer.DeserializeFromJsonBodyAsync(httpRequest, cancellationToken);
if (!deserializationResult.IsSuccessful)
{
await WriteErrorResponseAsync(httpResponse, writer, cancellationToken, "Body text could not be parsed. Body text should start with '{' for normal graphql query or with '[' for batched query.");
var message = deserializationResult.Exception is null
? "JSON body text could not be parsed."
: $"JSON body text could not be parsed. {deserializationResult.Exception.Message}";
await WriteErrorResponseAsync(httpResponse, writer, cancellationToken, message);
return;
}
bodyGQLRequest = deserializationResult.Single;
Expand All @@ -101,7 +105,12 @@ await WriteErrorResponseAsync(httpResponse, writer, cancellationToken,
break;

default:
await WriteErrorResponseAsync(httpResponse, writer, cancellationToken, $"Invalid 'Content-Type' header: non-supported media type. Must be of '{MediaType.JSON}', '{MediaType.GRAPH_QL}' or '{MediaType.FORM}'. {DOCS_URL}");
await WriteErrorResponseAsync(
httpResponse,
writer,
cancellationToken,
$"Invalid 'Content-Type' header: non-supported media type. Must be of '{MediaType.JSON}', '{MediaType.GRAPH_QL}' or '{MediaType.FORM}'. {DOCS_URL}",
HttpStatusCode.UnsupportedMediaType);
return;
}
}
Expand All @@ -125,7 +134,7 @@ await WriteErrorResponseAsync(httpResponse, writer, cancellationToken,
{
await WriteErrorResponseAsync(httpResponse, writer, cancellationToken,
"GraphQL query is missing.",
httpStatusCode: 400 // Bad Input
httpStatusCode: HttpStatusCode.BadRequest
);
return;
}
Expand Down Expand Up @@ -195,7 +204,7 @@ protected virtual Task RequestExecutedAsync(in GraphQLRequestExecutionResult req
}

private Task WriteErrorResponseAsync(HttpResponse httpResponse, IDocumentWriter writer, CancellationToken cancellationToken,
string errorMessage, int httpStatusCode = 400 /* BadRequest */)
string errorMessage, HttpStatusCode httpStatusCode = HttpStatusCode.BadRequest)
{
var result = new ExecutionResult
{
Expand All @@ -206,7 +215,7 @@ private Task WriteErrorResponseAsync(HttpResponse httpResponse, IDocumentWriter
};

httpResponse.ContentType = "application/json";
httpResponse.StatusCode = httpStatusCode;
httpResponse.StatusCode = (int)httpStatusCode;

return writer.WriteAsync(httpResponse.Body, result, cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace GraphQL.Server.Transports.AspNetCore
{
public class GraphQLRequestDeserializationException : Exception
{
public GraphQLRequestDeserializationException(string message) : base(message)
{
}

public GraphQLRequestDeserializationException(Exception inner) : base(inner.Message, inner)
{
}

public static GraphQLRequestDeserializationException InvalidFirstChar()
{
return new GraphQLRequestDeserializationException("Body text should start with '{' for normal graphql query or with '[' for batched query.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@ public class GraphQLRequestDeserializationResult
/// populated if the HTTP request body contained an array of JSON objects.
/// </summary>
public GraphQLRequest[] Batch { get; set; }

/// <summary>
/// If deserialization throws an exception, it is stored here
/// </summary>
public GraphQLRequestDeserializationException Exception { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@ namespace GraphQL.Server.Transports.AspNetCore
protected virtual System.Threading.Tasks.Task RequestExecutedAsync(in GraphQL.Server.Transports.AspNetCore.GraphQLRequestExecutionResult requestExecutionResult) { }
protected virtual System.Threading.Tasks.Task RequestExecutingAsync(GraphQL.Server.GraphQLRequest request, int? indexInBatch = default) { }
}
public class GraphQLRequestDeserializationException : System.Exception
{
public GraphQLRequestDeserializationException(System.Exception inner) { }
public GraphQLRequestDeserializationException(string message) { }
public static GraphQL.Server.Transports.AspNetCore.GraphQLRequestDeserializationException InvalidFirstChar() { }
}
public class GraphQLRequestDeserializationResult
{
public GraphQLRequestDeserializationResult() { }
public GraphQL.Server.GraphQLRequest[] Batch { get; set; }
public GraphQL.Server.Transports.AspNetCore.GraphQLRequestDeserializationException Exception { get; set; }
public bool IsSuccessful { get; set; }
public GraphQL.Server.GraphQLRequest Single { get; set; }
}
Expand Down
18 changes: 12 additions & 6 deletions tests/Samples.Server.Tests/ResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,12 @@ public async Task Wrong_Query_Should_Return_Error(HttpMethod httpMethod, HttpCon
"Invalid HTTP method. Only GET and POST are supported. See: http://graphql.org/learn/serving-over-http/.",
},

// POST with an invalid mime type should be a bad request
// I couldn't manage to hit this, asp.net core kept rejecting it too early

// POST with unsupported mime type should be a bad request
// POST with unsupported mime type should be a unsupported media type
new object[]
{
HttpMethod.Post,
new StringContent(Serializer.ToJson(new GraphQLRequest { Query = "query { __schema { queryType { name } } }" }), Encoding.UTF8, "something/unknown"),
HttpStatusCode.BadRequest,
HttpStatusCode.UnsupportedMediaType,
"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/."
},

Expand All @@ -108,7 +105,16 @@ public async Task Wrong_Query_Should_Return_Error(HttpMethod httpMethod, HttpCon
HttpMethod.Post,
new StringContent("Oops", Encoding.UTF8, "application/json"),
HttpStatusCode.BadRequest,
"Body text could not be parsed. Body text should start with '{' for normal graphql query or with '[' for batched query."
"JSON body text could not be parsed. Body text should start with '{' for normal graphql query or with '[' for batched query."
},

// POST with JSON mime type that is invalid JSON should be a bad request
new object[]
{
HttpMethod.Post,
new StringContent("{oops}", Encoding.UTF8, "application/json"),
HttpStatusCode.BadRequest,
"JSON body text could not be parsed. 'o' is an invalid start of a property name. Expected a '\"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1."
},

// GET with an empty QueryString should be a bad request
Expand Down
2 changes: 1 addition & 1 deletion tests/Samples.Server.Tests/ShouldBeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ public static void ShouldBeEquivalentJson(this string actualJson, string expecte
actualJson.ShouldBe(expectedJson);
}

private static string NormalizeJson(this string json) => json.Replace("'", @"\u0027");
private static string NormalizeJson(this string json) => json.Replace("'", @"\u0027").Replace("\"", @"\u0022");
}
}