Last active
January 8, 2020 20:42
-
-
Save marceloverdijk/ba4ee3e1a8440b75ae57c989ee8b4895 to your computer and use it in GitHub Desktop.
Quarkus GraphQL endpoint using RESTEasy-Jackson
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// build.gradle | |
// | |
implementation "com.graphql-java:graphql-java:13.0" | |
implementation "io.quarkus:quarkus-resteasy" | |
implementation "io.quarkus:quarkus-resteasy-jackson" | |
// | |
// GraphQLEndoint.java | |
// | |
package com.example.graphql.endpoint; | |
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; | |
import static javax.ws.rs.core.MediaType.APPLICATION_JSON; | |
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; | |
import static javax.ws.rs.core.Response.Status.BAD_REQUEST; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import graphql.ExecutionInput; | |
import graphql.GraphQL; | |
import java.io.IOException; | |
import java.util.Collections; | |
import java.util.Map; | |
import javax.ws.rs.GET; | |
import javax.ws.rs.HeaderParam; | |
import javax.ws.rs.POST; | |
import javax.ws.rs.Path; | |
import javax.ws.rs.Produces; | |
import javax.ws.rs.QueryParam; | |
import javax.ws.rs.core.MediaType; | |
import javax.ws.rs.core.Response; | |
/** | |
* The GraphQL controller. | |
* | |
* @author Marcel Overdijk | |
*/ | |
@Path("/graphql") | |
@Produces(APPLICATION_JSON) | |
public class GraphQLEndpoint { | |
private static final MediaType APPLICATION_GRAPHQL_TYPE = new MediaType("application", "graphql"); | |
private final GraphQL graphQL; | |
private final ObjectMapper objectMapper; | |
public GraphQLEndpoint(GraphQL graphQL, ObjectMapper objectMapper) { | |
this.graphQL = graphQL; | |
this.objectMapper = objectMapper; | |
} | |
@GET | |
public Response get( | |
@QueryParam("query") String query, | |
@QueryParam("operationName") String operationName, | |
@QueryParam("variables") String variables) { | |
// https://graphql.org/learn/serving-over-http/#get-request | |
// | |
// When receiving an HTTP GET request, the GraphQL query should be specified in the "query" query string. | |
// For example, if we wanted to execute the following GraphQL query: | |
// | |
// { | |
// me { | |
// name | |
// } | |
// } | |
// | |
// This request could be sent via an HTTP GET like so: | |
// | |
// http://myapi/graphql?query={me{name}} | |
// | |
// Query variables can be sent as a JSON-encoded string in an additional query parameter called "variables". | |
// If the query contains several named operations, | |
// an "operationName" query parameter can be used to control which one should be executed. | |
return executeRequest(query, operationName, jsonToMap(variables)); | |
} | |
@POST | |
public Response post( | |
@HeaderParam(CONTENT_TYPE) String contentType, | |
@QueryParam("query") String query, | |
@QueryParam("operationName") String operationName, | |
@QueryParam("variables") String variables, | |
String body) { | |
MediaType mediaType = null; | |
try { | |
mediaType = MediaType.valueOf(contentType); | |
} catch (IllegalArgumentException ignore) { | |
} | |
// https://graphql.org/learn/serving-over-http/#post-request | |
// | |
// A standard GraphQL POST request should use the application/json content type, | |
// and include a JSON-encoded body of the following form: | |
// | |
// { | |
// "query": "...", | |
// "operationName": "...", | |
// "variables": { "myVariable": "someValue", ... } | |
// } | |
if (APPLICATION_JSON_TYPE.isCompatible(mediaType)) { | |
var request = jsonToObject(body, GraphQLRequest.class); | |
return executeRequest(request.getQuery(), request.getOperationName(), request.getVariables()); | |
} | |
// In addition to the above, we recommend supporting two additional cases: | |
// | |
// * If the "query" query string parameter is present (as in the GET example above), | |
// it should be parsed and handled in the same way as the HTTP GET case. | |
if (query != null) { | |
return executeRequest(query, operationName, jsonToMap(variables)); | |
} | |
// * If the "application/graphql" Content-Type header is present, | |
// treat the HTTP POST body contents as the GraphQL query string. | |
if (APPLICATION_GRAPHQL_TYPE.isCompatible(mediaType)) { | |
return executeRequest(body, null, null); | |
} | |
return Response.status(BAD_REQUEST).build(); | |
} | |
private Response executeRequest(String query, String operationName, Map<String, Object> variables) { | |
var executionInput = ExecutionInput.newExecutionInput() | |
.query(query != null ? query : "") | |
.operationName(operationName) | |
.variables(variables) | |
.build(); | |
var executionResult = graphQL.execute(executionInput); | |
try { | |
String json = objectMapper.writeValueAsString(executionResult.toSpecification()); | |
return Response.ok(json).build(); | |
} catch (IOException e) { | |
throw new RuntimeException("Could not convert object to JSON: " + e.getMessage(), e); | |
} | |
} | |
private Map<String, Object> jsonToMap(String jsonMap) { | |
if (jsonMap == null) { | |
return Collections.emptyMap(); | |
} | |
return jsonToObject(jsonMap, Map.class); | |
} | |
private <T> T jsonToObject(String json, Class<T> requiredType) { | |
try { | |
return objectMapper.readValue(json, requiredType); | |
} catch (IOException e) { | |
throw new RuntimeException("Could not convert JSON to object: " + e.getMessage(), e); | |
} | |
} | |
} | |
// | |
// GraphQLRequest.java | |
// | |
package com.example.graphql.endpoint; | |
import java.util.Map; | |
/** | |
* Represents a GraphQL request. | |
* | |
* @author Marcel Overdijk | |
*/ | |
public class GraphQLRequest { | |
private String query; | |
private String operationName; | |
private Map<String, Object> variables; | |
public String getQuery() { | |
return query; | |
} | |
public void setQuery(String query) { | |
this.query = query; | |
} | |
public String getOperationName() { | |
return operationName; | |
} | |
public void setOperationName(String operationName) { | |
this.operationName = operationName; | |
} | |
public Map<String, Object> getVariables() { | |
return variables; | |
} | |
public void setVariables(Map<String, Object> variables) { | |
this.variables = variables; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment