This is an automated email from the ASF dual-hosted git repository. Cole-Greer pushed a commit to branch GLVBehaviouralAlignment in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 4846117c33d7873e6b82c1e4f5c823aaaf651d0d Author: Cole Greer <[email protected]> AuthorDate: Tue Jun 2 12:37:55 2026 -0700 Wrap malformed-response deserialization errors in gremlin-dotnet - Add ResponseDeserializationException and wrap response-deserialization failures so a malformed response yields a single consistent exception type instead of a non-deterministic IOException/KeyNotFoundException (tinkerpop-9t5). - Leave transport-level HttpIOException (premature connection close) unwrapped so the partial-content-close path keeps its transport-error semantics. - Update behavioral test assertions. --- .../src/Gremlin.Net/Driver/Connection.cs | 8 +++++ .../Exceptions/ResponseDeserializationException.cs | 42 ++++++++++++++++++++++ .../Driver/ClientBehaviorIntegrationTests.cs | 14 +++----- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs index e5c94611d4..89419592f7 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs @@ -30,6 +30,7 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using Gremlin.Net.Driver.Exceptions; using Gremlin.Net.Driver.Messages; using Gremlin.Net.Process; using Gremlin.Net.Process.Traversal; @@ -264,6 +265,13 @@ namespace Gremlin.Net.Driver } channel.Writer.Complete(); } + catch (Exception ex) when (ex is not ResponseException + and not OperationCanceledException + and not HttpIOException) + { + channel.Writer.Complete( + new ResponseDeserializationException(ex)); + } catch (Exception ex) { channel.Writer.Complete(ex); diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Exceptions/ResponseDeserializationException.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Exceptions/ResponseDeserializationException.cs new file mode 100644 index 0000000000..01912507c6 --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Exceptions/ResponseDeserializationException.cs @@ -0,0 +1,42 @@ +#region License + +/* + * 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. + */ + +#endregion + +using System; + +namespace Gremlin.Net.Driver.Exceptions +{ + /// <summary> + /// The exception that is thrown when the driver fails to deserialize a response received from Gremlin Server. + /// </summary> + public class ResponseDeserializationException : Exception + { + /// <summary> + /// Initializes a new instance of the <see cref="ResponseDeserializationException" /> class. + /// </summary> + /// <param name="innerException">The exception that caused the deserialization failure.</param> + public ResponseDeserializationException(Exception innerException) + : base("Failed to deserialize the response received from Gremlin Server.", innerException) + { + } + } +} diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ClientBehaviorIntegrationTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ClientBehaviorIntegrationTests.cs index 03977423b2..cb346adb20 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ClientBehaviorIntegrationTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/ClientBehaviorIntegrationTests.cs @@ -173,17 +173,12 @@ namespace Gremlin.Net.IntegrationTest.Driver { SkipIfServerUnavailable(); - // NOTE: the driver surfaces a low-level exception (no Gremlin-aware wrapping,). - // The exact type is non-deterministic for malformed bytes: either an IOException - // at the stream layer or a KeyNotFoundException from the GraphBinary deserializer, - // depending on how the chunk is read. - var ex = await Assert.ThrowsAnyAsync<Exception>(async () => + var ex = await Assert.ThrowsAsync<ResponseDeserializationException>(async () => { var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinMalformedResponse); await resultSet.ToListAsync(); }); - Assert.True(ex is System.IO.IOException or KeyNotFoundException, - $"Unexpected exception type: {ex.GetType().FullName}"); + Assert.NotNull(ex.InnerException); // Recovery var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex); @@ -196,12 +191,13 @@ namespace Gremlin.Net.IntegrationTest.Driver { SkipIfServerUnavailable(); - var ex = await Assert.ThrowsAsync<System.IO.IOException>(async () => + var ex = await Assert.ThrowsAsync<ResponseDeserializationException>(async () => { var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinEmptyBody); await resultSet.ToListAsync(); }); - Assert.Contains("Unexpected end of stream", ex.Message); + Assert.IsType<System.IO.IOException>(ex.InnerException); + Assert.Contains("Unexpected end of stream", ex.InnerException!.Message); // Recovery var resultSet = await _client!.SubmitAsync<dynamic>(SocketServerConstants.GremlinSingleVertex);
