This is an automated email from the ASF dual-hosted git repository.

valentyn pushed a commit to branch valentyn/http-error-handling
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 42e292cb0b6f9015bb068b83ef02524120b33170
Author: Valentyn Kahamlyk <[email protected]>
AuthorDate: Fri Apr 19 13:47:47 2024 -0700

    errors mapping
---
 .../gremlin/server/ProcessingException.java        |  14 +-
 .../server/handler/HttpGremlinEndpointHandler.java | 162 ++++++-------------
 .../gremlin/server/handler/HttpHandlerUtil.java    |  40 ++---
 .../gremlin/server/util/GremlinError.java          | 173 +++++++++++++++++++++
 .../gremlin/server/HttpDriverIntegrateTest.java    |   2 +-
 .../gremlin/util/message/ResponseStatusCode.java   |  15 ++
 .../ser/AbstractGraphSONMessageSerializerV4.java   |   5 +-
 7 files changed, 263 insertions(+), 148 deletions(-)

diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java
index 2174e96e67..a68d9d3eea 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/ProcessingException.java
@@ -18,20 +18,20 @@
  */
 package org.apache.tinkerpop.gremlin.server;
 
-import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
+import org.apache.tinkerpop.gremlin.server.util.GremlinError;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
 public class ProcessingException extends Exception {
-    private final ResponseMessage responseMessage;
+    private final GremlinError error;
 
-    public ProcessingException(final String message, final ResponseMessage 
responseMessage) {
-        super(message);
-        this.responseMessage = responseMessage;
+    public ProcessingException(final GremlinError error) {
+        super(error.getMessage());
+        this.error = error;
     }
 
-    public ResponseMessage getResponseMessage() {
-        return this.responseMessage;
+    public GremlinError getError() {
+        return this.error;
     }
 }
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
index 2196795250..e784826f1d 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
@@ -49,6 +49,7 @@ import org.apache.tinkerpop.gremlin.server.GremlinServer;
 import org.apache.tinkerpop.gremlin.server.Settings;
 import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
 import org.apache.tinkerpop.gremlin.server.ProcessingException;
+import org.apache.tinkerpop.gremlin.server.util.GremlinError;
 import org.apache.tinkerpop.gremlin.server.util.MetricManager;
 import org.apache.tinkerpop.gremlin.server.util.TraverserIterator;
 import org.apache.tinkerpop.gremlin.structure.Column;
@@ -81,7 +82,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.Future;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.RejectedExecutionException;
@@ -217,21 +217,9 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
                         iterateTraversal(requestCtx, serializer.getValue1(), 
translateBytecodeToTraversal(requestCtx));
                         break;
                     case Tokens.OPS_INVALID:
-                        final String msgInvalid =
-                                String.format("Message could not be parsed. 
Check the format of the request. [%s]", requestMessage);
-                        throw new ProcessingException(msgInvalid,
-                                ResponseMessage.buildV4()
-                                        
.code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST)
-                                        .statusMessage(msgInvalid)
-                                        .create());
+                        throw new 
ProcessingException(GremlinError.invalidGremlinType(requestMessage));
                     default:
-                        final String msgDefault =
-                                String.format("Message with gremlin of type 
[%s] is not recognized.", requestMessage.getGremlinType());
-                        throw new ProcessingException(msgDefault,
-                                ResponseMessage.buildV4()
-                                        
.code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST)
-                                        .statusMessage(msgDefault)
-                                        .create());
+                        throw new 
ProcessingException(GremlinError.unknownGremlinType(requestMessage));
                 }
             } catch (Throwable t) {
                 writeError(requestCtx, formErrorResponseMessage(t, 
requestMessage), serializer.getValue1());
@@ -257,27 +245,16 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
                 
requestCtx.setTimeoutExecutor(requestCtx.getScheduledExecutorService().schedule(()
 -> {
                     executionFuture.cancel(true);
                     if (!requestCtx.getStartedResponse()) {
-                        final String errorMessage = String.format("A timeout 
occurred during traversal evaluation of [%s] - consider increasing the limit 
given to evaluationTimeout", requestMessage);
-                        writeError(requestCtx,
-                                ResponseMessage.buildV4()
-                                        
.code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
-                                        .statusMessage(errorMessage)
-                                        .create(),
-                                serializer.getValue1());
+                        writeError(requestCtx, 
GremlinError.timeout(requestMessage), serializer.getValue1());
                     }
                 }, seto, TimeUnit.MILLISECONDS));
             }
         } catch (RejectedExecutionException ree) {
-            writeError(requestCtx,
-                    
ResponseMessage.buildV4().code(ResponseStatusCode.TOO_MANY_REQUESTS).statusMessage("Rate
 limiting").create(),
-                    serializer.getValue1());
+            writeError(requestCtx, GremlinError.rateLimiting(), 
serializer.getValue1());
         }
     }
 
