Refine input exception handling and validation

This commit is contained in:
Rossen Stoyanchev
2020-09-17 18:52:16 +01:00
parent 42464b6748
commit 8dc56ceaa5
3 changed files with 45 additions and 27 deletions

View File

@@ -4,6 +4,8 @@ import java.util.Collections;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebInputException;
/**
* @author Andreas Marek
@@ -50,4 +52,10 @@ class RequestInput {
public void setVariables(Map<String, Object> variables) {
this.variables = variables;
}
public void validate() {
if (!StringUtils.hasText(getQuery())) {
throw new ServerWebInputException("Missing query");
}
}
}

View File

@@ -6,10 +6,15 @@ import graphql.GraphQL;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
public class WebFluxGraphQLHandler {
/**
* GraphQL handler to be exposed as a WebFlux.fn endpoint via
* {@link org.springframework.web.reactive.function.server.RouterFunctions}.
*/
public class WebFluxGraphQLHandler implements HandlerFunction<ServerResponse> {
private final GraphQL graphQL;
@@ -19,15 +24,12 @@ public class WebFluxGraphQLHandler {
public Mono<ServerResponse> handle(ServerRequest request) {
return request.bodyToMono(RequestInput.class)
.flatMap(body -> {
String query = body.getQuery();
if (query == null) {
query = "";
}
.flatMap(requestInput -> {
requestInput.validate();
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.operationName(body.getOperationName())
.variables(body.getVariables())
.query(requestInput.getQuery())
.operationName(requestInput.getOperationName())
.variables(requestInput.getVariables())
.build();
// Invoke GraphQLInterceptor's preHandle here
return customizeExecutionInput(executionInput, request.headers().asHttpHeaders());

View File

@@ -12,12 +12,18 @@ import graphql.ExecutionResult;
import graphql.GraphQL;
import org.springframework.http.HttpHeaders;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.servlet.function.HandlerFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
public class WebMvcGraphQLHandler {
/**
* GraphQL handler to be exposed as a WebMvc.fn endpoint via
* {@link org.springframework.web.servlet.function.RouterFunctions}.
*/
public class WebMvcGraphQLHandler implements HandlerFunction<ServerResponse> {
private final GraphQL graphQL;
@@ -25,36 +31,38 @@ public class WebMvcGraphQLHandler {
this.graphQL = graphQL.build();
}
public ServerResponse handle(ServerRequest serverRequest) {
RequestInput body;
/**
* {@inheritDoc}
*
* @throws ServletException may be raised when reading the request body,
* e.g. {@link HttpMediaTypeNotSupportedException}.
*/
public ServerResponse handle(ServerRequest request) throws ServletException {
RequestInput requestInput;
try {
body = serverRequest.body(RequestInput.class);
requestInput = request.body(RequestInput.class);
requestInput.validate();
}
catch (ServletException | IOException ex) {
throw new ServerWebInputException("Failed to read request body", null, ex);
catch (IOException ex) {
throw new ServerWebInputException("I/O error while reading request body", null, ex);
}
String query = body.getQuery();
if (query == null) {
query = "";
}
ExecutionInput input = ExecutionInput.newExecutionInput()
.query(query)
.operationName(body.getOperationName())
.variables(body.getVariables())
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(requestInput.getQuery())
.operationName(requestInput.getOperationName())
.variables(requestInput.getVariables())
.build();
// Invoke GraphQLInterceptor's preHandle here
CompletableFuture<Map<String, Object>> future =
customizeExecutionInput(input, serverRequest.headers().asHttpHeaders())
customizeExecutionInput(executionInput, request.headers().asHttpHeaders())
.thenCompose(this::execute)
.thenApply(ExecutionResult::toSpecification);
// Invoke GraphQLInterceptor's postHandle here
return future.isDone() ?
ServerResponse.ok().body(getResult(future)) :
ServerResponse.ok().body(future);
return ServerResponse.ok().body(future.isDone() ? getResult(future) : future);
}
protected CompletableFuture<ExecutionInput> customizeExecutionInput(ExecutionInput input, HttpHeaders headers) {