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

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/master by this push:
     new 6b53bb6a49 Report malformed gremlin-go responses as deserialization 
errors
6b53bb6a49 is described below

commit 6b53bb6a49053c2d5e8fc38c669775b3201eab3f
Author: Stephen Mallette <[email protected]>
AuthorDate: Wed Jul 29 09:36:24 2026 -0400

    Report malformed gremlin-go responses as deserialization errors
    
    A malformed or truncated GraphBinary response surfaced whichever bare
    message the decoder happened to produce, including a typo'd "marker value
    erorr". Decode failures are now wrapped as deserialization errors.
    
    Assisted-by: Claude Code:claude-opus-5
---
 CHANGELOG.asciidoc                                 |  1 +
 gremlin-go/driver/connection.go                    | 24 +++++++++++++---------
 gremlin-go/driver/error_codes.go                   |  1 +
 gremlin-go/driver/marker.go                        |  4 +---
 gremlin-go/driver/resources/error-messages/en.json |  1 +
 5 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index e588872e70..50a76c696c 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -52,6 +52,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Fixed `gremlin-dotnet` deflate response decompression, which threw on the 
server's zlib-framed output because it used `DeflateStream` (raw DEFLATE, RFC 
1951) instead of `ZLibStream` (zlib, RFC 1950); the bug was previously masked 
because compression was off by default.
 * Fixed `gremlin-dotnet` SSL options cloning (used on the 
skip-certificate-validation path) to copy `ClientCertificateContext` and 
`AllowTlsResume`, which were previously dropped, breaking mTLS client 
certificates and silently re-enabling TLS resumption.
 * Fixed `gremlin-python` read timeout to derive from a single source (aiohttp 
`sock_read`), removing a redundant `async_timeout` read wrapper that could race 
it; a read timeout now deterministically raises `ReadTimeoutError` (a builtin 
`TimeoutError` subclass).
+* Fixed `gremlin-go` to report a malformed or truncated GraphBinary response 
as a deserialization error rather than a bare decoder message.
 * Removed `Transaction.open()` in favor of `begin()`, which is now the single 
transaction-start primitive across embedded and remote contexts.
 * Changed `begin()` and `close()` to be idempotent and calling it when a 
transaction is already in that state no longer throws.
 * Added `maxTransactionLifetimeMillis` setting to Gremlin Server, an absolute 
cap on the total age of an HTTP transaction that interrupts a running operation 
and rolls the transaction back when it fires (default 600000ms, set to `0` to 
disable).
diff --git a/gremlin-go/driver/connection.go b/gremlin-go/driver/connection.go
index 93502910c3..8686fe8f84 100644
--- a/gremlin-go/driver/connection.go
+++ b/gremlin-go/driver/connection.go
@@ -387,6 +387,15 @@ func (c *connection) getReader(resp *http.Response) 
(io.Reader, io.Closer, error
        return resp.Body, nil, nil
 }
 