-    private ResponseMessage formErrorResponseMessage(Throwable t, 
RequestMessageV4 requestMessage) {
-        final ResponseMessage errorResponseMessage;
-        String logMessage = String.format("Exception processing request 
[%s].", requestMessage);
-
+    private GremlinError formErrorResponseMessage(Throwable t, 
RequestMessageV4 requestMessage) {
         if (t instanceof UndeclaredThrowableException) t = t.getCause();
 
         // if any exception in the chain is TemporaryException or Failure then 
we should respond with the
@@ -285,63 +262,43 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
         final Optional<Throwable> possibleSpecialException = 
determineIfSpecialException(t);
         if (possibleSpecialException.isPresent()) {
             final Throwable special = possibleSpecialException.get();
-            final ResponseMessage.Builder specialResponseMsg = 
ResponseMessage.buildV4().
-                    statusMessage(special.getMessage()).
-                    statusAttributeException(special);
             if (special instanceof TemporaryException) {
-                
specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
-            } else if (special instanceof Failure) {
-                final Failure failure = (Failure) special;
-                
specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
-                        
statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+                return GremlinError.temporary(special);
             }
-            errorResponseMessage = specialResponseMsg.create();
-        } else if (t instanceof ProcessingException) {
-            errorResponseMessage = ((ProcessingException) 
t).getResponseMessage();
-        } else {
-            t = ExceptionHelper.getRootCause(t);
-
-            if (t instanceof TooLongFrameException) {
-                return ResponseMessage.buildV4()
-                        .code(ResponseStatusCode.SERVER_ERROR)
-                        .statusMessage(t.getMessage() + " - increase the 
maxContentLength")
-                        .create();
-            } else if (t instanceof InterruptedException || t instanceof 
TraversalInterruptedException) {
-                final String errorMessage = String.format("A timeout occurred 
during traversal evaluation of [%s] - consider increasing the limit given to 
evaluationTimeout", requestMessage);
-                errorResponseMessage = ResponseMessage.buildV4()
-                        .code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
-                        .statusMessage(errorMessage)
-                        .statusAttributeException(t)
-                        .create();
-            } else if (t instanceof TimedInterruptTimeoutException) {
-                // occurs when the TimedInterruptCustomizerProvider is in play
-                logMessage = String.format("A timeout occurred within the 
script during evaluation of [%s] - consider increasing the limit given to 
TimedInterruptCustomizerProvider", requestMessage);
-                errorResponseMessage = 
ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
-                        .statusMessage("Timeout during script evaluation 
triggered by TimedInterruptCustomizerProvider")
-                        .statusAttributeException(t).create();
-            } else if (t instanceof TimeoutException) {
-                logMessage = String.format("Script evaluation exceeded the 
configured threshold for request [%s]", requestMessage);
-                errorResponseMessage = 
ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
-                        .statusMessage(t.getMessage())
-                        .statusAttributeException(t).create();
-            } else if (t instanceof MultipleCompilationErrorsException && 
t.getMessage().contains("Method too large") &&
-                    ((MultipleCompilationErrorsException) 
t).getErrorCollector().getErrorCount() == 1) {
-                final String errorMessage = String.format("The Gremlin 
statement that was submitted exceeds the maximum compilation size allowed by 
the JVM, please split it into multiple smaller statements - %s", 
requestMessage.trimMessage(1021));
-                logMessage = errorMessage;
-                errorResponseMessage = 
ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
-                        .statusMessage(errorMessage)
-                        .statusAttributeException(t).create();
-            } else {
-                errorResponseMessage = ResponseMessage.buildV4()
-                        .code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
-                        .statusMessage((t.getMessage() == null) ? t.toString() 
: t.getMessage())
-                        .statusAttributeException(t)
-                        .create();
+            if (special instanceof Failure) {
+                return GremlinError.failStep((Failure) special);
             }
+            return GremlinError.general(special);
+        }
+        if (t instanceof ProcessingException) {
+            return ((ProcessingException) t).getError();
+        }
+        t = ExceptionHelper.getRootCause(t);
+
+        if (t instanceof TooLongFrameException) {
+            return GremlinError.longFrame(t);
+        }
+        if (t instanceof InterruptedException || t instanceof 
TraversalInterruptedException) {
+            return GremlinError.timeout(requestMessage);
+        }
+        if (t instanceof TimedInterruptTimeoutException) {
+            // occurs when the TimedInterruptCustomizerProvider is in play
+            logger.warn(String.format("A timeout occurred within the script 
during evaluation of [%s] - consider increasing the limit given to 
TimedInterruptCustomizerProvider", requestMessage));
+            return GremlinError.timedInterruptTimeout();
+        }
+        if (t instanceof TimeoutException) {
+            logger.warn(String.format("Script evaluation exceeded the 
configured threshold for request [%s]", requestMessage));
+            return GremlinError.timeout(requestMessage);
+        }
+        if (t instanceof MultipleCompilationErrorsException && 
t.getMessage().contains("Method too large") &&
+                ((MultipleCompilationErrorsException) 
t).getErrorCollector().getErrorCount() == 1) {
+            final GremlinError error = 
GremlinError.longRequest(requestMessage);
+            logger.warn(error.getMessage());
+            return error;
         }
 
-        logger.warn(logMessage, t);
-        return errorResponseMessage;
+        logger.warn(String.format("Exception processing request [%s].", 
requestMessage));
+        return GremlinError.general(t);
     }
 
     private void iterateScriptEvalResult(final Context context, 
MessageTextSerializerV4<?> serializer, final RequestMessageV4 message)
@@ -349,21 +306,17 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
         if (message.optionalField(Tokens.ARGS_BINDINGS).isPresent()) {
             final Map bindings = (Map) 
message.getFields().get(Tokens.ARGS_BINDINGS);
             if (IteratorUtils.anyMatch(bindings.keySet().iterator(), k -> null 
== k || !(k instanceof String))) {
-                final String msg = String.format("The [%s] message is using 
one or more invalid binding keys - they must be of type String and cannot be 
null", Tokens.OPS_EVAL);
-                throw new ProcessingException(msg, 
ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+                throw new ProcessingException(GremlinError.binding());
             }
 
             final Set<String> badBindings = 
IteratorUtils.set(IteratorUtils.<String>filter(bindings.keySet().iterator(), 
INVALID_BINDINGS_KEYS::contains));
             if (!badBindings.isEmpty()) {
-                final String msg = String.format("The [%s] message supplies 
one or more invalid parameters key of [%s] - these are reserved names.", 
Tokens.OPS_EVAL, badBindings);
-                throw new ProcessingException(msg, 
ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+                throw new 
ProcessingException(GremlinError.binding(badBindings));
             }
 
             // ignore control bindings that get passed in with the "#jsr223" 
prefix - those aren't used in compilation
             if 
(IteratorUtils.count(IteratorUtils.filter(bindings.keySet().iterator(), k -> 
!k.toString().startsWith("#jsr223"))) > settings.maxParameters) {
-                final String msg = String.format("The [%s] message contains %s 
bindings which is more than is allowed by the server %s configuration",
-                        Tokens.OPS_EVAL, bindings.size(), 
settings.maxParameters);
-                throw new ProcessingException(msg, 
ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+                throw new 
ProcessingException(GremlinError.binding(bindings.size(), 
settings.maxParameters));
             }
         }
 
@@ -380,21 +333,17 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
     private static Traversal.Admin<?,?> translateBytecodeToTraversal(Context 
ctx) throws ProcessingException {
         final RequestMessageV4 requestMsg = ctx.getRequestMessage();
         if (!(requestMsg.getGremlin() instanceof Bytecode)) {
-            final String msg = String.format("A [%s] message requires a 
gremlin argument that is of type %s.",
-                    Tokens.OPS_BYTECODE, Tokens.ARGS_GREMLIN, 
Bytecode.class.getSimpleName());
-            throw new ProcessingException(msg, 
ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+            throw new ProcessingException(GremlinError.gremlinType());
         }
 
         final Optional<String> alias = requestMsg.optionalField(Tokens.ARGS_G);
         if (!alias.isPresent()) {
-            final String msg = String.format("A [%s] message requires a [%s] 
argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_G);
-            throw new ProcessingException(msg, 
ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+            throw new ProcessingException(GremlinError.traversalSource());
         }
 
         final String traversalSourceName = alias.get();
         if 
(!ctx.getGraphManager().getTraversalSourceNames().contains(traversalSourceName))
 {
-            final String msg = String.format("The traversal source [%s] for 
alias [%s] is not configured on the server.", traversalSourceName, 
Tokens.VAL_TRAVERSAL_SOURCE_ALIAS);
-            throw new ProcessingException(msg, 
ResponseMessage.buildV4().code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create());
+            throw new 
ProcessingException(GremlinError.traversalSource(traversalSourceName));
         }
 
         final TraversalSource g = 
ctx.getGraphManager().getTraversalSource(traversalSourceName);
@@ -407,16 +356,10 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
                 return ctx.getGremlinExecutor().eval(bytecode, EMPTY_BINDINGS, 
lambdaLanguage.get(), traversalSourceName);
         } catch (ScriptException ex) {
             logger.error("Traversal contains a lambda that cannot be 
compiled", ex);
-            throw new ProcessingException("Traversal contains a lambda that 
cannot be compiled",
-                    
ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
-                            .statusMessage(ex.getMessage())
-                            .statusAttributeException(ex).create());
+            throw new ProcessingException(GremlinError.lambdaNotSupported(ex));
         } catch (Exception ex) {
             logger.error("Could not deserialize the Traversal instance", ex);
-            throw new ProcessingException("Could not deserialize the Traversal 
instance",
-                    
ResponseMessage.buildV4().code(ResponseStatusCode.SERVER_ERROR_SERIALIZATION)
-                            .statusMessage(ex.getMessage())
-                            .statusAttributeException(ex).create());
+            throw new 
ProcessingException(GremlinError.deserializeTraversal(ex));
         }
     }
 
@@ -456,10 +399,7 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
             // this validation is important to calls to GraphManager.commit() 
and rollback() as they both
             // expect that the aliases supplied are valid
             if (!found) {
-                final String error = String.format("Could not alias [%s] to 
[%s] as [%s] not in the Graph or TraversalSource global bindings",
-                        Tokens.ARGS_G, aliased, aliased);
-                throw new ProcessingException(error, ResponseMessage.buildV4()
-                        
.code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(error).create());
+                throw new ProcessingException(GremlinError.binding(aliased));
             }
         }
 
@@ -658,13 +598,7 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
 
         } catch (Exception ex) {
             logger.warn("The result [{}] in the request {} could not be 
serialized and returned.", aggregate, msg.getRequestId(), ex);
-            final String errorMessage = String.format("Error during 
serialization: %s", ExceptionHelper.getMessageFromExceptionOrCause(ex));
-            writeError(ctx,
-                    ResponseMessage.build(msg.getRequestId())
-                            .statusMessage(errorMessage)
-                            .statusAttributeException(ex)
-                            
.code(ResponseStatusCode.SERVER_ERROR_SERIALIZATION).create(),
-                    serializer);
+            writeError(ctx, GremlinError.serialization(ex), serializer);
             throw ex;
         }
     }
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
index 61f16280cc..bfdf18fe24 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpHandlerUtil.java
@@ -21,56 +21,36 @@ package org.apache.tinkerpop.gremlin.server.handler;
 import com.codahale.metrics.Meter;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelFutureListener;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.DefaultFullHttpResponse;
 import io.netty.handler.codec.http.DefaultHttpContent;
 import io.netty.handler.codec.http.DefaultLastHttpContent;
-import io.netty.handler.codec.http.FullHttpRequest;
 import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.netty.handler.codec.http.HttpUtil;
 import io.netty.util.CharsetUtil;
-import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.tinkerpop.gremlin.server.Context;
 import org.apache.tinkerpop.gremlin.server.GremlinServer;
-import org.apache.tinkerpop.gremlin.server.Settings;
+import org.apache.tinkerpop.gremlin.server.util.GremlinError;
 import org.apache.tinkerpop.gremlin.server.util.MetricManager;
 import org.apache.tinkerpop.gremlin.util.MessageSerializer;
-import org.apache.tinkerpop.gremlin.util.Tokens;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
-import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
 import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
 import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
 import org.apache.tinkerpop.gremlin.util.ser.MessageTextSerializerV4;
 import org.apache.tinkerpop.gremlin.util.ser.SerTokens;
 import org.apache.tinkerpop.gremlin.util.ser.SerializationException;
-import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
-import org.apache.tinkerpop.shaded.jackson.databind.node.ArrayNode;
 import org.apache.tinkerpop.shaded.jackson.databind.node.ObjectNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
 
 import static com.codahale.metrics.MetricRegistry.name;
 import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
-import static io.netty.handler.codec.http.HttpMethod.POST;
 import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
-import static io.netty.handler.codec.http.LastHttpContent.EMPTY_LAST_CONTENT;
 
 /**
  * Provides methods shared by the HTTP handlers.
@@ -90,9 +70,9 @@ public class HttpHandlerUtil {
      * Helper method to send errors back as JSON. Only to be used when the 
RequestMessage couldn't be parsed, because
      * a proper serialized ResponseMessage should be sent in that case.
      *
-     * @param ctx       The netty channel context.
-     * @param status    The HTTP error status code.
-     * @param message   The error message to contain the body.
+     * @param ctx     The netty channel context.
+     * @param status  The HTTP error status code.
+     * @param message The error message to contain the body.
      */
     public static void sendError(final ChannelHandlerContext ctx, final 
HttpResponseStatus status, final String message) {
         logger.warn(String.format("Invalid request - responding with %s and 
%s", status, message));
@@ -108,7 +88,7 @@ public class HttpHandlerUtil {
         ctx.writeAndFlush(response);
     }
 
-    static void writeError(final Context context, ResponseMessage 
responseMessage, final MessageSerializer<?> serializer) {
+    static void writeError(final Context context, final ResponseMessage 
responseMessage, final MessageSerializer<?> serializer) {
         try {
             final ChannelHandlerContext ctx = 
context.getChannelHandlerContext();
             final ByteBuf ByteBuf = context.getRequestState() == 
HttpGremlinEndpointHandler.RequestState.STREAMING
@@ -124,6 +104,16 @@ public class HttpHandlerUtil {
         }
     }
 
+    static void writeError(final Context context, final GremlinError error, 
final MessageSerializer<?> serializer) {
+        final ResponseMessage responseMessage = ResponseMessage.buildV4()
+                .code(error.getCode())
+                .statusMessage(error.getMessage())
+                .exception(error.getException())
+                .create();
+
+        writeError(context, responseMessage, serializer);
+    }
+
     static void sendTrailingHeaders(final ChannelHandlerContext ctx, final 
ResponseStatusCode statusCode, final String message) {
         final DefaultLastHttpContent defaultLastHttpContent = new 
DefaultLastHttpContent();
         defaultLastHttpContent.trailingHeaders().add(SerTokens.TOKEN_CODE, 
statusCode.getValue());
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
new file mode 100644
index 0000000000..1e1185e3fd
--- /dev/null
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.tinkerpop.gremlin.server.util;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.util.ExceptionHelper;
+import org.apache.tinkerpop.gremlin.util.Tokens;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessageV4;
+import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode;
+
+import java.util.Set;
+
+public class GremlinError {
+    private final ResponseStatusCode code;
+    private final String message;
+    private final String exception;
+
+    private GremlinError(ResponseStatusCode code, String message, String 
exception) {
+
+        this.code = code;
+        this.message = message;
+        this.exception = exception;
+    }
+
+    public ResponseStatusCode getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public String getException() {
+        return exception;
+    }
+
+    // ------------ request validation errors
+
+    // script type errors
+    public static GremlinError invalidGremlinType(final RequestMessageV4 
requestMessage ) {
+        final String message = String.format("Message could not be parsed. 
Check the format of the request. [%s]",
+                requestMessage);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError unknownGremlinType(final RequestMessageV4 
requestMessage ) {
+        final String message = String.format("Message with gremlin of type 
[%s] is not recognized.",
+                requestMessage.getGremlinType());
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    // script errors
+    public static GremlinError binding() {
+        final String message = String.format("The [%s] message is using one or 
more invalid binding keys - they must be of type String and cannot be null",
+                Tokens.OPS_EVAL);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError binding(final Set<String> badBindings) {
+        final String message = String.format("The [%s] message supplies one or 
more invalid parameters key of [%s] - these are reserved names.",
+                Tokens.OPS_EVAL, badBindings);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError binding(final int bindingsCount, final int 
allowedSize) {
+        final String message = String.format("The [%s] message contains %s 
bindings which is more than is allowed by the server %s configuration",
+                Tokens.OPS_EVAL, bindingsCount, allowedSize);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError binding(final String aliased) {
+        final String message = String.format("Could not alias [%s] to [%s] as 
[%s] not in the Graph or TraversalSource global bindings",
+                Tokens.ARGS_G, aliased, aliased);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    // bytecode errors
+
+    public static GremlinError gremlinType() {
+        final String message = String.format("A [%s] message requires a 
gremlin argument that is of type %s.",
+                Tokens.OPS_BYTECODE, Tokens.ARGS_GREMLIN);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError traversalSource() {
+        final String message = String.format("A [%s] message requires a [%s] 
argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_G);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError traversalSource(final String 
traversalSourceName ) {
+        final String message = String.format("The traversal source [%s] for 
alias [%s] is not configured on the server.",
+                traversalSourceName, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS);
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, message, 
"InvalidRequestException");
+    }
+
+    public static GremlinError lambdaNotSupported(final Throwable t) {
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, 
t.getMessage(), "InvalidRequestException");
+    }
+
+    public static GremlinError deserializeTraversal(final Throwable t) {
+        return new GremlinError(ResponseStatusCode.BAD_REQUEST, 
t.getMessage(), "InvalidRequestException");
+    }
+
+    // execution errors
+    public static GremlinError timeout(final RequestMessageV4 requestMessage ) 
{
+        final String message = String.format("A timeout occurred during 
traversal evaluation of [%s] - consider increasing the limit given to 
evaluationTimeout",
+                requestMessage);
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, 
"ServerTimeoutExceededException");
+    }
+
+    public static GremlinError timedInterruptTimeout() {
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR,
+                "Timeout during script evaluation triggered by 
TimedInterruptCustomizerProvider",
+                "ServerTimeoutExceededException");
+    }
+
+    public static GremlinError rateLimiting() {
+        return new GremlinError(ResponseStatusCode.TOO_MANY_REQUESTS,
+                "Too many requests have been sent in a given amount of time.", 
"TooManyRequestsException");
+    }
+
+    public static GremlinError serialization(Exception ex) {
+        final String message = String.format("Error during serialization: %s", 
ExceptionHelper.getMessageFromExceptionOrCause(ex));
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, 
"ServerSerializationException");
+    }
+
+    public static GremlinError wrongSerializer(Exception ex) {
+        final String message = String.format("Error during serialization: %s", 
ExceptionHelper.getMessageFromExceptionOrCause(ex));
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, 
"ServerSerializationException");
+    }
+
+    public static GremlinError longFrame(Throwable t) {
+        final String message = t.getMessage() + " - increase the 
maxContentLength";
+        // todo: ResponseEntityTooLargeException? !!!
+        return new GremlinError(ResponseStatusCode.PAYLOAD_TOO_LARGE, message, 
"RequestEntityTooLargeException");
+    }
+
+    public static GremlinError longRequest(final RequestMessageV4 
requestMessage ) {
+        final String message = String.format("The Gremlin statement that was 
submitted exceeds the maximum compilation size allowed by the JVM, please split 
it into multiple smaller statements - %s", requestMessage.trimMessage(1021));
+        return new GremlinError(ResponseStatusCode.PAYLOAD_TOO_LARGE, message, 
"RequestEntityTooLargeException");
+    }
+
+    public static GremlinError temporary(final Throwable t) {
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR, 
t.getMessage(), "ServerEvaluationException");
+    }
+
+    public static GremlinError failStep(final Failure failure) {
+        // todo: double check message
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR,
+                failure.getMessage() + ". " + failure.format(), 
"ServerFailStepException");
+    }
+
+    public static GremlinError general(final Throwable t) {
+        final String message = (t.getMessage() == null) ? t.toString() : 
t.getMessage();
+        return new GremlinError(ResponseStatusCode.SERVER_ERROR, message, 
"ServerErrorException");
+    }
+}
diff --git 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java
 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java
index 40bf9dd811..ff61b50d08 100644
--- 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java
+++ 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/HttpDriverIntegrateTest.java
@@ -486,7 +486,7 @@ public class HttpDriverIntegrateTest extends 
AbstractGremlinServerIntegrationTes
             } catch (Exception ex) {
                 final Throwable inner = ExceptionHelper.getRootCause(ex);
                 assertTrue(inner instanceof ResponseException);
-                assertEquals(ResponseStatusCode.SERVER_ERROR_SERIALIZATION, 
((ResponseException) inner).getResponseStatusCode());
+                assertEquals(ResponseStatusCode.SERVER_ERROR, 
((ResponseException) inner).getResponseStatusCode());
             }
 
             // should not die completely just because we had a bad 
serialization error.  that kind of stuff happens
diff --git 
a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java
 
b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java
index 98c727e7c0..45a2eed281 100644
--- 
a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java
+++ 
b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/message/ResponseStatusCode.java
@@ -59,6 +59,14 @@ public enum ResponseStatusCode {
      */
     PARTIAL_CONTENT(206),
 
+    /**
+     * The server cannot or will not process the request due to an apparent 
client error (e.g., malformed request
+     * syntax, size too large, invalid request message framing, or deceptive 
request routing).
+     *
+     * @since 4.0.0
+     */
+    BAD_REQUEST(400),
+
     /**
      * Code 401: The server could not authenticate the request or the client 
requested a resource it did not have
      * access to.
@@ -82,6 +90,13 @@ public enum ResponseStatusCode {
      */
     AUTHENTICATE(407),
 
+    /**
+     * The request is larger than the server is willing or able to process.
+     *
+     * @since 4.0.0
+     */
+    PAYLOAD_TOO_LARGE(413),
+
     /**
      * Code 429: Indicates that too many requests have been sent in a given 
amount of time.
      *
diff --git 
a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
 
b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
index 489bf0ac4f..0b3a5b69ba 100644
--- 
a/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
+++ 
b/gremlin-util/src/main/java/org/apache/tinkerpop/gremlin/util/ser/AbstractGraphSONMessageSerializerV4.java
@@ -366,7 +366,10 @@ public abstract class AbstractGraphSONMessageSerializerV4 
extends AbstractGraphS
             GraphSONUtil.writeStartObject(responseMessage, jsonGenerator, 
typeSerializer);
             jsonGenerator.writeStringField(SerTokens.TOKEN_MESSAGE, 
responseMessage.getStatus().getMessage());
             jsonGenerator.writeNumberField(SerTokens.TOKEN_CODE, 
responseMessage.getStatus().getCode().getValue());
-            jsonGenerator.writeStringField(SerTokens.TOKEN_EXCEPTION, 
responseMessage.getStatus().getException());
+            if (responseMessage.getStatus().getCode() != 
ResponseStatusCode.SUCCESS &&
+                    responseMessage.getStatus().getException() != null) {
+                jsonGenerator.writeStringField(SerTokens.TOKEN_EXCEPTION, 
responseMessage.getStatus().getException());
+            }
             GraphSONUtil.writeEndObject(responseMessage, jsonGenerator, 
typeSerializer);
 
             GraphSONUtil.writeEndObject(responseMessage, jsonGenerator, 
typeSerializer);


Reply via email to