Skip to content

Commit

Permalink
✨ Align GraphQL Response Content Types and Status Codes with Specific…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
ujibang committed Nov 2, 2023
1 parent df46251 commit 73869f3
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 65 deletions.
126 changes: 126 additions & 0 deletions commons/src/main/java/org/restheart/exchange/BadRequestException.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ public class BadRequestException extends RuntimeException {
private static final long serialVersionUID = -8466126772297299751L;

int statusCode = HttpStatus.SC_BAD_REQUEST;
private final boolean jsonMessage;
private final String contentType;

/**
*
*/
public BadRequestException() {
super();
this.jsonMessage = false;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
Expand All @@ -48,6 +52,8 @@ public BadRequestException() {
public BadRequestException(int statusCode) {
super();
this.statusCode = statusCode;
this.jsonMessage = false;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
Expand All @@ -56,6 +62,31 @@ public BadRequestException(int statusCode) {
*/
public BadRequestException(String message) {
super(message);
this.jsonMessage = false;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param jsonMessage mark message as json
*/
public BadRequestException(String message, boolean jsonMessage) {
super(message);
this.jsonMessage = jsonMessage;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param jsonMessage mark message as json
* @param contentType error response content type
*/
public BadRequestException(String message, boolean jsonMessage, String contentType) {
super(message);
this.jsonMessage = jsonMessage;
this.contentType = contentType;
}

/**
Expand All @@ -66,6 +97,35 @@ public BadRequestException(String message) {
public BadRequestException(String message, int statusCode) {
super(message);
this.statusCode = statusCode;
this.jsonMessage = false;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param statusCode
* @param jsonMessage mark message as json
*/
public BadRequestException(String message, int statusCode, boolean jsonMessage) {
super(message);
this.statusCode = statusCode;
this.jsonMessage = jsonMessage;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param statusCode
* @param jsonMessage mark message as json
* @param contentType error response content type
*/
public BadRequestException(String message, int statusCode, boolean jsonMessage, String contentType) {
super(message);
this.statusCode = statusCode;
this.jsonMessage = jsonMessage;
this.contentType = contentType;
}

/**
Expand All @@ -75,6 +135,33 @@ public BadRequestException(String message, int statusCode) {
*/
public BadRequestException(String message, Throwable cause) {
super(message, cause);
this.jsonMessage = false;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param cause
* @param jsonMessage mark message as json
*/
public BadRequestException(String message, boolean jsonMessage, Throwable cause) {
super(message, cause);
this.jsonMessage = jsonMessage;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param cause
* @param jsonMessage mark message as json
* @param contentType error response content type
*/
public BadRequestException(String message, boolean jsonMessage, String contentType, Throwable cause) {
super(message, cause);
this.jsonMessage = jsonMessage;
this.contentType = contentType;
}

/**
Expand All @@ -86,6 +173,37 @@ public BadRequestException(String message, Throwable cause) {
public BadRequestException(String message, int statusCode, Throwable cause) {
super(message, cause);
this.statusCode = statusCode;
this.jsonMessage = false;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param statusCode
* @param jsonMessage mark message as json
* @param cause
*/
public BadRequestException(String message, int statusCode, boolean jsonMessage, Throwable cause) {
super(message, cause);
this.statusCode = statusCode;
this.jsonMessage = jsonMessage;
this.contentType = Exchange.JSON_MEDIA_TYPE;
}

/**
*
* @param message
* @param statusCode
* @param jsonMessage mark message as json
* @param contentType error response content type
* @param cause
*/
public BadRequestException(String message, int statusCode, boolean jsonMessage, String contentType, Throwable cause) {
super(message, cause);
this.statusCode = statusCode;
this.jsonMessage = jsonMessage;
this.contentType = contentType;
}

/**
Expand All @@ -94,4 +212,12 @@ public BadRequestException(String message, int statusCode, Throwable cause) {
public int getStatusCode() {
return statusCode;
}

public boolean isJsonMessage() {
return jsonMessage;
}

public String contentType() {
return this.contentType;
}
}
43 changes: 12 additions & 31 deletions commons/src/main/java/org/restheart/exchange/GraphQLRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@
*/
package org.restheart.exchange;

import java.io.IOException;

import org.restheart.utils.ChannelReader;
import org.restheart.utils.HttpStatus;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.MalformedJsonException;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.Headers;

import org.restheart.utils.ChannelReader;
import org.restheart.utils.HttpStatus;

import java.io.IOException;
public class GraphQLRequest extends ServiceRequest<JsonElement> {

private static final String GRAPHQL_CONTENT_TYPE = "application/graphql";
Expand All @@ -47,7 +47,7 @@ private GraphQLRequest(HttpServerExchange exchange, String appUri) {
this.appUri = appUri;
}

public static GraphQLRequest init(HttpServerExchange exchange, String appUri) throws IOException, BadRequestException {
public static GraphQLRequest init(HttpServerExchange exchange, String appUri) throws IOException, JsonSyntaxException, BadRequestException {
var ret = new GraphQLRequest(exchange, appUri);

var method = exchange.getRequestMethod();
Expand All @@ -56,16 +56,12 @@ public static GraphQLRequest init(HttpServerExchange exchange, String appUri) th
throw new BadRequestException("Method not allowed", 405);
}

try {
if (isContentTypeGraphQL(exchange)){
ret.injectContentGraphQL();
} else if (isContentTypeJson(exchange)){
ret.injectContentJson();
} else if (!method.equalToString("OPTIONS")) {
throw new BadRequestException("Bad request: " + Headers.CONTENT_TYPE + " must be either " + GRAPHQL_CONTENT_TYPE + " or application/json", HttpStatus.SC_BAD_REQUEST);
}
} catch (JsonSyntaxException ex) {
throw new BadRequestException(jseCleanMessage(ex), HttpStatus.SC_BAD_REQUEST, ex);
if (isContentTypeGraphQL(exchange)) {
ret.injectContentGraphQL();
} else if (isContentTypeJson(exchange)) {
ret.injectContentJson();
} else if (!method.equalToString("OPTIONS")) {
throw new BadRequestException("Bad request: " + Headers.CONTENT_TYPE + " must be either " + GRAPHQL_CONTENT_TYPE + " or application/json", HttpStatus.SC_BAD_REQUEST);
}

return ret;
Expand All @@ -75,21 +71,6 @@ public static GraphQLRequest of(HttpServerExchange exchange) {
return of(exchange, GraphQLRequest.class);
}

private static String jseCleanMessage(JsonSyntaxException ex) {
if (ex.getCause() instanceof MalformedJsonException mje) {
return "Bad Request: " + mje.getMessage();
} else if (ex.getMessage() != null) {
if (ex.getMessage().indexOf("MalformedJsonException") > 0) {
var index = ex.getMessage().indexOf("MalformedJsonException" + "MalformedJsonException".length());
return "Bad Request: " + ex.getMessage().substring(index);
} else {
return "Bad Request: " + ex.getMessage();
}
} else {
return "Bad Request";
}
}

private void injectContentJson() throws IOException, JsonSyntaxException, BadRequestException {
var body = ChannelReader.readString(wrapped);
var json = JsonParser.parseString(body);
Expand Down
44 changes: 44 additions & 0 deletions commons/src/main/java/org/restheart/exchange/GraphQLResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*-
* ========================LICENSE_START=================================
* restheart-commons
* %%
* Copyright (C) 2019 - 2023 SoftInstigate
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================LICENSE_END==================================
*/
package org.restheart.exchange;

import io.undertow.server.HttpServerExchange;

/**
*
* @author Andrea Di Cesare {@literal <[email protected]>}
*/
public class GraphQLResponse extends MongoResponse {
public static final String GRAPHQL_RESPONSE_CONTENT_TYPE = "application/graphql-response+json";

protected GraphQLResponse(HttpServerExchange exchange) {
super(exchange);
// set response content type as application/graphql-response+json
setContentType(GRAPHQL_RESPONSE_CONTENT_TYPE);
}

public static GraphQLResponse init(HttpServerExchange exchange) {
return new GraphQLResponse(exchange);
}

public static GraphQLResponse of(HttpServerExchange exchange) {
return GraphQLResponse.of(exchange, GraphQLResponse.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@
*/
package org.restheart.handlers;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.Headers;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonString;
import org.restheart.exchange.BadRequestException;
import org.restheart.exchange.Exchange;
import org.restheart.exchange.UninitializedRequest;
import org.restheart.exchange.UninitializedResponse;
import org.restheart.plugins.PluginsRegistry;
import org.restheart.plugins.PluginsRegistryImpl;
import org.restheart.utils.HttpStatus;
import org.restheart.utils.BsonUtils;
import org.restheart.utils.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.Headers;

/**
* Initializes the Request and the Response invoking requestInitializer() and
* responseInitializer() functions defined by the handling service
Expand Down Expand Up @@ -91,8 +91,11 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
} catch (BadRequestException bre) {
LOGGER.debug("Error handling the request: {}", bre.getMessage(), bre);
exchange.setStatusCode(bre.getStatusCode());
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, Exchange.JSON_MEDIA_TYPE);
exchange.getResponseSender().send(BsonUtils.toJson(getErrorDocument(bre.getStatusCode(), bre.getMessage())));
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, bre.contentType());
exchange.getResponseSender().send(
bre.isJsonMessage()
? bre.getMessage()
: BsonUtils.toJson(getErrorDocument(bre.getStatusCode(), bre.getMessage())));
return;
} catch (Throwable t) {
exchange.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
Expand Down
6 changes: 3 additions & 3 deletions core/src/test/java/karate/graphql/query.feature
Original file line number Diff line number Diff line change
Expand Up @@ -373,16 +373,16 @@ Feature: GraphQL query response test
Given request { users(limit: 1) { posts { text } } } # this is invalid since body should be { query: "{...}" }
When method POST
Then status 400
And match response.message == 'missing query field'
And match response.errors[0].message == 'missing query field'

* call confDestroyer

Scenario: JSON bad query request due to not existing operation

Given request { query: "{ users (limit: 1) { posts { text } } }", operationName: "foo" }
When method POST
Then status 404
And match response.message == 'Unknown operation named \'foo\'.'
Then status 400
And match response.errors[0].message == 'Unknown operation named \'foo\'.'

* call confDestroyer

Loading

0 comments on commit 73869f3

Please sign in to comment.