diff --git a/graphql-kickstart-spring-boot-autoconfigure-webflux/src/test/java/graphql/kickstart/spring/webflux/boot/InvalidJsonRequestTest.java b/graphql-kickstart-spring-boot-autoconfigure-webflux/src/test/java/graphql/kickstart/spring/webflux/boot/InvalidJsonRequestTest.java new file mode 100644 index 00000000..8e198779 --- /dev/null +++ b/graphql-kickstart-spring-boot-autoconfigure-webflux/src/test/java/graphql/kickstart/spring/webflux/boot/InvalidJsonRequestTest.java @@ -0,0 +1,42 @@ +package graphql.kickstart.spring.webflux.boot; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.assertj.core.util.Files; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class InvalidJsonRequestTest { + + @Autowired private WebTestClient webTestClient; + + @ParameterizedTest + @ValueSource(strings = {"\"false\":true", "not-a-json"}) + @DisplayName("Should return valid response to a request with invalid JSON or non-JSON body.") + void testHandlingInvalidJsonRequest(String badRequestBody) throws IOException { + // GIVEN + final String expectedJson = + Files.contentOf( + new ClassPathResource("response-to-invalid-request.json").getFile(), + StandardCharsets.UTF_8); + // WHEN - THEN + webTestClient + .post() + .uri("/graphql") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(badRequestBody) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.OK) + .expectBody() + .json(expectedJson); + } +} diff --git a/graphql-kickstart-spring-boot-autoconfigure-webflux/src/test/resources/response-to-invalid-request.json b/graphql-kickstart-spring-boot-autoconfigure-webflux/src/test/resources/response-to-invalid-request.json new file mode 100644 index 00000000..03aec2d6 --- /dev/null +++ b/graphql-kickstart-spring-boot-autoconfigure-webflux/src/test/resources/response-to-invalid-request.json @@ -0,0 +1,9 @@ +{ + "errors": [ + { + "message": "Bad request - invalid request body.", + "locations": [] + } + ], + "data": null +} \ No newline at end of file diff --git a/graphql-kickstart-spring-support/src/main/java/graphql/kickstart/spring/AbstractGraphQLController.java b/graphql-kickstart-spring-support/src/main/java/graphql/kickstart/spring/AbstractGraphQLController.java index 1d3c14b8..789419ae 100644 --- a/graphql-kickstart-spring-support/src/main/java/graphql/kickstart/spring/AbstractGraphQLController.java +++ b/graphql-kickstart-spring-support/src/main/java/graphql/kickstart/spring/AbstractGraphQLController.java @@ -1,12 +1,15 @@ package graphql.kickstart.spring; +import graphql.ExecutionResultImpl; import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.error.GenericGraphQLError; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -20,8 +23,11 @@ import org.springframework.web.server.ServerWebExchange; @RequiredArgsConstructor +@Slf4j public abstract class AbstractGraphQLController { + private static final String INVALID_REQUEST_BODY_MESSAGE = "Bad request - invalid request body."; + private final GraphQLObjectMapper objectMapper; @PostMapping( @@ -34,13 +40,17 @@ public Object graphqlPOST( @Nullable @RequestParam(value = "operationName", required = false) String operationName, @Nullable @RequestParam(value = "variables", required = false) String variablesJson, @Nullable @RequestBody(required = false) String body, - ServerWebExchange serverWebExchange) - throws IOException { + ServerWebExchange serverWebExchange) { body = Optional.ofNullable(body).orElse(""); if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) { - GraphQLRequest request = objectMapper.readGraphQLRequest(body); + GraphQLRequest request; + try { + request = objectMapper.readGraphQLRequest(body); + } catch (IOException e) { + return handleBodyParsingException(e, serverWebExchange); + } if (request.getQuery() == null) { request.setQuery(""); } @@ -95,4 +105,11 @@ protected abstract Object executeRequest( String operationName, Map variables, ServerWebExchange serverWebExchange); + + protected Object handleBodyParsingException( + Exception exception, ServerWebExchange serverWebExchange) { + log.error("{} {}", INVALID_REQUEST_BODY_MESSAGE, exception.getMessage()); + return objectMapper.createResultFromExecutionResult( + new ExecutionResultImpl(new GenericGraphQLError(INVALID_REQUEST_BODY_MESSAGE))); + } }