Repository: quarkusio/quarkus
Issue: Correct handling when empty GraphQL requests arrive

### Describe the bug

When an empty POST request arrives at the `/graphql` endpoint, the client receives a `500 Internal Server Error` and this appears in the log:
```
2021-09-21 08:53:19,800 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-4) HTTP Request to /graphql/ failed, error id: 87f1643d-a786-485b-89c4-6d105c1c1ea8-1: java.lang.NullPointerException
	at java.base/java.io.StringReader.<init>(StringReader.java:50)
	at io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLExecutionHandler.getJsonObjectFromBody(SmallRyeGraphQLExecutionHandler.java:173)
	at io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLExecutionHandler.handlePost(SmallRyeGraphQLExecutionHandler.java:89)
	at io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLExecutionHandler.doHandle(SmallRyeGraphQLExecutionHandler.java:70)
	at io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLAbstractHandler.handleWithIdentity(SmallRyeGraphQLAbstractHandler.java:66)
	at io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLAbstractHandler.handle(SmallRyeGraphQLAbstractHandler.java:48)
	at io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLAbstractHandler.handle(SmallRyeGraphQLAbstractHandler.java:23)
	at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
	at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
	at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
	at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:829)
```

an empty GET request yields `204 No Content`

### Expected behavior

I believe that the correct behavior for both cases is to issue `400 Bad Request`, per the spec: https://github.com/graphql/graphql-over-http/blob/40a50a351a887799b3d1550b665b2101b26dcda4/spec/GraphQLOverHTTP.md#unparseable-or-invalid-request-body

### Actual behavior

_No response_

### How to Reproduce?

```
curl -v -H"Content-Type: application/json" -X POST localhost:8080/graphql
curl -v -H"Content-Type: application/json" -X GET localhost:8080/graphql/
```

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

_No response_

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

_No response_

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

_No response_

### Additional information

_No response_

Changed files:
extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java

diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java
index babd92de9a5..161305c4565 100644
--- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java
+++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java
@@ -38,6 +38,7 @@ public class SmallRyeGraphQLExecutionHandler extends SmallRyeGraphQLAbstractHand
             + StandardCharsets.UTF_8.name();
     private static final String DEFAULT_REQUEST_CONTENT_TYPE = "application/json; charset="
             + StandardCharsets.UTF_8.name();
+    private static final String MISSING_OPERATION = "Missing operation body";
 
     public SmallRyeGraphQLExecutionHandler(boolean allowGet, boolean allowPostWithQueryParameters,
             CurrentIdentityAssociation currentIdentityAssociation,
@@ -90,10 +91,23 @@ private void handlePost(HttpServerResponse response, RoutingContext ctx, String
             String postResponse;
             if (hasQueryParameters(ctx) && allowPostWithQueryParameters) {
                 JsonObject jsonObjectFromQueryParameters = getJsonObjectFromQueryParameters(ctx);
-                JsonObject mergedJsonObject = Json.createMergePatch(jsonObjectFromQueryParameters).apply(jsonObjectFromBody)
-                        .asJsonObject();
+                JsonObject mergedJsonObject;
+                if (jsonObjectFromBody != null) {
+                    mergedJsonObject = Json.createMergePatch(jsonObjectFromQueryParameters).apply(jsonObjectFromBody)
+                            .asJsonObject();
+                } else {
+                    mergedJsonObject = jsonObjectFromQueryParameters;
+                }
+                if (!mergedJsonObject.containsKey(QUERY)) {
+                    response.setStatusCode(400).end(MISSING_OPERATION);
+                    return;
+                }
                 postResponse = doRequest(mergedJsonObject);
             } else {
+                if (jsonObjectFromBody == null) {
+                    response.setStatusCode(400).end(MISSING_OPERATION);
+                    return;
+                }
                 postResponse = doRequest(jsonObjectFromBody);
             }
             response.setStatusCode(200).setStatusMessage(OK).end(Buffer.buffer(postResponse, requestedCharset));
@@ -114,7 +128,7 @@ private void handleGet(HttpServerResponse response, RoutingContext ctx, String r
                             .end(Buffer.buffer(getResponse, requestedCharset));
 
                 } else {
-                    response.setStatusCode(204).end();
+                    response.setStatusCode(400).end(MISSING_OPERATION);
                 }
             } catch (UnsupportedEncodingException uee) {
                 throw new RuntimeException(uee);
@@ -170,6 +184,9 @@ private JsonObject getJsonObjectFromBody(RoutingContext ctx) throws IOException
             return input.build();
             // Else we expect a Json in the content    
         } else {
+            if (body == null || body.isEmpty()) {
+                return null;
+            }
             try (StringReader bodyReader = new StringReader(body);
                     JsonReader jsonReader = jsonReaderFactory.createReader(bodyReader)) {
                 return jsonReader.readObject();