+// setDeserializationError reports a failure to decode the GraphBinary 
response stream. The
+// underlying error is wrapped so that callers can tell a malformed or 
truncated response apart
+// from a transport failure, regardless of which point in the stream the 
decode gave out at.
+func (c *connection) setDeserializationError(rs ResultSet, err error) {
+       deserErr := fmt.Errorf("failed to deserialize response: %w", err)
+       c.logHandler.logf(Error, failedToReceiveResponse, deserErr.Error())
+       rs.setError(deserErr)
+}
+
 func (c *connection) streamToResultSet(reader io.Reader, rs ResultSet) {
        var d *GraphBinaryDeserializer
        if c.connSettings.pdtRegistry != nil {
@@ -400,8 +409,7 @@ func (c *connection) streamToResultSet(reader io.Reader, rs 
ResultSet) {
                        c.logHandler.logf(Error, failedToReceiveResponse, 
emptyBodyErr.Error())
                        rs.setError(emptyBodyErr)
                } else {
-                       c.logHandler.logf(Error, failedToReceiveResponse, 
err.Error())
-                       rs.setError(err)
+                       c.setDeserializationError(rs, err)
                }
                return
        }
@@ -410,8 +418,7 @@ func (c *connection) streamToResultSet(reader io.Reader, rs 
ResultSet) {
                obj, err := d.ReadFullyQualified()
                if err != nil {
                        if err != io.EOF {
-                               c.logHandler.logf(Error, 
failedToReceiveResponse, err.Error())
-                               rs.setError(err)
+                               c.setDeserializationError(rs, err)
                        }
                        return
                }
@@ -419,8 +426,7 @@ func (c *connection) streamToResultSet(reader io.Reader, rs 
ResultSet) {
                if marker, ok := obj.(Marker); ok && marker == EndOfStream() {
                        code, msg, _, err := d.ReadStatus()
                        if err != nil {
-                               c.logHandler.logf(Error, 
failedToReceiveResponse, err.Error())
-                               rs.setError(err)
+                               c.setDeserializationError(rs, err)
                                return
                        }
                        if code != 200 && code != 0 {
@@ -432,14 +438,12 @@ func (c *connection) streamToResultSet(reader io.Reader, 
rs ResultSet) {
                if d.IsBulked() {
                        bulkObj, err := d.ReadFullyQualified()
                        if err != nil {
-                               c.logHandler.logf(Error, 
failedToReceiveResponse, err.Error())
-                               rs.setError(err)
+                               c.setDeserializationError(rs, err)
                                return
                        }
                        bulk, ok := bulkObj.(int64)
                        if !ok {
-                               c.logHandler.logf(Error, 
failedToReceiveResponse, "expected int64 bulk count")
-                               rs.setError(fmt.Errorf("expected int64 bulk 
count, got %T", bulkObj))
+                               c.setDeserializationError(rs, 
fmt.Errorf("expected int64 bulk count, got %T", bulkObj))
                                return
                        }
                        rs.Channel() <- &Result{&Traverser{Bulk: bulk, Value: 
obj}}
diff --git a/gremlin-go/driver/error_codes.go b/gremlin-go/driver/error_codes.go
index 9e727c0460..2927ab05c5 100644
--- a/gremlin-go/driver/error_codes.go
+++ b/gremlin-go/driver/error_codes.go
@@ -54,6 +54,7 @@ const (
        err0406EnumReaderInvalidTypeError                errorCode = 
"E0406_GRAPH_BINARY_ENUMREADER_INVALID_TYPE_ERROR"
        err0407GetSerializerToWriteUnknownTypeError      errorCode = 
"E0407_GRAPH_BINARY_GETSERIALIZERTOWRITE_UNKNOWN_TYPE_ERROR"
        err0408GetSerializerToReadUnknownTypeError       errorCode = 
"E0408_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_TYPE_ERROR"
+       err0410UnexpectedMarkerValueError                errorCode = 
"E0410_GRAPH_BINARY_UNEXPECTED_MARKER_VALUE_ERROR"
 
        // response handling errors
        err0501ResponseResultSetNotCreatedError errorCode = 
"E0501_RESPONSE_NO_RESULTSET_ON_DATA_RECEIVE"
diff --git a/gremlin-go/driver/marker.go b/gremlin-go/driver/marker.go
index 435508c3b8..fe056e767b 100644
--- a/gremlin-go/driver/marker.go
+++ b/gremlin-go/driver/marker.go
@@ -19,8 +19,6 @@ under the License.
 
 package gremlingo
 
-import "fmt"
-
 // Marker is used in response
 type Marker interface {
        GetValue() byte
@@ -40,7 +38,7 @@ func (m marker) GetValue() byte {
 
 func Of(value byte) (Marker, error) {
        if value != 0 {
-               return nil, fmt.Errorf("marker value erorr")
+               return nil, newError(err0410UnexpectedMarkerValueError, value)
        }
        return EndOfStream(), nil
 }
diff --git a/gremlin-go/driver/resources/error-messages/en.json 
b/gremlin-go/driver/resources/error-messages/en.json
index 0c8fa6c4bd..bcc54552b8 100644
--- a/gremlin-go/driver/resources/error-messages/en.json
+++ b/gremlin-go/driver/resources/error-messages/en.json
@@ -20,6 +20,7 @@
   "E0407_GRAPH_BINARY_GETSERIALIZERTOWRITE_UNKNOWN_TYPE_ERROR":"E0407: unknown 
data type to serialize %s",
   "E0408_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_TYPE_ERROR": "E0408: unknown 
data type to deserialize 0x%x",
   "E0409_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_CUSTOM_TYPE_ERROR": "E0409: 
unknown custom data type to deserialize %s",
+  "E0410_GRAPH_BINARY_UNEXPECTED_MARKER_VALUE_ERROR": "E0410: failed to 
deserialize marker, expected end-of-stream value 0x00 but read 0x%x",
 
   "E0501_RESPONSE_NO_RESULTSET_ON_DATA_RECEIVE":"E0501: resultSet was not 
created before data was received",
   "E0502_RESPONSE_ERROR": "E0502: error handling response, error message 
'%+v'. statusCode: %d",

Reply via email to