diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/AuthenticationIT.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/AuthenticationIT.cs index cfda97f3d..a7594b180 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/AuthenticationIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/AuthenticationIT.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using FluentAssertions; using Neo4j.Driver.Internal; using Neo4j.Driver.V1; @@ -9,10 +10,22 @@ namespace Neo4j.Driver.IntegrationTests { public class AuthenticationIT : DirectDriverIT { - public AuthenticationIT(ITestOutputHelper output, IntegrationTestFixture fixture) + public AuthenticationIT(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) : base(output, fixture) { } + [Fact] + public void AuthenticationErrorIfWrongAuthToken() + { + Exception exception; + using (var driver = GraphDatabase.Driver(ServerEndPoint, AuthTokens.Basic("fake", "fake"))) + { + exception = Record.Exception(()=>driver.Session()); + } + exception.Should().BeOfType(); + exception.Message.Should().Contain("The client is unauthorized due to authentication failure."); + } + [Fact] public void ShouldProvideRealmWithBasicAuthToken() { diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/DirectDriverIT.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/DirectDriverIT.cs index 349a4c012..e783685d7 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/DirectDriverIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/DirectDriverIT.cs @@ -23,8 +23,8 @@ namespace Neo4j.Driver.IntegrationTests { - [Collection(IntegrationCollection.CollectionName)] - public class DirectDriverIT : IDisposable + [Collection(SAIntegrationCollection.CollectionName)] + public abstract class DirectDriverIT : IDisposable { public static readonly Config DebugConfig = Config.Builder.WithLogger(new DebugLogger {Level = LogLevel.Debug}).ToConfig(); protected ITestOutputHelper Output { get; } @@ -32,7 +32,7 @@ public class DirectDriverIT : IDisposable protected string ServerEndPoint { get; } protected IAuthToken AuthToken { get; } - public DirectDriverIT(ITestOutputHelper output, IntegrationTestFixture fixture) + protected DirectDriverIT(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) { Output = output; Server = fixture.StandAlone; diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/ResultIT.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/ResultIT.cs index f8a79e9bd..262efd561 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/ResultIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/ResultIT.cs @@ -12,7 +12,7 @@ public class ResultIT : DirectDriverIT { private IDriver Driver => Server.Driver; - public ResultIT(ITestOutputHelper output, IntegrationTestFixture fixture) : base(output, fixture) + public ResultIT(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) : base(output, fixture) {} [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionIT.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionIT.cs index eb817e998..92125b65a 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionIT.cs @@ -4,6 +4,7 @@ using Xunit; using Xunit.Abstractions; using System.Linq; +using System.Net.Sockets; namespace Neo4j.Driver.IntegrationTests { @@ -11,10 +12,24 @@ public class SessionIT : DirectDriverIT { private IDriver Driver => Server.Driver; - public SessionIT(ITestOutputHelper output, IntegrationTestFixture fixture) : base(output, fixture) + public SessionIT(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) : base(output, fixture) { } + [Fact] + public void ServiceUnavailableErrorWhenFailedToConn() + { + Exception exception; + using (var driver = GraphDatabase.Driver("bolt://localhost:123")) + { + exception = Record.Exception(()=>driver.Session()); + } + exception.Should().BeOfType(); + exception.Message.Should().Be("Connection with the server breaks due to AggregateException: One or more errors occurred."); + exception.GetBaseException().Should().BeOfType(); + exception.GetBaseException().Message.Should().Contain("No connection could be made because the target machine actively refused it"); + } + [Fact] public void DisallowNewSessionAfterDriverDispose() { diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionResetIT.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionResetIT.cs index 4e423a245..856507e8f 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionResetIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/DirectDriver/SessionResetIT.cs @@ -15,7 +15,7 @@ public class SessionResetIT : DirectDriverIT { private IDriver Driver => Server.Driver; - public SessionResetIT(ITestOutputHelper output, IntegrationTestFixture fixture) + public SessionResetIT(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) : base(output, fixture) { Server.RestartServerWithProcedures(new DirectoryInfo("../../Resources/longRunningStatement.jar").FullName); diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Examples.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Examples.cs index 485374db4..50ba1be51 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Examples.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Examples.cs @@ -27,13 +27,13 @@ namespace Neo4j.Driver.Examples { - [Collection(IntegrationCollection.CollectionName)] + [Collection(SAIntegrationCollection.CollectionName)] public class Examples : IDisposable { private ITestOutputHelper Output { get; } private IDriver Driver { get; } - public Examples(ITestOutputHelper output, IntegrationTestFixture fixture) + public Examples(ITestOutputHelper output, StandAloneIntegrationTestFixture fixture) { Output = output; Driver = fixture.StandAlone.Driver; diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/IntegrationTestFixture.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/IntegrationTestFixture.cs index 89fd01ba1..cc89cf9e0 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/IntegrationTestFixture.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/IntegrationTestFixture.cs @@ -20,37 +20,64 @@ namespace Neo4j.Driver.IntegrationTests { - public class IntegrationTestFixture : IDisposable + public class StandAloneIntegrationTestFixture : IDisposable { public StandAlone StandAlone { get; } - public CausalCluster Cluster { get; } - public IntegrationTestFixture() + public StandAloneIntegrationTestFixture() { try { StandAlone = new StandAlone(); - Cluster = new CausalCluster(); } catch (Exception) { Dispose(); throw; } - } public void Dispose() { StandAlone?.Dispose(); + } + } + + public class CausalClusterIntegrationTestFixture : IDisposable + { + public CausalCluster Cluster { get; } + + public CausalClusterIntegrationTestFixture() + { + try + { + Cluster = new CausalCluster(); + } + catch (Exception) + { + Dispose(); + throw; + } + } + public void Dispose() + { Cluster?.Dispose(); } } [CollectionDefinition(CollectionName)] - public class IntegrationCollection : ICollectionFixture + public class SAIntegrationCollection : ICollectionFixture + { + public const string CollectionName = "StandAloneIntegration"; + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. + } + + [CollectionDefinition(CollectionName)] + public class CCIntegrationCollection : ICollectionFixture { - public const string CollectionName = "Integration"; + public const string CollectionName = "CausalClusterIntegration"; // This class has no code, and is never created. Its purpose is simply // to be the place to apply [CollectionDefinition] and all the // ICollectionFixture<> interfaces. diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/RoutingDriver/RoutingDriverIT.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/RoutingDriver/RoutingDriverIT.cs index b41404782..676b86cd0 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/RoutingDriver/RoutingDriverIT.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/RoutingDriver/RoutingDriverIT.cs @@ -10,8 +10,7 @@ namespace Neo4j.Driver.IntegrationTests { - // If I have a cluster, then I should be able to do the following tests - [Collection(IntegrationCollection.CollectionName)] + [Collection(CCIntegrationCollection.CollectionName)] public class RoutingDriverIT : IDisposable { public static readonly Config DebugConfig = Config.Builder.WithLogger(new DebugLogger { Level = LogLevel.Debug }).ToConfig(); @@ -22,7 +21,7 @@ public class RoutingDriverIT : IDisposable private string RoutingServer => Cluster.AnyCore().BoltRoutingUri.ToString(); private string WrongServer => "bolt+routing://localhost:1234"; - public RoutingDriverIT(ITestOutputHelper output, IntegrationTestFixture fixture) + public RoutingDriverIT(ITestOutputHelper output, CausalClusterIntegrationTestFixture fixture) { Output = output; Cluster = fixture.Cluster; diff --git a/Neo4j.Driver/Neo4j.Driver.Tck.Tests/Tck/ErrorReportingSteps.cs b/Neo4j.Driver/Neo4j.Driver.Tck.Tests/Tck/ErrorReportingSteps.cs index 12478718e..a1534be9b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tck.Tests/Tck/ErrorReportingSteps.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tck.Tests/Tck/ErrorReportingSteps.cs @@ -77,7 +77,7 @@ public void WhenISetUpADriverToAnIncorrectPort() using (var driver = GraphDatabase.Driver("bolt://localhost:1234")) { var ex = Xunit.Record.Exception(() => driver.Session()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); ex = ex.GetBaseException(); ex.Should().BeOfType(); ex.Message.Should().Be("No connection could be made because the target machine actively refused it 127.0.0.1:1234"); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs index 0c96656d3..13c37f90b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/ConnectionPoolTests.cs @@ -57,11 +57,11 @@ public void ShouldAddExternalConnectionHandlerIfNotNull() var mockedHandler = new Mock(); var connectionPool = new ConnectionPool(mock.Object, exteralErrorHandler:mockedHandler.Object); // When - connectionPool.Acquire(); + var conn = connectionPool.Acquire(); //Then - mock.Verify(x=>x.AddConnectionErrorHander(mockedHandler.Object), Times.Once); - mock.Verify(x => x.Init(), Times.Once); + mock.Verify(x => x.ExternalConnectionErrorHander(It.IsAny()), Times.Once); + ((PooledConnection)conn).ExternalConnectionErrorHandler().Should().Be(mockedHandler.Object); } [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MessageResponseHandlerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MessageResponseHandlerTests.cs index 6139049cf..9ab8ba543 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MessageResponseHandlerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/MessageResponseHandlerTests.cs @@ -219,6 +219,30 @@ public void ShouldCreateClientExceptionWhenClassificationContainsClientError(str mrh.Error.Should().BeOfType(); } + [Theory, MemberData("ProtocolErrors")] + public void ShouldCreateProtocolExceptionWhenClassificationContainsProtocolError(string code) + { + var mockResultBuilder = new Mock(); + var mrh = new MessageResponseHandler(); + mrh.EnqueueMessage(new PullAllMessage(), mockResultBuilder.Object); + + mrh.HandleFailureMessage(code, "message"); + mrh.HasError.Should().BeTrue(); + mrh.Error.Should().BeOfType(); + } + + [Theory, MemberData("AuthErrors")] + public void ShouldCreateAuthExceptionWhenClassificationContainsAuthError(string code) + { + var mockResultBuilder = new Mock(); + var mrh = new MessageResponseHandler(); + mrh.EnqueueMessage(new PullAllMessage(), mockResultBuilder.Object); + + mrh.HandleFailureMessage(code, "message"); + mrh.HasError.Should().BeTrue(); + mrh.Error.Should().BeOfType(); + } + [Theory, MemberData("TransientErrors")] public void ShouldCreateTransientExceptionWhenClassificationContainsTransientError(string code) { @@ -291,12 +315,22 @@ public void LogsTheMessageToDebug() } #region Test Data + + public static IEnumerable ProtocolErrors => new[] + { + new object[] {"Neo.ClientError.Request.Invalid"}, + new object[] {"Neo.ClientError.Request.InvalidFormat"} + }; + + public static IEnumerable AuthErrors => new[] + { + new object[] {"Neo.ClientError.Security.Unauthorized"} + }; + public static IEnumerable ClientErrors => new[] { new object[] {"Neo.ClientError.General.ReadOnly"}, new object[] {"Neo.ClientError.LegacyIndex.NoSuchIndex"}, - new object[] {"Neo.ClientError.Request.Invalid"}, - new object[] {"Neo.ClientError.Request.InvalidFormat"}, new object[] {"Neo.ClientError.Schema.ConstraintAlreadyExists"}, new object[] {"Neo.ClientError.Schema.ConstraintVerificationFailure"}, new object[] {"Neo.ClientError.Schema.ConstraintViolation"}, diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/PooledConnectionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/PooledConnectionTests.cs index 4a07cf4c7..8265e6aac 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/PooledConnectionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/PooledConnectionTests.cs @@ -33,7 +33,7 @@ public void ShouldAddPooledConnectionErrorHandler() { var mockedSocketConn = new Mock(); var conn = new PooledConnection(mockedSocketConn.Object); - mockedSocketConn.Verify(x=>x.AddConnectionErrorHander(It.IsAny()), Times.Once); + mockedSocketConn.Verify(x=>x.ExternalConnectionErrorHander(It.IsAny()), Times.Once); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs index 6a1f1f5da..07b0a9fd5 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketClientTests.cs @@ -61,7 +61,7 @@ public async Task ShouldThrowExceptionIfVersionIsNotSupported(byte[] response, s } } - public class SendMethod + public class SendReceiveMethod { [Fact] public async Task ShouldSendMessagesAsExpected() @@ -204,12 +204,12 @@ public async Task ShouldStopClientAndThrowExceptionWhenProtocolErrorOccurs() await harness.Client.Start(); // force to recive an error - messageHandler.Error = new ClientException("Neo.ClientError.Request.Invalid", "Test Message"); + messageHandler.Error = new ProtocolException("Neo.ClientError.Request.Invalid", "Test Message"); // When harness.Client.Send(messages); var ex = Record.Exception(() => harness.Client.Receive(messageHandler)); - ex.Should().BeOfType(); + ex.Should().BeOfType(); harness.MockTcpSocketClient.Verify(x => x.DisconnectAsync(), Times.Once); harness.MockTcpSocketClient.Verify(x => x.Dispose(), Times.Once); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs index 304ab4e6c..82acd8437 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/SocketConnectionTests.cs @@ -16,6 +16,7 @@ // limitations under the License. using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using FluentAssertions; using Moq; @@ -118,7 +119,7 @@ public void ShouldThrowClientErrorIfFailedToConnectToServerWithinTimeout() // When var error = Exception(()=>conn.Init()); // Then - error.Should().BeOfType(); + error.Should().BeOfType(); error.Message.Should().Be("Failed to connect to the server neo4j.com:80 within connection timeout 5000ms"); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/PackerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/PackerTests.cs index d0eb47daf..473a05376 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/PackerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/PackerTests.cs @@ -22,6 +22,7 @@ using Neo4j.Driver.Internal.Connector; using Neo4j.Driver.Internal.Packstream; using Neo4j.Driver.Internal; +using Neo4j.Driver.V1; using Xunit; namespace Neo4j.Driver.Tests @@ -440,7 +441,7 @@ public void ShouldThrowExceptionIfTypeUnknown() { var packer = new PackStream.Packer(null); var ex = Xunit.Record.Exception(() => packer.Pack(new {Name = "Test"})); - ex.Should().BeOfType(); + ex.Should().BeOfType(); } } @@ -564,7 +565,7 @@ public void ShouldThrowExceptionIfSizeIsGreaterThanShortMax() { var packer = new PackStream.Packer(null); var ex = Xunit.Record.Exception(() => packer.PackStructHeader(short.MaxValue +1, 0x1)); - ex.Should().BeOfType(); + ex.Should().BeOfType(); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/UnpackerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/UnpackerTests.cs index 4f0af4245..ec5e91b66 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/UnpackerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/PackStream/UnpackerTests.cs @@ -20,6 +20,7 @@ using Moq; using Neo4j.Driver.Internal.Connector; using Neo4j.Driver.Internal.Packstream; +using Neo4j.Driver.V1; using Xunit; namespace Neo4j.Driver.Tests @@ -52,7 +53,7 @@ public void ShouldThrowExceptionIfMarkerByteNotNull() var unpacker = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => unpacker.UnpackNull()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); } } @@ -92,7 +93,7 @@ public void ShouldThrowExceptionIfMarkerByteNotTrueOrFalse() var unpacker = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => unpacker.UnpackBoolean()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); } } @@ -186,7 +187,7 @@ public void ShouldThrowExceptionIfMarkerByteNotLong() var unpacker = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => unpacker.UnpackLong()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); } } @@ -216,7 +217,7 @@ public void ShouldThrowExceptionIfMarkerByteNotDouble() var unpacker = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => unpacker.UnpackDouble()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); } } @@ -309,7 +310,7 @@ public void ShouldThrowExceptionWhenUnpackString32ReturnsStringSizeLonggerThanIn var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackString()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); mockInput.Verify(x => x.ReadInt(), Times.Once); } @@ -323,7 +324,7 @@ public void ShouldThrowExceptionIfMarkerByteNotString() var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackString()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); } } @@ -397,7 +398,7 @@ public void ShouldThrowExceptionWhenUnpackBytes32ReturnsBytesSizeLonggerThanIntM var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackBytes()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); mockInput.Verify(x => x.ReadInt(), Times.Once); } @@ -411,7 +412,7 @@ public void ShouldThrowExceptionIfMarkerByteNotBytes() var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackBytes()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); } } @@ -480,7 +481,7 @@ public void ShouldThrowExceptionIfMarkerByteNotMap() var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackMapHeader()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); } } @@ -549,7 +550,7 @@ public void ShouldThrowExceptionIfMarkerByteNotList() var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackListHeader()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); } } @@ -620,7 +621,7 @@ public void ShouldThrowExceptionIfMarkerByteNotStruct() var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.UnpackStructHeader()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.ReadByte(), Times.Once); } } @@ -674,7 +675,7 @@ public void ShouldThrowExceptionIfMarkerByteUnDefined() var u = new PackStream.Unpacker(mockInput.Object); var ex = Xunit.Record.Exception(() => u.PeekNextType()); - ex.Should().BeOfType(); + ex.Should().BeOfType(); mockInput.Verify(x => x.PeekByte(), Times.Once); } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryManagerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryManagerTests.cs index 14414d897..1bab778d3 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryManagerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/ClusterDiscoveryManagerTests.cs @@ -137,7 +137,7 @@ public void ShouldProtocolErrorWhenRecordUnparsable() // Then exception.Should().BeOfType(); - exception.Message.Should().Be("Error when parsing `getServers` result: keys (2) did not equal values (1)."); + exception.Message.Should().Be("Error when parsing `getServers` result: keys (2) does not equal to values (1)."); clientMock.Verify(x => x.Dispose(), Times.Once); } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs index bc9855d40..f0e7c09ea 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Routing/LoadBalancerTests.cs @@ -250,7 +250,7 @@ public void ShouldForgetAndTryNextRouterWhenFailedWithConnectionError() if (connection.Server.Address.Equals(uriA.ToString())) // uri2 { throw balancer.CreateClusterPooledConnectionErrorHandler(uriA) - .OnConnectionError(new IOException("failed init")); + .OnConnectionError(new ServiceUnavailableException("failed init")); } if (connection.Server.Address.Equals(uriB.ToString())) // uri2 { @@ -465,7 +465,7 @@ public void ShouldForgetServerWhenFailedToEstablishConn(AccessMode mode) .Callback(() => { throw balancer.CreateClusterPooledConnectionErrorHandler(uri) - .OnConnectionError(new IOException("failed init")); + .OnConnectionError(new ServiceUnavailableException("failed init")); }); // When diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs index af569d79e..208c1afc8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs @@ -123,28 +123,6 @@ public void ShouldNotAllowMoreTransactionsInSessionWhileConnectionClosed() var error = Record.Exception(() => session.BeginTransaction()); error.Should().BeOfType(); } - - [Fact] - public void ShouldNotAllowMoreStatementsInSessionWhileConnectionHasUnrecoverableError() - { - var mockConn = new Mock(); - mockConn.Setup(x => x.HasUnrecoverableError).Returns(true); - var session = new Session(mockConn.Object, null); - - var error = Record.Exception(() => session.Run("lalal")); - error.Should().BeOfType(); - } - - [Fact] - public void ShouldNotAllowMoreTransactionsInSessionWhileConnectionHasUnrecoverableError() - { - var mockConn = new Mock(); - mockConn.Setup(x => x.HasUnrecoverableError).Returns(true); - var session = new Session(mockConn.Object, null); - - var error = Record.Exception(() => session.BeginTransaction()); - error.Should().BeOfType(); - } } public class DisposeMethod diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs b/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs index b618eb0ce..4a66376cc 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/ConnectionPool.cs @@ -51,14 +51,14 @@ public ConnectionPool( ConnectionSettings connectionSettings, ConnectionPoolSettings connectionPoolSettings, ILogger logger, - IConnectionErrorHandler exteralErrorHandler = null) + IConnectionErrorHandler externalErrorHandler = null) : base(logger) { _uri = uri; _connectionSettings = connectionSettings; _idleSessionPoolSize = connectionPoolSettings.MaxIdleSessionPoolSize; - _externalErrorHandler = exteralErrorHandler; + _externalErrorHandler = externalErrorHandler; _logger = logger; } @@ -87,7 +87,7 @@ private IPooledConnection CreateNewPooledConnection() : new PooledConnection(new SocketConnection(_uri, _connectionSettings, _logger), Release); if (_externalErrorHandler != null) { - conn.AddConnectionErrorHander(_externalErrorHandler); + conn.ExternalConnectionErrorHander(_externalErrorHandler); } conn.Init(); return conn; @@ -264,10 +264,5 @@ internal interface IPooledConnection : IConnection /// Try to reset the connection to a clean state to prepare it for a new session. /// void ClearConnection(); - - /// - /// Return true if unrecoverable error has been received on this connection, otherwise false. - /// - bool HasUnrecoverableError { get; } } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedInputStream.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedInputStream.cs index 729fbed76..d4622b65c 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedInputStream.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedInputStream.cs @@ -127,7 +127,7 @@ private void ReadSpecifiedSize(byte[] buffer) var numberOfbytesRead = _tcpSocketClient.ReadStream.Read(buffer); if (numberOfbytesRead != buffer.Length) { - throw new Neo4jException($"Expect {buffer.Length}, but got {numberOfbytesRead}"); + throw new ProtocolException($"Expect {buffer.Length}, but got {numberOfbytesRead}"); } _logger?.Trace("S: ", buffer, 0, buffer.Length); @@ -139,7 +139,7 @@ public void ReadMessageTail() ReadSpecifiedSize(_headTailBuffer); if (_headTailBuffer.Equals(Tail)) { - throw new Neo4jException("Not chunked correctly"); + throw new ProtocolException("Not chunked correctly"); } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedOutputStream.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedOutputStream.cs index 0cf613c77..afe06acce 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedOutputStream.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/ChunkedOutputStream.cs @@ -134,7 +134,7 @@ private void Ensure(int size) private void WriteBytesInChunk(byte[] bytes, int offset = 0, int? length = null) { - Throw.ArgumentException.IfNotTrue(_isInChunk, nameof(_isInChunk)); + Throw.ArgumentOutOfRangeException.IfFalse(_isInChunk, nameof(_isInChunk)); Array.Copy(bytes, offset, _buffer, _pos, length ?? bytes.Length); _pos += length ?? bytes.Length; _chunkLength += length ?? bytes.Length; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs index 8f40ebc5a..62ad737b4 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnection.cs @@ -57,9 +57,10 @@ internal interface IConnection : IDisposable void Close(); /// - /// Adds an extra error handler that you wish to be called back when a consreponding error is received + /// Adds a external error handler that you wish to be called backa when a consreponding error is received. + /// The external error handler will be called after internal error handlers if any has been registered internally. /// /// The extra error handler to add. - void AddConnectionErrorHander(IConnectionErrorHandler handler); + void ExternalConnectionErrorHander(IConnectionErrorHandler handler); } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnectionErrorHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnectionErrorHandler.cs index 62b0b2bf7..b6e364215 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnectionErrorHandler.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/IConnectionErrorHandler.cs @@ -15,13 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. using System; +using System.IO; using Neo4j.Driver.V1; namespace Neo4j.Driver.Internal.Connector { internal interface IConnectionErrorHandler { + /// + /// Define what to do when a connection error happens when sending and receiving data + /// + /// + /// Exception OnConnectionError(Exception e); - Neo4jException OnNeo4jError(Neo4jException e); + /// + /// Define what to do when a failure received from the server after data received from the server + /// + /// + /// + Neo4jException OnServerError(Neo4jException e); } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/MessageResponseHandler.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/MessageResponseHandler.cs index d535d63ce..856551123 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/MessageResponseHandler.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/MessageResponseHandler.cs @@ -35,8 +35,7 @@ internal class MessageResponseHandler : IMessageResponseHandler public Neo4jException Error { get; set; } public bool HasError => Error != null; - public bool HasProtocolViolationError - => HasError && Error.Code.ToLowerInvariant().Contains("clienterror.request"); + public bool HasProtocolViolationError => HasError && Error is ProtocolException; internal Queue ResultBuilders => new Queue(_resultBuilders); internal Queue SentMessages => new Queue(_unhandledMessages); @@ -86,10 +85,14 @@ public void HandleFailureMessage(string code, string message) switch (classification) { case "clienterror": - if (code.Equals(AuthenticationException.ErrorCode)) + if (AuthenticationException.IsAuthenticationError(code)) { Error = new AuthenticationException(message); } + else if (ProtocolException.IsProtocolError(code)) + { + Error = new ProtocolException(code, message); + } else { Error = new ClientException(code, message); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs index c0c3def8d..0fa1d6648 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/PooledConnection.cs @@ -16,6 +16,8 @@ // limitations under the License. using System; using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; using Neo4j.Driver.Internal.Result; using Neo4j.Driver.V1; @@ -25,6 +27,7 @@ internal class PooledConnection : IPooledConnection { private readonly Action _releaseAction; private readonly IConnection _connection; + private IConnectionErrorHandler _externalErrorHandler; public PooledConnection(IConnection connection, Action releaseAction = null) { @@ -32,7 +35,7 @@ public PooledConnection(IConnection connection, Action releaseAction = nul _releaseAction = releaseAction ?? (x => { }); //Adds call back error handler - AddConnectionErrorHander(new PooledConnectionErrorHandler(OnNeo4jError)); + _connection.ExternalConnectionErrorHander(new PooledConnectionErrorHandler(OnServerError, OnConnectionError)); } public Guid Id { get; } = Guid.NewGuid(); @@ -93,11 +96,6 @@ public void Close() _connection.Close(); } - public void AddConnectionErrorHander(IConnectionErrorHandler handler) - { - _connection.AddConnectionErrorHander(handler); - } - /// /// Disposing a pooled connection will try to release the connection resource back to pool /// @@ -106,9 +104,13 @@ public void Dispose() _releaseAction(Id); } - public bool HasUnrecoverableError { private set; get; } + /// + /// Return true if unrecoverable error has been received on this connection, otherwise false. + /// The connection that has been marked as has unrecoverable errors will be eventally closed when returning back to the pool. + /// + internal bool HasUnrecoverableError { private set; get; } - private Neo4jException OnNeo4jError(Neo4jException error) + private Neo4jException OnServerError(Neo4jException error) { if (error.IsRecoverableError()) { @@ -118,30 +120,55 @@ private Neo4jException OnNeo4jError(Neo4jException error) { HasUnrecoverableError = true; } - return error; + return _externalErrorHandler == null ? error: _externalErrorHandler.OnServerError(error); + } + + private Exception OnConnectionError(Exception error) + { + // No connection error is recoverable + HasUnrecoverableError = true; + + if ( error is IOException || error is SocketException || + error.GetBaseException() is IOException || error.GetBaseException() is SocketException ) + { + error = new ServiceUnavailableException( + $"Connection with the server breaks due to {error.GetType().Name}: {error.Message}", error); + } + + return _externalErrorHandler == null ? error: _externalErrorHandler.OnConnectionError(error); + } + + public void ExternalConnectionErrorHander(IConnectionErrorHandler handler) + { + _externalErrorHandler = handler; + } + + internal IConnectionErrorHandler ExternalConnectionErrorHandler() + { + return _externalErrorHandler; } internal class PooledConnectionErrorHandler : IConnectionErrorHandler { - private readonly Func _onNeo4jErrorFunc; + private readonly Func _onServerErrorFunc; private readonly Func _onConnErrorFunc; public PooledConnectionErrorHandler( - Func onNeo4JErrorFunc, - Func onConnectionErrorFunc = null) + Func onServerErrorFunc, + Func onConnectionErrorFunc) { - _onNeo4jErrorFunc = onNeo4JErrorFunc; + _onServerErrorFunc = onServerErrorFunc; _onConnErrorFunc = onConnectionErrorFunc; } public Exception OnConnectionError(Exception e) { - return _onConnErrorFunc == null ? e : _onConnErrorFunc.Invoke(e); + return _onConnErrorFunc.Invoke(e); } - public Neo4jException OnNeo4jError(Neo4jException e) + public Neo4jException OnServerError(Neo4jException e) { - return _onNeo4jErrorFunc.Invoke(e); + return _onServerErrorFunc.Invoke(e); } } } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs index a8c04aacb..37db29aad 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketClient.cs @@ -100,12 +100,21 @@ private async Task Stop() public void Send(IEnumerable messages) { - foreach (var message in messages) + try + { + foreach (var message in messages) + { + _writer.Write(message); + _logger?.Debug("C: ", message); + } + _writer.Flush(); + } + catch (Exception ex) { - _writer.Write(message); - _logger?.Debug("C: ", message); + _logger?.Info($"Unable to send message to server {_uri}, connection will be terminated. ", ex); + Task.Run(() => Stop()).Wait(); + throw; } - _writer.Flush(); } public bool IsOpen { get; private set; } @@ -126,12 +135,13 @@ public void ReceiveOne(IMessageResponseHandler responseHandler) } catch (Exception ex) { - _logger?.Error("Unable to unpack message from server, connection has been terminated.", ex); + _logger?.Info($"Unable to read message from server {_uri}, connection will be terminated.", ex); Task.Run(() => Stop()).Wait(); throw; } if (responseHandler.HasProtocolViolationError) { + _logger?.Info($"Received bolt protocol error from server {_uri}, connection will be terminated.", responseHandler.Error); Task.Run(() => Stop()).Wait(); throw responseHandler.Error; } diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs index 395205428..4269cbded 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/SocketConnection.cs @@ -16,6 +16,7 @@ // limitations under the License. using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using Neo4j.Driver.Internal.Messaging; @@ -39,7 +40,7 @@ internal class SocketConnection : IConnection private readonly object _syncLock = new object(); private readonly ILogger _logger; - private readonly IList _handlers = new List(); + private IConnectionErrorHandler _externalErrorHandler; public SocketConnection(Uri uri, ConnectionSettings connectionSettings, ILogger logger) : this(new SocketClient(uri, connectionSettings.EncryptionManager, logger), @@ -74,7 +75,7 @@ public void Init() var connected = Task.Run(() => _client.Start()).Wait(_connectionTimeout); if (!connected) { - throw new ClientException($"Failed to connect to the server {Server.Address} within connection timeout {_connectionTimeout.TotalMilliseconds}ms"); + throw new IOException($"Failed to connect to the server {Server.Address} within connection timeout {_connectionTimeout.TotalMilliseconds}ms"); } } catch (Exception error) @@ -205,11 +206,6 @@ public void Close() Dispose(); } - public void AddConnectionErrorHander(IConnectionErrorHandler handler) - { - _handlers.Add(handler); - } - public void Dispose() { Dispose(true); @@ -238,7 +234,7 @@ private void AssertNoServerFailure() { var error = _responseHandler.Error; - error = OnNeo4jError(error); + error = OnServerError(error); _responseHandler.Error = null; _interrupted = false; @@ -248,20 +244,12 @@ private void AssertNoServerFailure() private Exception OnConnectionError(Exception e) { - foreach (var handler in _handlers) - { - e = handler.OnConnectionError(e); - } - return e; + return _externalErrorHandler == null ? e : _externalErrorHandler.OnConnectionError(e); } - public Neo4jException OnNeo4jError(Neo4jException e) + public Neo4jException OnServerError(Neo4jException e) { - foreach (var handler in _handlers) - { - e = handler.OnNeo4jError(e); - } - return e; + return _externalErrorHandler == null ? e : _externalErrorHandler.OnServerError(e); } private void Enqueue(IRequestMessage requestMessage, IMessageResponseCollector resultBuilder = null, IRequestMessage requestStreamingMessage = null) @@ -300,5 +288,10 @@ private void EnsureNotInterrupted() } } } + + public void ExternalConnectionErrorHander(IConnectionErrorHandler handler) + { + _externalErrorHandler = handler; + } } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs index 021c0f56a..d0c587d96 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs @@ -28,7 +28,6 @@ internal class TcpSocketClient : ITcpSocketClient { private readonly TcpClient _client; private Stream _stream; - private ILogger _logger; private readonly EncryptionManager _encryptionManager; @@ -36,7 +35,6 @@ public TcpSocketClient(EncryptionManager encryptionManager, ILogger logger = nul { _encryptionManager = encryptionManager; _client = new TcpClient(); - _logger = logger; } public Stream ReadStream => _stream; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/Throw.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/Throw.cs index 995718a29..3fcb92ed3 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/Throw.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Extensions/Throw.cs @@ -36,7 +36,7 @@ public static void If(Func func, string paramName) } } - public static class ArgumentException + public static class ProtocolException { public static void IfNotEqual(int first, int second, string firstParam, string secondParam) { @@ -54,13 +54,13 @@ internal static void IfNotEqual(object first, object second, string firstParam, public static void If(Func func, object first, object second, string firstParam, string secondParam) { if(func()) - throw new System.ArgumentException($"{firstParam} ({first}) did not equal {secondParam} ({second})"); + throw new V1.ProtocolException($"{firstParam} ({first}) does not equal to {secondParam} ({second})"); } - public static void IfNotTrue(bool value, string nameofValue) + public static void IfFalse(bool value, string nameofValue) { if(!value) - throw new System.ArgumentException($"Expecting {nameofValue} to be true, however the value is false"); + throw new V1.ProtocolException($"Expecting {nameofValue} to be true, however the value is false"); } } @@ -77,6 +77,11 @@ public static void IfValueGreaterThan(long value, long limit, string parameterNa if(value > limit) throw new System.ArgumentOutOfRangeException(parameterName, value, $"Value given ({value}) cannot be greater than {limit}."); } + public static void IfFalse(bool value, string nameofValue) + { + if (!value) + throw new System.ArgumentOutOfRangeException($"Expecting {nameofValue} to be true, however the value is false"); + } } } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStream.cs b/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStream.cs index 834bc4cdf..31459c233 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStream.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStream.cs @@ -19,6 +19,7 @@ using System.Globalization; using Neo4j.Driver.Internal.Connector; using Neo4j.Driver.Internal.Messaging; +using Neo4j.Driver.V1; namespace Neo4j.Driver.Internal.Packstream { @@ -218,7 +219,7 @@ public void Pack(object value) } else { - throw new ArgumentOutOfRangeException(nameof(value), value.GetType(), + throw new ProtocolException( $"Cannot understand {nameof(value)} with type {value.GetType().FullName}"); } } @@ -345,7 +346,7 @@ public void PackStructHeader(int size, byte signature) _out.Write(STRUCT_16, BitConverter.GetBytes((short) size)).Write(signature); } else - throw new ArgumentOutOfRangeException(nameof(size), size, + throw new ProtocolException( $"Structures cannot have more than {short.MaxValue} fields"); } } @@ -365,7 +366,7 @@ public object UnpackNull() byte markerByte = _in.ReadByte(); if (markerByte != NULL) { - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, + throw new ProtocolException( $"Expected a null, but got: 0x{(markerByte & 0xFF).ToString("X2")}"); } return null; @@ -381,7 +382,7 @@ public bool UnpackBoolean() case FALSE: return false; default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, + throw new ProtocolException( $"Expected a boolean, but got: 0x{(markerByte & 0xFF).ToString("X2")}"); } } @@ -404,7 +405,7 @@ public long UnpackLong() case INT_64: return _in.ReadLong(); default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, + throw new ProtocolException( $"Expected an integer, but got: 0x{markerByte.ToString("X2")}"); } } @@ -416,7 +417,7 @@ public double UnpackDouble() { return _in.ReadDouble(); } - throw new ArgumentOutOfRangeException( nameof(markerByte), markerByte, + throw new ProtocolException( $"Expected a double, but got: 0x{markerByte.ToString("X2")}"); } @@ -450,12 +451,12 @@ public byte[] UnpackBytes() } else { - throw new ArgumentOutOfRangeException(nameof(size), size, + throw new ProtocolException( $"BYTES_32 {size} too long for PackStream"); } } default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, + throw new ProtocolException( $"Expected binary data, but got: 0x{(markerByte & 0xFF).ToString("X2")}"); } } @@ -489,11 +490,11 @@ private byte[] UnpackUtf8(byte markerByte) { return UnpackBytes((int) size); } - throw new ArgumentOutOfRangeException(nameof(size), size, + throw new ProtocolException( $"STRING_32 {size} too long for PackStream"); } default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, + throw new ProtocolException( $"Expected a string, but got: 0x{(markerByte & 0xFF).ToString("X2")}"); } } @@ -517,8 +518,8 @@ public long UnpackMapHeader() case MAP_32: return UnpackUint32(); default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, - $"Expected a map, but got: {markerByte.ToString("X2")}"); + throw new ProtocolException( + $"Expected a map, but got: 0x{markerByte.ToString("X2")}"); } } @@ -541,8 +542,8 @@ public long UnpackListHeader() case LIST_32: return UnpackUint32(); default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, - $"Expected a list, but got: {(markerByte & 0xFF).ToString("X2")}"); + throw new ProtocolException( + $"Expected a list, but got: 0x{(markerByte & 0xFF).ToString("X2")}"); } } @@ -568,8 +569,8 @@ public long UnpackStructHeader() case STRUCT_16: return UnpackUint16(); default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, - $"Expected a struct, but got: {markerByte.ToString("X2")}"); + throw new ProtocolException( + $"Expected a struct, but got: 0x{markerByte.ToString("X2")}"); } } @@ -627,7 +628,7 @@ public PackType PeekNextType() case INT_64: return PackType.Integer; default: - throw new ArgumentOutOfRangeException(nameof(markerByte), markerByte, + throw new ProtocolException( $"Unknown type 0x{markerByte.ToString("X2")}"); } } @@ -658,7 +659,6 @@ internal interface IWriter internal interface IReader { -// bool HasNext(); void Read(IMessageResponseHandler responseHandler); } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStreamMessageFormatV1.cs b/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStreamMessageFormatV1.cs index f002fcd1f..949122506 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStreamMessageFormatV1.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/PackStream/PackStreamMessageFormatV1.cs @@ -67,7 +67,7 @@ public void Read(IMessageResponseHandler responseHandler) UnpackIgnoredMessage(responseHandler); break; default: - throw new IOException("Unknown requestMessage type: " + type); + throw new ProtocolException("Unknown requestMessage type: " + type); } UnPackMessageTail(); } @@ -98,13 +98,13 @@ public object UnpackValue() switch (_unpacker.UnpackStructSignature()) { case NODE: - Throw.ArgumentException.IfNotEqual(NodeFields, size, nameof(NodeFields), nameof(size)); + Throw.ProtocolException.IfNotEqual(NodeFields, size, nameof(NodeFields), nameof(size)); return UnpackNode(); case RELATIONSHIP: - Throw.ArgumentException.IfNotEqual(RelationshipFields, size, nameof(RelationshipFields), nameof(size)); + Throw.ProtocolException.IfNotEqual(RelationshipFields, size, nameof(RelationshipFields), nameof(size)); return UnpackRelationship(); case PATH: - Throw.ArgumentException.IfNotEqual(PathFields, size, nameof(PathFields), nameof(size)); + Throw.ProtocolException.IfNotEqual(PathFields, size, nameof(PathFields), nameof(size)); return UnpackPath(); } break; @@ -118,8 +118,8 @@ private IPath UnpackPath() var uniqNodes = new INode[(int) _unpacker.UnpackListHeader()]; for(int i = 0; i < uniqNodes.Length; i ++) { - Throw.ArgumentException.IfNotEqual(NodeFields, _unpacker.UnpackStructHeader(), nameof(NodeFields), $"received{nameof(NodeFields)}"); - Throw.ArgumentException.IfNotEqual(NODE, _unpacker.UnpackStructSignature(),nameof(NODE), $"received{nameof(NODE)}"); + Throw.ProtocolException.IfNotEqual(NodeFields, _unpacker.UnpackStructHeader(), nameof(NodeFields), $"received{nameof(NodeFields)}"); + Throw.ProtocolException.IfNotEqual(NODE, _unpacker.UnpackStructSignature(),nameof(NODE), $"received{nameof(NODE)}"); uniqNodes[i]=UnpackNode(); } @@ -127,8 +127,8 @@ private IPath UnpackPath() var uniqRels = new Relationship[(int)_unpacker.UnpackListHeader()]; for (int i = 0; i < uniqRels.Length; i++) { - Throw.ArgumentException.IfNotEqual( UnboundRelationshipFields, _unpacker.UnpackStructHeader(), nameof(UnboundRelationshipFields), $"received{nameof(UnboundRelationshipFields)}"); - Throw.ArgumentException.IfNotEqual(UNBOUND_RELATIONSHIP, _unpacker.UnpackStructSignature(), nameof(UNBOUND_RELATIONSHIP), $"received{nameof(UNBOUND_RELATIONSHIP)}"); + Throw.ProtocolException.IfNotEqual( UnboundRelationshipFields, _unpacker.UnpackStructHeader(), nameof(UnboundRelationshipFields), $"received{nameof(UnboundRelationshipFields)}"); + Throw.ProtocolException.IfNotEqual(UNBOUND_RELATIONSHIP, _unpacker.UnpackStructSignature(), nameof(UNBOUND_RELATIONSHIP), $"received{nameof(UNBOUND_RELATIONSHIP)}"); var urn = _unpacker.UnpackLong(); var relType = _unpacker.UnpackString(); var props = UnpackMap(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs index 168343268..e766b42e8 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/Record.cs @@ -29,7 +29,7 @@ internal class Record : IRecord public Record(Listkeys, object[] values) { - Throw.ArgumentException.IfNotEqual(keys.Count, values.Length, nameof(keys), nameof(values)); + Throw.ProtocolException.IfNotEqual(keys.Count, values.Length, nameof(keys), nameof(values)); var valueKeys = new Dictionary(); diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs index eb718844a..8ba25be04 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Routing/LoadBalancer.cs @@ -202,17 +202,16 @@ private IRoutingTable Rediscovery(IPooledConnection conn) private Exception OnConnectionError(Exception e, Uri uri) { - if (e is SecurityException || e is ProtocolException) + if (e is ServiceUnavailableException) { - return e; + _logger?.Info($"Server at {uri} is no longer available due to error: {e.Message}."); + Forget(uri); + return new SessionExpiredException($"Server at {uri} is no longer available due to error: {e.Message}.", e); } - - _logger?.Info($"Server at {uri} is no longer available due to error: {e.Message}."); - Forget(uri); - return new SessionExpiredException($"Server at {uri} is no longer available due to error: {e.Message}.", e); + return e; } - private Neo4jException OnNeo4jError(Neo4jException error, Uri uri) + private Neo4jException OnServerError(Neo4jException error, Uri uri) { if (error.IsClusterNotALeaderError()) { @@ -233,18 +232,18 @@ private Neo4jException OnNeo4jError(Neo4jException error, Uri uri) internal ClusterPooledConnectionErrorHandler CreateClusterPooledConnectionErrorHandler(Uri uri) { - return new ClusterPooledConnectionErrorHandler(x => OnConnectionError(x, uri), x => OnNeo4jError(x, uri)); + return new ClusterPooledConnectionErrorHandler(x => OnConnectionError(x, uri), x => OnServerError(x, uri)); } internal class ClusterPooledConnectionErrorHandler : IConnectionErrorHandler { private Func _onConnectionErrorFunc; - private readonly Func _onNeo4jErrorFunc; + private readonly Func _onServerErrorFunc; - public ClusterPooledConnectionErrorHandler(Func onConnectionErrorFuncFunc, Func onNeo4JErrorFuncFunc) + public ClusterPooledConnectionErrorHandler(Func onConnectionErrorFuncFunc, Func onServerErrorFuncFunc) { _onConnectionErrorFunc = onConnectionErrorFuncFunc; - _onNeo4jErrorFunc = onNeo4JErrorFuncFunc; + _onServerErrorFunc = onServerErrorFuncFunc; } public Exception OnConnectionError(Exception e) @@ -252,9 +251,9 @@ public Exception OnConnectionError(Exception e) return _onConnectionErrorFunc.Invoke(e); } - public Neo4jException OnNeo4jError(Neo4jException e) + public Neo4jException OnServerError(Neo4jException e) { - return _onNeo4jErrorFunc.Invoke(e); + return _onServerErrorFunc.Invoke(e); } } diff --git a/Neo4j.Driver/Neo4j.Driver/V1/Neo4jException.cs b/Neo4j.Driver/Neo4j.Driver/V1/Neo4jException.cs index 8488dca0f..bd262acaf 100644 --- a/Neo4j.Driver/Neo4j.Driver/V1/Neo4jException.cs +++ b/Neo4j.Driver/Neo4j.Driver/V1/Neo4jException.cs @@ -16,6 +16,7 @@ // limitations under the License. using System; using System.Runtime.Serialization; +using Neo4j.Driver.Internal.Routing; namespace Neo4j.Driver.V1 { @@ -168,10 +169,22 @@ public SessionExpiredException(string message, Exception innerException) : base( [DataContract] public class ProtocolException : Neo4jException { + private const string ErrorCodeInvalid = "Neo.ClientError.Request.Invalid"; + private const string ErrorCodeInvalidFormat = "Neo.ClientError.Request.InvalidFormat"; + + internal static bool IsProtocolError(string code) + { + return code.Equals(ErrorCodeInvalid) || code.Equals(ErrorCodeInvalidFormat); + } + public ProtocolException(string message) : base(message) { } + public ProtocolException(string code, string message) : base(code, message) + { + } + public ProtocolException(string message, Exception internaException) : base(message, internaException) { } @@ -204,7 +217,12 @@ public SecurityException(string message, Exception internaException) : base(mess [DataContract] public class AuthenticationException : SecurityException { - internal const string ErrorCode = "Neo.ClientError.Security.Unauthorized"; + private const string ErrorCode = "Neo.ClientError.Security.Unauthorized"; + + internal static bool IsAuthenticationError(string code) + { + return code.Equals(ErrorCode); + } public AuthenticationException(string message) : base(ErrorCode, message) {