Skip to content

Commit

Permalink
Implement POST with application/json in GraphQL Middleware. (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoniaelek authored and ttu committed Jul 17, 2018
1 parent 887eaab commit 223627e
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 10 deletions.
46 changes: 45 additions & 1 deletion FakeServer.Test/FakeServerSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,13 +1040,57 @@ public async Task PostGraphQL_Filter_Bool()
}

[Fact]
public async Task PostGraphQL_Error()
public async Task PostGraphQL_Json()
{
using (var client = new HttpClient())
{
var content = new StringContent(@"{""query"":""{users}""}", Encoding.UTF8, "application/json");
var result = await client.PostAsync($"{_fixture.BaseUrl}/graphql", content);

Assert.Equal(HttpStatusCode.OK, result.StatusCode);

var data = JsonConvert.DeserializeObject<JObject>(await result.Content.ReadAsStringAsync());

Assert.NotNull(data["data"]);
Assert.Null(data["errors"]);
}
}

[Fact]
public async Task PostGraphQL_Error_InvalidJson()
{
using (var client = new HttpClient())
{
var content = new StringContent("{ users }", Encoding.UTF8, "application/json");
var result = await client.PostAsync($"{_fixture.BaseUrl}/graphql", content);
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
}
}

[Fact]
public async Task PostGraphQL_Error_MissingQuery()
{
using (var client = new HttpClient())
{
var content = new StringContent(@"{ }", Encoding.UTF8, "application/json");
var result = await client.PostAsync($"{_fixture.BaseUrl}/graphql", content);
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
}
}

[Fact]
public async Task GetGraphQL_NotImplemented()
{
using (var client = new HttpClient())
{
var result = await client.GetAsync($"{_fixture.BaseUrl}/graphql?query={{users}}");

Assert.Equal(HttpStatusCode.NotImplemented, result.StatusCode);

var data = JsonConvert.DeserializeObject<JObject>(await result.Content.ReadAsStringAsync());

Assert.Null(data["data"]);
Assert.NotNull(data["errors"]);
}
}
}
Expand Down
58 changes: 49 additions & 9 deletions FakeServer/GraphQL/GraphQLMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using JsonFlatFileDataStore;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
Expand All @@ -16,6 +18,7 @@ public class GraphQLMiddleware
private readonly IDataStore _datastore;
private readonly IMessageBus _bus;
private readonly bool _authenticationEnabled;
private readonly string[] _allowedTypes = new[] { "application/graphql", "application/json" };

public GraphQLMiddleware(RequestDelegate next, IDataStore datastore, IMessageBus bus, bool authenticationEnabled)
{
Expand All @@ -28,7 +31,7 @@ public GraphQLMiddleware(RequestDelegate next, IDataStore datastore, IMessageBus
public async Task Invoke(HttpContext context)
{
// POST application/graphql body is query
// TODO: POST application/json and { "query": "..." }
// POST application/json and { "query": "..." }
// TODO: POST /graphql?query={users{name}}
// TODO: GET /graphql?query={users{name}}

Expand All @@ -44,25 +47,29 @@ public async Task Invoke(HttpContext context)
return;
}

if (context.Request.Method != "POST" || !context.Request.ContentType.Contains("application/graphql"))
if (context.Request.Method != "POST" || !_allowedTypes.Any(context.Request.ContentType.Contains))
{
context.Response.StatusCode = (int)HttpStatusCode.NotImplemented;
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { errors = new[] { "Not implemented" } }));
return;
}

var query = string.Empty;
GraphQLResult result = null;

using (var streamReader = new StreamReader(context.Request.Body))
var (success, query, error) = await ParseQuery(context);

if (!success)
{
query = await streamReader.ReadToEndAsync().ConfigureAwait(true);
result = new GraphQLResult { Errors = new List<string> { error } };
}
else
{
var toReplace = new[] { "\r\n", "\\r\\n", "\\n", "\n" };

var toReplace = new[] { "\r\n", "\\r\\n", "\\n", "\n" };

query = toReplace.Aggregate(query, (acc, curr) => acc.Replace(curr, ""));
query = toReplace.Aggregate(query, (acc, curr) => acc.Replace(curr, ""));

var result = await GraphQL.HandleQuery(query, _datastore);
result = await GraphQL.HandleQuery(query, _datastore);
}

var json = result.Errors?.Any() == true
? JsonConvert.SerializeObject(new { data = result.Data, errors = result.Errors })
Expand All @@ -75,5 +82,38 @@ public async Task Invoke(HttpContext context)

await context.Response.WriteAsync(json);
}

private static async Task<(bool success, string body, string error)> ParseQuery(HttpContext context)
{
string body;

using (var streamReader = new StreamReader(context.Request.Body))
{
body = await streamReader.ReadToEndAsync().ConfigureAwait(true);
}

if (context.Request.ContentType.StartsWith("application/graphql"))
{
return (true, body, null);
}

dynamic jsonBody;

try
{
jsonBody = JsonConvert.DeserializeObject(body);
}
catch (Exception e)
{
return (false, null, e.Message);
}

if (jsonBody.query is null)
{
return (false, null, "Missing query property in json.");
}

return (true, jsonBody.query, null);
}
}
}
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -926,13 +926,18 @@ Delay value is milliseconds. Default value is 2000ms.
### GraphQL

GraphQL implementation is experimental and supports only basic queries and mutations. At the moment this is a good way to compare simple GraphQL and REST queries.
`/graphql` endpoint accepts requests with `application/graphql` or `application/json` content type. If the first, request body is GraphQL query string, whereas if the latter, request body is expected to be a valid JSON with parameter `query` containing the GraphQL query string.

```
> POST /graphql
Content-type: application/graphql
Body: [query/mutation]
OR
Content-type: application/json
Body: { "query": "[query/mutation]" }
200 OK : Query/mutation successful
400 Bad Request : Query/mutation contains errors
Expand Down

0 comments on commit 223627e

Please sign in to comment.