From d613ba3d2ad17bedd1d65bfc486f00707b847070 Mon Sep 17 00:00:00 2001 From: William Chong Date: Wed, 8 Jan 2025 16:52:57 +0400 Subject: [PATCH] Publish actions --- .github/workflows/base.yml | 2 +- .github/workflows/publish.yml | 393 ++++---- samples/Directory.Build.props | 2 +- samples/Samples.sln | 19 +- samples/appending-events/Program.cs | 14 +- .../appending-events/appending-events.csproj | 3 +- samples/connecting-to-a-cluster/Program.cs | 18 +- .../connecting-to-a-cluster.csproj | 3 +- .../connecting-to-a-single-node/Program.cs | 27 +- .../connecting-to-a-single-node.csproj | 3 +- samples/diagnostics/Program.cs | 9 +- samples/diagnostics/diagnostics.csproj | 5 +- samples/persistent-subscriptions/Program.cs | 36 +- .../persistent-subscriptions.csproj | 3 +- samples/projection-management/Program.cs | 52 +- .../projection-management.csproj | 3 +- samples/quick-start/Program.cs | 6 +- samples/quick-start/quick-start.csproj | 3 +- samples/reading-events/Program.cs | 34 +- samples/reading-events/reading-events.csproj | 3 +- samples/secure-with-tls/Program.cs | 4 +- .../secure-with-tls/secure-with-tls.csproj | 2 +- samples/server-side-filtering/Program.cs | 16 +- .../server-side-filtering.csproj | 3 +- .../Controllers/EventStoreController.cs | 8 +- .../Startup.cs | 4 +- .../setting-up-dependency-injection.csproj | 2 +- samples/subscribing-to-streams/Program.cs | 24 +- .../subscribing-to-streams.csproj | 3 +- samples/user-certificates/Program.cs | 4 +- .../user-certificates.csproj | 2 +- ...ore.Client.Extensions.OpenTelemetry.csproj | 20 - .../TracerProviderBuilderExtensions.cs | 19 - .../Core/Certificates/X509Certificates.cs | 114 --- .../Core/ChannelBaseExtensions.cs | 10 - src/EventStore.Client/Core/ChannelCache.cs | 146 --- src/EventStore.Client/Core/ChannelFactory.cs | 106 -- src/EventStore.Client/Core/ChannelInfo.cs | 9 - src/EventStore.Client/Core/ChannelSelector.cs | 24 - src/EventStore.Client/Core/ClusterMessage.cs | 28 - .../Common/AsyncStreamReaderExtensions.cs | 29 - .../Core/Common/Constants.cs | 62 -- .../Diagnostics/ActivitySourceExtensions.cs | 90 -- .../ActivityTagsCollectionExtensions.cs | 37 - .../Diagnostics/Core/ActivityExtensions.cs | 52 - .../Common/Diagnostics/Core/ActivityStatus.cs | 13 - .../Core/ActivityStatusCodeHelper.cs | 24 - .../Core/ActivityTagsCollectionExtensions.cs | 25 - .../Diagnostics/Core/ExceptionExtensions.cs | 25 - .../Core/Telemetry/TelemetryTags.cs | 35 - .../Core/Tracing/TracingConstants.cs | 10 - .../Core/Tracing/TracingMetadata.cs | 32 - .../Diagnostics/EventMetadataExtensions.cs | 84 -- .../EventStoreClientDiagnostics.cs | 8 - .../Diagnostics/Telemetry/TelemetryTags.cs | 12 - .../Diagnostics/Tracing/TracingConstants.cs | 10 - .../Core/Common/EnumerableTaskExtensions.cs | 11 - .../Core/Common/EpochExtensions.cs | 25 - .../Core/Common/EventStoreCallOptions.cs | 74 -- .../Core/Common/MetadataExtensions.cs | 29 - .../Core/Common/Shims/Index.cs | 110 -- .../Core/Common/Shims/IsExternalInit.cs | 10 - .../Core/Common/Shims/Range.cs | 75 -- .../Core/Common/Shims/TaskCompletionSource.cs | 11 - .../Core/DefaultRequestVersionHandler.cs | 14 - .../Core/EndPointExtensions.cs | 41 - src/EventStore.Client/Core/EventData.cs | 65 -- src/EventStore.Client/Core/EventRecord.cs | 83 -- .../Core/EventStoreClientBase.cs | 151 --- .../EventStoreClientConnectivitySettings.cs | 128 --- .../Core/EventStoreClientOperationOptions.cs | 46 - ...entStoreClientSettings.ConnectionString.cs | 410 -------- .../Core/EventStoreClientSettings.cs | 61 -- src/EventStore.Client/Core/EventTypeFilter.cs | 145 --- .../Core/Exceptions/AccessDeniedException.cs | 22 - .../ConnectionStringParseException.cs | 15 - .../ConnectionString/DuplicateKeyException.cs | 13 - .../InvalidClientCertificateException.cs | 14 - .../ConnectionString/InvalidHostException.cs | 13 - .../InvalidKeyValuePairException.cs | 13 - .../InvalidSchemeException.cs | 14 - .../InvalidSettingException.cs | 12 - .../InvalidUserCredentialsException.cs | 13 - .../ConnectionString/NoSchemeException.cs | 12 - .../Core/Exceptions/DiscoveryException.cs | 33 - .../Exceptions/NotAuthenticatedException.cs | 16 - .../Core/Exceptions/NotLeaderException.cs | 26 - ...equiredMetadataPropertyMissingException.cs | 18 - .../Exceptions/ScavengeNotFoundException.cs | 23 - .../Core/Exceptions/StreamDeletedException.cs | 24 - .../Exceptions/StreamNotFoundException.cs | 23 - .../Core/Exceptions/UserNotFoundException.cs | 23 - .../WrongExpectedVersionException.cs | 66 -- src/EventStore.Client/Core/FromAll.cs | 100 -- src/EventStore.Client/Core/FromStream.cs | 100 -- .../Core/GossipChannelSelector.cs | 99 -- .../Core/GrpcGossipClient.cs | 30 - .../Core/GrpcServerCapabilitiesClient.cs | 75 -- src/EventStore.Client/Core/HashCode.cs | 37 - src/EventStore.Client/Core/HttpFallback.cs | 143 --- .../Core/IChannelSelector.cs | 14 - src/EventStore.Client/Core/IEventFilter.cs | 21 - src/EventStore.Client/Core/IGossipClient.cs | 10 - src/EventStore.Client/Core/IPosition.cs | 7 - .../Core/IServerCapabilitiesClient.cs | 9 - .../Interceptors/ConnectionNameInterceptor.cs | 45 - .../Interceptors/ReportLeaderInterceptor.cs | 126 --- .../Interceptors/TypedExceptionInterceptor.cs | 165 --- src/EventStore.Client/Core/NodePreference.cs | 26 - .../Core/NodePreferenceComparers.cs | 49 - src/EventStore.Client/Core/NodeSelector.cs | 63 -- src/EventStore.Client/Core/Position.cs | 200 ---- .../Core/PrefixFilterExpression.cs | 64 -- .../Core/ReconnectionRequired.cs | 15 - .../Core/RegularFilterExpression.cs | 86 -- src/EventStore.Client/Core/ResolvedEvent.cs | 60 -- .../Core/ServerCapabilities.cs | 10 - src/EventStore.Client/Core/SharingProvider.cs | 111 -- .../Core/SingleNodeChannelSelector.cs | 36 - .../Core/SingleNodeHttpHandler.cs | 22 - src/EventStore.Client/Core/StreamFilter.cs | 134 --- .../Core/StreamIdentifier.cs | 28 - src/EventStore.Client/Core/StreamPosition.cs | 201 ---- src/EventStore.Client/Core/StreamRevision.cs | 196 ---- src/EventStore.Client/Core/StreamState.cs | 88 -- .../Core/SubscriptionDroppedReason.cs | 19 - src/EventStore.Client/Core/SystemRoles.cs | 21 - src/EventStore.Client/Core/SystemStreams.cs | 54 - src/EventStore.Client/Core/TaskExtensions.cs | 22 - src/EventStore.Client/Core/UserCredentials.cs | 54 - src/EventStore.Client/Core/Uuid.cs | 203 ---- src/EventStore.Client/Core/protos/code.proto | 186 ---- .../Core/protos/gossip.proto | 44 - .../Core/protos/operations.proto | 45 - .../Core/protos/persistentsubscriptions.proto | 370 ------- .../Core/protos/projectionmanagement.proto | 174 ---- .../Core/protos/serverfeatures.proto | 19 - .../Core/protos/shared.proto | 61 -- .../Core/protos/status.proto | 48 - .../Core/protos/streams.proto | 316 ------ .../Core/protos/usermanagement.proto | 119 --- .../EventStore.Client.csproj | 57 -- .../EventStore.Client.csproj.DotSettings | 5 - .../Operations/DatabaseScavengeResult.cs | 81 -- .../EventStoreOperationsClient.Admin.cs | 103 -- .../EventStoreOperationsClient.Scavenge.cs | 82 -- .../Operations/EventStoreOperationsClient.cs | 33 - ...ationsClientServiceCollectionExtensions.cs | 74 -- .../Operations/ScavengeResult.cs | 25 - ...orePersistentSubscriptionsClient.Create.cs | 252 ----- ...orePersistentSubscriptionsClient.Delete.cs | 55 - ...StorePersistentSubscriptionsClient.Info.cs | 77 -- ...StorePersistentSubscriptionsClient.List.cs | 111 -- ...StorePersistentSubscriptionsClient.Read.cs | 478 --------- ...sistentSubscriptionsClient.ReplayParked.cs | 94 -- ...entSubscriptionsClient.RestartSubsystem.cs | 32 - ...orePersistentSubscriptionsClient.Update.cs | 165 --- ...EventStorePersistentSubscriptionsClient.cs | 46 - ...SubscriptionsClientCollectionExtensions.cs | 61 -- .../MaximumSubscribersReachedException.cs | 30 - .../PersistentSubscription.cs | 205 ---- ...entSubscriptionDroppedByServerException.cs | 31 - .../PersistentSubscriptionExtraStatistic.cs | 23 - .../PersistentSubscriptionInfo.cs | 224 ----- .../PersistentSubscriptionMessage.cs | 33 - .../PersistentSubscriptionNakEventAction.cs | 31 - ...PersistentSubscriptionNotFoundException.cs | 29 - .../PersistentSubscriptionSettings.cs | 112 --- .../SystemConsumerStrategies.cs | 22 - ...StoreProjectionManagementClient.Control.cs | 102 -- ...tStoreProjectionManagementClient.Create.cs | 80 -- ...ntStoreProjectionManagementClient.State.cs | 205 ---- ...reProjectionManagementClient.Statistics.cs | 104 -- ...tStoreProjectionManagementClient.Update.cs | 40 - .../EventStoreProjectionManagementClient.cs | 32 - ...ionManagementClientCollectionExtensions.cs | 74 -- .../ProjectionManagement/ProjectionDetails.cs | 164 --- .../Streams/ConditionalWriteResult.cs | 87 -- .../Streams/ConditionalWriteStatus.cs | 21 - src/EventStore.Client/Streams/DeadLine.cs | 12 - src/EventStore.Client/Streams/DeleteResult.cs | 46 - src/EventStore.Client/Streams/Direction.cs | 16 - .../Streams/EventStoreClient.Append.cs | 430 -------- .../Streams/EventStoreClient.Delete.cs | 65 -- .../Streams/EventStoreClient.Metadata.cs | 106 -- .../Streams/EventStoreClient.Read.cs | 468 --------- .../Streams/EventStoreClient.Subscriptions.cs | 302 ------ .../Streams/EventStoreClient.Tombstone.cs | 63 -- .../Streams/EventStoreClient.cs | 166 --- .../Streams/EventStoreClientExtensions.cs | 109 -- ...tStoreClientServiceCollectionExtensions.cs | 153 --- src/EventStore.Client/Streams/IWriteResult.cs | 23 - .../Streams/InvalidTransactionException.cs | 31 - .../MaximumAppendSizeExceededException.cs | 33 - src/EventStore.Client/Streams/ReadState.cs | 15 - src/EventStore.Client/Streams/StreamAcl.cs | 107 -- .../Streams/StreamAclJsonConverter.cs | 111 -- .../Streams/StreamMessage.cs | 83 -- .../Streams/StreamMetadata.cs | 111 -- .../Streams/StreamMetadataJsonConverter.cs | 145 --- .../Streams/StreamMetadataResult.cs | 83 -- .../Streams/StreamSubscription.cs | 165 --- .../Streams/Streams/AppendReq.cs | 15 - .../Streams/Streams/BatchAppendReq.cs | 34 - .../Streams/Streams/BatchAppendResp.cs | 49 - .../Streams/Streams/DeleteReq.cs | 15 - .../Streams/Streams/ReadReq.cs | 86 -- .../Streams/Streams/TombstoneReq.cs | 15 - .../Streams/SubscriptionFilterOptions.cs | 54 - .../Streams/SuccessResult.cs | 57 -- .../Streams/SystemEventTypes.cs | 31 - .../Streams/SystemMetadata.cs | 71 -- .../Streams/SystemSettings.cs | 53 - .../Streams/SystemSettingsJsonConverter.cs | 62 -- .../Streams/WriteResultExtensions.cs | 12 - .../Streams/WrongExpectedVersionResult.cs | 58 -- .../EventStoreUserManagementClient.cs | 279 ------ ...serManagementClientCollectionExtensions.cs | 72 -- .../EventStoreUserManagerClientExtensions.cs | 23 - .../UserManagement/UserDetails.cs | 97 -- .../TracerProviderBuilderExtensions.cs | 2 +- .../Fixtures/KurrentFixtureOptions.cs | 3 + ...xistingWithStartFromNotSetObsoleteTests.cs | 51 + ...hStartFromSetToEndPositionObsoleteTests.cs | 46 + ...nectWithoutReadPermissionsObsoleteTests.cs | 34 + .../SubscribeToAllFilterObsoleteTests.cs | 131 +++ .../SubscribeToAllGetInfoObsoleteTests.cs | 97 ++ ...stWithIncorrectCredentialsObsoleteTests.cs | 64 ++ ...eToAllNoDefaultCredentialsObsoleteTests.cs | 103 ++ .../Obsolete/SubscribeToAllObsoleteTests.cs | 786 +++++++++++++++ ...SubscribeToAllReplayParkedObsoleteTests.cs | 87 ++ ...tWithNormalUserCredentialsObsoleteTests.cs | 35 + ...AllReturnsAllSubscriptionsObsoleteTests.cs | 42 + ...nsSubscriptionsToAllStreamObsoleteTests.cs | 36 + ...dateExistingWithCheckpointObsoleteTests.cs | 88 ++ .../SubscribeToAllWithoutPSObsoleteTests.cs | 17 + ...nnectToExistingWithStartFromNotSetTests.cs | 2 +- ...stingWithStartFromSetToEndPositionTests.cs | 0 ...ToAllConnectWithoutReadPermissionsTests.cs | 0 .../SubscribeToAllFilterTests.cs | 0 .../SubscribeToAllGetInfoTests.cs | 0 ...eToAllListWithIncorrectCredentialsTests.cs | 0 ...SubscribeToAllNoDefaultCredentialsTests.cs | 0 .../SubscribeToAllReplayParkedTests.cs | 0 ...AllResultWithNormalUserCredentialsTests.cs | 0 .../SubscribeToAllReturnsAllSubscriptions.cs | 0 ...AllReturnsSubscriptionsToAllStreamTests.cs | 0 .../SubscribeToAllTests.cs | 0 ...beToAllUpdateExistingWithCheckpointTest.cs | 0 .../SubscribeToAllWithoutPSTests.cs | 0 ...oExistingWithoutPermissionObsoleteTests.cs | 36 + .../SubscribeToStreamGetInfoObsoleteTests.cs | 201 ++++ .../SubscribeToStreamObsoleteTests.cs | 948 ++++++++++++++++++ ...ctToExistingWithStartFromBeginningTests.cs | 0 .../SubscribeToStreamGetInfoTests.cs | 10 +- .../SubscribeToStreamListTests.cs | 0 ...scribeToStreamNoDefaultCredentialsTests.cs | 0 .../SubscribeToStreamReplayParkedTests.cs | 0 .../SubscribeToStreamTests.cs | 0 .../Obsolete/SubscribeToAllObsoleteTests.cs | 616 ++++++++++++ .../SubscribeToStreamObsoleteTests.cs | 322 ++++++ .../Subscriptions/SubscribeToAllTests.cs | 497 +++++++++ .../SubscribeToStreamTests.cs | 0 .../SubscriptionDroppedResult.cs | 18 + 264 files changed, 4616 insertions(+), 14803 deletions(-) delete mode 100644 src/EventStore.Client.Extensions.OpenTelemetry/EventStore.Client.Extensions.OpenTelemetry.csproj delete mode 100644 src/EventStore.Client.Extensions.OpenTelemetry/TracerProviderBuilderExtensions.cs delete mode 100644 src/EventStore.Client/Core/Certificates/X509Certificates.cs delete mode 100644 src/EventStore.Client/Core/ChannelBaseExtensions.cs delete mode 100644 src/EventStore.Client/Core/ChannelCache.cs delete mode 100644 src/EventStore.Client/Core/ChannelFactory.cs delete mode 100644 src/EventStore.Client/Core/ChannelInfo.cs delete mode 100644 src/EventStore.Client/Core/ChannelSelector.cs delete mode 100644 src/EventStore.Client/Core/ClusterMessage.cs delete mode 100644 src/EventStore.Client/Core/Common/AsyncStreamReaderExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Constants.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/ActivitySourceExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/ActivityTagsCollectionExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatus.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatusCodeHelper.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityTagsCollectionExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/ExceptionExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/Telemetry/TelemetryTags.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingConstants.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingMetadata.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/EventMetadataExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/EventStoreClientDiagnostics.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Telemetry/TelemetryTags.cs delete mode 100644 src/EventStore.Client/Core/Common/Diagnostics/Tracing/TracingConstants.cs delete mode 100644 src/EventStore.Client/Core/Common/EnumerableTaskExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/EpochExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/EventStoreCallOptions.cs delete mode 100644 src/EventStore.Client/Core/Common/MetadataExtensions.cs delete mode 100644 src/EventStore.Client/Core/Common/Shims/Index.cs delete mode 100644 src/EventStore.Client/Core/Common/Shims/IsExternalInit.cs delete mode 100644 src/EventStore.Client/Core/Common/Shims/Range.cs delete mode 100644 src/EventStore.Client/Core/Common/Shims/TaskCompletionSource.cs delete mode 100644 src/EventStore.Client/Core/DefaultRequestVersionHandler.cs delete mode 100644 src/EventStore.Client/Core/EndPointExtensions.cs delete mode 100644 src/EventStore.Client/Core/EventData.cs delete mode 100644 src/EventStore.Client/Core/EventRecord.cs delete mode 100644 src/EventStore.Client/Core/EventStoreClientBase.cs delete mode 100644 src/EventStore.Client/Core/EventStoreClientConnectivitySettings.cs delete mode 100644 src/EventStore.Client/Core/EventStoreClientOperationOptions.cs delete mode 100644 src/EventStore.Client/Core/EventStoreClientSettings.ConnectionString.cs delete mode 100644 src/EventStore.Client/Core/EventStoreClientSettings.cs delete mode 100644 src/EventStore.Client/Core/EventTypeFilter.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/AccessDeniedException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/ConnectionStringParseException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/DuplicateKeyException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidClientCertificateException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidHostException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidKeyValuePairException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSchemeException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSettingException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidUserCredentialsException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ConnectionString/NoSchemeException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/DiscoveryException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/NotAuthenticatedException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/NotLeaderException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/RequiredMetadataPropertyMissingException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/ScavengeNotFoundException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/StreamDeletedException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/StreamNotFoundException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/UserNotFoundException.cs delete mode 100644 src/EventStore.Client/Core/Exceptions/WrongExpectedVersionException.cs delete mode 100644 src/EventStore.Client/Core/FromAll.cs delete mode 100644 src/EventStore.Client/Core/FromStream.cs delete mode 100644 src/EventStore.Client/Core/GossipChannelSelector.cs delete mode 100644 src/EventStore.Client/Core/GrpcGossipClient.cs delete mode 100644 src/EventStore.Client/Core/GrpcServerCapabilitiesClient.cs delete mode 100644 src/EventStore.Client/Core/HashCode.cs delete mode 100644 src/EventStore.Client/Core/HttpFallback.cs delete mode 100644 src/EventStore.Client/Core/IChannelSelector.cs delete mode 100644 src/EventStore.Client/Core/IEventFilter.cs delete mode 100644 src/EventStore.Client/Core/IGossipClient.cs delete mode 100644 src/EventStore.Client/Core/IPosition.cs delete mode 100644 src/EventStore.Client/Core/IServerCapabilitiesClient.cs delete mode 100644 src/EventStore.Client/Core/Interceptors/ConnectionNameInterceptor.cs delete mode 100644 src/EventStore.Client/Core/Interceptors/ReportLeaderInterceptor.cs delete mode 100644 src/EventStore.Client/Core/Interceptors/TypedExceptionInterceptor.cs delete mode 100644 src/EventStore.Client/Core/NodePreference.cs delete mode 100644 src/EventStore.Client/Core/NodePreferenceComparers.cs delete mode 100644 src/EventStore.Client/Core/NodeSelector.cs delete mode 100644 src/EventStore.Client/Core/Position.cs delete mode 100644 src/EventStore.Client/Core/PrefixFilterExpression.cs delete mode 100644 src/EventStore.Client/Core/ReconnectionRequired.cs delete mode 100644 src/EventStore.Client/Core/RegularFilterExpression.cs delete mode 100644 src/EventStore.Client/Core/ResolvedEvent.cs delete mode 100644 src/EventStore.Client/Core/ServerCapabilities.cs delete mode 100644 src/EventStore.Client/Core/SharingProvider.cs delete mode 100644 src/EventStore.Client/Core/SingleNodeChannelSelector.cs delete mode 100644 src/EventStore.Client/Core/SingleNodeHttpHandler.cs delete mode 100644 src/EventStore.Client/Core/StreamFilter.cs delete mode 100644 src/EventStore.Client/Core/StreamIdentifier.cs delete mode 100644 src/EventStore.Client/Core/StreamPosition.cs delete mode 100644 src/EventStore.Client/Core/StreamRevision.cs delete mode 100644 src/EventStore.Client/Core/StreamState.cs delete mode 100644 src/EventStore.Client/Core/SubscriptionDroppedReason.cs delete mode 100644 src/EventStore.Client/Core/SystemRoles.cs delete mode 100644 src/EventStore.Client/Core/SystemStreams.cs delete mode 100644 src/EventStore.Client/Core/TaskExtensions.cs delete mode 100644 src/EventStore.Client/Core/UserCredentials.cs delete mode 100644 src/EventStore.Client/Core/Uuid.cs delete mode 100644 src/EventStore.Client/Core/protos/code.proto delete mode 100644 src/EventStore.Client/Core/protos/gossip.proto delete mode 100644 src/EventStore.Client/Core/protos/operations.proto delete mode 100644 src/EventStore.Client/Core/protos/persistentsubscriptions.proto delete mode 100644 src/EventStore.Client/Core/protos/projectionmanagement.proto delete mode 100644 src/EventStore.Client/Core/protos/serverfeatures.proto delete mode 100644 src/EventStore.Client/Core/protos/shared.proto delete mode 100644 src/EventStore.Client/Core/protos/status.proto delete mode 100644 src/EventStore.Client/Core/protos/streams.proto delete mode 100644 src/EventStore.Client/Core/protos/usermanagement.proto delete mode 100644 src/EventStore.Client/EventStore.Client.csproj delete mode 100644 src/EventStore.Client/EventStore.Client.csproj.DotSettings delete mode 100644 src/EventStore.Client/Operations/DatabaseScavengeResult.cs delete mode 100644 src/EventStore.Client/Operations/EventStoreOperationsClient.Admin.cs delete mode 100644 src/EventStore.Client/Operations/EventStoreOperationsClient.Scavenge.cs delete mode 100644 src/EventStore.Client/Operations/EventStoreOperationsClient.cs delete mode 100644 src/EventStore.Client/Operations/EventStoreOperationsClientServiceCollectionExtensions.cs delete mode 100644 src/EventStore.Client/Operations/ScavengeResult.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Create.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Delete.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Info.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.List.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Read.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.ReplayParked.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.RestartSubsystem.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Update.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClientCollectionExtensions.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/MaximumSubscribersReachedException.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscription.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionDroppedByServerException.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionExtraStatistic.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionInfo.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionMessage.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNakEventAction.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNotFoundException.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionSettings.cs delete mode 100644 src/EventStore.Client/PersistentSubscriptions/SystemConsumerStrategies.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Control.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Create.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.State.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Statistics.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Update.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClientCollectionExtensions.cs delete mode 100644 src/EventStore.Client/ProjectionManagement/ProjectionDetails.cs delete mode 100644 src/EventStore.Client/Streams/ConditionalWriteResult.cs delete mode 100644 src/EventStore.Client/Streams/ConditionalWriteStatus.cs delete mode 100644 src/EventStore.Client/Streams/DeadLine.cs delete mode 100644 src/EventStore.Client/Streams/DeleteResult.cs delete mode 100644 src/EventStore.Client/Streams/Direction.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.Append.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.Delete.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.Metadata.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.Read.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.Subscriptions.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.Tombstone.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClient.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClientExtensions.cs delete mode 100644 src/EventStore.Client/Streams/EventStoreClientServiceCollectionExtensions.cs delete mode 100644 src/EventStore.Client/Streams/IWriteResult.cs delete mode 100644 src/EventStore.Client/Streams/InvalidTransactionException.cs delete mode 100644 src/EventStore.Client/Streams/MaximumAppendSizeExceededException.cs delete mode 100644 src/EventStore.Client/Streams/ReadState.cs delete mode 100644 src/EventStore.Client/Streams/StreamAcl.cs delete mode 100644 src/EventStore.Client/Streams/StreamAclJsonConverter.cs delete mode 100644 src/EventStore.Client/Streams/StreamMessage.cs delete mode 100644 src/EventStore.Client/Streams/StreamMetadata.cs delete mode 100644 src/EventStore.Client/Streams/StreamMetadataJsonConverter.cs delete mode 100644 src/EventStore.Client/Streams/StreamMetadataResult.cs delete mode 100644 src/EventStore.Client/Streams/StreamSubscription.cs delete mode 100644 src/EventStore.Client/Streams/Streams/AppendReq.cs delete mode 100644 src/EventStore.Client/Streams/Streams/BatchAppendReq.cs delete mode 100644 src/EventStore.Client/Streams/Streams/BatchAppendResp.cs delete mode 100644 src/EventStore.Client/Streams/Streams/DeleteReq.cs delete mode 100644 src/EventStore.Client/Streams/Streams/ReadReq.cs delete mode 100644 src/EventStore.Client/Streams/Streams/TombstoneReq.cs delete mode 100644 src/EventStore.Client/Streams/SubscriptionFilterOptions.cs delete mode 100644 src/EventStore.Client/Streams/SuccessResult.cs delete mode 100644 src/EventStore.Client/Streams/SystemEventTypes.cs delete mode 100644 src/EventStore.Client/Streams/SystemMetadata.cs delete mode 100644 src/EventStore.Client/Streams/SystemSettings.cs delete mode 100644 src/EventStore.Client/Streams/SystemSettingsJsonConverter.cs delete mode 100644 src/EventStore.Client/Streams/WriteResultExtensions.cs delete mode 100644 src/EventStore.Client/Streams/WrongExpectedVersionResult.cs delete mode 100644 src/EventStore.Client/UserManagement/EventStoreUserManagementClient.cs delete mode 100644 src/EventStore.Client/UserManagement/EventStoreUserManagementClientCollectionExtensions.cs delete mode 100644 src/EventStore.Client/UserManagement/EventStoreUserManagerClientExtensions.cs delete mode 100644 src/EventStore.Client/UserManagement/UserDetails.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllListWithIncorrectCredentialsObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReplayParkedObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllResultWithNormalUserCredentialsObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsAllSubscriptionsObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsSubscriptionsToAllStreamObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllWithoutPSObsoleteTests.cs rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs (90%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllConnectWithoutReadPermissionsTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllFilterTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllGetInfoTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllListWithIncorrectCredentialsTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllNoDefaultCredentialsTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllReplayParkedTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllResultWithNormalUserCredentialsTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllReturnsAllSubscriptions.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllReturnsSubscriptionsToAllStreamTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllUpdateExistingWithCheckpointTest.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToAll}/SubscribeToAllWithoutPSTests.cs (100%) create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToStream}/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToStream}/SubscribeToStreamGetInfoTests.cs (96%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToStream}/SubscribeToStreamListTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToStream}/SubscribeToStreamNoDefaultCredentialsTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToStream}/SubscribeToStreamReplayParkedTests.cs (100%) rename test/Kurrent.Client.Tests/PersistentSubscriptions/{ => SubscribeToStream}/SubscribeToStreamTests.cs (100%) create mode 100644 test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs create mode 100644 test/Kurrent.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs rename test/Kurrent.Client.Tests/Streams/{ => Subscriptions}/SubscribeToStreamTests.cs (100%) create mode 100644 test/Kurrent.Client.Tests/Streams/Subscriptions/SubscriptionDroppedResult.cs diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index c4efd3227..4023569e8 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net9.0 ] + framework: [ net8.0, net9.0 ] os: [ ubuntu-latest ] configuration: [ release ] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ebe348e3e..0aabc306f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,193 +1,200 @@ -#name: Publish -# -#on: -# pull_request: -# push: -# branches: -# - master -# tags: -# - v* -# -#jobs: -# vulnerability-scan: -# timeout-minutes: 10 -# strategy: -# fail-fast: false -# matrix: -# framework: [ net8.0 ] -# os: [ ubuntu-latest, windows-latest ] -# runs-on: ${{ matrix.os }} -# name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# - name: Install dotnet SDKs -# uses: actions/setup-dotnet@v3 -# with: -# dotnet-version: | -# 8.0.x -# - name: Scan for Vulnerabilities -# shell: bash -# run: | -# dotnet nuget list source -# dotnet restore -# dotnet list package --vulnerable --include-transitive --framework ${{ matrix.framework }} | tee vulnerabilities.txt -# ! cat vulnerabilities.txt | grep -q "has the following vulnerable packages" -# -# build-samples: -# timeout-minutes: 5 -# name: build-samples/${{ matrix.framework }} -# runs-on: ubuntu-latest -# strategy: -# fail-fast: false -# matrix: -# framework: [ net8.0 ] -# services: -# esdb: -# image: docker.eventstore.com/eventstore-ce/eventstoredb-ce:lts -# env: -# EVENTSTORE_INSECURE: true -# EVENTSTORE_MEM_DB: false -# EVENTSTORE_RUN_PROJECTIONS: all -# EVENTSTORE_START_STANDARD_PROJECTIONS: true -# ports: -# - 2113:2113 -# options: --health-cmd "exit 0" -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# - name: Install dotnet SDKs -# uses: actions/setup-dotnet@v3 -# with: -# dotnet-version: | -# 8.0.x -# - name: Compile -# shell: bash -# run: | -# dotnet build samples -# - name: Run -# shell: bash -# run: | -# find samples/ -type f -iname "*.csproj" -print0 | xargs -0L1 dotnet run --framework ${{ matrix.framework }} --project -# -# generate-certificates: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout code -# uses: actions/checkout@v4 -# - name: Generate certificates -# run: | -# mkdir -p certs -# docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-ca -out /tmp/ca -# docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost -# docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-user -username admin -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-admin -# docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-user -username invalid -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-invalid -# - name: Set permissions on certificates -# run: | -# sudo chown -R $USER:$USER certs -# sudo chmod -R 755 certs -# - name: Upload certificates -# uses: actions/upload-artifact@v4 -# with: -# name: certs -# path: certs -# -# test: -# needs: generate-certificates -# timeout-minutes: 20 -# strategy: -# fail-fast: false -# matrix: -# framework: [ net8.0 ] -# os: [ ubuntu-latest, windows-latest ] -# configuration: [ release ] -# runs-on: ${{ matrix.os }} -# name: test/EventStore.Client/${{ matrix.os }}/${{ matrix.framework }} -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# - shell: bash -# run: | -# git fetch --prune --unshallow -# - name: Install dotnet SDKs -# uses: actions/setup-dotnet@v3 -# with: -# dotnet-version: | -# 8.0.x -# - name: Compile -# shell: bash -# run: | -# dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/EventStore.Client -# - name: Download certificates -# uses: actions/download-artifact@v4 -# with: -# name: certs -# path: certs -# - name: Run Tests (Linux) -# if: runner.os == 'Linux' -# shell: bash -# run: | -# dotnet test --configuration ${{ matrix.configuration }} --blame \ -# --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ -# --framework ${{ matrix.framework }} \ -# test/EventStore.Client.Tests -# - name: Run Tests (Windows) -# if: runner.os == 'Windows' -# shell: pwsh -# run: | -# dotnet test --configuration ${{ matrix.configuration }} --blame ` -# --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" ` -# --framework ${{ matrix.framework }} ` -# test/EventStore.Client.Tests -# -# publish: -# timeout-minutes: 5 -# needs: [ vulnerability-scan, test, build-samples ] -# runs-on: ubuntu-latest -# name: publish -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# - name: Get Version -# id: get_version -# run: | -# echo "branch=${GITHUB_REF:10}" >> $GITHUB_OUTPUT -# dotnet nuget list source -# dotnet tool restore -# version=$(dotnet tool run minver -- --tag-prefix=v) -# echo "version=${version}" >> $GITHUB_OUTPUT -# - shell: bash -# run: | -# git fetch --prune --unshallow -# - name: Install dotnet SDKs -# uses: actions/setup-dotnet@v3 -# with: -# dotnet-version: | -# 8.0.x -# - name: Dotnet Pack -# shell: bash -# run: | -# mkdir -p packages -# dotnet pack /p:Version=${{ steps.get_version.outputs.version }} --configuration=Release \ -# /p:PublishDir=./packages \ -# /p:NoWarn=NU5105 \ -# /p:RepositoryUrl=https://github.com/EventStore/EventStore-Client-Dotnet \ -# /p:RepositoryType=git -# - name: Publish Artifacts -# uses: actions/upload-artifact@v4 -# with: -# path: packages -# name: nuget-packages -# - name: Dotnet Push to Github Packages -# shell: bash -# if: github.event_name == 'push' -# run: | -# dotnet tool restore -# find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.github_token }} --source https://nuget.pkg.github.com/EventStore/index.json --skip-duplicate -# - name: Dotnet Push to Nuget.org -# shell: bash -# if: contains(steps.get_version.outputs.branch, 'v') -# run: | -# dotnet nuget list source -# dotnet tool restore -# find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate +name: Publish + +on: + pull_request: + push: + branches: + - master + tags: + - v* + +jobs: + vulnerability-scan: + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + framework: [ net8.0, net9.0 ] + os: [ ubuntu-latest, windows-latest ] + runs-on: ${{ matrix.os }} + name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + - name: Scan for Vulnerabilities + shell: bash + run: | + dotnet nuget list source + dotnet restore + dotnet list package --vulnerable --include-transitive --framework ${{ matrix.framework }} | tee vulnerabilities.txt + ! cat vulnerabilities.txt | grep -q "has the following vulnerable packages" + + build-samples: + timeout-minutes: 5 + name: build-samples/${{ matrix.framework }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + framework: [ net8.0, net9.0 ] + services: + esdb: + image: docker.eventstore.com/eventstore-ce/eventstoredb-ce:lts + env: + EVENTSTORE_INSECURE: true + EVENTSTORE_MEM_DB: false + EVENTSTORE_RUN_PROJECTIONS: all + EVENTSTORE_START_STANDARD_PROJECTIONS: true + ports: + - 2113:2113 + options: --health-cmd "exit 0" + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + - name: Compile + shell: bash + run: | + dotnet build samples + - name: Run + shell: bash + run: | + find samples/ -type f -iname "*.csproj" -print0 | xargs -0L1 dotnet run --framework ${{ matrix.framework }} --project + + generate-certificates: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Generate certificates + run: | + mkdir -p certs + docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-ca -out /tmp/ca + docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost + docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-user -username admin -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-admin + docker run --rm --user root --volume "$PWD/certs:/tmp" docker.eventstore.com/eventstore-utils/es-gencert-cli:latest create-user -username invalid -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-invalid + - name: Set permissions on certificates + run: | + sudo chown -R $USER:$USER certs + sudo chmod -R 755 certs + - name: Upload certificates + uses: actions/upload-artifact@v4 + with: + name: certs + path: certs + + test: + needs: generate-certificates + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + framework: [ net8.0, 9.0 ] + os: [ ubuntu-latest, windows-latest ] + configuration: [ release ] + test: [ Streams, PersistentSubscriptions, Operations, ProjectionManagement, UserManagement, Security, Misc ] + runs-on: ${{ matrix.os }} + name: ${{ inputs.test }} (${{ matrix.os }}, ${{ matrix.framework }}) + steps: + - name: Checkout + uses: actions/checkout@v3 + - shell: bash + run: | + git fetch --prune --unshallow + - name: Install dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + - name: Compile + shell: bash + run: | + dotnet build --configuration ${{ matrix.configuration }} --framework ${{ matrix.framework }} src/Kurrent.Client + - name: Download certificates + uses: actions/download-artifact@v4 + with: + name: certs + path: certs + - name: Run Tests (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + dotnet test --configuration ${{ matrix.configuration }} --blame \ + --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" \ + --filter "Category=Target:${{ inputs.test }}" \ + --framework ${{ matrix.framework }} \ + test/Kurrent.Client.Tests + - name: Run Tests (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + dotnet test --configuration ${{ matrix.configuration }} --blame ` + --logger:"GitHubActions;report-warnings=false" --logger:"console;verbosity=normal" ` + --framework ${{ matrix.framework }} ` + --filter "Category=Target:${{ inputs.test }}" ` + test/Kurrent.Client.Tests + + publish: + timeout-minutes: 5 + needs: [ vulnerability-scan, test, build-samples ] + runs-on: ubuntu-latest + name: publish + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Get Version + id: get_version + run: | + echo "branch=${GITHUB_REF:10}" >> $GITHUB_OUTPUT + dotnet nuget list source + dotnet tool restore + version=$(dotnet tool run minver -- --tag-prefix=v) + echo "version=${version}" >> $GITHUB_OUTPUT + - shell: bash + run: | + git fetch --prune --unshallow + - name: Install dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + - name: Dotnet Pack + shell: bash + run: | + mkdir -p packages + dotnet pack /p:Version=${{ steps.get_version.outputs.version }} --configuration=Release \ + /p:PublishDir=./packages \ + /p:NoWarn=NU5105 \ + /p:RepositoryUrl=https://github.com/EventStore/EventStore-Client-Dotnet \ + /p:RepositoryType=git + - name: Publish Artifacts + uses: actions/upload-artifact@v4 + with: + path: packages + name: nuget-packages + - name: Dotnet Push to Github Packages + shell: bash + if: github.event_name == 'push' + run: | + dotnet tool restore + find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.github_token }} --source https://nuget.pkg.github.com/EventStore/index.json --skip-duplicate + - name: Dotnet Push to Nuget.org + shell: bash + if: contains(steps.get_version.outputs.branch, 'v') + run: | + dotnet nuget list source + dotnet tool restore + find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.nuget_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 54497a508..4e510486d 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/samples/Samples.sln b/samples/Samples.sln index d3c46d188..ec2611d7b 100644 --- a/samples/Samples.sln +++ b/samples/Samples.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "quick-start", "quick-start\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{EBB93BBC-42A7-48E4-B1EA-0EA3953D347C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventStore.Client", "..\src\EventStore.Client\EventStore.Client.csproj", "{A71A13F7-8480-4E48-B88D-A2364F7C95B6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "connecting-to-a-cluster", "connecting-to-a-cluster\connecting-to-a-cluster.csproj", "{C4CA324A-450D-4621-82F1-B4ECD18216B6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "appending-events", "appending-events\appending-events.csproj", "{496D9886-AF65-4579-AECE-2C147B9AF3C1}" @@ -33,7 +31,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "diagnostics", "diagnostics\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "user-certificates", "user-certificates\user-certificates.csproj", "{28112410-D02D-427A-9D36-3FE3A6DC6B0D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Client.Extensions.OpenTelemetry", "..\src\EventStore.Client.Extensions.OpenTelemetry\EventStore.Client.Extensions.OpenTelemetry.csproj", "{29E3F07A-6676-45C1-805C-46BDF6CF325B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kurrent.Client", "..\src\Kurrent.Client\Kurrent.Client.csproj", "{16F61817-6E46-4F52-879F-E4B92A6A90C6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -45,10 +43,6 @@ Global {521987E5-4394-4EE0-B217-E3DC9DB0D327}.Debug|Any CPU.Build.0 = Debug|Any CPU {521987E5-4394-4EE0-B217-E3DC9DB0D327}.Release|Any CPU.ActiveCfg = Release|Any CPU {521987E5-4394-4EE0-B217-E3DC9DB0D327}.Release|Any CPU.Build.0 = Release|Any CPU - {A71A13F7-8480-4E48-B88D-A2364F7C95B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A71A13F7-8480-4E48-B88D-A2364F7C95B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A71A13F7-8480-4E48-B88D-A2364F7C95B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A71A13F7-8480-4E48-B88D-A2364F7C95B6}.Release|Any CPU.Build.0 = Release|Any CPU {C4CA324A-450D-4621-82F1-B4ECD18216B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C4CA324A-450D-4621-82F1-B4ECD18216B6}.Debug|Any CPU.Build.0 = Debug|Any CPU {C4CA324A-450D-4621-82F1-B4ECD18216B6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -97,17 +91,16 @@ Global {28112410-D02D-427A-9D36-3FE3A6DC6B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {28112410-D02D-427A-9D36-3FE3A6DC6B0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {28112410-D02D-427A-9D36-3FE3A6DC6B0D}.Release|Any CPU.Build.0 = Release|Any CPU - {29E3F07A-6676-45C1-805C-46BDF6CF325B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29E3F07A-6676-45C1-805C-46BDF6CF325B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29E3F07A-6676-45C1-805C-46BDF6CF325B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29E3F07A-6676-45C1-805C-46BDF6CF325B}.Release|Any CPU.Build.0 = Release|Any CPU + {16F61817-6E46-4F52-879F-E4B92A6A90C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16F61817-6E46-4F52-879F-E4B92A6A90C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16F61817-6E46-4F52-879F-E4B92A6A90C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16F61817-6E46-4F52-879F-E4B92A6A90C6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {A71A13F7-8480-4E48-B88D-A2364F7C95B6} = {EBB93BBC-42A7-48E4-B1EA-0EA3953D347C} - {29E3F07A-6676-45C1-805C-46BDF6CF325B} = {EBB93BBC-42A7-48E4-B1EA-0EA3953D347C} + {16F61817-6E46-4F52-879F-E4B92A6A90C6} = {EBB93BBC-42A7-48E4-B1EA-0EA3953D347C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E7E3A6C-21DA-4DBD-9EB3-68DFC5CDE48F} diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index 2e1bb758f..98f12fb1a 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -1,10 +1,10 @@ #pragma warning disable CS8321 // Local function is declared but never used -var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); +var settings = KurrentClientSettings.Create("esdb://localhost:2113?tls=false"); settings.OperationOptions.ThrowOnAppendFailure = false; -await using var client = new EventStoreClient(settings); +await using var client = new KurrentClient(settings); await AppendToStream(client); await AppendWithConcurrencyCheck(client); @@ -13,7 +13,7 @@ return; -static async Task AppendToStream(EventStoreClient client) { +static async Task AppendToStream(KurrentClient client) { #region append-to-stream var eventData = new EventData( @@ -33,7 +33,7 @@ await client.AppendToStreamAsync( #endregion append-to-stream } -static async Task AppendWithSameId(EventStoreClient client) { +static async Task AppendWithSameId(KurrentClient client) { #region append-duplicate-event var eventData = new EventData( @@ -62,7 +62,7 @@ await client.AppendToStreamAsync( #endregion append-duplicate-event } -static async Task AppendWithNoStream(EventStoreClient client) { +static async Task AppendWithNoStream(KurrentClient client) { #region append-with-no-stream var eventDataOne = new EventData( @@ -97,7 +97,7 @@ await client.AppendToStreamAsync( #endregion append-with-no-stream } -static async Task AppendWithConcurrencyCheck(EventStoreClient client) { +static async Task AppendWithConcurrencyCheck(KurrentClient client) { await client.AppendToStreamAsync( "concurrency-stream", StreamRevision.None, @@ -148,7 +148,7 @@ await client.AppendToStreamAsync( #endregion append-with-concurrency-check } -static async Task AppendOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) { +static async Task AppendOverridingUserCredentials(KurrentClient client, CancellationToken cancellationToken) { var eventData = new EventData( Uuid.NewUuid(), "TestEvent", diff --git a/samples/appending-events/appending-events.csproj b/samples/appending-events/appending-events.csproj index 3dbe997bc..c74c70ae9 100644 --- a/samples/appending-events/appending-events.csproj +++ b/samples/appending-events/appending-events.csproj @@ -3,8 +3,7 @@ Exe appending_events - - + diff --git a/samples/connecting-to-a-cluster/Program.cs b/samples/connecting-to-a-cluster/Program.cs index a31666fc6..01b033c6c 100644 --- a/samples/connecting-to-a-cluster/Program.cs +++ b/samples/connecting-to-a-cluster/Program.cs @@ -1,10 +1,12 @@ -#pragma warning disable CS8321 // Local function is declared but never used +using EventStore.Client; + +#pragma warning disable CS8321 // Local function is declared but never used static void ConnectingToACluster() { #region connecting-to-a-cluster - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:1114,localhost:2114,localhost:3114") + using var client = new KurrentClient( + KurrentClientSettings.Create("esdb://localhost:1114,localhost:2114,localhost:3114") ); #endregion connecting-to-a-cluster @@ -13,8 +15,8 @@ static void ConnectingToACluster() { static void ProvidingDefaultCredentials() { #region providing-default-credentials - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114") + using var client = new KurrentClient( + KurrentClientSettings.Create("esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114") ); #endregion providing-default-credentials @@ -23,11 +25,11 @@ static void ProvidingDefaultCredentials() { static void ConnectingToAClusterComplex() { #region connecting-to-a-cluster-complex - using var client = new EventStoreClient( - EventStoreClientSettings.Create( + using var client = new KurrentClient( + KurrentClientSettings.Create( "esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114?DiscoveryInterval=30000;GossipTimeout=10000;NodePreference=leader;MaxDiscoverAttempts=5" ) ); #endregion connecting-to-a-cluster-complex -} \ No newline at end of file +} diff --git a/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj b/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj index af05183b9..f7304d3f3 100644 --- a/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj +++ b/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj @@ -3,8 +3,7 @@ Exe connecting_to_a_cluster - - + diff --git a/samples/connecting-to-a-single-node/Program.cs b/samples/connecting-to-a-single-node/Program.cs index f50e1689f..63d0b3e98 100644 --- a/samples/connecting-to-a-single-node/Program.cs +++ b/samples/connecting-to-a-single-node/Program.cs @@ -1,11 +1,12 @@ using connecting_to_a_single_node; +using EventStore.Client; #pragma warning disable CS8321 // Local function is declared but never used static void SimpleConnection() { #region creating-simple-connection - using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113")); + using var client = new KurrentClient(KurrentClientSettings.Create("esdb://localhost:2113")); #endregion creating-simple-connection } @@ -13,7 +14,7 @@ static void SimpleConnection() { static void ProvidingDefaultCredentials() { #region providing-default-credentials - using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113")); + using var client = new KurrentClient(KurrentClientSettings.Create("esdb://admin:changeit@localhost:2113")); #endregion providing-default-credentials } @@ -21,8 +22,8 @@ static void ProvidingDefaultCredentials() { static void SpecifyingAConnectionName() { #region setting-the-connection-name - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection") + using var client = new KurrentClient( + KurrentClientSettings.Create("esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection") ); #endregion setting-the-connection-name @@ -31,8 +32,8 @@ static void SpecifyingAConnectionName() { static void OverridingTheTimeout() { #region overriding-timeout - using var client = new EventStoreClient( - EventStoreClientSettings.Create($"esdb://admin:changeit@localhost:2113?OperationTimeout=30000") + using var client = new KurrentClient( + KurrentClientSettings.Create($"esdb://admin:changeit@localhost:2113?OperationTimeout=30000") ); #endregion overriding-timeout @@ -41,8 +42,8 @@ static void OverridingTheTimeout() { static void CombiningSettings() { #region overriding-timeout - using var client = new EventStoreClient( - EventStoreClientSettings.Create( + using var client = new KurrentClient( + KurrentClientSettings.Create( $"esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection&OperationTimeout=30000" ) ); @@ -53,7 +54,7 @@ static void CombiningSettings() { static void CreatingAnInterceptor() { #region adding-an-interceptor - var settings = new EventStoreClientSettings { + var settings = new KurrentClientSettings { Interceptors = new[] { new DemoInterceptor() }, ConnectivitySettings = { Address = new Uri("https://localhost:2113") @@ -62,13 +63,13 @@ static void CreatingAnInterceptor() { #endregion adding-an-interceptor - var client = new EventStoreClient(settings); + var client = new KurrentClient(settings); } static void CustomHttpMessageHandler() { #region adding-an-custom-http-message-handler - var settings = new EventStoreClientSettings { + var settings = new KurrentClientSettings { CreateHttpMessageHandler = () => new HttpClientHandler { ServerCertificateCustomValidationCallback = @@ -81,5 +82,5 @@ static void CustomHttpMessageHandler() { #endregion adding-an-custom-http-message-handler - var client = new EventStoreClient(settings); -} \ No newline at end of file + var client = new KurrentClient(settings); +} diff --git a/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj b/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj index 7700f4667..97609c80a 100644 --- a/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj +++ b/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj @@ -2,8 +2,7 @@ Exe - - + diff --git a/samples/diagnostics/Program.cs b/samples/diagnostics/Program.cs index 5804be7a4..86f9a6e3a 100644 --- a/samples/diagnostics/Program.cs +++ b/samples/diagnostics/Program.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using EventStore.Client; using EventStore.Client.Extensions.OpenTelemetry; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -23,17 +24,17 @@ dotnet add package OpenTelemetry.Extensions.Hosting # endregion import-required-packages **/ -var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); +var settings = KurrentClientSettings.Create("esdb://localhost:2113?tls=false"); settings.OperationOptions.ThrowOnAppendFailure = false; -await using var client = new EventStoreClient(settings); +await using var client = new KurrentClient(settings); await TraceAppendToStream(client); return; -static async Task TraceAppendToStream(EventStoreClient client) { +static async Task TraceAppendToStream(KurrentClient client) { const string serviceName = "sample"; var host = Host.CreateDefaultBuilder() @@ -76,7 +77,7 @@ static void ConfigureTracerProviderBuilder(TracerProviderBuilder tracerProviderB #region register-instrumentation tracerProviderBuilder - .AddEventStoreClientInstrumentation(); + .AddKurrentClientInstrumentation(); #endregion register-instrumentation diff --git a/samples/diagnostics/diagnostics.csproj b/samples/diagnostics/diagnostics.csproj index d0ca46e87..56bd7c49c 100644 --- a/samples/diagnostics/diagnostics.csproj +++ b/samples/diagnostics/diagnostics.csproj @@ -13,9 +13,6 @@ - - - - + diff --git a/samples/persistent-subscriptions/Program.cs b/samples/persistent-subscriptions/Program.cs index e1e9387c6..6d07b7947 100644 --- a/samples/persistent-subscriptions/Program.cs +++ b/samples/persistent-subscriptions/Program.cs @@ -1,5 +1,5 @@ -await using var client = new EventStorePersistentSubscriptionsClient( - EventStoreClientSettings.Create("esdb://localhost:2113?tls=false&tlsVerifyCert=false") +await using var client = new KurrentPersistentSubscriptionsClient( + KurrentClientSettings.Create("esdb://localhost:2113?tls=false&tlsVerifyCert=false") ); await DeletePersistentSubscription(client); @@ -36,7 +36,7 @@ return; -static async Task CreatePersistentSubscription(EventStorePersistentSubscriptionsClient client) { +static async Task CreatePersistentSubscription(KurrentPersistentSubscriptionsClient client) { #region create-persistent-subscription-to-stream var userCredentials = new UserCredentials("admin", "changeit"); @@ -54,7 +54,7 @@ await client.CreateToStreamAsync( #endregion create-persistent-subscription-to-stream } -static async Task ConnectToPersistentSubscriptionToStream(EventStorePersistentSubscriptionsClient client, +static async Task ConnectToPersistentSubscriptionToStream(KurrentPersistentSubscriptionsClient client, CancellationToken ct) { #region subscribe-to-persistent-subscription-to-stream @@ -77,7 +77,7 @@ static async Task ConnectToPersistentSubscriptionToStream(EventStorePersistentSu #endregion subscribe-to-persistent-subscription-to-stream } -static async Task CreatePersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { +static async Task CreatePersistentSubscriptionToAll(KurrentPersistentSubscriptionsClient client) { #region create-persistent-subscription-to-all var userCredentials = new UserCredentials("admin", "changeit"); @@ -96,7 +96,7 @@ await client.CreateToAllAsync( #endregion create-persistent-subscription-to-all } -static async Task ConnectToPersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client, +static async Task ConnectToPersistentSubscriptionToAll(KurrentPersistentSubscriptionsClient client, CancellationToken ct) { #region subscribe-to-persistent-subscription-to-all @@ -118,7 +118,7 @@ static async Task ConnectToPersistentSubscriptionToAll(EventStorePersistentSubsc #endregion subscribe-to-persistent-subscription-to-all } -static async Task ConnectToPersistentSubscriptionWithManualAcks(EventStorePersistentSubscriptionsClient client, +static async Task ConnectToPersistentSubscriptionWithManualAcks(KurrentPersistentSubscriptionsClient client, CancellationToken ct) { #region subscribe-to-persistent-subscription-with-manual-acks @@ -145,7 +145,7 @@ static async Task ConnectToPersistentSubscriptionWithManualAcks(EventStorePersis #endregion subscribe-to-persistent-subscription-with-manual-acks } -static async Task UpdatePersistentSubscription(EventStorePersistentSubscriptionsClient client) { +static async Task UpdatePersistentSubscription(KurrentPersistentSubscriptionsClient client) { #region update-persistent-subscription var userCredentials = new UserCredentials("admin", "changeit"); @@ -163,7 +163,7 @@ await client.UpdateToStreamAsync( #endregion update-persistent-subscription } -static async Task DeletePersistentSubscription(EventStorePersistentSubscriptionsClient client) { +static async Task DeletePersistentSubscription(KurrentPersistentSubscriptionsClient client) { #region delete-persistent-subscription try { @@ -184,7 +184,7 @@ await client.DeleteToStreamAsync( #endregion delete-persistent-subscription } -static async Task DeletePersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { +static async Task DeletePersistentSubscriptionToAll(KurrentPersistentSubscriptionsClient client) { #region delete-persistent-subscription-to-all try { @@ -204,7 +204,7 @@ await client.DeleteToAllAsync( #endregion delete-persistent-subscription-to-all } -static async Task GetPersistentSubscriptionToStreamInfo(EventStorePersistentSubscriptionsClient client) { +static async Task GetPersistentSubscriptionToStreamInfo(KurrentPersistentSubscriptionsClient client) { #region get-persistent-subscription-to-stream-info var userCredentials = new UserCredentials("admin", "changeit"); @@ -219,7 +219,7 @@ static async Task GetPersistentSubscriptionToStreamInfo(EventStorePersistentSubs #endregion get-persistent-subscription-to-stream-info } -static async Task GetPersistentSubscriptionToAllInfo(EventStorePersistentSubscriptionsClient client) { +static async Task GetPersistentSubscriptionToAllInfo(KurrentPersistentSubscriptionsClient client) { #region get-persistent-subscription-to-all-info var userCredentials = new UserCredentials("admin", "changeit"); @@ -233,7 +233,7 @@ static async Task GetPersistentSubscriptionToAllInfo(EventStorePersistentSubscri #endregion get-persistent-subscription-to-all-info } -static async Task ReplayParkedToStream(EventStorePersistentSubscriptionsClient client) { +static async Task ReplayParkedToStream(KurrentPersistentSubscriptionsClient client) { #region replay-parked-of-persistent-subscription-to-stream var userCredentials = new UserCredentials("admin", "changeit"); @@ -249,7 +249,7 @@ await client.ReplayParkedMessagesToStreamAsync( #endregion persistent-subscription-replay-parked-to-stream } -static async Task ReplayParkedToAll(EventStorePersistentSubscriptionsClient client) { +static async Task ReplayParkedToAll(KurrentPersistentSubscriptionsClient client) { #region replay-parked-of-persistent-subscription-to-all var userCredentials = new UserCredentials("admin", "changeit"); @@ -264,7 +264,7 @@ await client.ReplayParkedMessagesToAllAsync( #endregion replay-parked-of-persistent-subscription-to-all } -static async Task ListPersistentSubscriptionsToStream(EventStorePersistentSubscriptionsClient client) { +static async Task ListPersistentSubscriptionsToStream(KurrentPersistentSubscriptionsClient client) { #region list-persistent-subscriptions-to-stream var userCredentials = new UserCredentials("admin", "changeit"); @@ -281,7 +281,7 @@ static async Task ListPersistentSubscriptionsToStream(EventStorePersistentSubscr #endregion list-persistent-subscriptions-to-stream } -static async Task ListPersistentSubscriptionsToAll(EventStorePersistentSubscriptionsClient client) { +static async Task ListPersistentSubscriptionsToAll(KurrentPersistentSubscriptionsClient client) { #region list-persistent-subscriptions-to-all var userCredentials = new UserCredentials("admin", "changeit"); @@ -295,7 +295,7 @@ static async Task ListPersistentSubscriptionsToAll(EventStorePersistentSubscript #endregion list-persistent-subscriptions-to-all } -static async Task ListAllPersistentSubscriptions(EventStorePersistentSubscriptionsClient client) { +static async Task ListAllPersistentSubscriptions(KurrentPersistentSubscriptionsClient client) { #region list-persistent-subscriptions var userCredentials = new UserCredentials("admin", "changeit"); @@ -309,7 +309,7 @@ static async Task ListAllPersistentSubscriptions(EventStorePersistentSubscriptio #endregion list-persistent-subscriptions } -static async Task RestartPersistentSubscriptionSubsystem(EventStorePersistentSubscriptionsClient client) { +static async Task RestartPersistentSubscriptionSubsystem(KurrentPersistentSubscriptionsClient client) { #region restart-persistent-subscription-subsystem var userCredentials = new UserCredentials("admin", "changeit"); diff --git a/samples/persistent-subscriptions/persistent-subscriptions.csproj b/samples/persistent-subscriptions/persistent-subscriptions.csproj index 4204ed387..e296fca91 100644 --- a/samples/persistent-subscriptions/persistent-subscriptions.csproj +++ b/samples/persistent-subscriptions/persistent-subscriptions.csproj @@ -3,8 +3,7 @@ Exe persistent_subscriptions - - + diff --git a/samples/projection-management/Program.cs b/samples/projection-management/Program.cs index 0f3d66b70..478b88b10 100644 --- a/samples/projection-management/Program.cs +++ b/samples/projection-management/Program.cs @@ -64,20 +64,20 @@ return; -static EventStoreProjectionManagementClient ManagementClient(string connection) { +static KurrentProjectionManagementClient ManagementClient(string connection) { #region createClient - var settings = EventStoreClientSettings.Create(connection); + var settings = KurrentClientSettings.Create(connection); settings.ConnectionName = "Projection management client"; settings.DefaultCredentials = new UserCredentials("admin", "changeit"); - var managementClient = new EventStoreProjectionManagementClient(settings); + var managementClient = new KurrentProjectionManagementClient(settings); #endregion createClient return managementClient; } -static async Task RestartSubSystem(EventStoreProjectionManagementClient managementClient) { +static async Task RestartSubSystem(KurrentProjectionManagementClient managementClient) { #region RestartSubSystem await managementClient.RestartSubsystemAsync(); @@ -85,7 +85,7 @@ static async Task RestartSubSystem(EventStoreProjectionManagementClient manageme #endregion RestartSubSystem } -static async Task Disable(EventStoreProjectionManagementClient managementClient) { +static async Task Disable(KurrentProjectionManagementClient managementClient) { #region Disable await managementClient.DisableAsync("$by_category"); @@ -93,7 +93,7 @@ static async Task Disable(EventStoreProjectionManagementClient managementClient) #endregion Disable } -static async Task DisableNotFound(EventStoreProjectionManagementClient managementClient) { +static async Task DisableNotFound(KurrentProjectionManagementClient managementClient) { #region DisableNotFound try { @@ -109,7 +109,7 @@ static async Task DisableNotFound(EventStoreProjectionManagementClient managemen #endregion DisableNotFound } -static async Task Enable(EventStoreProjectionManagementClient managementClient) { +static async Task Enable(KurrentProjectionManagementClient managementClient) { #region Enable await managementClient.EnableAsync("$by_category"); @@ -117,7 +117,7 @@ static async Task Enable(EventStoreProjectionManagementClient managementClient) #endregion Enable } -static async Task EnableNotFound(EventStoreProjectionManagementClient managementClient) { +static async Task EnableNotFound(KurrentProjectionManagementClient managementClient) { #region EnableNotFound try { @@ -133,7 +133,7 @@ static async Task EnableNotFound(EventStoreProjectionManagementClient management #endregion EnableNotFound } -static Task Delete(EventStoreProjectionManagementClient managementClient) { +static Task Delete(KurrentProjectionManagementClient managementClient) { #region Delete // this is not yet available in the .net grpc client @@ -143,7 +143,7 @@ static Task Delete(EventStoreProjectionManagementClient managementClient) { return Task.CompletedTask; } -static async Task Abort(EventStoreProjectionManagementClient managementClient) { +static async Task Abort(KurrentProjectionManagementClient managementClient) { try { var js = "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; @@ -165,7 +165,7 @@ static async Task Abort(EventStoreProjectionManagementClient managementClient) { #endregion Abort } -static async Task Abort_NotFound(EventStoreProjectionManagementClient managementClient) { +static async Task Abort_NotFound(KurrentProjectionManagementClient managementClient) { #region Abort_NotFound try { @@ -181,7 +181,7 @@ static async Task Abort_NotFound(EventStoreProjectionManagementClient management #endregion Abort_NotFound } -static async Task Reset(EventStoreProjectionManagementClient managementClient) { +static async Task Reset(KurrentProjectionManagementClient managementClient) { try { var js = "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; @@ -203,7 +203,7 @@ static async Task Reset(EventStoreProjectionManagementClient managementClient) { #endregion Reset } -static async Task Reset_NotFound(EventStoreProjectionManagementClient managementClient) { +static async Task Reset_NotFound(KurrentProjectionManagementClient managementClient) { #region Reset_NotFound try { @@ -219,14 +219,14 @@ static async Task Reset_NotFound(EventStoreProjectionManagementClient management #endregion Reset_NotFound } -static async Task CreateOneTime(EventStoreProjectionManagementClient managementClient) { +static async Task CreateOneTime(KurrentProjectionManagementClient managementClient) { const string js = "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; await managementClient.CreateOneTimeAsync(js); } -static async Task CreateContinuous(EventStoreProjectionManagementClient managementClient) { +static async Task CreateContinuous(KurrentProjectionManagementClient managementClient) { #region CreateContinuous const string js = @"fromAll() @@ -248,7 +248,7 @@ static async Task CreateContinuous(EventStoreProjectionManagementClient manageme #endregion CreateContinuous } -static async Task CreateContinuous_Conflict(EventStoreProjectionManagementClient managementClient) { +static async Task CreateContinuous_Conflict(KurrentProjectionManagementClient managementClient) { const string js = @"fromAll() .when({ $init: function() { @@ -281,7 +281,7 @@ static async Task CreateContinuous_Conflict(EventStoreProjectionManagementClient #endregion CreateContinuous_Conflict } -static async Task Update(EventStoreProjectionManagementClient managementClient) { +static async Task Update(KurrentProjectionManagementClient managementClient) { #region Update const string js = @"fromAll() @@ -305,7 +305,7 @@ static async Task Update(EventStoreProjectionManagementClient managementClient) #endregion Update } -static async Task Update_NotFound(EventStoreProjectionManagementClient managementClient) { +static async Task Update_NotFound(KurrentProjectionManagementClient managementClient) { #region Update_NotFound try { @@ -321,7 +321,7 @@ static async Task Update_NotFound(EventStoreProjectionManagementClient managemen #endregion Update_NotFound } -static async Task ListAll(EventStoreProjectionManagementClient managementClient) { +static async Task ListAll(KurrentProjectionManagementClient managementClient) { #region ListAll var details = managementClient.ListAllAsync(); @@ -333,7 +333,7 @@ static async Task ListAll(EventStoreProjectionManagementClient managementClient) #endregion ListAll } -static async Task ListContinuous(EventStoreProjectionManagementClient managementClient) { +static async Task ListContinuous(KurrentProjectionManagementClient managementClient) { #region ListContinuous var details = managementClient.ListContinuousAsync(); @@ -345,7 +345,7 @@ static async Task ListContinuous(EventStoreProjectionManagementClient management #endregion ListContinuous } -static async Task GetStatus(EventStoreProjectionManagementClient managementClient) { +static async Task GetStatus(KurrentProjectionManagementClient managementClient) { const string js = "fromAll().when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; @@ -362,7 +362,7 @@ static async Task GetStatus(EventStoreProjectionManagementClient managementClien #endregion GetStatus } -static async Task GetState(EventStoreProjectionManagementClient managementClient) { +static async Task GetState(KurrentProjectionManagementClient managementClient) { // will have to wait for the client to be fixed before we import in the doc #region GetState @@ -393,7 +393,7 @@ static async Task DocToString(JsonDocument d) { #endregion GetState } -static async Task GetResult(EventStoreProjectionManagementClient managementClient) { +static async Task GetResult(KurrentProjectionManagementClient managementClient) { #region GetResult const string js = @"fromAll() @@ -433,9 +433,9 @@ static string DocToString(JsonDocument d) { } static async Task Populate(string connection, int numberOfEvents) { - var settings = EventStoreClientSettings.Create(connection); + var settings = KurrentClientSettings.Create(connection); settings.DefaultCredentials = new UserCredentials("admin", "changeit"); - var client = new EventStoreClient(settings); + var client = new KurrentClient(settings); var messages = Enumerable.Range(0, numberOfEvents).Select( number => new EventData( @@ -452,4 +452,4 @@ public class Result { public int count { get; set; } public override string ToString() => $"count= {count}"; -}; \ No newline at end of file +}; diff --git a/samples/projection-management/projection-management.csproj b/samples/projection-management/projection-management.csproj index 80fe56f78..f6a6206aa 100644 --- a/samples/projection-management/projection-management.csproj +++ b/samples/projection-management/projection-management.csproj @@ -2,8 +2,7 @@ projection_management - - + diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 0cd070137..dd763ae42 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -7,9 +7,9 @@ const string connectionString = "esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false"; -var settings = EventStoreClientSettings.Create(connectionString); +var settings = KurrentClientSettings.Create(connectionString); -var client = new EventStoreClient(settings); +var client = new KurrentClient(settings); #endregion createClient @@ -67,4 +67,4 @@ await client.AppendToStreamAsync( public class TestEvent { public string? EntityId { get; set; } public string? ImportantData { get; set; } -} \ No newline at end of file +} diff --git a/samples/quick-start/quick-start.csproj b/samples/quick-start/quick-start.csproj index 74eea2001..5fe35463c 100644 --- a/samples/quick-start/quick-start.csproj +++ b/samples/quick-start/quick-start.csproj @@ -2,8 +2,7 @@ quick_start - - + diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index 854634da8..751086d5c 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -1,6 +1,6 @@ #pragma warning disable CS8321 // Local function is declared but never used -await using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113?tls=false")); +await using var client = new KurrentClient(KurrentClientSettings.Create("esdb://localhost:2113?tls=false")); var events = Enumerable.Range(0, 20) .Select( @@ -21,7 +21,7 @@ await client.AppendToStreamAsync( return; -static async Task ReadFromStream(EventStoreClient client) { +static async Task ReadFromStream(KurrentClient client) { #region read-from-stream var events = client.ReadStreamAsync( @@ -46,7 +46,7 @@ static async Task ReadFromStream(EventStoreClient client) { #endregion } -static async Task ReadFromStreamMessages(EventStoreClient client) { +static async Task ReadFromStreamMessages(KurrentClient client) { #region read-from-stream-messages var streamPosition = StreamPosition.Start; @@ -90,7 +90,7 @@ static async Task ReadFromStreamMessages(EventStoreClient client) { #endregion iterate-stream-messages } -static async Task ReadFromStreamPosition(EventStoreClient client) { +static async Task ReadFromStreamPosition(KurrentClient client) { #region read-from-stream-position var events = client.ReadStreamAsync( @@ -109,7 +109,7 @@ static async Task ReadFromStreamPosition(EventStoreClient client) { #endregion iterate-stream } -static async Task ReadFromStreamPositionCheck(EventStoreClient client) { +static async Task ReadFromStreamPositionCheck(KurrentClient client) { #region checking-for-stream-presence var result = client.ReadStreamAsync( @@ -126,7 +126,7 @@ static async Task ReadFromStreamPositionCheck(EventStoreClient client) { #endregion checking-for-stream-presence } -static async Task ReadFromStreamBackwards(EventStoreClient client) { +static async Task ReadFromStreamBackwards(KurrentClient client) { #region reading-backwards var events = client.ReadStreamAsync( @@ -140,7 +140,7 @@ static async Task ReadFromStreamBackwards(EventStoreClient client) { #endregion reading-backwards } -static async Task ReadFromStreamMessagesBackwards(EventStoreClient client) { +static async Task ReadFromStreamMessagesBackwards(KurrentClient client) { #region read-from-stream-messages-backwards var results = client.ReadStreamAsync( @@ -175,7 +175,7 @@ static async Task ReadFromStreamMessagesBackwards(EventStoreClient client) { #endregion iterate-stream-messages-backwards } -static async Task ReadFromAllStream(EventStoreClient client) { +static async Task ReadFromAllStream(KurrentClient client) { #region read-from-all-stream var events = client.ReadAllAsync(Direction.Forwards, Position.Start); @@ -189,7 +189,7 @@ static async Task ReadFromAllStream(EventStoreClient client) { #endregion read-from-all-stream-iterate } -static async Task ReadFromAllStreamMessages(EventStoreClient client) { +static async Task ReadFromAllStreamMessages(KurrentClient client) { #region read-from-all-stream-messages var position = Position.Start; @@ -216,7 +216,7 @@ static async Task ReadFromAllStreamMessages(EventStoreClient client) { #endregion iterate-all-stream-messages } -static async Task IgnoreSystemEvents(EventStoreClient client) { +static async Task IgnoreSystemEvents(KurrentClient client) { #region ignore-system-events var events = client.ReadAllAsync(Direction.Forwards, Position.Start); @@ -230,7 +230,7 @@ static async Task IgnoreSystemEvents(EventStoreClient client) { #endregion ignore-system-events } -static async Task ReadFromAllStreamBackwards(EventStoreClient client) { +static async Task ReadFromAllStreamBackwards(KurrentClient client) { #region read-from-all-stream-backwards var events = client.ReadAllAsync(Direction.Backwards, Position.End); @@ -244,7 +244,7 @@ static async Task ReadFromAllStreamBackwards(EventStoreClient client) { #endregion read-from-all-stream-iterate } -static async Task ReadFromAllStreamBackwardsMessages(EventStoreClient client) { +static async Task ReadFromAllStreamBackwardsMessages(KurrentClient client) { #region read-from-all-stream-messages-backwards var position = Position.End; @@ -272,7 +272,7 @@ static async Task ReadFromAllStreamBackwardsMessages(EventStoreClient client) { #endregion iterate-all-stream-messages-backwards } -static async Task FilteringOutSystemEvents(EventStoreClient client) { +static async Task FilteringOutSystemEvents(KurrentClient client) { var events = client.ReadAllAsync(Direction.Forwards, Position.Start); await foreach (var e in events) @@ -280,7 +280,7 @@ static async Task FilteringOutSystemEvents(EventStoreClient client) { Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); } -static void ReadStreamOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) { +static void ReadStreamOverridingUserCredentials(KurrentClient client, CancellationToken cancellationToken) { #region overriding-user-credentials var result = client.ReadStreamAsync( @@ -294,7 +294,7 @@ static void ReadStreamOverridingUserCredentials(EventStoreClient client, Cancell #endregion overriding-user-credentials } -static void ReadAllOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) { +static void ReadAllOverridingUserCredentials(KurrentClient client, CancellationToken cancellationToken) { #region read-all-overriding-user-credentials var result = client.ReadAllAsync( @@ -307,7 +307,7 @@ static void ReadAllOverridingUserCredentials(EventStoreClient client, Cancellati #endregion read-all-overriding-user-credentials } -static void ReadAllResolvingLinkTos(EventStoreClient client, CancellationToken cancellationToken) { +static void ReadAllResolvingLinkTos(KurrentClient client, CancellationToken cancellationToken) { #region read-from-all-stream-resolving-link-Tos var result = client.ReadAllAsync( @@ -318,4 +318,4 @@ static void ReadAllResolvingLinkTos(EventStoreClient client, CancellationToken c ); #endregion read-from-all-stream-resolving-link-Tos -} \ No newline at end of file +} diff --git a/samples/reading-events/reading-events.csproj b/samples/reading-events/reading-events.csproj index d29b531fb..d79ede9fc 100644 --- a/samples/reading-events/reading-events.csproj +++ b/samples/reading-events/reading-events.csproj @@ -2,8 +2,7 @@ reading_events - - + diff --git a/samples/secure-with-tls/Program.cs b/samples/secure-with-tls/Program.cs index 621ee46b1..b2dcd05bb 100644 --- a/samples/secure-with-tls/Program.cs +++ b/samples/secure-with-tls/Program.cs @@ -6,7 +6,7 @@ Console.WriteLine($"Connecting to EventStoreDB at: {connectionString}"); -await using var client = new EventStoreClient(EventStoreClientSettings.Create(connectionString)); +await using var client = new KurrentClient(KurrentClientSettings.Create(connectionString)); var eventData = new EventData(Uuid.NewUuid(), "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); @@ -48,4 +48,4 @@ } Console.WriteLine($"FAILED! {exception}"); -} \ No newline at end of file +} diff --git a/samples/secure-with-tls/secure-with-tls.csproj b/samples/secure-with-tls/secure-with-tls.csproj index 9d29b3972..a11ffdb55 100644 --- a/samples/secure-with-tls/secure-with-tls.csproj +++ b/samples/secure-with-tls/secure-with-tls.csproj @@ -14,6 +14,6 @@ - + diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index b9e5de8c8..b8641905a 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -6,7 +6,7 @@ var semaphore = new SemaphoreSlim(eventCount); -await using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113?tls=false")); +await using var client = new KurrentClient(KurrentClientSettings.Create("esdb://localhost:2113?tls=false")); _ = Task.Run(async () => { await using var subscription = client.SubscribeToAll( @@ -46,7 +46,7 @@ await client.AppendToStreamAsync( return; -static async Task ExcludeSystemEvents(EventStoreClient client) { +static async Task ExcludeSystemEvents(KurrentClient client) { #region exclude-system await using var subscription = client.SubscribeToAll( @@ -63,7 +63,7 @@ static async Task ExcludeSystemEvents(EventStoreClient client) { #endregion exclude-system } -static async Task EventTypePrefix(EventStoreClient client) { +static async Task EventTypePrefix(KurrentClient client) { #region event-type-prefix var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.Prefix("customer-")); @@ -80,7 +80,7 @@ static async Task EventTypePrefix(EventStoreClient client) { } } -static async Task EventTypeRegex(EventStoreClient client) { +static async Task EventTypeRegex(KurrentClient client) { #region event-type-regex var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.RegularExpression("^user|^company")); @@ -97,7 +97,7 @@ static async Task EventTypeRegex(EventStoreClient client) { } } -static async Task StreamPrefix(EventStoreClient client) { +static async Task StreamPrefix(KurrentClient client) { #region stream-prefix var filterOptions = new SubscriptionFilterOptions(StreamFilter.Prefix("user-")); @@ -114,7 +114,7 @@ static async Task StreamPrefix(EventStoreClient client) { } } -static async Task StreamRegex(EventStoreClient client) { +static async Task StreamRegex(KurrentClient client) { #region stream-regex var filterOptions = new SubscriptionFilterOptions(StreamFilter.RegularExpression("^account|^savings")); @@ -131,7 +131,7 @@ static async Task StreamRegex(EventStoreClient client) { } } -static async Task CheckpointCallback(EventStoreClient client) { +static async Task CheckpointCallback(KurrentClient client) { #region checkpoint var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents()); @@ -151,7 +151,7 @@ static async Task CheckpointCallback(EventStoreClient client) { #endregion checkpoint } -static async Task CheckpointCallbackWithInterval(EventStoreClient client) { +static async Task CheckpointCallbackWithInterval(KurrentClient client) { #region checkpoint-with-interval var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents(), 1000); diff --git a/samples/server-side-filtering/server-side-filtering.csproj b/samples/server-side-filtering/server-side-filtering.csproj index 0c13c9497..f3a5ffc58 100644 --- a/samples/server-side-filtering/server-side-filtering.csproj +++ b/samples/server-side-filtering/server-side-filtering.csproj @@ -2,8 +2,7 @@ server_side_filtering - - + diff --git a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs index d4c3f4d59..f66c237ce 100644 --- a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs +++ b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs @@ -5,15 +5,15 @@ namespace setting_up_dependency_injection.Controllers { [Route("[controller]")] public class EventStoreController : ControllerBase { #region using-dependency - private readonly EventStoreClient _eventStoreClient; + private readonly KurrentClient _KurrentClient; - public EventStoreController(EventStoreClient eventStoreClient) { - _eventStoreClient = eventStoreClient; + public EventStoreController(KurrentClient KurrentClient) { + _KurrentClient = KurrentClient; } [HttpGet] public IAsyncEnumerable Get() { - return _eventStoreClient.ReadAllAsync(Direction.Forwards, Position.Start); + return _KurrentClient.ReadAllAsync(Direction.Forwards, Position.Start); } #endregion using-dependency } diff --git a/samples/setting-up-dependency-injection/Startup.cs b/samples/setting-up-dependency-injection/Startup.cs index a1d618aff..cb8c9fb94 100644 --- a/samples/setting-up-dependency-injection/Startup.cs +++ b/samples/setting-up-dependency-injection/Startup.cs @@ -10,7 +10,7 @@ public void ConfigureServices(IServiceCollection services) { #region setting-up-dependency - services.AddEventStoreClient("esdb://admin:changeit@localhost:2113?tls=false"); + services.AddKurrentClient("esdb://admin:changeit@localhost:2113?tls=false"); #endregion setting-up-dependency } @@ -22,4 +22,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } -} \ No newline at end of file +} diff --git a/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj b/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj index 3a57950a1..e4f581a89 100644 --- a/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj +++ b/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj @@ -4,6 +4,6 @@ - + diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index 90fa29737..01990a17e 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -3,7 +3,7 @@ // ReSharper disable UnusedParameter.Local // ReSharper disable UnusedVariable -await using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113?tls=false")); +await using var client = new KurrentClient(KurrentClientSettings.Create("esdb://localhost:2113?tls=false")); await Task.WhenAll(YieldSamples().Select(async sample => { try { @@ -27,7 +27,7 @@ IEnumerable YieldSamples() { yield return OverridingUserCredentials(client, GetCT()); } -static async Task SubscribeToStreamFromPosition(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToStreamFromPosition(KurrentClient client, CancellationToken ct) { #region subscribe-to-stream-from-position await using var subscription = client.SubscribeToStream( @@ -46,7 +46,7 @@ static async Task SubscribeToStreamFromPosition(EventStoreClient client, Cancell #endregion subscribe-to-stream-from-position } -static async Task SubscribeToStreamLive(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToStreamLive(KurrentClient client, CancellationToken ct) { #region subscribe-to-stream-live await using var subscription = client.SubscribeToStream( @@ -65,7 +65,7 @@ static async Task SubscribeToStreamLive(EventStoreClient client, CancellationTok #endregion subscribe-to-stream-live } -static async Task SubscribeToStreamResolvingLinkTos(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToStreamResolvingLinkTos(KurrentClient client, CancellationToken ct) { #region subscribe-to-stream-resolving-linktos await using var subscription = client.SubscribeToStream( @@ -85,7 +85,7 @@ static async Task SubscribeToStreamResolvingLinkTos(EventStoreClient client, Can #endregion subscribe-to-stream-resolving-linktos } -static async Task SubscribeToStreamSubscriptionDropped(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToStreamSubscriptionDropped(KurrentClient client, CancellationToken ct) { #region subscribe-to-stream-subscription-dropped var checkpoint = await ReadStreamCheckpointAsync() switch { @@ -120,7 +120,7 @@ static async Task SubscribeToStreamSubscriptionDropped(EventStoreClient client, #endregion subscribe-to-stream-subscription-dropped } -static async Task SubscribeToStream(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToStream(KurrentClient client, CancellationToken ct) { #region subscribe-to-stream await using var subscription = client.SubscribeToStream( @@ -139,7 +139,7 @@ static async Task SubscribeToStream(EventStoreClient client, CancellationToken c #endregion subscribe-to-stream } -static async Task SubscribeToAll(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToAll(KurrentClient client, CancellationToken ct) { #region subscribe-to-all await using var subscription = client.SubscribeToAll( @@ -157,7 +157,7 @@ static async Task SubscribeToAll(EventStoreClient client, CancellationToken ct) #endregion subscribe-to-all } -static async Task SubscribeToAllFromPosition(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToAllFromPosition(KurrentClient client, CancellationToken ct) { #region subscribe-to-all-from-position var result = await client.AppendToStreamAsync( @@ -182,7 +182,7 @@ static async Task SubscribeToAllFromPosition(EventStoreClient client, Cancellati #endregion subscribe-to-all-from-position } -static async Task SubscribeToAllLive(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToAllLive(KurrentClient client, CancellationToken ct) { #region subscribe-to-all-live var subscription = client.SubscribeToAll( @@ -200,7 +200,7 @@ static async Task SubscribeToAllLive(EventStoreClient client, CancellationToken #endregion subscribe-to-all-live } -static async Task SubscribeToAllSubscriptionDropped(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToAllSubscriptionDropped(KurrentClient client, CancellationToken ct) { #region subscribe-to-all-subscription-dropped var checkpoint = await ReadCheckpointAsync() switch { @@ -237,7 +237,7 @@ static async Task SubscribeToAllSubscriptionDropped(EventStoreClient client, Can #endregion subscribe-to-all-subscription-dropped } -static async Task SubscribeToFiltered(EventStoreClient client, CancellationToken ct) { +static async Task SubscribeToFiltered(KurrentClient client, CancellationToken ct) { #region stream-prefix-filtered-subscription var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); @@ -267,7 +267,7 @@ static async Task SubscribeToFiltered(EventStoreClient client, CancellationToken #endregion stream-regex-filtered-subscription } -static async Task OverridingUserCredentials(EventStoreClient client, CancellationToken ct) { +static async Task OverridingUserCredentials(KurrentClient client, CancellationToken ct) { #region overriding-user-credentials await using var subscription = client.SubscribeToAll( diff --git a/samples/subscribing-to-streams/subscribing-to-streams.csproj b/samples/subscribing-to-streams/subscribing-to-streams.csproj index 92424b533..83a740e45 100644 --- a/samples/subscribing-to-streams/subscribing-to-streams.csproj +++ b/samples/subscribing-to-streams/subscribing-to-streams.csproj @@ -2,8 +2,7 @@ subscribing_to_streams - - + diff --git a/samples/user-certificates/Program.cs b/samples/user-certificates/Program.cs index dddbb1c7f..98f9e882e 100644 --- a/samples/user-certificates/Program.cs +++ b/samples/user-certificates/Program.cs @@ -9,11 +9,11 @@ static async Task ClientWithUserCertificates() { const string userCertFile = "/path/to/user.crt"; const string userKeyFile = "/path/to/user.key"; - var settings = EventStoreClientSettings.Create( + var settings = KurrentClientSettings.Create( $"esdb://localhost:2113/?tls=true&tlsVerifyCert=true&userCertFile={userCertFile}&userKeyFile={userKeyFile}" ); - await using var client = new EventStoreClient(settings); + await using var client = new KurrentClient(settings); # endregion client-with-user-certificates } catch (InvalidClientCertificateException) { diff --git a/samples/user-certificates/user-certificates.csproj b/samples/user-certificates/user-certificates.csproj index 8cb9d2294..14de7d848 100644 --- a/samples/user-certificates/user-certificates.csproj +++ b/samples/user-certificates/user-certificates.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/EventStore.Client.Extensions.OpenTelemetry/EventStore.Client.Extensions.OpenTelemetry.csproj b/src/EventStore.Client.Extensions.OpenTelemetry/EventStore.Client.Extensions.OpenTelemetry.csproj deleted file mode 100644 index 81f4d38ea..000000000 --- a/src/EventStore.Client.Extensions.OpenTelemetry/EventStore.Client.Extensions.OpenTelemetry.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - EventStore.Client.Extensions.OpenTelemetry - - - - EventStore.Client.Extensions.OpenTelemetry - Extensions used to facilitate instrumentation of the EventStore Client. - - - - - - - - - - - diff --git a/src/EventStore.Client.Extensions.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/EventStore.Client.Extensions.OpenTelemetry/TracerProviderBuilderExtensions.cs deleted file mode 100644 index 6a8b42b96..000000000 --- a/src/EventStore.Client.Extensions.OpenTelemetry/TracerProviderBuilderExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using EventStore.Client.Diagnostics; -using JetBrains.Annotations; -using OpenTelemetry.Trace; - -namespace EventStore.Client.Extensions.OpenTelemetry; - -/// -/// Extension methods used to facilitate tracing instrumentation of the EventStore Client. -/// -[PublicAPI] -public static class TracerProviderBuilderExtensions { - /// - /// Adds the EventStore client ActivitySource name to the list of subscribed sources on the - /// - /// being configured. - /// The instance of to chain configuration. - public static TracerProviderBuilder AddEventStoreClientInstrumentation(this TracerProviderBuilder builder) => - builder.AddSource(EventStoreClientDiagnostics.InstrumentationName); -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Certificates/X509Certificates.cs b/src/EventStore.Client/Core/Certificates/X509Certificates.cs deleted file mode 100644 index 3fe1006f5..000000000 --- a/src/EventStore.Client/Core/Certificates/X509Certificates.cs +++ /dev/null @@ -1,114 +0,0 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -#if NET48 -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Security; -#endif - -namespace EventStore.Client; - -static class X509Certificates { - // TODO SS: Use .NET 8 X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath) once the Windows32Exception issue is resolved - public static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) { - try { -#if NET9_0_OR_GREATER - using var publicCert = X509CertificateLoader.LoadCertificateFromFile(certPemFilePath); -#else - using var publicCert = new X509Certificate2(certPemFilePath); -#endif - using var privateKey = RSA.Create().ImportPrivateKeyFromFile(keyPemFilePath); - using var certificate = publicCert.CopyWithPrivateKey(privateKey); - -#if NET48 - return new(certificate.Export(X509ContentType.Pfx)); -#else - return X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath); -#endif - } catch (Exception ex) { - throw new CryptographicException($"Failed to load private key: {ex.Message}"); - } - - // Notes: - // using X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath) would be the ideal choice here, - // but it's currently causing a Win32Exception specifically on Windows. Alternative implementation is used until the issue is resolved. - // - // Error: The SSL connection could not be established, see inner exception. AuthenticationException: Authentication failed because the platform - // does not support ephemeral keys. Win32Exception: No credentials are available in the security package - // - // public static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) => - // X509Certificate2.CreateFromPemFile(certPemFilePath, keyPemFilePath); - } -} - -public static class RsaExtensions { -#if NET48 - public static RSA ImportPrivateKeyFromFile(this RSA rsa, string privateKeyPath) { - var (content, label) = LoadPemKeyFile(privateKeyPath); - - using var reader = new PemReader(new StringReader(string.Join(Environment.NewLine, content))); - - var keyParameters = reader.ReadObject() switch { - RsaPrivateCrtKeyParameters parameters => parameters, - AsymmetricCipherKeyPair keyPair => keyPair.Private as RsaPrivateCrtKeyParameters, - _ => throw new NotSupportedException($"Invalid private key format: {label}") - }; - - rsa.ImportParameters(DotNetUtilities.ToRSAParameters(keyParameters)); - - return rsa; - } -#else - public static RSA ImportPrivateKeyFromFile(this RSA rsa, string privateKeyPath) { - var (content, label) = LoadPemKeyFile(privateKeyPath); - - var privateKey = string.Join(string.Empty, content[1..^1]); - var privateKeyBytes = Convert.FromBase64String(privateKey); - - if (label == RsaPemLabels.Pkcs8PrivateKey) - rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); - else if (label == RsaPemLabels.RSAPrivateKey) - rsa.ImportRSAPrivateKey(privateKeyBytes, out _); - - return rsa; - } -#endif - - static (string[] Content, string Label) LoadPemKeyFile(string privateKeyPath) { - var content = File.ReadAllLines(privateKeyPath); - var label = RsaPemLabels.ParseKeyLabel(content[0]); - - if (RsaPemLabels.IsEncryptedPrivateKey(label)) - throw new NotSupportedException("Encrypted private keys are not supported"); - - return (content, label); - } -} - -static class RsaPemLabels { - public const string RSAPrivateKey = "RSA PRIVATE KEY"; - public const string Pkcs8PrivateKey = "PRIVATE KEY"; - public const string EncryptedPkcs8PrivateKey = "ENCRYPTED PRIVATE KEY"; - - public static readonly string[] PrivateKeyLabels = [RSAPrivateKey, Pkcs8PrivateKey, EncryptedPkcs8PrivateKey]; - - public static bool IsPrivateKey(string label) => Array.IndexOf(PrivateKeyLabels, label) != -1; - - public static bool IsEncryptedPrivateKey(string label) => label == EncryptedPkcs8PrivateKey; - - const string LabelPrefix = "-----BEGIN "; - const string LabelSuffix = "-----"; - - public static string ParseKeyLabel(string pemFileHeader) { - var label = pemFileHeader.Replace(LabelPrefix, string.Empty).Replace(LabelSuffix, string.Empty); - - if (!IsPrivateKey(label)) - throw new CryptographicException($"Unknown private key label: {label}"); - - return label; - } -} diff --git a/src/EventStore.Client/Core/ChannelBaseExtensions.cs b/src/EventStore.Client/Core/ChannelBaseExtensions.cs deleted file mode 100644 index 3edbf59fd..000000000 --- a/src/EventStore.Client/Core/ChannelBaseExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client; - -static class ChannelBaseExtensions { - public static async ValueTask DisposeAsync(this ChannelBase channel) { - await channel.ShutdownAsync().ConfigureAwait(false); - (channel as IDisposable)?.Dispose(); - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/ChannelCache.cs b/src/EventStore.Client/Core/ChannelCache.cs deleted file mode 100644 index a3369e25f..000000000 --- a/src/EventStore.Client/Core/ChannelCache.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.Net; -using TChannel = Grpc.Net.Client.GrpcChannel; - -namespace EventStore.Client { - // Maintains Channels keyed by DnsEndPoint so the channels can be reused. - // Deals with the disposal difference between grpc.net and grpc.core - // Thread safe. - internal class ChannelCache : - IAsyncDisposable { - - private readonly EventStoreClientSettings _settings; - private readonly Random _random; - private readonly Dictionary _channels; - private readonly object _lock = new(); - private bool _disposed; - - public ChannelCache(EventStoreClientSettings settings) { - _settings = settings; - _random = new Random(0); - _channels = new Dictionary( - DnsEndPointEqualityComparer.Instance); - } - - public TChannel GetChannelInfo(DnsEndPoint endPoint) { - lock (_lock) { - ThrowIfDisposed(); - - if (!_channels.TryGetValue(endPoint, out var channel)) { - channel = ChannelFactory.CreateChannel( - settings: _settings, - endPoint: endPoint); - _channels[endPoint] = channel; - } - - return channel; - } - } - - public KeyValuePair[] GetRandomOrderSnapshot() { - lock (_lock) { - ThrowIfDisposed(); - - return _channels - .OrderBy(_ => _random.Next()) - .ToArray(); - } - } - - // Update the cache to contain channels for exactly these endpoints - public void UpdateCache(IEnumerable endPoints) { - lock (_lock) { - ThrowIfDisposed(); - - // remove - var endPointsToDiscard = _channels.Keys - .Except(endPoints, DnsEndPointEqualityComparer.Instance) - .ToArray(); - - var channelsToDispose = new List(endPointsToDiscard.Length); - - foreach (var endPoint in endPointsToDiscard) { - if (!_channels.TryGetValue(endPoint, out var channel)) - continue; - - _channels.Remove(endPoint); - channelsToDispose.Add(channel); - } - - _ = DisposeChannelsAsync(channelsToDispose); - - // add - foreach (var endPoint in endPoints) { - GetChannelInfo(endPoint); - } - } - } - - public void Dispose() { - lock (_lock) { - if (_disposed) - return; - - _disposed = true; - - foreach (var channel in _channels.Values) { - channel.Dispose(); - } - - _channels.Clear(); - } - } - - public async ValueTask DisposeAsync() { - var channelsToDispose = Array.Empty(); - - lock (_lock) { - if (_disposed) - return; - _disposed = true; - - channelsToDispose = _channels.Values.ToArray(); - _channels.Clear(); - } - - await DisposeChannelsAsync(channelsToDispose).ConfigureAwait(false); - } - - private void ThrowIfDisposed() { - lock (_lock) { - if (_disposed) { - throw new ObjectDisposedException(GetType().ToString()); - } - } - } - - private static async Task DisposeChannelsAsync(IEnumerable channels) { - foreach (var channel in channels) - await channel.DisposeAsync().ConfigureAwait(false); - } - - private class DnsEndPointEqualityComparer : IEqualityComparer { - public static readonly DnsEndPointEqualityComparer Instance = new(); - - public bool Equals(DnsEndPoint? x, DnsEndPoint? y) { - if (ReferenceEquals(x, y)) - return true; - if (x is null) - return false; - if (y is null) - return false; - if (x.GetType() != y.GetType()) - return false; - return - string.Equals(x.Host, y.Host, StringComparison.OrdinalIgnoreCase) && - x.Port == y.Port; - } - - public int GetHashCode(DnsEndPoint obj) { - unchecked { - return (StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Host) * 397) ^ - obj.Port; - } - } - } - } -} diff --git a/src/EventStore.Client/Core/ChannelFactory.cs b/src/EventStore.Client/Core/ChannelFactory.cs deleted file mode 100644 index 0dc28ee8e..000000000 --- a/src/EventStore.Client/Core/ChannelFactory.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using Grpc.Net.Client; -using EndPoint = System.Net.EndPoint; -using TChannel = Grpc.Net.Client.GrpcChannel; - -namespace EventStore.Client { - - internal static class ChannelFactory { - private const int MaxReceiveMessageLength = 17 * 1024 * 1024; - - public static TChannel CreateChannel(EventStoreClientSettings settings, EndPoint endPoint) { - var address = endPoint.ToUri(!settings.ConnectivitySettings.Insecure); - - if (settings.ConnectivitySettings.Insecure) { - //this must be switched on before creation of the HttpMessageHandler - AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); - } - - return TChannel.ForAddress( - address, - new GrpcChannelOptions { -#if NET48 - HttpHandler = CreateHandler(settings), -#else - HttpClient = new HttpClient(CreateHandler(settings), true) { - Timeout = System.Threading.Timeout.InfiniteTimeSpan, - DefaultRequestVersion = new Version(2, 0) - }, -#endif - LoggerFactory = settings.LoggerFactory, - Credentials = settings.ChannelCredentials, - DisposeHttpClient = true, - MaxReceiveMessageSize = MaxReceiveMessageLength - } - ); - - -#if NET48 - static HttpMessageHandler CreateHandler(EventStoreClientSettings settings) { - if (settings.CreateHttpMessageHandler is not null) - return settings.CreateHttpMessageHandler.Invoke(); - - var handler = new WinHttpHandler { - TcpKeepAliveEnabled = true, - TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, - TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, - EnableMultipleHttp2Connections = true - }; - - if (settings.ConnectivitySettings.Insecure) return handler; - - if (settings.ConnectivitySettings.ClientCertificate is not null) - handler.ClientCertificates.Add(settings.ConnectivitySettings.ClientCertificate); - - handler.ServerCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch { - false => delegate { return true; }, - true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => { - if (chain is null) return false; - - chain.ChainPolicy.ExtraStore.Add(settings.ConnectivitySettings.TlsCaFile); - return chain.Build(certificate); - }, - _ => null - }; - - return handler; - } -#else - static HttpMessageHandler CreateHandler(EventStoreClientSettings settings) { - if (settings.CreateHttpMessageHandler is not null) - return settings.CreateHttpMessageHandler.Invoke(); - - var handler = new SocketsHttpHandler { - KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, - KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, - EnableMultipleHttp2Connections = true - }; - - if (settings.ConnectivitySettings.Insecure) - return handler; - - if (settings.ConnectivitySettings.ClientCertificate is not null) { - handler.SslOptions.ClientCertificates = new X509CertificateCollection { - settings.ConnectivitySettings.ClientCertificate - }; - } - - handler.SslOptions.RemoteCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch { - false => delegate { return true; }, - true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => { - if (certificate is not X509Certificate2 peerCertificate || chain is null) return false; - - chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - chain.ChainPolicy.CustomTrustStore.Add(settings.ConnectivitySettings.TlsCaFile); - return chain.Build(peerCertificate); - }, - _ => null - }; - - return handler; - } -#endif - } - } -} diff --git a/src/EventStore.Client/Core/ChannelInfo.cs b/src/EventStore.Client/Core/ChannelInfo.cs deleted file mode 100644 index 87c80b644..000000000 --- a/src/EventStore.Client/Core/ChannelInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client { -#pragma warning disable 1591 - public record ChannelInfo( - ChannelBase Channel, - ServerCapabilities ServerCapabilities, - CallInvoker CallInvoker); -} diff --git a/src/EventStore.Client/Core/ChannelSelector.cs b/src/EventStore.Client/Core/ChannelSelector.cs deleted file mode 100644 index 354c0a9f5..000000000 --- a/src/EventStore.Client/Core/ChannelSelector.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; - -namespace EventStore.Client { - internal class ChannelSelector : IChannelSelector { - private readonly IChannelSelector _inner; - - public ChannelSelector( - EventStoreClientSettings settings, - ChannelCache channelCache) { - _inner = settings.ConnectivitySettings.IsSingleNode - ? new SingleNodeChannelSelector(settings, channelCache) - : new GossipChannelSelector(settings, channelCache, new GrpcGossipClient(settings)); - } - - public Task SelectChannelAsync(CancellationToken cancellationToken) => - _inner.SelectChannelAsync(cancellationToken); - - public ChannelBase SelectChannel(DnsEndPoint endPoint) => - _inner.SelectChannel(endPoint); - } -} diff --git a/src/EventStore.Client/Core/ClusterMessage.cs b/src/EventStore.Client/Core/ClusterMessage.cs deleted file mode 100644 index 64701781e..000000000 --- a/src/EventStore.Client/Core/ClusterMessage.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Net; - -namespace EventStore.Client { - internal static class ClusterMessages { - public record ClusterInfo(MemberInfo[] Members); - - public record MemberInfo(Uuid InstanceId, VNodeState State, bool IsAlive, DnsEndPoint EndPoint); - - public enum VNodeState { - Initializing = 0, - DiscoverLeader = 1, - Unknown = 2, - PreReplica = 3, - CatchingUp = 4, - Clone = 5, - Follower = 6, - PreLeader = 7, - Leader = 8, - Manager = 9, - ShuttingDown = 10, - Shutdown = 11, - ReadOnlyLeaderless = 12, - PreReadOnlyReplica = 13, - ReadOnlyReplica = 14, - ResigningLeader = 15 - } - } -} diff --git a/src/EventStore.Client/Core/Common/AsyncStreamReaderExtensions.cs b/src/EventStore.Client/Core/Common/AsyncStreamReaderExtensions.cs deleted file mode 100644 index 98f9de54d..000000000 --- a/src/EventStore.Client/Core/Common/AsyncStreamReaderExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Threading.Channels; -using System.Runtime.CompilerServices; -using Grpc.Core; - -namespace EventStore.Client; - -static class AsyncStreamReaderExtensions { - public static async IAsyncEnumerable ReadAllAsync( - this IAsyncStreamReader reader, - [EnumeratorCancellation] - CancellationToken cancellationToken = default - ) { - while (await reader.MoveNext(cancellationToken).ConfigureAwait(false)) - yield return reader.Current; - } - - public static async IAsyncEnumerable ReadAllAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) { -#if NET - await foreach (var item in reader.ReadAllAsync(cancellationToken)) - yield return item; -#else - while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - while (reader.TryRead(out T? item)) { - yield return item; - } - } -#endif - } -} diff --git a/src/EventStore.Client/Core/Common/Constants.cs b/src/EventStore.Client/Core/Common/Constants.cs deleted file mode 100644 index 2ed9d7c82..000000000 --- a/src/EventStore.Client/Core/Common/Constants.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace EventStore.Client; - -static class Constants { - public static class Exceptions { - public const string ExceptionKey = "exception"; - - public const string AccessDenied = "access-denied"; - public const string InvalidTransaction = "invalid-transaction"; - public const string StreamDeleted = "stream-deleted"; - public const string WrongExpectedVersion = "wrong-expected-version"; - public const string StreamNotFound = "stream-not-found"; - public const string MaximumAppendSizeExceeded = "maximum-append-size-exceeded"; - public const string MissingRequiredMetadataProperty = "missing-required-metadata-property"; - public const string NotLeader = "not-leader"; - - public const string PersistentSubscriptionFailed = "persistent-subscription-failed"; - public const string PersistentSubscriptionDoesNotExist = "persistent-subscription-does-not-exist"; - public const string PersistentSubscriptionExists = "persistent-subscription-exists"; - public const string MaximumSubscribersReached = "maximum-subscribers-reached"; - public const string PersistentSubscriptionDropped = "persistent-subscription-dropped"; - - public const string UserNotFound = "user-not-found"; - public const string UserConflict = "user-conflict"; - - public const string ScavengeNotFound = "scavenge-not-found"; - - public const string ExpectedVersion = "expected-version"; - public const string ActualVersion = "actual-version"; - public const string StreamName = "stream-name"; - public const string GroupName = "group-name"; - public const string Reason = "reason"; - public const string MaximumAppendSize = "maximum-append-size"; - public const string RequiredMetadataProperties = "required-metadata-properties"; - public const string ScavengeId = "scavenge-id"; - public const string LeaderEndpointHost = "leader-endpoint-host"; - public const string LeaderEndpointPort = "leader-endpoint-port"; - - public const string LoginName = "login-name"; - } - - public static class Metadata { - public const string Type = "type"; - public const string Created = "created"; - public const string ContentType = "content-type"; - - public static readonly string[] RequiredMetadata = [Type, ContentType]; - - public static class ContentTypes { - public const string ApplicationJson = "application/json"; - public const string ApplicationOctetStream = "application/octet-stream"; - } - } - - public static class Headers { - public const string Authorization = "authorization"; - public const string BasicScheme = "Basic"; - public const string BearerScheme = "Bearer"; - - public const string ConnectionName = "connection-name"; - public const string RequiresLeader = "requires-leader"; - } -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/ActivitySourceExtensions.cs b/src/EventStore.Client/Core/Common/Diagnostics/ActivitySourceExtensions.cs deleted file mode 100644 index 71f4372da..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/ActivitySourceExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - -using System.Diagnostics; -using EventStore.Diagnostics; -using EventStore.Diagnostics.Telemetry; -using EventStore.Diagnostics.Tracing; - -namespace EventStore.Client.Diagnostics; - -static class ActivitySourceExtensions { - public static async ValueTask TraceClientOperation( - this ActivitySource source, - Func> tracedOperation, - string operationName, - Func? tagsFactory = null - ) { - if (source.HasNoActiveListeners()) - return await tracedOperation().ConfigureAwait(false); - - var tags = tagsFactory?.Invoke(); - - using var activity = StartActivity(source, operationName, ActivityKind.Client, tags, Activity.Current?.Context); - - try { - var res = await tracedOperation().ConfigureAwait(false); - activity?.StatusOk(); - return res; - } catch (Exception ex) { - activity?.StatusError(ex); - throw; - } - } - - public static void TraceSubscriptionEvent( - this ActivitySource source, - string? subscriptionId, - ResolvedEvent resolvedEvent, - ChannelInfo channelInfo, - EventStoreClientSettings settings, - UserCredentials? userCredentials - ) { - if (source.HasNoActiveListeners() || resolvedEvent.Event is null) - return; - - var parentContext = resolvedEvent.Event.Metadata.ExtractPropagationContext(); - - if (parentContext == default(ActivityContext)) return; - - var tags = new ActivityTagsCollection() - .WithRequiredTag(TelemetryTags.EventStore.Stream, resolvedEvent.OriginalEvent.EventStreamId) - .WithOptionalTag(TelemetryTags.EventStore.SubscriptionId, subscriptionId) - .WithRequiredTag(TelemetryTags.EventStore.EventId, resolvedEvent.OriginalEvent.EventId.ToString()) - .WithRequiredTag(TelemetryTags.EventStore.EventType, resolvedEvent.OriginalEvent.EventType) - // Ensure consistent server.address attribute when connecting to cluster via dns discovery - .WithGrpcChannelServerTags(settings, channelInfo) - .WithClientSettingsServerTags(settings) - .WithOptionalTag( - TelemetryTags.Database.User, - userCredentials?.Username ?? settings.DefaultCredentials?.Username - ); - - StartActivity(source, TracingConstants.Operations.Subscribe, ActivityKind.Consumer, tags, parentContext) - ?.Dispose(); - } - - static Activity? StartActivity( - this ActivitySource source, - string operationName, ActivityKind activityKind, ActivityTagsCollection? tags = null, - ActivityContext? parentContext = null - ) { - if (source.HasNoActiveListeners()) - return null; - - (tags ??= new ActivityTagsCollection()) - .WithRequiredTag(TelemetryTags.Database.System, "eventstoredb") - .WithRequiredTag(TelemetryTags.Database.Operation, operationName); - - return source - .CreateActivity( - operationName, - activityKind, - parentContext ?? default, - tags, - idFormat: ActivityIdFormat.W3C - ) - ?.Start(); - } - - static bool HasNoActiveListeners(this ActivitySource source) => !source.HasListeners(); -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/ActivityTagsCollectionExtensions.cs b/src/EventStore.Client/Core/Common/Diagnostics/ActivityTagsCollectionExtensions.cs deleted file mode 100644 index bc3c2aa75..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/ActivityTagsCollectionExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; -using EventStore.Diagnostics; -using EventStore.Diagnostics.Telemetry; - -namespace EventStore.Client.Diagnostics; - -static class ActivityTagsCollectionExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ActivityTagsCollection WithGrpcChannelServerTags(this ActivityTagsCollection tags, EventStoreClientSettings settings, ChannelInfo? channelInfo) { - if (channelInfo is null) - return tags; - - var authorityParts = channelInfo.Channel.Target.Split([':'], StringSplitOptions.RemoveEmptyEntries); - - string host = authorityParts[0]; - int port = authorityParts.Length == 1 - ? settings.ConnectivitySettings.Insecure ? 80 : 443 - : int.Parse(authorityParts[1]); - - return tags - .WithRequiredTag(TelemetryTags.Server.Address, host) - .WithRequiredTag(TelemetryTags.Server.Port, port); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ActivityTagsCollection WithClientSettingsServerTags(this ActivityTagsCollection source, EventStoreClientSettings settings) { - if (settings.ConnectivitySettings.DnsGossipSeeds?.Length != 1) - return source; - - var gossipSeed = settings.ConnectivitySettings.DnsGossipSeeds[0]; - - return source - .WithRequiredTag(TelemetryTags.Server.Address, gossipSeed.Host) - .WithRequiredTag(TelemetryTags.Server.Port, gossipSeed.Port); - } -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityExtensions.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityExtensions.cs deleted file mode 100644 index 4594d1000..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using EventStore.Diagnostics.Telemetry; -using EventStore.Diagnostics.Tracing; - -namespace EventStore.Diagnostics; - -static class ActivityExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TracingMetadata GetTracingMetadata(this Activity activity) => - new(activity.TraceId.ToString(), activity.SpanId.ToString()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Activity StatusOk(this Activity activity, string? description = null) => - activity.SetActivityStatus(ActivityStatus.Ok(description)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Activity StatusError(this Activity activity, Exception exception) => - activity.SetActivityStatus(ActivityStatus.Error(exception)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Activity RecordException(this Activity activity, Exception? exception) { - if (exception is null) return activity; - - var ex = exception is AggregateException aex ? aex.Flatten() : exception; - - var tags = new ActivityTagsCollection { - { TelemetryTags.Exception.Type, ex.GetType().FullName }, - { TelemetryTags.Exception.Stacktrace, ex.ToInvariantString() } - }; - - if (!string.IsNullOrWhiteSpace(exception.Message)) - tags.Add(TelemetryTags.Exception.Message, ex.Message); - - activity.AddEvent(new ActivityEvent(TelemetryTags.Exception.EventName, default, tags)); - - return activity; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Activity SetActivityStatus(this Activity activity, ActivityStatus status) { - var statusCode = ActivityStatusCodeHelper.GetTagValueForStatusCode(status.StatusCode); - - activity.SetStatus(status.StatusCode, status.Description); - activity.SetTag(TelemetryTags.Otel.StatusCode, statusCode); - activity.SetTag(TelemetryTags.Otel.StatusDescription, status.Description); - - return activity.IsAllDataRequested ? activity.RecordException(status.Exception) : activity; - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatus.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatus.cs deleted file mode 100644 index a25b7326f..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatus.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Diagnostics; - -namespace EventStore.Diagnostics; - -record ActivityStatus(ActivityStatusCode StatusCode, string? Description, Exception? Exception) { - public static ActivityStatus Ok(string? description = null) => - new(ActivityStatusCode.Ok, description, null); - - public static ActivityStatus Error(Exception exception, string? description = null) => - new(ActivityStatusCode.Error, description ?? exception.Message, exception); -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatusCodeHelper.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatusCodeHelper.cs deleted file mode 100644 index 77810aa6c..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityStatusCodeHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -using static System.Diagnostics.ActivityStatusCode; -using static System.StringComparison; - -namespace EventStore.Diagnostics; - -static class ActivityStatusCodeHelper { - public const string UnsetStatusCodeTagValue = "UNSET"; - public const string OkStatusCodeTagValue = "OK"; - public const string ErrorStatusCodeTagValue = "ERROR"; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? GetTagValueForStatusCode(ActivityStatusCode statusCode) => - statusCode switch { - Unset => UnsetStatusCodeTagValue, - Error => ErrorStatusCodeTagValue, - Ok => OkStatusCodeTagValue, - _ => null - }; -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityTagsCollectionExtensions.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityTagsCollectionExtensions.cs deleted file mode 100644 index 15aa0662e..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/ActivityTagsCollectionExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace EventStore.Diagnostics; - -static class ActivityTagsCollectionExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ActivityTagsCollection WithRequiredTag(this ActivityTagsCollection source, string key, object? value) { - source[key] = value ?? throw new ArgumentNullException(key); - return source; - } - - /// - /// - If the key previously existed in the collection and the value is , the collection item matching the key will get removed from the collection. - /// - If the key previously existed in the collection and the value is not , the value will replace the old value stored in the collection. - /// - Otherwise, a new item will get added to the collection. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ActivityTagsCollection WithOptionalTag(this ActivityTagsCollection source, string key, object? value) { - source[key] = value; - return source; - } -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/ExceptionExtensions.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/ExceptionExtensions.cs deleted file mode 100644 index 8403c3c01..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/ExceptionExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Globalization; - -namespace EventStore.Diagnostics; - -static class ExceptionExtensions { - /// - /// Returns a culture-independent string representation of the given object, - /// appropriate for diagnostics tracing. - /// - /// Exception to convert to string. - /// Exception as string with no culture. - public static string ToInvariantString(this Exception exception) { - var originalUiCulture = Thread.CurrentThread.CurrentUICulture; - - try { - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; - return exception.ToString(); - } - finally { - Thread.CurrentThread.CurrentUICulture = originalUiCulture; - } - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/Telemetry/TelemetryTags.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/Telemetry/TelemetryTags.cs deleted file mode 100644 index f7d5e4b17..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/Telemetry/TelemetryTags.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ReSharper disable CheckNamespace - -namespace EventStore.Diagnostics.Telemetry; - -// The attributes below match the specification of v1.24.0 of the Open Telemetry semantic conventions. -// Some attributes are ignored where not required or relevant. -// https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/general/trace.md -// https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/database/database-spans.md -// https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/exceptions/exceptions-spans.md - -static partial class TelemetryTags { - public static class Database { - public const string User = "db.user"; - public const string System = "db.system"; - public const string Operation = "db.operation"; - } - - public static class Server { - public const string Address = "server.address"; - public const string Port = "server.port"; - public const string SocketAddress = "server.socket.address"; // replaces: "net.peer.ip" (AttributeNetPeerIp) - } - - public static class Exception { - public const string EventName = "exception"; - public const string Type = "exception.type"; - public const string Message = "exception.message"; - public const string Stacktrace = "exception.stacktrace"; - } - - public static class Otel { - public const string StatusCode = "otel.status_code"; - public const string StatusDescription = "otel.status_description"; - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingConstants.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingConstants.cs deleted file mode 100644 index 26aa2be21..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingConstants.cs +++ /dev/null @@ -1,10 +0,0 @@ -// ReSharper disable CheckNamespace - -namespace EventStore.Diagnostics.Tracing; - -static partial class TracingConstants { - public static class Metadata { - public const string TraceId = "$traceId"; - public const string SpanId = "$spanId"; - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingMetadata.cs b/src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingMetadata.cs deleted file mode 100644 index ecb9c68c6..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Core/Tracing/TracingMetadata.cs +++ /dev/null @@ -1,32 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Diagnostics; -using System.Text.Json.Serialization; - -namespace EventStore.Diagnostics.Tracing; - -readonly record struct TracingMetadata( - [property: JsonPropertyName(TracingConstants.Metadata.TraceId)] - string? TraceId, - [property: JsonPropertyName(TracingConstants.Metadata.SpanId)] - string? SpanId -) { - public static readonly TracingMetadata None = new(null, null); - - [JsonIgnore] public bool IsValid => TraceId != null && SpanId != null; - - public ActivityContext? ToActivityContext(bool isRemote = true) { - try { - return IsValid - ? new ActivityContext( - ActivityTraceId.CreateFromString(new ReadOnlySpan(TraceId!.ToCharArray())), - ActivitySpanId.CreateFromString(new ReadOnlySpan(SpanId!.ToCharArray())), - ActivityTraceFlags.Recorded, - isRemote: isRemote - ) - : default; - } catch (Exception) { - return default; - } - } -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/EventMetadataExtensions.cs b/src/EventStore.Client/Core/Common/Diagnostics/EventMetadataExtensions.cs deleted file mode 100644 index 4245d5bb7..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/EventMetadataExtensions.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text.Json; -using EventStore.Diagnostics; -using EventStore.Diagnostics.Tracing; - -namespace EventStore.Client.Diagnostics; - -static class EventMetadataExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan InjectTracingContext( - this ReadOnlyMemory eventMetadata, Activity? activity - ) => - eventMetadata.InjectTracingMetadata(activity?.GetTracingMetadata() ?? TracingMetadata.None); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ActivityContext? ExtractPropagationContext(this ReadOnlyMemory eventMetadata) => - eventMetadata.ExtractTracingMetadata().ToActivityContext(isRemote: true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TracingMetadata ExtractTracingMetadata(this ReadOnlyMemory eventMetadata) { - if (eventMetadata.IsEmpty) - return TracingMetadata.None; - - var reader = new Utf8JsonReader(eventMetadata.Span); - try { - if (!JsonDocument.TryParseValue(ref reader, out var doc)) - return TracingMetadata.None; - - using (doc) { - if (!doc.RootElement.TryGetProperty(TracingConstants.Metadata.TraceId, out var traceId) - || !doc.RootElement.TryGetProperty(TracingConstants.Metadata.SpanId, out var spanId)) - return TracingMetadata.None; - - return new TracingMetadata(traceId.GetString(), spanId.GetString()); - } - } catch (Exception) { - return TracingMetadata.None; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static ReadOnlySpan InjectTracingMetadata( - this ReadOnlyMemory eventMetadata, TracingMetadata tracingMetadata - ) { - if (tracingMetadata == TracingMetadata.None || !tracingMetadata.IsValid) - return eventMetadata.Span; - - return eventMetadata.IsEmpty - ? JsonSerializer.SerializeToUtf8Bytes(tracingMetadata) - : TryInjectTracingMetadata(eventMetadata, tracingMetadata).ToArray(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static ReadOnlyMemory TryInjectTracingMetadata( - this ReadOnlyMemory utf8Json, TracingMetadata tracingMetadata - ) { - try { - using var doc = JsonDocument.Parse(utf8Json); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream); - - writer.WriteStartObject(); - - if (doc.RootElement.ValueKind != JsonValueKind.Object) - return utf8Json; - - foreach (var prop in doc.RootElement.EnumerateObject()) - prop.WriteTo(writer); - - writer.WritePropertyName(TracingConstants.Metadata.TraceId); - writer.WriteStringValue(tracingMetadata.TraceId); - writer.WritePropertyName(TracingConstants.Metadata.SpanId); - writer.WriteStringValue(tracingMetadata.SpanId); - - writer.WriteEndObject(); - writer.Flush(); - - return stream.ToArray(); - } catch (Exception) { - return utf8Json; - } - } -} diff --git a/src/EventStore.Client/Core/Common/Diagnostics/EventStoreClientDiagnostics.cs b/src/EventStore.Client/Core/Common/Diagnostics/EventStoreClientDiagnostics.cs deleted file mode 100644 index 6328387ab..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/EventStoreClientDiagnostics.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Diagnostics; - -namespace EventStore.Client.Diagnostics; - -public static class EventStoreClientDiagnostics { - public const string InstrumentationName = "eventstoredb"; - public static readonly ActivitySource ActivitySource = new ActivitySource(InstrumentationName); -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Telemetry/TelemetryTags.cs b/src/EventStore.Client/Core/Common/Diagnostics/Telemetry/TelemetryTags.cs deleted file mode 100644 index ea05d04fa..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Telemetry/TelemetryTags.cs +++ /dev/null @@ -1,12 +0,0 @@ -// ReSharper disable CheckNamespace - -namespace EventStore.Diagnostics.Telemetry; - -static partial class TelemetryTags { - public static class EventStore { - public const string Stream = "db.eventstoredb.stream"; - public const string SubscriptionId = "db.eventstoredb.subscription.id"; - public const string EventId = "db.eventstoredb.event.id"; - public const string EventType = "db.eventstoredb.event.type"; - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Diagnostics/Tracing/TracingConstants.cs b/src/EventStore.Client/Core/Common/Diagnostics/Tracing/TracingConstants.cs deleted file mode 100644 index e43102ebe..000000000 --- a/src/EventStore.Client/Core/Common/Diagnostics/Tracing/TracingConstants.cs +++ /dev/null @@ -1,10 +0,0 @@ -// ReSharper disable CheckNamespace - -namespace EventStore.Diagnostics.Tracing; - -static partial class TracingConstants { - public static class Operations { - public const string Append = "streams.append"; - public const string Subscribe = "streams.subscribe"; - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/EnumerableTaskExtensions.cs b/src/EventStore.Client/Core/Common/EnumerableTaskExtensions.cs deleted file mode 100644 index eb4517006..000000000 --- a/src/EventStore.Client/Core/Common/EnumerableTaskExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Diagnostics; - -namespace EventStore.Client; - -static class EnumerableTaskExtensions { - [DebuggerStepThrough] - public static Task WhenAll(this IEnumerable source) => Task.WhenAll(source); - - [DebuggerStepThrough] - public static Task WhenAll(this IEnumerable> source) => Task.WhenAll(source); -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/EpochExtensions.cs b/src/EventStore.Client/Core/Common/EpochExtensions.cs deleted file mode 100644 index db59e620d..000000000 --- a/src/EventStore.Client/Core/Common/EpochExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace EventStore.Client; - -static class EpochExtensions { -#if NET - static readonly DateTime UnixEpoch = DateTime.UnixEpoch; -#else - const long TicksPerMillisecond = 10000; - const long TicksPerSecond = TicksPerMillisecond * 1000; - const long TicksPerMinute = TicksPerSecond * 60; - const long TicksPerHour = TicksPerMinute * 60; - const long TicksPerDay = TicksPerHour * 24; - const int DaysPerYear = 365; - const int DaysPer4Years = DaysPerYear * 4 + 1; - const int DaysPer100Years = DaysPer4Years * 25 - 1; - const int DaysPer400Years = DaysPer100Years * 4 + 1; - const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; - const long UnixEpochTicks = DaysTo1970 * TicksPerDay; - - static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc); -#endif - - public static DateTime FromTicksSinceEpoch(this long value) => new(UnixEpoch.Ticks + value, DateTimeKind.Utc); - - public static long ToTicksSinceEpoch(this DateTime value) => (value - UnixEpoch).Ticks; -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/EventStoreCallOptions.cs b/src/EventStore.Client/Core/Common/EventStoreCallOptions.cs deleted file mode 100644 index e6058a170..000000000 --- a/src/EventStore.Client/Core/Common/EventStoreCallOptions.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Grpc.Core; -using static System.Threading.Timeout; - -namespace EventStore.Client; - -static class EventStoreCallOptions { - // deadline falls back to infinity - public static CallOptions CreateStreaming( - EventStoreClientSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => - Create(settings, deadline, userCredentials, cancellationToken); - - // deadline falls back to connection DefaultDeadline - public static CallOptions CreateNonStreaming( - EventStoreClientSettings settings, - CancellationToken cancellationToken - ) => - Create( - settings, - settings.DefaultDeadline, - settings.DefaultCredentials, - cancellationToken - ); - - public static CallOptions CreateNonStreaming( - EventStoreClientSettings settings, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken - ) => - Create( - settings, - deadline ?? settings.DefaultDeadline, - userCredentials, - cancellationToken - ); - - static CallOptions Create( - EventStoreClientSettings settings, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken - ) => - new( - cancellationToken: cancellationToken, - deadline: DeadlineAfter(deadline), - headers: new() { - { - Constants.Headers.RequiresLeader, - settings.ConnectivitySettings.NodePreference == NodePreference.Leader - ? bool.TrueString - : bool.FalseString - } - }, - credentials: (userCredentials ?? settings.DefaultCredentials) == null - ? null - : CallCredentials.FromInterceptor( - async (_, metadata) => { - var credentials = userCredentials ?? settings.DefaultCredentials; - - var authorizationHeader = await settings.OperationOptions - .GetAuthenticationHeaderValue(credentials!, CancellationToken.None) - .ConfigureAwait(false); - - metadata.Add(Constants.Headers.Authorization, authorizationHeader); - } - ) - ); - - static DateTime? DeadlineAfter(TimeSpan? timeoutAfter) => - !timeoutAfter.HasValue - ? new DateTime?() - : timeoutAfter.Value == TimeSpan.MaxValue || timeoutAfter.Value == InfiniteTimeSpan - ? DateTime.MaxValue - : DateTime.UtcNow.Add(timeoutAfter.Value); -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/MetadataExtensions.cs b/src/EventStore.Client/Core/Common/MetadataExtensions.cs deleted file mode 100644 index e547970fd..000000000 --- a/src/EventStore.Client/Core/Common/MetadataExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client; - -static class MetadataExtensions { - public static bool TryGetValue(this Metadata metadata, string key, out string? value) { - value = default; - - foreach (var entry in metadata) { - if (entry.Key != key) - continue; - - value = entry.Value; - return true; - } - - return false; - } - - public static StreamRevision GetStreamRevision(this Metadata metadata, string key) => - metadata.TryGetValue(key, out var s) && ulong.TryParse(s, out var value) - ? new(value) - : StreamRevision.None; - - public static int GetIntValueOrDefault(this Metadata metadata, string key) => - metadata.TryGetValue(key, out var s) && int.TryParse(s, out var value) - ? value - : default; -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Shims/Index.cs b/src/EventStore.Client/Core/Common/Shims/Index.cs deleted file mode 100644 index 357bbd34d..000000000 --- a/src/EventStore.Client/Core/Common/Shims/Index.cs +++ /dev/null @@ -1,110 +0,0 @@ -#if !NET -using System.Runtime.CompilerServices; - -namespace System; - -/// Represent a type can be used to index a collection either from the start or the end. -/// -/// Index is used by the C# compiler to support the new index syntax -/// -/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; -/// int lastElement = someArray[^1]; // lastElement = 5 -/// -/// -readonly struct Index : IEquatable { - readonly int _value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - - if (fromEnd) - _value = ~value; - else - _value = value; - } - - // The following private constructors mainly created for perf reason to avoid the checks - Index(int value) => _value = value; - - /// Create an Index pointing at first element. - public static Index Start => new(0); - - /// Create an Index pointing at beyond last element. - public static Index End => new(~0); - - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromStart(int value) { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - - return new Index(value); - } - - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromEnd(int value) { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - - return new Index(~value); - } - - /// Returns the index value. - public int Value { - get { - if (_value < 0) - return ~_value; - else - return _value; - } - } - - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => _value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) { - var offset = _value; - if (IsFromEnd) - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - offset += length + 1; - - return offset; - } - - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; - - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(Index other) => _value == other._value; - - /// Returns the hash code for this instance. - public override int GetHashCode() => _value; - - /// Converts integer number to an Index. - public static implicit operator Index(int value) => FromStart(value); - - /// Converts the value of the current Index object to its equivalent string representation. - public override string ToString() => IsFromEnd ? $"^{(uint)Value}" : ((uint)Value).ToString(); -} -#endif \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Shims/IsExternalInit.cs b/src/EventStore.Client/Core/Common/Shims/IsExternalInit.cs deleted file mode 100644 index 7dc4fea3d..000000000 --- a/src/EventStore.Client/Core/Common/Shims/IsExternalInit.cs +++ /dev/null @@ -1,10 +0,0 @@ -#if !NET - -using System.ComponentModel; - -// ReSharper disable once CheckNamespace -namespace System.Runtime.CompilerServices; - -[EditorBrowsable(EditorBrowsableState.Never)] -class IsExternalInit{} -#endif diff --git a/src/EventStore.Client/Core/Common/Shims/Range.cs b/src/EventStore.Client/Core/Common/Shims/Range.cs deleted file mode 100644 index 3a0b34fde..000000000 --- a/src/EventStore.Client/Core/Common/Shims/Range.cs +++ /dev/null @@ -1,75 +0,0 @@ -#if !NET -// ReSharper disable CheckNamespace - -using System.Runtime.CompilerServices; - -namespace System; - -/// Represent a range has start and end indexes. -/// -/// Range is used by the C# compiler to support the range syntax. -/// -/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; -/// int[] subArray1 = someArray[0..2]; // { 1, 2 } -/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } -/// -/// -readonly struct Range : IEquatable { - /// Represent the inclusive start index of the Range. - public Index Start { get; } - - /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) { - Start = start; - End = end; - } - - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); - - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - - /// Returns the hash code for this instance. - public override int GetHashCode() => Start.GetHashCode() * 31 + End.GetHashCode(); - - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() => $"{Start}..{End}"; - - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new(Index.Start, end); - - /// Create a Range object starting from first element to the end. - public static Range All => new(Index.Start, Index.End); - - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) { - var start = Start.GetOffset(length); - var end = End.GetOffset(length); - - if ((uint)end > (uint)length || (uint)start > (uint)end) throw new ArgumentOutOfRangeException(nameof(length)); - - return (start, end - start); - } -} -#endif \ No newline at end of file diff --git a/src/EventStore.Client/Core/Common/Shims/TaskCompletionSource.cs b/src/EventStore.Client/Core/Common/Shims/TaskCompletionSource.cs deleted file mode 100644 index ad6573c4a..000000000 --- a/src/EventStore.Client/Core/Common/Shims/TaskCompletionSource.cs +++ /dev/null @@ -1,11 +0,0 @@ -#if !NET -// ReSharper disable CheckNamespace - -namespace System.Threading.Tasks; - -class TaskCompletionSource : TaskCompletionSource { - public void SetResult() => base.SetResult(null); - public bool TrySetResult() => base.TrySetResult(null); -} - -#endif \ No newline at end of file diff --git a/src/EventStore.Client/Core/DefaultRequestVersionHandler.cs b/src/EventStore.Client/Core/DefaultRequestVersionHandler.cs deleted file mode 100644 index b6cda6a7f..000000000 --- a/src/EventStore.Client/Core/DefaultRequestVersionHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.Client { - internal class DefaultRequestVersionHandler : DelegatingHandler { - public DefaultRequestVersionHandler(HttpMessageHandler innerHandler) : base(innerHandler) { } - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - request.Version = new Version(2, 0); - return base.SendAsync(request, cancellationToken); - } - } -} diff --git a/src/EventStore.Client/Core/EndPointExtensions.cs b/src/EventStore.Client/Core/EndPointExtensions.cs deleted file mode 100644 index 8ebad38e0..000000000 --- a/src/EventStore.Client/Core/EndPointExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Net; - -namespace EventStore.Client { - internal static class EndPointExtensions { - public static string GetHost(this EndPoint endpoint) => - endpoint switch { - IPEndPoint ip => ip.Address.ToString(), - DnsEndPoint dns => dns.Host, - _ => throw new ArgumentOutOfRangeException(nameof(endpoint), endpoint?.GetType(), - "An invalid endpoint has been provided") - }; - - public static int GetPort(this EndPoint endpoint) => - endpoint switch { - IPEndPoint ip => ip.Port, - DnsEndPoint dns => dns.Port, - _ => throw new ArgumentOutOfRangeException(nameof(endpoint), endpoint?.GetType(), - "An invalid endpoint has been provided") - }; - - public static Uri ToUri(this EndPoint endPoint, bool https) => new UriBuilder { - Scheme = https ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, - Host = endPoint.GetHost(), - Port = endPoint.GetPort() - }.Uri; - - public static string? ToHttpUrl(this EndPoint endPoint, string schema, string? rawUrl = null) => - endPoint switch { - IPEndPoint ipEndPoint => CreateHttpUrl(schema, ipEndPoint.Address.ToString(), ipEndPoint.Port, - rawUrl != null ? rawUrl.TrimStart('/') : string.Empty), - DnsEndPoint dnsEndpoint => CreateHttpUrl(schema, dnsEndpoint.Host, dnsEndpoint.Port, - rawUrl != null ? rawUrl.TrimStart('/') : string.Empty), - _ => null - }; - - private static string CreateHttpUrl(string schema, string host, int port, string path) { - return $"{schema}://{host}:{port}/{path}"; - } - } -} diff --git a/src/EventStore.Client/Core/EventData.cs b/src/EventStore.Client/Core/EventData.cs deleted file mode 100644 index 3849ff364..000000000 --- a/src/EventStore.Client/Core/EventData.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Net.Http.Headers; - -namespace EventStore.Client { - /// - /// Represents an event to be written. - /// - public sealed class EventData { - /// - /// The raw bytes of the event data. - /// - public readonly ReadOnlyMemory Data; - - /// - /// The of the event, used as part of the idempotent write check. - /// - public readonly Uuid EventId; - - /// - /// The raw bytes of the event metadata. - /// - public readonly ReadOnlyMemory Metadata; - - /// - /// The name of the event type. It is strongly recommended that these - /// use lowerCamelCase if projections are to be used. - /// - public readonly string Type; - - /// - /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. - /// - public readonly string ContentType; - - /// - /// Constructs a new . - /// - /// The of the event, used as part of the idempotent write check. - /// The name of the event type. It is strongly recommended that these use lowerCamelCase if projections are to be used. - /// The raw bytes of the event data. - /// The raw bytes of the event metadata. - /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. - /// - public EventData(Uuid eventId, string type, ReadOnlyMemory data, ReadOnlyMemory? metadata = null, - string contentType = Constants.Metadata.ContentTypes.ApplicationJson) { - if (eventId == Uuid.Empty) { - throw new ArgumentOutOfRangeException(nameof(eventId)); - } - - MediaTypeHeaderValue.Parse(contentType); - - if (contentType != Constants.Metadata.ContentTypes.ApplicationJson && - contentType != Constants.Metadata.ContentTypes.ApplicationOctetStream) { - throw new ArgumentOutOfRangeException(nameof(contentType), contentType, - $"Only {Constants.Metadata.ContentTypes.ApplicationJson} or {Constants.Metadata.ContentTypes.ApplicationOctetStream} are acceptable values."); - } - - EventId = eventId; - Type = type; - Data = data; - Metadata = metadata ?? Array.Empty(); - ContentType = contentType; - } - } -} diff --git a/src/EventStore.Client/Core/EventRecord.cs b/src/EventStore.Client/Core/EventRecord.cs deleted file mode 100644 index 67a7b04cf..000000000 --- a/src/EventStore.Client/Core/EventRecord.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace EventStore.Client { - /// - /// Represents a previously written event. - /// - public class EventRecord { - /// - /// The stream that this event belongs to. - /// - public readonly string EventStreamId; - - /// - /// The representing this event. - /// - public readonly Uuid EventId; - - /// - /// The of this event in the stream. - /// - public readonly StreamPosition EventNumber; - - /// - /// The type of event this is. - /// - public readonly string EventType; - - /// - /// The raw bytes representing the data of this event. - /// - public readonly ReadOnlyMemory Data; - - /// - /// The raw bytes representing the metadata of this event. - /// - public readonly ReadOnlyMemory Metadata; - - /// - /// A UTC representing when this event was created in the system. - /// - public readonly DateTime Created; - - /// - /// The of this event in the $all stream. - /// - public readonly Position Position; - - /// - /// The Content-Type of the event's data. - /// - public readonly string ContentType; - - /// - /// Constructs a new . - /// - /// - /// - /// - /// - /// - /// - /// - public EventRecord( - string eventStreamId, - Uuid eventId, - StreamPosition eventNumber, - Position position, - IDictionary metadata, - ReadOnlyMemory data, - ReadOnlyMemory customMetadata) { - EventStreamId = eventStreamId; - EventId = eventId; - EventNumber = eventNumber; - Position = position; - Data = data; - Metadata = customMetadata; - EventType = metadata[Constants.Metadata.Type]; - Created = Convert.ToInt64(metadata[Constants.Metadata.Created]).FromTicksSinceEpoch(); - ContentType = metadata[Constants.Metadata.ContentType]; - } - } -} diff --git a/src/EventStore.Client/Core/EventStoreClientBase.cs b/src/EventStore.Client/Core/EventStoreClientBase.cs deleted file mode 100644 index 2b4a5b2f6..000000000 --- a/src/EventStore.Client/Core/EventStoreClientBase.cs +++ /dev/null @@ -1,151 +0,0 @@ -using EventStore.Client.Interceptors; -using Grpc.Core; -using Grpc.Core.Interceptors; -using Enum = System.Enum; - -namespace EventStore.Client { - /// - /// The base class used by clients used to communicate with the EventStoreDB. - /// - public abstract class EventStoreClientBase : - IDisposable, // for grpc.net we can dispose synchronously, but not for grpc.core - IAsyncDisposable { - private readonly Dictionary> _exceptionMap; - private readonly CancellationTokenSource _cts; - private readonly ChannelCache _channelCache; - private readonly SharingProvider _channelInfoProvider; - private readonly Lazy _httpFallback; - - /// The name of the connection. - public string ConnectionName { get; } - - /// The . - protected EventStoreClientSettings Settings { get; } - - /// Constructs a new . - protected EventStoreClientBase( - EventStoreClientSettings? settings, - Dictionary> exceptionMap - ) { - Settings = settings ?? new EventStoreClientSettings(); - _exceptionMap = exceptionMap; - _cts = new CancellationTokenSource(); - _channelCache = new(Settings); - _httpFallback = new Lazy(() => new HttpFallback(Settings)); - - ConnectionName = Settings.ConnectionName ?? $"ES-{Guid.NewGuid()}"; - - var channelSelector = new ChannelSelector(Settings, _channelCache); - _channelInfoProvider = new SharingProvider( - factory: (endPoint, onBroken) => - GetChannelInfoExpensive(endPoint, onBroken, channelSelector, _cts.Token), - factoryRetryDelay: Settings.ConnectivitySettings.DiscoveryInterval, - initialInput: ReconnectionRequired.Rediscover.Instance, - loggerFactory: Settings.LoggerFactory - ); - } - - // Select a channel and query its capabilities. This is an expensive call that - // we don't want to do often. - private async Task GetChannelInfoExpensive( - ReconnectionRequired reconnectionRequired, - Action onReconnectionRequired, - IChannelSelector channelSelector, - CancellationToken cancellationToken - ) { - var channel = reconnectionRequired switch { - ReconnectionRequired.Rediscover => await channelSelector.SelectChannelAsync(cancellationToken) - .ConfigureAwait(false), - ReconnectionRequired.NewLeader (var endPoint) => channelSelector.SelectChannel(endPoint), - _ => throw new ArgumentException(null, nameof(reconnectionRequired)) - }; - - var invoker = channel.CreateCallInvoker() - .Intercept(new TypedExceptionInterceptor(_exceptionMap)) - .Intercept(new ConnectionNameInterceptor(ConnectionName)) - .Intercept(new ReportLeaderInterceptor(onReconnectionRequired)); - - if (Settings.Interceptors is not null) { - foreach (var interceptor in Settings.Interceptors) { - invoker = invoker.Intercept(interceptor); - } - } - - var caps = await new GrpcServerCapabilitiesClient(Settings) - .GetAsync(invoker, cancellationToken) - .ConfigureAwait(false); - - return new(channel, caps, invoker); - } - - /// Gets the current channel info. - protected async ValueTask GetChannelInfo(CancellationToken cancellationToken) => - await _channelInfoProvider.CurrentAsync.WithCancellation(cancellationToken).ConfigureAwait(false); - - /// - /// Only exists so that we can manually trigger rediscovery in the tests - /// in cases where the server doesn't yet let the client know that it needs to. - /// note if rediscovery is already in progress it will continue, not restart. - /// - internal Task RediscoverAsync() { - _channelInfoProvider.Reset(); - return Task.CompletedTask; - } - - /// Returns the result of an HTTP Get request based on the client settings. - protected async Task HttpGet( - string path, Action onNotFound, ChannelInfo channelInfo, - TimeSpan? deadline, UserCredentials? userCredentials, CancellationToken cancellationToken - ) { - return await _httpFallback.Value - .HttpGetAsync(path, channelInfo, deadline, userCredentials, onNotFound, cancellationToken) - .ConfigureAwait(false); - } - - /// Executes an HTTP Post request based on the client settings. - protected async Task HttpPost( - string path, string query, Action onNotFound, ChannelInfo channelInfo, - TimeSpan? deadline, UserCredentials? userCredentials, CancellationToken cancellationToken - ) { - await _httpFallback.Value - .HttpPostAsync( - path, - query, - channelInfo, - deadline, - userCredentials, - onNotFound, - cancellationToken - ) - .ConfigureAwait(false); - } - - /// - public virtual void Dispose() { - _channelInfoProvider.Dispose(); - _cts.Cancel(); - _cts.Dispose(); - _channelCache.Dispose(); - - if (_httpFallback.IsValueCreated) { - _httpFallback.Value.Dispose(); - } - } - - /// - public virtual async ValueTask DisposeAsync() { - _channelInfoProvider.Dispose(); - _cts.Cancel(); - _cts.Dispose(); - await _channelCache.DisposeAsync().ConfigureAwait(false); - - if (_httpFallback.IsValueCreated) { - _httpFallback.Value.Dispose(); - } - } - - /// Returns an InvalidOperation exception. - protected Exception InvalidOption(T option) where T : Enum => - new InvalidOperationException($"The {typeof(T).Name} {option:x} was not valid."); - } -} diff --git a/src/EventStore.Client/Core/EventStoreClientConnectivitySettings.cs b/src/EventStore.Client/Core/EventStoreClientConnectivitySettings.cs deleted file mode 100644 index 92890d2f2..000000000 --- a/src/EventStore.Client/Core/EventStoreClientConnectivitySettings.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Net; -using System.Security.Cryptography.X509Certificates; - -namespace EventStore.Client { - /// - /// A class used to describe how to connect to an instance of EventStoreDB. - /// - public class EventStoreClientConnectivitySettings { - private const int DefaultPort = 2113; - private bool _insecure; - private Uri? _address; - - /// - /// The of the EventStoreDB. Use this when connecting to a single node. - /// - public Uri? Address { - get => IsSingleNode ? _address : null; - set => _address = value; - } - - internal Uri ResolvedAddressOrDefault => Address ?? DefaultAddress; - - private Uri DefaultAddress => - new UriBuilder { - Scheme = _insecure ? Uri.UriSchemeHttp : Uri.UriSchemeHttps, - Port = DefaultPort - }.Uri; - - /// - /// The maximum number of times to attempt discovery. - /// - public int MaxDiscoverAttempts { get; set; } - - /// - /// An array of s used to seed gossip. - /// - public EndPoint[] GossipSeeds => - ((object?)DnsGossipSeeds ?? IpGossipSeeds) switch { - DnsEndPoint[] dns => Array.ConvertAll(dns, x => x), - IPEndPoint[] ip => Array.ConvertAll(ip, x => x), - _ => Array.Empty() - }; - - /// - /// An array of s to use for seeding gossip. This will be checked before . - /// - public DnsEndPoint[]? DnsGossipSeeds { get; set; } - - /// - /// An array of s to use for seeding gossip. This will be checked after . - /// - public IPEndPoint[]? IpGossipSeeds { get; set; } - - /// - /// The after which an attempt to discover gossip will fail. - /// - public TimeSpan GossipTimeout { get; set; } - - /// - /// Whether or not to use HTTPS when communicating via gossip. - /// - [Obsolete] - public bool GossipOverHttps { get; set; } = true; - - /// - /// The polling interval used to discover the . - /// - public TimeSpan DiscoveryInterval { get; set; } - - /// - /// The to use when connecting. - /// - public NodePreference NodePreference { get; set; } - - /// - /// The optional amount of time to wait after which a keepalive ping is sent on the transport. - /// - public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10); - - /// - /// The optional amount of time to wait after which a sent keepalive ping is considered timed out. - /// - public TimeSpan KeepAliveTimeout { get; set; } = TimeSpan.FromSeconds(10); - - /// - /// True if pointing to a single EventStoreDB node. - /// - public bool IsSingleNode => GossipSeeds.Length == 0; - - /// - /// True if communicating over an insecure channel; otherwise false. - /// - public bool Insecure { - get => IsSingleNode ? string.Equals(Address?.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) : _insecure; - set => _insecure = value; - } - - /// - /// True if certificates will be validated; otherwise false. - /// - public bool TlsVerifyCert { get; set; } = true; - - /// - /// Path to a certificate file for secure connection. Not required for enabling secure connection. Useful for self-signed certificate - /// that are not installed on the system trust store. - /// - public X509Certificate2? TlsCaFile { get; set; } - - /// - /// Client certificate used for user authentication. - /// - public X509Certificate2? ClientCertificate { get; set; } - - /// - /// The default . - /// - public static EventStoreClientConnectivitySettings Default => new EventStoreClientConnectivitySettings { - MaxDiscoverAttempts = 10, - GossipTimeout = TimeSpan.FromSeconds(5), - DiscoveryInterval = TimeSpan.FromMilliseconds(100), - NodePreference = NodePreference.Leader, - KeepAliveInterval = TimeSpan.FromSeconds(10), - KeepAliveTimeout = TimeSpan.FromSeconds(10), - TlsVerifyCert = true, - }; - } -} diff --git a/src/EventStore.Client/Core/EventStoreClientOperationOptions.cs b/src/EventStore.Client/Core/EventStoreClientOperationOptions.cs deleted file mode 100644 index 94561e213..000000000 --- a/src/EventStore.Client/Core/EventStoreClientOperationOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.Client { - /// - /// A class representing the options to apply to an individual operation. - /// - public class EventStoreClientOperationOptions { - /// - /// Whether or not to immediately throw a when an append fails. - /// - public bool ThrowOnAppendFailure { get; set; } - - /// - /// The batch size, in bytes. - /// - public int BatchAppendSize { get; set; } - - /// - /// A callback function to extract the authorize header value from the used in the operation. - /// - public Func> GetAuthenticationHeaderValue { get; set; } = - null!; - - /// - /// The default . - /// - public static EventStoreClientOperationOptions Default => new() { - ThrowOnAppendFailure = true, - GetAuthenticationHeaderValue = (userCredentials, _) => new ValueTask(userCredentials.ToString()), - BatchAppendSize = 3 * 1024 * 1024 - }; - - - /// - /// Clones a copy of the current . - /// - /// - public EventStoreClientOperationOptions Clone() => new() { - ThrowOnAppendFailure = ThrowOnAppendFailure, - GetAuthenticationHeaderValue = GetAuthenticationHeaderValue, - BatchAppendSize = BatchAppendSize - }; - } -} diff --git a/src/EventStore.Client/Core/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/Core/EventStoreClientSettings.ConnectionString.cs deleted file mode 100644 index 2aadebf64..000000000 --- a/src/EventStore.Client/Core/EventStoreClientSettings.ConnectionString.cs +++ /dev/null @@ -1,410 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Security; -using System.Security.Authentication; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Timeout_ = System.Threading.Timeout; - -namespace EventStore.Client { - public partial class EventStoreClientSettings { - /// - /// Creates client settings from a connection string - /// - /// - /// - public static EventStoreClientSettings Create(string connectionString) => - ConnectionStringParser.Parse(connectionString); - - private static class ConnectionStringParser { - private const string SchemeSeparator = "://"; - private const string UserInfoSeparator = "@"; - private const string Colon = ":"; - private const string Slash = "/"; - private const string Comma = ","; - private const string Ampersand = "&"; - private const string Equal = "="; - private const string QuestionMark = "?"; - - private const string Tls = nameof(Tls); - private const string ConnectionName = nameof(ConnectionName); - private const string MaxDiscoverAttempts = nameof(MaxDiscoverAttempts); - private const string DiscoveryInterval = nameof(DiscoveryInterval); - private const string GossipTimeout = nameof(GossipTimeout); - private const string NodePreference = nameof(NodePreference); - private const string TlsVerifyCert = nameof(TlsVerifyCert); - private const string TlsCaFile = nameof(TlsCaFile); - private const string DefaultDeadline = nameof(DefaultDeadline); - private const string ThrowOnAppendFailure = nameof(ThrowOnAppendFailure); - private const string KeepAliveInterval = nameof(KeepAliveInterval); - private const string KeepAliveTimeout = nameof(KeepAliveTimeout); - private const string UserCertFile = nameof(UserCertFile); - private const string UserKeyFile = nameof(UserKeyFile); - - private const string UriSchemeDiscover = "esdb+discover"; - - private static readonly string[] Schemes = { "esdb", UriSchemeDiscover }; - private static readonly int DefaultPort = EventStoreClientConnectivitySettings.Default.ResolvedAddressOrDefault.Port; - private static readonly bool DefaultUseTls = true; - - private static readonly Dictionary SettingsType = - new(StringComparer.InvariantCultureIgnoreCase) { - { ConnectionName, typeof(string) }, - { MaxDiscoverAttempts, typeof(int) }, - { DiscoveryInterval, typeof(int) }, - { GossipTimeout, typeof(int) }, - { NodePreference, typeof(string) }, - { Tls, typeof(bool) }, - { TlsVerifyCert, typeof(bool) }, - { TlsCaFile, typeof(string) }, - { DefaultDeadline, typeof(int) }, - { ThrowOnAppendFailure, typeof(bool) }, - { KeepAliveInterval, typeof(int) }, - { KeepAliveTimeout, typeof(int) }, - { UserCertFile, typeof(string) }, - { UserKeyFile, typeof(string) }, - }; - - public static EventStoreClientSettings Parse(string connectionString) { - var currentIndex = 0; - var schemeIndex = connectionString.IndexOf(SchemeSeparator, currentIndex, StringComparison.Ordinal); - if (schemeIndex == -1) - throw new NoSchemeException(); - - var scheme = ParseScheme(connectionString.Substring(0, schemeIndex)); - - currentIndex = schemeIndex + SchemeSeparator.Length; - var userInfoIndex = connectionString.IndexOf(UserInfoSeparator, currentIndex, StringComparison.Ordinal); - (string user, string pass)? userInfo = null; - if (userInfoIndex != -1) { - userInfo = ParseUserInfo(connectionString.Substring(currentIndex, userInfoIndex - currentIndex)); - currentIndex = userInfoIndex + UserInfoSeparator.Length; - } - - var slashIndex = connectionString.IndexOf(Slash, currentIndex, StringComparison.Ordinal); - var questionMarkIndex = connectionString.IndexOf(QuestionMark, currentIndex, StringComparison.Ordinal); - var endIndex = connectionString.Length; - - //for simpler substring operations: - if (slashIndex == -1) slashIndex = int.MaxValue; - if (questionMarkIndex == -1) questionMarkIndex = int.MaxValue; - - var hostSeparatorIndex = Math.Min(Math.Min(slashIndex, questionMarkIndex), endIndex); - var hosts = ParseHosts(connectionString.Substring(currentIndex, hostSeparatorIndex - currentIndex)); - currentIndex = hostSeparatorIndex; - - string path = ""; - if (slashIndex != int.MaxValue) - path = connectionString.Substring( - currentIndex, - Math.Min(questionMarkIndex, endIndex) - currentIndex - ); - - if (path != "" && path != "/") - throw new ConnectionStringParseException( - $"The specified path must be either an empty string or a forward slash (/) but the following path was found instead: '{path}'" - ); - - var options = new Dictionary(); - if (questionMarkIndex != int.MaxValue) { - currentIndex = questionMarkIndex + QuestionMark.Length; - options = ParseKeyValuePairs(connectionString.Substring(currentIndex)); - } - - return CreateSettings(scheme, userInfo, hosts, options); - } - - private static EventStoreClientSettings CreateSettings( - string scheme, (string user, string pass)? userInfo, - EndPoint[] hosts, Dictionary options - ) { - var settings = new EventStoreClientSettings { - ConnectivitySettings = EventStoreClientConnectivitySettings.Default, - OperationOptions = EventStoreClientOperationOptions.Default - }; - - if (userInfo.HasValue) - settings.DefaultCredentials = new UserCredentials(userInfo.Value.user, userInfo.Value.pass); - - var typedOptions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var kv in options) { - if (!SettingsType.TryGetValue(kv.Key, out var type)) - throw new InvalidSettingException($"Unknown option: {kv.Key}"); - - if (type == typeof(int)) { - if (!int.TryParse(kv.Value, out var intValue)) - throw new InvalidSettingException($"{kv.Key} must be an integer value"); - - typedOptions.Add(kv.Key, intValue); - } else if (type == typeof(bool)) { - if (!bool.TryParse(kv.Value, out var boolValue)) - throw new InvalidSettingException($"{kv.Key} must be either true or false"); - - typedOptions.Add(kv.Key, boolValue); - } else if (type == typeof(string)) { - typedOptions.Add(kv.Key, kv.Value); - } - } - - if (typedOptions.TryGetValue(ConnectionName, out object? connectionName)) - settings.ConnectionName = (string)connectionName; - - if (typedOptions.TryGetValue(MaxDiscoverAttempts, out object? maxDiscoverAttempts)) - settings.ConnectivitySettings.MaxDiscoverAttempts = (int)maxDiscoverAttempts; - - if (typedOptions.TryGetValue(DiscoveryInterval, out object? discoveryInterval)) - settings.ConnectivitySettings.DiscoveryInterval = TimeSpan.FromMilliseconds((int)discoveryInterval); - - if (typedOptions.TryGetValue(GossipTimeout, out object? gossipTimeout)) - settings.ConnectivitySettings.GossipTimeout = TimeSpan.FromMilliseconds((int)gossipTimeout); - - if (typedOptions.TryGetValue(NodePreference, out object? nodePreference)) { - settings.ConnectivitySettings.NodePreference = ((string)nodePreference).ToLowerInvariant() switch { - "leader" => EventStore.Client.NodePreference.Leader, - "follower" => EventStore.Client.NodePreference.Follower, - "random" => EventStore.Client.NodePreference.Random, - "readonlyreplica" => EventStore.Client.NodePreference.ReadOnlyReplica, - _ => throw new InvalidSettingException($"Invalid NodePreference: {nodePreference}") - }; - } - - var useTls = DefaultUseTls; - if (typedOptions.TryGetValue(Tls, out object? tls)) { - useTls = (bool)tls; - } - - if (typedOptions.TryGetValue(DefaultDeadline, out object? operationTimeout)) - settings.DefaultDeadline = TimeSpan.FromMilliseconds((int)operationTimeout); - - if (typedOptions.TryGetValue(ThrowOnAppendFailure, out object? throwOnAppendFailure)) - settings.OperationOptions.ThrowOnAppendFailure = (bool)throwOnAppendFailure; - - if (typedOptions.TryGetValue(KeepAliveInterval, out var keepAliveIntervalMs)) { - settings.ConnectivitySettings.KeepAliveInterval = keepAliveIntervalMs switch { - -1 => Timeout_.InfiniteTimeSpan, - int value and >= 0 => TimeSpan.FromMilliseconds(value), - _ => throw new InvalidSettingException($"Invalid KeepAliveInterval: {keepAliveIntervalMs}") - }; - } - - if (typedOptions.TryGetValue(KeepAliveTimeout, out var keepAliveTimeoutMs)) { - settings.ConnectivitySettings.KeepAliveTimeout = keepAliveTimeoutMs switch { - -1 => Timeout_.InfiniteTimeSpan, - int value and >= 0 => TimeSpan.FromMilliseconds(value), - _ => throw new InvalidSettingException($"Invalid KeepAliveTimeout: {keepAliveTimeoutMs}") - }; - } - - settings.ConnectivitySettings.Insecure = !useTls; - - if (hosts.Length == 1 && scheme != UriSchemeDiscover) { - settings.ConnectivitySettings.Address = hosts[0].ToUri(useTls); - } else { - if (hosts.Any(x => x is DnsEndPoint)) - settings.ConnectivitySettings.DnsGossipSeeds = - Array.ConvertAll(hosts, x => new DnsEndPoint(x.GetHost(), x.GetPort())); - else - settings.ConnectivitySettings.IpGossipSeeds = Array.ConvertAll(hosts, x => (IPEndPoint)x); - } - - if (typedOptions.TryGetValue(TlsVerifyCert, out var tlsVerifyCert)) { - settings.ConnectivitySettings.TlsVerifyCert = (bool)tlsVerifyCert; - } - - if (typedOptions.TryGetValue(TlsCaFile, out var tlsCaFile)) { - var tlsCaFilePath = Path.GetFullPath((string)tlsCaFile); - if (!string.IsNullOrEmpty(tlsCaFilePath) && !File.Exists(tlsCaFilePath)) { - throw new InvalidClientCertificateException($"Failed to load certificate. File was not found."); - } - - try { -#if NET9_0_OR_GREATER - settings.ConnectivitySettings.TlsCaFile = X509CertificateLoader.LoadCertificateFromFile(tlsCaFilePath); -#else - settings.ConnectivitySettings.TlsCaFile = new X509Certificate2(tlsCaFilePath); -#endif - } catch (CryptographicException) { - throw new InvalidClientCertificateException("Failed to load certificate. Invalid file format."); - } - } - - ConfigureClientCertificate(settings, typedOptions); - - settings.CreateHttpMessageHandler = CreateDefaultHandler; - - return settings; - -#if NET48 - HttpMessageHandler CreateDefaultHandler() { - if (settings.CreateHttpMessageHandler is not null) - return settings.CreateHttpMessageHandler.Invoke(); - - var handler = new WinHttpHandler { - TcpKeepAliveEnabled = true, - TcpKeepAliveTime = settings.ConnectivitySettings.KeepAliveTimeout, - TcpKeepAliveInterval = settings.ConnectivitySettings.KeepAliveInterval, - EnableMultipleHttp2Connections = true - }; - - if (settings.ConnectivitySettings.Insecure) return handler; - - if (settings.ConnectivitySettings.ClientCertificate is not null) - handler.ClientCertificates.Add(settings.ConnectivitySettings.ClientCertificate); - - handler.ServerCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch { - false => delegate { return true; }, - true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => { - if (chain is null) return false; - - chain.ChainPolicy.ExtraStore.Add(settings.ConnectivitySettings.TlsCaFile); - return chain.Build(certificate); - }, - _ => null - }; - - return handler; - } -#else - HttpMessageHandler CreateDefaultHandler() { - var handler = new SocketsHttpHandler { - KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, - KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout, - EnableMultipleHttp2Connections = true - }; - - if (settings.ConnectivitySettings.Insecure) - return handler; - - if (settings.ConnectivitySettings.ClientCertificate is not null) { - handler.SslOptions.ClientCertificates = new X509CertificateCollection { - settings.ConnectivitySettings.ClientCertificate - }; - } - - handler.SslOptions.RemoteCertificateValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch { - false => delegate { return true; }, - true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => { - if (certificate is not X509Certificate2 peerCertificate || chain is null) return false; - - chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - chain.ChainPolicy.CustomTrustStore.Add(settings.ConnectivitySettings.TlsCaFile); - return chain.Build(peerCertificate); - }, - _ => null - }; - - return handler; - } -#endif - } - - static void ConfigureClientCertificate(EventStoreClientSettings settings, IReadOnlyDictionary options) { - var certPemFilePath = GetOptionValueAsString(UserCertFile); - var keyPemFilePath = GetOptionValueAsString(UserKeyFile); - - if (string.IsNullOrEmpty(certPemFilePath) && string.IsNullOrEmpty(keyPemFilePath)) - return; - - if (string.IsNullOrEmpty(certPemFilePath) || string.IsNullOrEmpty(keyPemFilePath)) - throw new InvalidClientCertificateException("Invalid client certificate settings. Both UserCertFile and UserKeyFile must be set."); - - if (!File.Exists(certPemFilePath)) - throw new InvalidClientCertificateException( - $"Invalid client certificate settings. The specified UserCertFile does not exist: {certPemFilePath}" - ); - - if (!File.Exists(keyPemFilePath)) - throw new InvalidClientCertificateException( - $"Invalid client certificate settings. The specified UserKeyFile does not exist: {keyPemFilePath}" - ); - - try { - settings.ConnectivitySettings.ClientCertificate = - X509Certificates.CreateFromPemFile(certPemFilePath, keyPemFilePath); - } catch (Exception ex) { - throw new InvalidClientCertificateException("Failed to create client certificate.", ex); - } - - return; - - string GetOptionValueAsString(string key) => options.TryGetValue(key, out var value) ? (string)value : ""; - } - - private static string ParseScheme(string s) => - !Schemes.Contains(s) ? throw new InvalidSchemeException(s, Schemes) : s; - - private static (string, string) ParseUserInfo(string s) { - var tokens = s.Split(Colon[0]); - if (tokens.Length != 2) throw new InvalidUserCredentialsException(s); - - return (tokens[0], tokens[1]); - } - - private static EndPoint[] ParseHosts(string s) { - var hostsTokens = s.Split(Comma[0]); - var hosts = new List(); - foreach (var hostToken in hostsTokens) { - var hostPortToken = hostToken.Split(Colon[0]); - string host; - int port; - switch (hostPortToken.Length) { - case 1: - host = hostPortToken[0]; - port = DefaultPort; - break; - - case 2: { - host = hostPortToken[0]; - if (!int.TryParse(hostPortToken[1], out port)) - throw new InvalidHostException(hostToken); - - break; - } - - default: - throw new InvalidHostException(hostToken); - } - - if (host.Length == 0) { - throw new InvalidHostException(hostToken); - } - - if (IPAddress.TryParse(host, out IPAddress? ip)) { - hosts.Add(new IPEndPoint(ip, port)); - } else { - hosts.Add(new DnsEndPoint(host, port)); - } - } - - return hosts.ToArray(); - } - - private static Dictionary ParseKeyValuePairs(string s) { - var options = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - var optionsTokens = s.Split(Ampersand[0]); - foreach (var optionToken in optionsTokens) { - var (key, val) = ParseKeyValuePair(optionToken); - try { - options.Add(key, val); - } catch (ArgumentException) { - throw new DuplicateKeyException(key); - } - } - - return options; - } - - private static (string, string) ParseKeyValuePair(string s) { - var keyValueToken = s.Split(Equal[0]); - if (keyValueToken.Length != 2) { - throw new InvalidKeyValuePairException(s); - } - - return (keyValueToken[0], keyValueToken[1]); - } - } - } -} diff --git a/src/EventStore.Client/Core/EventStoreClientSettings.cs b/src/EventStore.Client/Core/EventStoreClientSettings.cs deleted file mode 100644 index f6a65219e..000000000 --- a/src/EventStore.Client/Core/EventStoreClientSettings.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Grpc.Core; -using Grpc.Core.Interceptors; - -using Microsoft.Extensions.Logging; - -namespace EventStore.Client { - /// - /// A class that represents the settings to use for operations made from an implementation of . - /// - public partial class EventStoreClientSettings { - /// - /// An optional list of s to use. - /// - public IEnumerable? Interceptors { get; set; } - - /// - /// The name of the connection. - /// - public string? ConnectionName { get; set; } - - /// - /// An optional factory. - /// - public Func? CreateHttpMessageHandler { get; set; } - - /// - /// An optional to use. - /// - public ILoggerFactory? LoggerFactory { get; set; } - - /// - /// The optional to use when creating the . - /// - public ChannelCredentials? ChannelCredentials { get; set; } - - /// - /// The default to use. - /// - public EventStoreClientOperationOptions OperationOptions { get; set; } = - EventStoreClientOperationOptions.Default; - - /// - /// The to use. - /// - public EventStoreClientConnectivitySettings ConnectivitySettings { get; set; } = - EventStoreClientConnectivitySettings.Default; - - /// - /// The optional to use if none have been supplied to the operation. - /// - public UserCredentials? DefaultCredentials { get; set; } - - /// - /// The default deadline for calls. Will not be applied to reads or subscriptions. - /// - public TimeSpan? DefaultDeadline { get; set; } = TimeSpan.FromSeconds(10); - } -} diff --git a/src/EventStore.Client/Core/EventTypeFilter.cs b/src/EventStore.Client/Core/EventTypeFilter.cs deleted file mode 100644 index 1e53e84e9..000000000 --- a/src/EventStore.Client/Core/EventTypeFilter.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; - -namespace EventStore.Client { - /// - /// A structure representing a filter on event types for read operations. - /// - public readonly struct EventTypeFilter : IEquatable, IEventFilter { - /// - /// An empty . - /// - public static readonly EventTypeFilter None = default; - - readonly PrefixFilterExpression[] _prefixes; - - - /// - public PrefixFilterExpression[] Prefixes => _prefixes ?? Array.Empty(); - - /// - public RegularFilterExpression Regex { get; } - - /// - public uint? MaxSearchWindow { get; } - - /// - /// An that excludes system events (i.e., those whose types start with $). - /// - /// - /// - public static EventTypeFilter ExcludeSystemEvents(uint maxSearchWindow = 32) => - new EventTypeFilter(maxSearchWindow, RegularFilterExpression.ExcludeSystemEvents); - - /// - /// Creates an from a single prefix. - /// - /// - /// - public static IEventFilter Prefix(string prefix) - => new EventTypeFilter(new PrefixFilterExpression(prefix)); - - /// - /// Creates an from multiple prefixes. - /// - /// - /// - public static IEventFilter Prefix(params string[] prefixes) - => new EventTypeFilter(Array.ConvertAll(prefixes, prefix => new PrefixFilterExpression(prefix))); - - /// - /// Creates an from a search window and multiple prefixes. - /// - /// - /// - /// - public static IEventFilter Prefix(uint maxSearchWindow, params string[] prefixes) - => new EventTypeFilter(maxSearchWindow, - Array.ConvertAll(prefixes, prefix => new PrefixFilterExpression(prefix))); - - /// - /// Creates an from a regular expression and a search window. - /// - /// - /// - /// - public static IEventFilter RegularExpression(string regex, uint maxSearchWindow = 32) - => new EventTypeFilter(maxSearchWindow, new RegularFilterExpression(regex)); - - /// - /// Creates an from a regular expression and a search window. - /// - /// - /// - /// - public static IEventFilter RegularExpression(Regex regex, uint maxSearchWindow = 32) - => new EventTypeFilter(maxSearchWindow, new RegularFilterExpression(regex)); - - EventTypeFilter(uint maxSearchWindow, RegularFilterExpression regex) { - if (maxSearchWindow == 0) { - throw new ArgumentOutOfRangeException(nameof(maxSearchWindow), - maxSearchWindow, $"{nameof(maxSearchWindow)} must be greater than 0."); - } - - Regex = regex; - _prefixes = Array.Empty(); - MaxSearchWindow = maxSearchWindow; - } - - EventTypeFilter(params PrefixFilterExpression[] prefixes) : this(32, prefixes) { } - - EventTypeFilter(uint maxSearchWindow, params PrefixFilterExpression[] prefixes) { - if (prefixes.Length == 0) { - throw new ArgumentException(); - } - - if (maxSearchWindow == 0) { - throw new ArgumentOutOfRangeException(nameof(maxSearchWindow), - maxSearchWindow, $"{nameof(maxSearchWindow)} must be greater than 0."); - } - - _prefixes = prefixes; - Regex = RegularFilterExpression.None; - MaxSearchWindow = maxSearchWindow; - } - - /// - public bool Equals(EventTypeFilter other) => - Prefixes == null || other.Prefixes == null - ? Prefixes == other.Prefixes && - Regex.Equals(other.Regex) && - MaxSearchWindow.Equals(other.MaxSearchWindow) - : Prefixes.SequenceEqual(other.Prefixes) && - Regex.Equals(other.Regex) && - MaxSearchWindow.Equals(other.MaxSearchWindow); - - /// - public override bool Equals(object? obj) => obj is EventTypeFilter other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(Prefixes).Combine(Regex).Combine(MaxSearchWindow); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(EventTypeFilter left, EventTypeFilter right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(EventTypeFilter left, EventTypeFilter right) => !left.Equals(right); - - /// - public override string ToString() => - this == None - ? "(none)" - : $"{nameof(EventTypeFilter)} {(Prefixes.Length == 0 ? Regex.ToString() : $"[{string.Join(", ", Prefixes)}]")}"; - } -} diff --git a/src/EventStore.Client/Core/Exceptions/AccessDeniedException.cs b/src/EventStore.Client/Core/Exceptions/AccessDeniedException.cs deleted file mode 100644 index cfd69ee6d..000000000 --- a/src/EventStore.Client/Core/Exceptions/AccessDeniedException.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// Exception thrown when a user is not authorised to carry out - /// an operation. - /// - public class AccessDeniedException : Exception { - /// - /// Constructs a new . - /// - public AccessDeniedException(string message, Exception innerException) : base(message, innerException) { - } - - /// - /// Constructs a new . - /// - public AccessDeniedException() : base("Access denied.") { - - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/ConnectionStringParseException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/ConnectionStringParseException.cs deleted file mode 100644 index cb79f161d..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/ConnectionStringParseException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The base exception that is thrown when an EventStoreDB connection string could not be parsed. - /// - public class ConnectionStringParseException : Exception { - /// - /// Constructs a new . - /// - /// - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public ConnectionStringParseException(string message, Exception? innerException = null) : base(message, innerException) { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/DuplicateKeyException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/DuplicateKeyException.cs deleted file mode 100644 index 4027d08e8..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/DuplicateKeyException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when a key in the EventStoreDB connection string is duplicated. - /// - public class DuplicateKeyException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - /// - public DuplicateKeyException(string key) - : base($"Duplicate key: '{key}'") { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidClientCertificateException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidClientCertificateException.cs deleted file mode 100644 index 8415742f8..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidClientCertificateException.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when a certificate is invalid or not found in the EventStoreDB connection string. - /// - public class InvalidClientCertificateException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - /// - /// - public InvalidClientCertificateException(string message, Exception? innerException = null) - : base(message, innerException) { } - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidHostException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidHostException.cs deleted file mode 100644 index 27ba8f615..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidHostException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when there is an invalid host in the EventStoreDB connection string. - /// - public class InvalidHostException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - /// - public InvalidHostException(string host) - : base($"Invalid host: '{host}'") { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidKeyValuePairException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidKeyValuePairException.cs deleted file mode 100644 index 2e6fc58df..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidKeyValuePairException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when an invalid key value pair is found in an EventStoreDB connection string. - /// - public class InvalidKeyValuePairException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - /// - public InvalidKeyValuePairException(string keyValuePair) - : base($"Invalid key/value pair: '{keyValuePair}'") { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSchemeException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSchemeException.cs deleted file mode 100644 index 766c13f37..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSchemeException.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when an invalid scheme is defined in the EventStoreDB connection string. - /// - public class InvalidSchemeException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - /// - /// - public InvalidSchemeException(string scheme, string[] supportedSchemes) - : base($"Invalid scheme: '{scheme}'. Supported values are: {string.Join(",", supportedSchemes)}") { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSettingException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSettingException.cs deleted file mode 100644 index 0bb5da667..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidSettingException.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when an invalid setting is found in an EventStoreDB connection string. - /// - public class InvalidSettingException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - /// - public InvalidSettingException(string message) : base(message) { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidUserCredentialsException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidUserCredentialsException.cs deleted file mode 100644 index bd515fee4..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/InvalidUserCredentialsException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when an invalid is specified in the EventStoreDB connection string. - /// - public class InvalidUserCredentialsException : ConnectionStringParseException { - /// - /// - /// - /// - public InvalidUserCredentialsException(string userInfo) - : base($"Invalid user credentials: '{userInfo}'. Username & password must be delimited by a colon") { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ConnectionString/NoSchemeException.cs b/src/EventStore.Client/Core/Exceptions/ConnectionString/NoSchemeException.cs deleted file mode 100644 index 7551ad467..000000000 --- a/src/EventStore.Client/Core/Exceptions/ConnectionString/NoSchemeException.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EventStore.Client { - /// - /// The exception that is thrown when no scheme was specified in the EventStoreDB connection string. - /// - public class NoSchemeException : ConnectionStringParseException { - /// - /// Constructs a new . - /// - public NoSchemeException() - : base("Could not parse scheme from connection string") { } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/DiscoveryException.cs b/src/EventStore.Client/Core/Exceptions/DiscoveryException.cs deleted file mode 100644 index f3f490adb..000000000 --- a/src/EventStore.Client/Core/Exceptions/DiscoveryException.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when discovery fails. - /// - public class DiscoveryException : Exception { - /// - /// The configured number of discovery attempts. - /// - public int MaxDiscoverAttempts { get; } - - /// - /// Constructs a new . - /// - /// - /// - [Obsolete] - public DiscoveryException(string message, Exception? innerException = null) - : base(message, innerException) { - MaxDiscoverAttempts = 0; - } - - /// - /// Constructs a new . - /// - /// The configured number of discovery attempts. - public DiscoveryException(int maxDiscoverAttempts) : base( - $"Failed to discover candidate in {maxDiscoverAttempts} attempts.") { - MaxDiscoverAttempts = maxDiscoverAttempts; - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/NotAuthenticatedException.cs b/src/EventStore.Client/Core/Exceptions/NotAuthenticatedException.cs deleted file mode 100644 index 289cfa230..000000000 --- a/src/EventStore.Client/Core/Exceptions/NotAuthenticatedException.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when a user is not authenticated. - /// - public class NotAuthenticatedException : Exception { - /// - /// Constructs a new . - /// - /// - /// - public NotAuthenticatedException(string message, Exception? innerException = null) : base(message, innerException) { - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/NotLeaderException.cs b/src/EventStore.Client/Core/Exceptions/NotLeaderException.cs deleted file mode 100644 index 4ad5ca32a..000000000 --- a/src/EventStore.Client/Core/Exceptions/NotLeaderException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Net; - -namespace EventStore.Client { - /// - /// The exception that is thrown when an operation requiring a leader node is made on a follower node. - /// - public class NotLeaderException : Exception { - - /// - /// The of the current leader node. - /// - public DnsEndPoint LeaderEndpoint { get; } - - /// - /// Constructs a new - /// - /// - /// - /// - public NotLeaderException(string host, int port, Exception? exception = null) : base( - $"Not leader. New leader at {host}:{port}.", exception) { - LeaderEndpoint = new DnsEndPoint(host, port); - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/RequiredMetadataPropertyMissingException.cs b/src/EventStore.Client/Core/Exceptions/RequiredMetadataPropertyMissingException.cs deleted file mode 100644 index 079eb0444..000000000 --- a/src/EventStore.Client/Core/Exceptions/RequiredMetadataPropertyMissingException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// Exception thrown when a required metadata property is missing. - /// - public class RequiredMetadataPropertyMissingException : Exception { - /// - /// Constructs a new . - /// - /// - /// - public RequiredMetadataPropertyMissingException(string missingMetadataProperty, - Exception? innerException = null) : - base($"Required metadata property {missingMetadataProperty} is missing", innerException) { - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/ScavengeNotFoundException.cs b/src/EventStore.Client/Core/Exceptions/ScavengeNotFoundException.cs deleted file mode 100644 index 06a629661..000000000 --- a/src/EventStore.Client/Core/Exceptions/ScavengeNotFoundException.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when attempting to see the status of a scavenge operation that does not exist. - /// - public class ScavengeNotFoundException : Exception { - /// - /// The id of the scavenge operation. - /// - public string? ScavengeId { get; } - - /// - /// Constructs a new . - /// - /// - /// - public ScavengeNotFoundException(string? scavengeId, Exception? exception = null) : base( - $"Scavenge not found. The currently running scavenge is {scavengeId ?? ""}.", exception) { - ScavengeId = scavengeId; - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/StreamDeletedException.cs b/src/EventStore.Client/Core/Exceptions/StreamDeletedException.cs deleted file mode 100644 index 0b892578f..000000000 --- a/src/EventStore.Client/Core/Exceptions/StreamDeletedException.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// Exception thrown if an operation is attempted on a stream which - /// has been deleted. - /// - public class StreamDeletedException : Exception { - /// - /// The name of the deleted stream. - /// - public readonly string Stream; - - /// - /// Constructs a new instance of . - /// - /// The name of the deleted stream. - /// - public StreamDeletedException(string stream, Exception? exception = null) - : base($"Event stream '{stream}' is deleted.", exception) { - Stream = stream; - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/StreamNotFoundException.cs b/src/EventStore.Client/Core/Exceptions/StreamNotFoundException.cs deleted file mode 100644 index dc844df36..000000000 --- a/src/EventStore.Client/Core/Exceptions/StreamNotFoundException.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when an attempt is made to read or write to a stream that does not exist. - /// - public class StreamNotFoundException : Exception { - /// - /// The name of the stream. - /// - public readonly string Stream; - - /// - /// Constructs a new instance of . - /// - /// The name of the stream. - /// - public StreamNotFoundException(string stream, Exception? exception = null) - : base($"Event stream '{stream}' was not found.", exception) { - Stream = stream; - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/UserNotFoundException.cs b/src/EventStore.Client/Core/Exceptions/UserNotFoundException.cs deleted file mode 100644 index 3caa6cb68..000000000 --- a/src/EventStore.Client/Core/Exceptions/UserNotFoundException.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when an operation is performed on an internal user that does not exist. - /// - public class UserNotFoundException : Exception { - /// - /// The login name of the user. - /// - public string LoginName { get; } - - /// - /// Constructs a new . - /// - /// - /// - public UserNotFoundException(string loginName, Exception? exception = null) - : base($"User '{loginName}' was not found.", exception) { - LoginName = loginName; - } - } -} diff --git a/src/EventStore.Client/Core/Exceptions/WrongExpectedVersionException.cs b/src/EventStore.Client/Core/Exceptions/WrongExpectedVersionException.cs deleted file mode 100644 index ea2489e63..000000000 --- a/src/EventStore.Client/Core/Exceptions/WrongExpectedVersionException.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// Exception thrown if the expected version specified on an operation - /// does not match the version of the stream when the operation was attempted. - /// - public class WrongExpectedVersionException : Exception { - /// - /// The stream identifier. - /// - public string StreamName { get; } - - /// - /// If available, the expected version specified for the operation that failed. - /// - public long? ExpectedVersion { get; } - - /// - /// If available, the current version of the stream that the operation was attempted on. - /// - public long? ActualVersion { get; } - - /// - /// The current of the stream that the operation was attempted on. - /// - public StreamRevision ActualStreamRevision { get; } - - /// - /// If available, the expected version specified for the operation that failed. - /// - public StreamRevision ExpectedStreamRevision { get; } - - /// - /// Constructs a new instance of with the expected and actual versions if available. - /// - public WrongExpectedVersionException(string streamName, StreamRevision expectedStreamRevision, - StreamRevision actualStreamRevision, Exception? exception = null, string? message = null) : - base( - message ?? $"Append failed due to WrongExpectedVersion. Stream: {streamName}, Expected version: {expectedStreamRevision}, Actual version: {actualStreamRevision}", - exception) { - StreamName = streamName; - ActualStreamRevision = actualStreamRevision; - ExpectedStreamRevision = expectedStreamRevision; - ExpectedVersion = expectedStreamRevision == StreamRevision.None ? new long?() : expectedStreamRevision.ToInt64(); - ActualVersion = actualStreamRevision == StreamRevision.None ? new long?() : actualStreamRevision.ToInt64(); - } - - /// - /// Constructs a new instance of with the expected and actual versions if available. - /// - /// - /// - /// - /// - public WrongExpectedVersionException(string streamName, StreamState expectedStreamState, - StreamRevision actualStreamRevision, Exception? exception = null) : base( - $"Append failed due to WrongExpectedVersion. Stream: {streamName}, Expected state: {expectedStreamState}, Actual version: {actualStreamRevision}", - exception) { - StreamName = streamName; - ActualStreamRevision = actualStreamRevision; - ActualVersion = actualStreamRevision == StreamRevision.None ? new long?() : actualStreamRevision.ToInt64(); - ExpectedStreamRevision = StreamRevision.None; - } - } -} diff --git a/src/EventStore.Client/Core/FromAll.cs b/src/EventStore.Client/Core/FromAll.cs deleted file mode 100644 index 3be460659..000000000 --- a/src/EventStore.Client/Core/FromAll.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure representing the logical position of a subscription to all. /> - /// - public readonly struct FromAll : IEquatable, IComparable, IComparable { - /// - /// Represents a when no events have been seen (i.e., the beginning). - /// - public static readonly FromAll Start = new(null); - - /// - /// Represents a to receive events written after the subscription is confirmed. - /// - public static readonly FromAll End = new(Position.End); - - /// - /// Returns a for the given . - /// - /// The . - /// - /// - public static FromAll After(Position position) => position == Position.End - ? throw new ArgumentException($"Use '{nameof(FromAll)}.{nameof(End)}.'", nameof(position)) - : new(position); - - private readonly Position? _value; - - private FromAll(Position? value) => _value = value; - - /// - /// Converts the to a . - /// It is not meant to be used directly from your code. - /// - /// - /// - public (ulong commitPosition, ulong preparePosition) ToUInt64() => this == Start - ? throw new InvalidOperationException( - $"{nameof(FromAll)}.{nameof(Start)} may not be converted.") - : (_value!.Value.CommitPosition, _value!.Value.PreparePosition); - - /// - public bool Equals(FromAll other) => Nullable.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is FromAll other && Equals(other); - - /// - public override int GetHashCode() => _value.GetHashCode(); - -#pragma warning disable CS1591 - public static bool operator ==(FromAll left, FromAll right) => - Nullable.Equals(left, right); - - public static bool operator !=(FromAll left, FromAll right) => - !Nullable.Equals(left, right); - - public static bool operator >(FromAll left, FromAll right) => - left.CompareTo(right) > 0; - - public static bool operator <(FromAll left, FromAll right) => - left.CompareTo(right) < 0; - - public static bool operator >=(FromAll left, FromAll right) => - left.CompareTo(right) >= 0; - - public static bool operator <=(FromAll left, FromAll right) => - left.CompareTo(right) <= 0; -#pragma warning restore CS1591 - - /// - public int CompareTo(FromAll other) => (_value, other._value) switch { - (null, null) => 0, - (null, _) => -1, - (_, null) => 1, - _ => _value.Value.CompareTo(other._value.Value) - }; - - /// - public int CompareTo(object? obj) => obj switch { - null => 1, - FromAll other => CompareTo(other), - _ => throw new ArgumentException($"Object is not a {nameof(FromAll)}"), - }; - - /// - public override string ToString() { - if (_value is null) { - return "Start"; - } - - if (_value == Position.End) { - return "Live"; - } - - return _value.Value.ToString(); - } - } -} diff --git a/src/EventStore.Client/Core/FromStream.cs b/src/EventStore.Client/Core/FromStream.cs deleted file mode 100644 index 7cf99e081..000000000 --- a/src/EventStore.Client/Core/FromStream.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure representing the logical position of a subscription. /> - /// - public readonly struct FromStream : IEquatable, IComparable, IComparable { - /// - /// Represents a when no events have been seen (i.e., the beginning). - /// - public static readonly FromStream Start = new(null); - - /// - /// Represents a to receive events written after the subscription is confirmed. - /// - public static readonly FromStream End = new(StreamPosition.End); - - private readonly StreamPosition? _value; - - /// - /// Returns a for the given . - /// - /// The . - /// - /// - public static FromStream After(StreamPosition streamPosition) => - streamPosition == StreamPosition.End - ? throw new ArgumentException($"Use '{nameof(FromStream)}.{nameof(End)}.'", nameof(streamPosition)) - : new(streamPosition); - - private FromStream(StreamPosition? value) => _value = value; - - /// - /// Converts the to a . It is not meant to be used directly from your code. - /// - /// - /// - public ulong ToUInt64() => this == Start - ? throw new InvalidOperationException( - $"{nameof(FromStream)}.{nameof(Start)} may not be converted.") - : _value!.Value.ToUInt64(); - - /// - public bool Equals(FromStream other) => Nullable.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is FromStream other && Equals(other); - - /// - public override int GetHashCode() => _value.GetHashCode(); - -#pragma warning disable CS1591 - public static bool operator ==(FromStream left, FromStream right) => - left.Equals(right); - - public static bool operator !=(FromStream left, FromStream right) => - !left.Equals(right); - - public static bool operator <(FromStream left, FromStream right) => - left.CompareTo(right) < 0; - - public static bool operator >(FromStream left, FromStream right) => - left.CompareTo(right) > 0; - - public static bool operator <=(FromStream left, FromStream right) => - left.CompareTo(right) <= 0; - - public static bool operator >=(FromStream left, FromStream right) => - left.CompareTo(right) >= 0; -#pragma warning restore CS1591 - - /// - public int CompareTo(FromStream other) => (_value, other._value) switch { - (null, null) => 0, - (null, _) => -1, - (_, null) => 1, - _ => _value.Value.CompareTo(other._value.Value) - }; - - /// - public int CompareTo(object? obj) => obj switch { - null => 1, - FromStream other => CompareTo(other), - _ => throw new ArgumentException($"Object is not a {nameof(FromStream)}"), - }; - - /// - public override string ToString() { - if (_value is null) { - return "Start"; - } - - if (_value == StreamPosition.End) { - return "Live"; - } - - return _value.Value.ToString(); - } - } -} diff --git a/src/EventStore.Client/Core/GossipChannelSelector.cs b/src/EventStore.Client/Core/GossipChannelSelector.cs deleted file mode 100644 index 633ab0d84..000000000 --- a/src/EventStore.Client/Core/GossipChannelSelector.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace EventStore.Client { - // Thread safe - internal class GossipChannelSelector : IChannelSelector { - private readonly EventStoreClientSettings _settings; - private readonly ChannelCache _channels; - private readonly IGossipClient _gossipClient; - private readonly ILogger _log; - private readonly NodeSelector _nodeSelector; - - public GossipChannelSelector( - EventStoreClientSettings settings, - ChannelCache channelCache, - IGossipClient gossipClient) { - - _settings = settings; - _channels = channelCache; - _gossipClient = gossipClient; - _log = settings.LoggerFactory?.CreateLogger() ?? - new NullLogger(); - _nodeSelector = new(_settings); - } - - public ChannelBase SelectChannel(DnsEndPoint endPoint) { - return _channels.GetChannelInfo(endPoint); - } - - public async Task SelectChannelAsync(CancellationToken cancellationToken) { - var endPoint = await DiscoverAsync(cancellationToken).ConfigureAwait(false); - - _log.LogInformation("Successfully discovered candidate at {endPoint}.", endPoint); - - return _channels.GetChannelInfo(endPoint); - } - - private async Task DiscoverAsync(CancellationToken cancellationToken) { - for (var attempt = 1; attempt <= _settings.ConnectivitySettings.MaxDiscoverAttempts; attempt++) { - foreach (var kvp in _channels.GetRandomOrderSnapshot()) { - var endPointToGetGossip = kvp.Key; - var channelToGetGossip = kvp.Value; - - try { - var clusterInfo = await _gossipClient - .GetAsync(channelToGetGossip, cancellationToken) - .ConfigureAwait(false); - - var selectedEndpoint = _nodeSelector.SelectNode(clusterInfo); - - // Successfully selected an endpoint using this gossip! - // We want _channels to contain exactly the nodes in ClusterInfo. - // nodes no longer in the cluster can be forgotten. - // new nodes are added so we can use them to get gossip. - _channels.UpdateCache(clusterInfo.Members.Select(x => x.EndPoint)); - - return selectedEndpoint; - - } catch (Exception ex) { - _log.Log( - GetLogLevelForDiscoveryAttempt(attempt), - ex, - "Could not discover candidate from {endPoint}. Attempts remaining: {remainingAttempts}", - endPointToGetGossip, - _settings.ConnectivitySettings.MaxDiscoverAttempts - attempt); - } - } - - // couldn't select a node from any _channel. reseed the channels. - _channels.UpdateCache(_settings.ConnectivitySettings.GossipSeeds.Select(endPoint => - endPoint as DnsEndPoint ?? new DnsEndPoint(endPoint.GetHost(), endPoint.GetPort()))); - - await Task - .Delay(_settings.ConnectivitySettings.DiscoveryInterval, cancellationToken) - .ConfigureAwait(false); - } - - _log.LogError("Failed to discover candidate in {maxDiscoverAttempts} attempts.", - _settings.ConnectivitySettings.MaxDiscoverAttempts); - - throw new DiscoveryException(_settings.ConnectivitySettings.MaxDiscoverAttempts); - } - - private LogLevel GetLogLevelForDiscoveryAttempt(int attempt) => attempt switch { - _ when attempt == _settings.ConnectivitySettings.MaxDiscoverAttempts => - LogLevel.Error, - 1 => - LogLevel.Warning, - _ => - LogLevel.Debug - }; - } -} diff --git a/src/EventStore.Client/Core/GrpcGossipClient.cs b/src/EventStore.Client/Core/GrpcGossipClient.cs deleted file mode 100644 index 655d05b5f..000000000 --- a/src/EventStore.Client/Core/GrpcGossipClient.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; - -namespace EventStore.Client { - internal class GrpcGossipClient : IGossipClient { - private readonly EventStoreClientSettings _settings; - - public GrpcGossipClient(EventStoreClientSettings settings) { - _settings = settings; - } - - public async ValueTask GetAsync(ChannelBase channel, CancellationToken ct) { - var client = new Gossip.Gossip.GossipClient(channel); - using var call = client.ReadAsync( - new Empty(), - EventStoreCallOptions.CreateNonStreaming(_settings, ct)); - var result = await call.ResponseAsync.ConfigureAwait(false); - - return new(result.Members.Select(x => - new ClusterMessages.MemberInfo( - Uuid.FromDto(x.InstanceId), - (ClusterMessages.VNodeState)x.State, - x.IsAlive, - new DnsEndPoint(x.HttpEndPoint.Address, (int)x.HttpEndPoint.Port))).ToArray()); - } - } -} diff --git a/src/EventStore.Client/Core/GrpcServerCapabilitiesClient.cs b/src/EventStore.Client/Core/GrpcServerCapabilitiesClient.cs deleted file mode 100644 index 02cd41222..000000000 --- a/src/EventStore.Client/Core/GrpcServerCapabilitiesClient.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; - -namespace EventStore.Client { - internal class GrpcServerCapabilitiesClient : IServerCapabilitiesClient { - private readonly EventStoreClientSettings _settings; - - public GrpcServerCapabilitiesClient(EventStoreClientSettings settings) { - _settings = settings; - } - - public async Task GetAsync( - CallInvoker callInvoker, - CancellationToken cancellationToken) { - - var client = new ServerFeatures.ServerFeatures.ServerFeaturesClient(callInvoker); - using var call = client.GetSupportedMethodsAsync( - new(), - EventStoreCallOptions.CreateNonStreaming( - _settings, - _settings.ConnectivitySettings.GossipTimeout, - null, - cancellationToken)); - - try { - var supportsBatchAppend = false; - var supportsPersistentSubscriptionsToAll = false; - var supportsPersistentSubscriptionsGetInfo = false; - var supportsPersistentSubscriptionsRestartSubsystem = false; - var supportsPersistentSubscriptionsReplayParked = false; - var supportsPersistentSubscriptionsList = false; - - var response = await call.ResponseAsync.ConfigureAwait(false); - - foreach (var supportedMethod in response.Methods) { - switch (supportedMethod.ServiceName, supportedMethod.MethodName) { - case ("event_store.client.streams.streams", "batchappend"): - supportsBatchAppend = true; - continue; - case ("event_store.client.persistent_subscriptions.persistentsubscriptions", "read"): - supportsPersistentSubscriptionsToAll = supportedMethod.Features.Contains("all"); - continue; - case ("event_store.client.persistent_subscriptions.persistentsubscriptions", "getinfo"): - supportsPersistentSubscriptionsGetInfo = true; - continue; - case ("event_store.client.persistent_subscriptions.persistentsubscriptions", "restartsubsystem"): - supportsPersistentSubscriptionsRestartSubsystem = true; - continue; - case ("event_store.client.persistent_subscriptions.persistentsubscriptions", "replayparked"): - supportsPersistentSubscriptionsReplayParked = true; - continue; - case ("event_store.client.persistent_subscriptions.persistentsubscriptions", "list"): - supportsPersistentSubscriptionsList = true; - continue; - } - } - - return new( - SupportsBatchAppend: supportsBatchAppend, - SupportsPersistentSubscriptionsToAll: supportsPersistentSubscriptionsToAll, - SupportsPersistentSubscriptionsGetInfo: supportsPersistentSubscriptionsGetInfo, - SupportsPersistentSubscriptionsRestartSubsystem: supportsPersistentSubscriptionsRestartSubsystem, - SupportsPersistentSubscriptionsReplayParked: supportsPersistentSubscriptionsReplayParked, - SupportsPersistentSubscriptionsList: supportsPersistentSubscriptionsList); - - } catch (Exception ex) when (ex.GetBaseException() is RpcException rpcException && - rpcException.StatusCode == StatusCode.Unimplemented) { - - return new(); - } - } - } -} diff --git a/src/EventStore.Client/Core/HashCode.cs b/src/EventStore.Client/Core/HashCode.cs deleted file mode 100644 index 31f9c2514..000000000 --- a/src/EventStore.Client/Core/HashCode.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace EventStore.Client { - #pragma warning disable 1591 - public readonly struct HashCode { - private readonly int _value; - - private HashCode(int value) { - _value = value; - } - - public static readonly HashCode Hash = default; - - public HashCode Combine(T? value) where T : struct => Combine(value ?? default); - - public HashCode Combine(T value) where T: struct { - unchecked { - return new HashCode((_value * 397) ^ value.GetHashCode()); - } - } - - public HashCode Combine(string? value){ - unchecked { - return new HashCode((_value * 397) ^ (value?.GetHashCode() ?? 0)); - } - } - - public HashCode Combine(IEnumerable? values) where T: struct => - (values ?? Enumerable.Empty()).Aggregate(Hash, (previous, value) => previous.Combine(value)); - - public HashCode Combine(IEnumerable? values) => - (values ?? Enumerable.Empty()).Aggregate(Hash, (previous, value) => previous.Combine(value)); - - public static implicit operator int(HashCode value) => value._value; - } -} diff --git a/src/EventStore.Client/Core/HttpFallback.cs b/src/EventStore.Client/Core/HttpFallback.cs deleted file mode 100644 index 3e5420e9a..000000000 --- a/src/EventStore.Client/Core/HttpFallback.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.Client { - internal class HttpFallback : IDisposable { - private readonly HttpClient _httpClient; - private readonly JsonSerializerOptions _jsonSettings; - private readonly UserCredentials? _defaultCredentials; - private readonly string _addressScheme; - - internal HttpFallback(EventStoreClientSettings settings) { - _addressScheme = settings.ConnectivitySettings.ResolvedAddressOrDefault.Scheme; - _defaultCredentials = settings.DefaultCredentials; - - var handler = new HttpClientHandler(); - if (!settings.ConnectivitySettings.Insecure) { - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - - if (settings.ConnectivitySettings.ClientCertificate is not null) - handler.ClientCertificates.Add(settings.ConnectivitySettings.ClientCertificate); - - handler.ServerCertificateCustomValidationCallback = settings.ConnectivitySettings.TlsVerifyCert switch { - false => delegate { return true; }, - true when settings.ConnectivitySettings.TlsCaFile is not null => (sender, certificate, chain, errors) => { - if (certificate is null || chain is null) return false; - - chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - -#if NET48 - chain.ChainPolicy.ExtraStore.Add(settings.ConnectivitySettings.TlsCaFile); -#else - chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - chain.ChainPolicy.CustomTrustStore.Add(settings.ConnectivitySettings.TlsCaFile); -#endif - - return chain.Build(certificate); - }, - _ => null - }; - } - - _httpClient = new HttpClient(handler); - if (settings.DefaultDeadline.HasValue) { - _httpClient.Timeout = settings.DefaultDeadline.Value; - } - - _jsonSettings = new JsonSerializerOptions { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - } - - internal async Task HttpGetAsync(string path, ChannelInfo channelInfo, TimeSpan? deadline, - UserCredentials? userCredentials, Action onNotFound, CancellationToken cancellationToken) { - - var request = CreateRequest(path, HttpMethod.Get, channelInfo, userCredentials); - - var httpResult = await HttpSendAsync(request, onNotFound, deadline, cancellationToken).ConfigureAwait(false); - -#if NET - var json = await httpResult.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); -#else - var json = await httpResult.Content.ReadAsStringAsync().ConfigureAwait(false); -#endif - - var result = JsonSerializer.Deserialize(json, _jsonSettings); - if (result == null) { - throw new InvalidOperationException("Unable to deserialize response into object of type " + typeof(T)); - } - - return result; - } - - internal async Task HttpPostAsync(string path, string query, ChannelInfo channelInfo, TimeSpan? deadline, - UserCredentials? userCredentials, Action onNotFound, CancellationToken cancellationToken) { - - var request = CreateRequest(path, query, HttpMethod.Post, channelInfo, userCredentials); - - await HttpSendAsync(request, onNotFound, deadline, cancellationToken).ConfigureAwait(false); - } - - private async Task HttpSendAsync(HttpRequestMessage request, Action onNotFound, - TimeSpan? deadline, CancellationToken cancellationToken) { - - if (!deadline.HasValue) { - return await HttpSendAsync(request, onNotFound, cancellationToken).ConfigureAwait(false); - } - - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - cts.CancelAfter(deadline.Value); - - return await HttpSendAsync(request, onNotFound, cts.Token).ConfigureAwait(false); - } - - async Task HttpSendAsync(HttpRequestMessage request, Action onNotFound, - CancellationToken cancellationToken) { - - var httpResult = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - if (httpResult.IsSuccessStatusCode) { - return httpResult; - } - - if (httpResult.StatusCode == HttpStatusCode.Unauthorized) { - throw new AccessDeniedException(); - } - - if (httpResult.StatusCode == HttpStatusCode.NotFound) { - onNotFound(); - } - - throw new Exception($"The HTTP request failed with status code: {httpResult.StatusCode}"); - } - - private HttpRequestMessage CreateRequest(string path, HttpMethod method, ChannelInfo channelInfo, - UserCredentials? credentials) => CreateRequest(path, query: "", method, channelInfo, credentials); - - private HttpRequestMessage CreateRequest(string path, string query, HttpMethod method, ChannelInfo channelInfo, - UserCredentials? credentials) { - - var uriBuilder = new UriBuilder($"{_addressScheme}://{channelInfo.Channel.Target}") { - Path = path, - Query = query - }; - - var httpRequest = new HttpRequestMessage(method, uriBuilder.Uri); - httpRequest.Headers.Add("accept", "application/json"); - credentials ??= _defaultCredentials; - if (credentials != null) { - httpRequest.Headers.Add(Constants.Headers.Authorization, credentials.ToString()); - } - - return httpRequest; - } - - public void Dispose() { - _httpClient.Dispose(); - } - } -} diff --git a/src/EventStore.Client/Core/IChannelSelector.cs b/src/EventStore.Client/Core/IChannelSelector.cs deleted file mode 100644 index 1765ccebc..000000000 --- a/src/EventStore.Client/Core/IChannelSelector.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; - -namespace EventStore.Client { - internal interface IChannelSelector { - // Let the channel selector pick an endpoint. - Task SelectChannelAsync(CancellationToken cancellationToken); - - // Get a channel for the specified endpoint - ChannelBase SelectChannel(DnsEndPoint endPoint); - } -} diff --git a/src/EventStore.Client/Core/IEventFilter.cs b/src/EventStore.Client/Core/IEventFilter.cs deleted file mode 100644 index eb290685e..000000000 --- a/src/EventStore.Client/Core/IEventFilter.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace EventStore.Client { - /// - /// An interface that represents a search filter, used for read operations. - /// - public interface IEventFilter { - /// - /// The s associated with this . - /// - PrefixFilterExpression[] Prefixes { get; } - - /// - /// The associated with this . - /// - RegularFilterExpression Regex { get; } - - /// - /// The maximum number of events to read that do not match the filter before the operation returns. - /// - uint? MaxSearchWindow { get; } - } -} diff --git a/src/EventStore.Client/Core/IGossipClient.cs b/src/EventStore.Client/Core/IGossipClient.cs deleted file mode 100644 index 80c0e9395..000000000 --- a/src/EventStore.Client/Core/IGossipClient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; - -namespace EventStore.Client { - internal interface IGossipClient { - public ValueTask GetAsync(ChannelBase channel, - CancellationToken cancellationToken); - } -} diff --git a/src/EventStore.Client/Core/IPosition.cs b/src/EventStore.Client/Core/IPosition.cs deleted file mode 100644 index e3c5da9bc..000000000 --- a/src/EventStore.Client/Core/IPosition.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace EventStore.Client { - /// - /// Represents the position in a stream or transaction file - /// - public interface IPosition { - } -} diff --git a/src/EventStore.Client/Core/IServerCapabilitiesClient.cs b/src/EventStore.Client/Core/IServerCapabilitiesClient.cs deleted file mode 100644 index 20943805d..000000000 --- a/src/EventStore.Client/Core/IServerCapabilitiesClient.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; - -namespace EventStore.Client { - internal interface IServerCapabilitiesClient { - public Task GetAsync(CallInvoker callInvoker, CancellationToken cancellationToken); - } -} diff --git a/src/EventStore.Client/Core/Interceptors/ConnectionNameInterceptor.cs b/src/EventStore.Client/Core/Interceptors/ConnectionNameInterceptor.cs deleted file mode 100644 index d709a2a3b..000000000 --- a/src/EventStore.Client/Core/Interceptors/ConnectionNameInterceptor.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Grpc.Core; -using Grpc.Core.Interceptors; - -namespace EventStore.Client.Interceptors { - internal class ConnectionNameInterceptor : Interceptor { - private readonly string _connectionName; - - public ConnectionNameInterceptor(string connectionName) { - _connectionName = connectionName; - } - - public override AsyncUnaryCall AsyncUnaryCall(TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation) { - AddConnectionName(context); - return continuation(request, context); - } - - public override AsyncClientStreamingCall AsyncClientStreamingCall( - ClientInterceptorContext context, - AsyncClientStreamingCallContinuation continuation) { - AddConnectionName(context); - return continuation(context); - } - - public override AsyncServerStreamingCall AsyncServerStreamingCall( - TRequest request, - ClientInterceptorContext context, - AsyncServerStreamingCallContinuation continuation) { - AddConnectionName(context); - return continuation(request, context); - } - - public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( - ClientInterceptorContext context, - AsyncDuplexStreamingCallContinuation continuation) { - AddConnectionName(context); - return continuation(context); - } - - private void AddConnectionName(ClientInterceptorContext context) - where TRequest : class where TResponse : class => - context.Options.Headers?.Add(Constants.Headers.ConnectionName, _connectionName); - } -} diff --git a/src/EventStore.Client/Core/Interceptors/ReportLeaderInterceptor.cs b/src/EventStore.Client/Core/Interceptors/ReportLeaderInterceptor.cs deleted file mode 100644 index 6d9327858..000000000 --- a/src/EventStore.Client/Core/Interceptors/ReportLeaderInterceptor.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; -using Grpc.Core.Interceptors; - -namespace EventStore.Client.Interceptors { - // this has become more general than just detecting leader changes. - // triggers the action on any rpc exception with StatusCode.Unavailable - internal class ReportLeaderInterceptor : Interceptor { - private readonly Action _onReconnectionRequired; - - private const TaskContinuationOptions ContinuationOptions = - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted; - - internal ReportLeaderInterceptor(Action onReconnectionRequired) { - _onReconnectionRequired = onReconnectionRequired; - } - - public override AsyncUnaryCall AsyncUnaryCall(TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation) { - var response = continuation(request, context); - - response.ResponseAsync.ContinueWith(OnReconnectionRequired, ContinuationOptions); - - return new AsyncUnaryCall(response.ResponseAsync, response.ResponseHeadersAsync, - response.GetStatus, response.GetTrailers, response.Dispose); - } - - public override AsyncClientStreamingCall AsyncClientStreamingCall( - ClientInterceptorContext context, - AsyncClientStreamingCallContinuation continuation) { - var response = continuation(context); - - response.ResponseAsync.ContinueWith(OnReconnectionRequired, ContinuationOptions); - - return new AsyncClientStreamingCall( - new StreamWriter(response.RequestStream, OnReconnectionRequired), - response.ResponseAsync, - response.ResponseHeadersAsync, response.GetStatus, response.GetTrailers, response.Dispose); - } - - public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( - ClientInterceptorContext context, - AsyncDuplexStreamingCallContinuation continuation) { - var response = continuation(context); - - return new AsyncDuplexStreamingCall( - new StreamWriter(response.RequestStream, OnReconnectionRequired), - new StreamReader(response.ResponseStream, OnReconnectionRequired), - response.ResponseHeadersAsync, - response.GetStatus, response.GetTrailers, response.Dispose); - } - - public override AsyncServerStreamingCall AsyncServerStreamingCall( - TRequest request, ClientInterceptorContext context, - AsyncServerStreamingCallContinuation continuation) { - var response = continuation(request, context); - - return new AsyncServerStreamingCall( - new StreamReader(response.ResponseStream, OnReconnectionRequired), - response.ResponseHeadersAsync, - response.GetStatus, response.GetTrailers, response.Dispose); - } - - private void OnReconnectionRequired(Task task) { - ReconnectionRequired reconnectionRequired = task.Exception?.InnerException switch { - NotLeaderException ex => new ReconnectionRequired.NewLeader(ex.LeaderEndpoint), - RpcException { - StatusCode: StatusCode.Unavailable - // or StatusCode.Unknown or TODO: use RPC exceptions on server - } => ReconnectionRequired.Rediscover.Instance, - _ => ReconnectionRequired.None.Instance - }; - - if (reconnectionRequired is not ReconnectionRequired.None) - _onReconnectionRequired(reconnectionRequired); - } - - private class StreamWriter : IClientStreamWriter { - private readonly IClientStreamWriter _inner; - private readonly Action _reportNewLeader; - - public StreamWriter(IClientStreamWriter inner, Action reportNewLeader) { - _inner = inner; - _reportNewLeader = reportNewLeader; - } - - public WriteOptions? WriteOptions { - get => _inner.WriteOptions; - set => _inner.WriteOptions = value; - } - - public Task CompleteAsync() { - var task = _inner.CompleteAsync(); - task.ContinueWith(_reportNewLeader, ContinuationOptions); - return task; - } - - public Task WriteAsync(T message) { - var task = _inner.WriteAsync(message); - task.ContinueWith(_reportNewLeader, ContinuationOptions); - return task; - } - } - - private class StreamReader : IAsyncStreamReader { - private readonly IAsyncStreamReader _inner; - private readonly Action _reportNewLeader; - - public StreamReader(IAsyncStreamReader inner, Action reportNewLeader) { - _inner = inner; - _reportNewLeader = reportNewLeader; - } - - public Task MoveNext(CancellationToken cancellationToken) { - var task = _inner.MoveNext(cancellationToken); - task.ContinueWith(_reportNewLeader, ContinuationOptions); - return task; - } - - public T Current => _inner.Current; - } - } -} diff --git a/src/EventStore.Client/Core/Interceptors/TypedExceptionInterceptor.cs b/src/EventStore.Client/Core/Interceptors/TypedExceptionInterceptor.cs deleted file mode 100644 index 0f1368805..000000000 --- a/src/EventStore.Client/Core/Interceptors/TypedExceptionInterceptor.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Grpc.Core; -using Grpc.Core.Interceptors; -using static EventStore.Client.Constants; -using static Grpc.Core.StatusCode; - -namespace EventStore.Client.Interceptors; - -class TypedExceptionInterceptor : Interceptor { - static readonly Dictionary> DefaultExceptionMap = new() { - [Exceptions.AccessDenied] = ex => ex.ToAccessDeniedException(), - [Exceptions.NotLeader] = ex => ex.ToNotLeaderException(), - }; - - public TypedExceptionInterceptor(Dictionary> customExceptionMap) { -#if NET48 - var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap).ToDictionary(x => x.Key, x => x.Value)); -#else - var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); -#endif - ConvertRpcException = rpcEx => { - if (rpcEx.TryMapException(map, out var ex)) - throw ex; - - throw rpcEx.StatusCode switch { - Unavailable when rpcEx.Status.Detail == "Deadline Exceeded" => rpcEx.ToDeadlineExceededRpcException(), - Unauthenticated => rpcEx.ToNotAuthenticatedException(), - _ => rpcEx - }; - }; - } - - Func ConvertRpcException { get; } - - public override AsyncServerStreamingCall AsyncServerStreamingCall( - TRequest request, - ClientInterceptorContext context, - AsyncServerStreamingCallContinuation continuation - ) { - var response = continuation(request, context); - - return new AsyncServerStreamingCall( - response.ResponseStream.Apply(ConvertRpcException), - response.ResponseHeadersAsync, - response.GetStatus, - response.GetTrailers, - response.Dispose - ); - } - - public override AsyncClientStreamingCall AsyncClientStreamingCall( - ClientInterceptorContext context, - AsyncClientStreamingCallContinuation continuation - ) { - var response = continuation(context); - - return new AsyncClientStreamingCall( - response.RequestStream.Apply(ConvertRpcException), - response.ResponseAsync.Apply(ConvertRpcException), - response.ResponseHeadersAsync, - response.GetStatus, - response.GetTrailers, - response.Dispose - ); - } - - public override AsyncUnaryCall AsyncUnaryCall( - TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation - ) { - var response = continuation(request, context); - - return new AsyncUnaryCall( - response.ResponseAsync.Apply(ConvertRpcException), - response.ResponseHeadersAsync, - response.GetStatus, - response.GetTrailers, - response.Dispose - ); - } - - public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( - ClientInterceptorContext context, - AsyncDuplexStreamingCallContinuation continuation - ) { - var response = continuation(context); - - return new AsyncDuplexStreamingCall( - response.RequestStream, - response.ResponseStream.Apply(ConvertRpcException), - response.ResponseHeadersAsync, - response.GetStatus, - response.GetTrailers, - response.Dispose - ); - } -} - -static class RpcExceptionConversionExtensions { - public static IAsyncStreamReader Apply(this IAsyncStreamReader reader, Func convertException) => - new ExceptionConverterStreamReader(reader, convertException); - - public static Task Apply(this Task task, Func convertException) => - task.ContinueWith(t => t.Exception?.InnerException is RpcException ex ? throw convertException(ex) : t.Result); - - public static IClientStreamWriter Apply( - this IClientStreamWriter writer, Func convertException - ) => - new ExceptionConverterStreamWriter(writer, convertException); - - public static Task Apply(this Task task, Func convertException) => - task.ContinueWith(t => t.Exception?.InnerException is RpcException ex ? throw convertException(ex) : t); - - public static AccessDeniedException ToAccessDeniedException(this RpcException exception) => - new(exception.Message, exception); - - public static NotLeaderException ToNotLeaderException(this RpcException exception) { - var host = exception.Trailers.FirstOrDefault(x => x.Key == Exceptions.LeaderEndpointHost)?.Value!; - var port = exception.Trailers.GetIntValueOrDefault(Exceptions.LeaderEndpointPort); - return new NotLeaderException(host, port, exception); - } - - public static NotAuthenticatedException ToNotAuthenticatedException(this RpcException exception) => - new(exception.Message, exception); - - public static RpcException ToDeadlineExceededRpcException(this RpcException exception) => - new(new Status(DeadlineExceeded, exception.Status.Detail, exception.Status.DebugException)); - - public static bool TryMapException(this RpcException exception, Dictionary> map, out Exception createdException) { - if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { - createdException = factory.Invoke(exception); - return true; - } - - createdException = null!; - return false; - } -} - -class ExceptionConverterStreamReader(IAsyncStreamReader reader, Func convertException) : IAsyncStreamReader { - public TResponse Current => reader.Current; - - public async Task MoveNext(CancellationToken cancellationToken) { - try { - return await reader.MoveNext(cancellationToken).ConfigureAwait(false); - } - catch (RpcException ex) { - throw convertException(ex); - } - } -} - -class ExceptionConverterStreamWriter( - IClientStreamWriter writer, - Func convertException -) - : IClientStreamWriter { - public WriteOptions? WriteOptions { - get => writer.WriteOptions; - set => writer.WriteOptions = value; - } - - public Task WriteAsync(TRequest message) => writer.WriteAsync(message).Apply(convertException); - public Task CompleteAsync() => writer.CompleteAsync().Apply(convertException); -} diff --git a/src/EventStore.Client/Core/NodePreference.cs b/src/EventStore.Client/Core/NodePreference.cs deleted file mode 100644 index a3f453eb3..000000000 --- a/src/EventStore.Client/Core/NodePreference.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace EventStore.Client { - /// - /// Indicates the preferred EventStoreDB node type to connect to. - /// - public enum NodePreference { - /// - /// When attempting connection, prefers leader node. - /// - Leader, - - /// - /// When attempting connection, prefers follower node. - /// - Follower, - - /// - /// When attempting connection, has no node preference. - /// - Random, - - /// - /// When attempting connection, prefers read only replicas. - /// - ReadOnlyReplica - } -} diff --git a/src/EventStore.Client/Core/NodePreferenceComparers.cs b/src/EventStore.Client/Core/NodePreferenceComparers.cs deleted file mode 100644 index 07e8a1cc5..000000000 --- a/src/EventStore.Client/Core/NodePreferenceComparers.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace EventStore.Client { - internal static class NodePreferenceComparers { - public static readonly IComparer Leader = new Comparer(state => - state switch { - ClusterMessages.VNodeState.Leader => 0, - ClusterMessages.VNodeState.Follower => 1, - ClusterMessages.VNodeState.ReadOnlyReplica => 2, - ClusterMessages.VNodeState.PreReadOnlyReplica => 3, - ClusterMessages.VNodeState.ReadOnlyLeaderless => 4, - _ => int.MaxValue - }); - - public static readonly IComparer Follower = new Comparer(state => - state switch { - ClusterMessages.VNodeState.Follower => 0, - ClusterMessages.VNodeState.Leader => 1, - ClusterMessages.VNodeState.ReadOnlyReplica => 2, - ClusterMessages.VNodeState.PreReadOnlyReplica => 3, - ClusterMessages.VNodeState.ReadOnlyLeaderless => 4, - _ => int.MaxValue - }); - - public static readonly IComparer ReadOnlyReplica = new Comparer(state => - state switch { - ClusterMessages.VNodeState.ReadOnlyReplica => 0, - ClusterMessages.VNodeState.PreReadOnlyReplica => 1, - ClusterMessages.VNodeState.ReadOnlyLeaderless => 2, - ClusterMessages.VNodeState.Leader => 3, - ClusterMessages.VNodeState.Follower => 4, - _ => int.MaxValue - }); - - public static readonly IComparer None = new Comparer(_ => 0); - - private class Comparer : IComparer { - private readonly Func _getPriority; - - public Comparer(Func getPriority) { - _getPriority = getPriority; - } - - public int Compare(ClusterMessages.VNodeState left, ClusterMessages.VNodeState right) => - _getPriority(left).CompareTo(_getPriority(right)); - } - } -} diff --git a/src/EventStore.Client/Core/NodeSelector.cs b/src/EventStore.Client/Core/NodeSelector.cs deleted file mode 100644 index 94c00cc56..000000000 --- a/src/EventStore.Client/Core/NodeSelector.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; - -namespace EventStore.Client { - // Selects a node to connect to from a ClusterInfo, based on the node preference. - // Deals with endpoints, no grpc here. - // Thread safe. - internal class NodeSelector { - private static readonly ClusterMessages.VNodeState[] _notAllowedStates = { - ClusterMessages.VNodeState.Manager, - ClusterMessages.VNodeState.ShuttingDown, - ClusterMessages.VNodeState.Shutdown, - ClusterMessages.VNodeState.Unknown, - ClusterMessages.VNodeState.Initializing, - ClusterMessages.VNodeState.CatchingUp, - ClusterMessages.VNodeState.ResigningLeader, - ClusterMessages.VNodeState.PreLeader, - ClusterMessages.VNodeState.PreReplica, - ClusterMessages.VNodeState.PreReadOnlyReplica, - ClusterMessages.VNodeState.Clone, - ClusterMessages.VNodeState.DiscoverLeader, - }; - - private readonly Random _random; - private readonly IComparer? _nodeStateComparer; - - public NodeSelector(EventStoreClientSettings settings) { - _random = new Random(0); - _nodeStateComparer = settings.ConnectivitySettings.NodePreference switch { - NodePreference.Leader => NodePreferenceComparers.Leader, - NodePreference.Follower => NodePreferenceComparers.Follower, - NodePreference.ReadOnlyReplica => NodePreferenceComparers.ReadOnlyReplica, - _ => NodePreferenceComparers.None - }; - } - - public DnsEndPoint SelectNode(ClusterMessages.ClusterInfo clusterInfo) { - if (clusterInfo.Members.Length == 0) { - throw new Exception("No nodes in cluster info."); - } - - lock (_random) { - var node = clusterInfo.Members - .Where(IsConnectable) - .OrderBy(node => node.State, _nodeStateComparer) - .ThenBy(_ => _random.Next()) - .FirstOrDefault(); - - if (node is null) { - throw new Exception("No nodes are in a connectable state."); - } - - return node.EndPoint; - } - } - - private static bool IsConnectable(ClusterMessages.MemberInfo node) => - node.IsAlive && - !_notAllowedStates.Contains(node.State); - } -} diff --git a/src/EventStore.Client/Core/Position.cs b/src/EventStore.Client/Core/Position.cs deleted file mode 100644 index 169439804..000000000 --- a/src/EventStore.Client/Core/Position.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure referring to a potential logical record position - /// in the Event Store transaction file. - /// - public readonly struct Position : IEquatable, IComparable, IComparable, IPosition { - /// - /// Position representing the start of the transaction file - /// - public static readonly Position Start = new Position(0, 0); - - /// - /// Position representing the end of the transaction file - /// - public static readonly Position End = new Position(ulong.MaxValue, ulong.MaxValue); - - /// - /// The commit position of the record - /// - public readonly ulong CommitPosition; - - /// - /// The prepare position of the record. - /// - public readonly ulong PreparePosition; - - /// - /// Constructs a position with the given commit and prepare positions. - /// It is not guaranteed that the position is actually the start of a - /// record in the transaction file. - /// - /// The commit position cannot be less than the prepare position. - /// - /// The commit position of the record. - /// The prepare position of the record. - public Position(ulong commitPosition, ulong preparePosition) { - if (commitPosition < preparePosition) - throw new ArgumentOutOfRangeException( - nameof(commitPosition), - "The commit position cannot be less than the prepare position"); - - if (commitPosition > long.MaxValue && commitPosition != ulong.MaxValue) { - throw new ArgumentOutOfRangeException(nameof(commitPosition)); - } - - - if (preparePosition > long.MaxValue && preparePosition != ulong.MaxValue) { - throw new ArgumentOutOfRangeException(nameof(preparePosition)); - } - - CommitPosition = commitPosition; - PreparePosition = preparePosition; - } - - /// - /// Compares whether p1 < p2. - /// - /// A . - /// A . - /// True if p1 < p2. - public static bool operator <(Position p1, Position p2) => - p1.CommitPosition < p2.CommitPosition || - p1.CommitPosition == p2.CommitPosition && p1.PreparePosition < p2.PreparePosition; - - - /// - /// Compares whether p1 > p2. - /// - /// A . - /// A . - /// True if p1 > p2. - public static bool operator >(Position p1, Position p2) => - p1.CommitPosition > p2.CommitPosition || - p1.CommitPosition == p2.CommitPosition && p1.PreparePosition > p2.PreparePosition; - - /// - /// Compares whether p1 >= p2. - /// - /// A . - /// A . - /// True if p1 >= p2. - public static bool operator >=(Position p1, Position p2) => p1 > p2 || p1 == p2; - - /// - /// Compares whether p1 <= p2. - /// - /// A . - /// A . - /// True if p1 <= p2. - public static bool operator <=(Position p1, Position p2) => p1 < p2 || p1 == p2; - - /// - /// Compares p1 and p2 for equality. - /// - /// A . - /// A . - /// True if p1 is equal to p2. - public static bool operator ==(Position p1, Position p2) => - Equals(p1, p2); - - /// - /// Compares p1 and p2 for equality. - /// - /// A . - /// A . - /// True if p1 is not equal to p2. - public static bool operator !=(Position p1, Position p2) => !(p1 == p2); - - /// - public int CompareTo(Position other) => this == other ? 0 : this > other ? 1 : -1; - - /// - public int CompareTo(object? obj) => obj switch { - null => 1, - Position other => CompareTo(other), - _ => throw new ArgumentException("Object is not a Position"), - }; - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. 2 - public override bool Equals(object? obj) => obj is Position position && Equals(position); - - /// - /// Compares this instance of for equality - /// with another instance. - /// - /// A - /// True if this instance is equal to the other instance. - public bool Equals(Position other) => - CommitPosition == other.CommitPosition && PreparePosition == other.PreparePosition; - - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - /// 2 - public override int GetHashCode() => HashCode.Hash.Combine(CommitPosition).Combine(PreparePosition); - - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// - /// 2 - public override string ToString() => $"C:{CommitPosition}/P:{PreparePosition}"; - - /// - /// Tries to convert the string representation of a to its equivalent. - /// A return value indicates whether the conversion succeeded or failed. - /// - /// A string that represents the to convert. - /// Contains the that is equivalent to the string - /// representation, if the conversion succeeded, or null if the conversion failed. - /// true if the value was converted successfully; otherwise, false. - public static bool TryParse(string value, out Position? position) { - position = null; - var parts = value.Split('/'); - - if (parts.Length != 2) { - return false; - } - - if (!TryParsePosition("C", parts[0], out var commitPosition)) { - return false; - } - - if (!TryParsePosition("P", parts[1], out var preparePosition)) { - return false; - } - - position = new Position(commitPosition, preparePosition); - return true; - - static bool TryParsePosition(string expectedPrefix, string v, out ulong p) { - p = 0; - - var prts = v.Split(':'); - if (prts.Length != 2) { - return false; - } - - if (prts[0] != expectedPrefix) { - return false; - } - - return ulong.TryParse(prts[1], out p); - } - } - } -} diff --git a/src/EventStore.Client/Core/PrefixFilterExpression.cs b/src/EventStore.Client/Core/PrefixFilterExpression.cs deleted file mode 100644 index ea154aee3..000000000 --- a/src/EventStore.Client/Core/PrefixFilterExpression.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure representing a prefix (i.e., starts with) filter. - /// - public readonly struct PrefixFilterExpression : IEquatable { - /// - /// An empty . - /// - public static readonly PrefixFilterExpression None = default; - - private readonly string? _value; - - /// - /// Constructs a new . - /// - /// - /// - public PrefixFilterExpression(string value) { - if (value == null) { - throw new ArgumentNullException(nameof(value)); - } - - _value = value; - } - - /// - public bool Equals(PrefixFilterExpression other) => string.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is PrefixFilterExpression other && Equals(other); - - /// - public override int GetHashCode() => _value?.GetHashCode() ?? 0; - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(PrefixFilterExpression left, PrefixFilterExpression right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(PrefixFilterExpression left, PrefixFilterExpression right) => - !left.Equals(right); - - /// - /// Converts the to a . - /// - /// - /// - public static implicit operator string?(PrefixFilterExpression value) => value._value; - - /// - public override string? ToString() => _value; - } -} diff --git a/src/EventStore.Client/Core/ReconnectionRequired.cs b/src/EventStore.Client/Core/ReconnectionRequired.cs deleted file mode 100644 index bf448971d..000000000 --- a/src/EventStore.Client/Core/ReconnectionRequired.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Net; - -namespace EventStore.Client { - internal abstract record ReconnectionRequired { - public record None : ReconnectionRequired { - public static None Instance = new(); - } - - public record Rediscover : ReconnectionRequired { - public static Rediscover Instance = new(); - } - - public record NewLeader(DnsEndPoint EndPoint) : ReconnectionRequired; - } -} diff --git a/src/EventStore.Client/Core/RegularFilterExpression.cs b/src/EventStore.Client/Core/RegularFilterExpression.cs deleted file mode 100644 index b8e6a42b8..000000000 --- a/src/EventStore.Client/Core/RegularFilterExpression.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -namespace EventStore.Client { - /// - /// A structure representing a regular expression filter. - /// - public readonly struct RegularFilterExpression : IEquatable { - /// - /// An empty . - /// - public static readonly RegularFilterExpression None = default; - - /// - /// A that excludes system events (i.e., those whose types start with $). - /// - /// - public static readonly RegularFilterExpression ExcludeSystemEvents = - new RegularFilterExpression(new Regex(@"^[^\$].*")); - - private readonly string? _value; - - /// - /// Constructs a new . - /// - /// - /// - public RegularFilterExpression(string value) { - if (value == null) { - throw new ArgumentNullException(nameof(value)); - } - - _value = value; - } - - /// - /// Constructs a new . - /// - /// - /// - public RegularFilterExpression(Regex value) { - if (value == null) { - throw new ArgumentNullException(nameof(value)); - } - - _value = value.ToString(); - } - - /// - public bool Equals(RegularFilterExpression other) => string.Equals(_value, other._value); - - /// - public override bool Equals(object? obj) => obj is RegularFilterExpression other && Equals(other); - - /// - public override int GetHashCode() => _value?.GetHashCode() ?? 0; - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(RegularFilterExpression left, RegularFilterExpression right) => - left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(RegularFilterExpression left, RegularFilterExpression right) => - !left.Equals(right); - - /// - /// Converts a to a . - /// - /// - /// - public static implicit operator string?(RegularFilterExpression value) => value._value; - - /// - public override string? ToString() => _value; - } -} diff --git a/src/EventStore.Client/Core/ResolvedEvent.cs b/src/EventStore.Client/Core/ResolvedEvent.cs deleted file mode 100644 index 25ca13a78..000000000 --- a/src/EventStore.Client/Core/ResolvedEvent.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace EventStore.Client { - /// - /// A structure representing a single event or a resolved link event. - /// - public readonly struct ResolvedEvent { - /// - /// If this represents a link event, the - /// will be the resolved link event, otherwise it will be the single event. - /// - public readonly EventRecord Event; - - /// - /// The link event if this is a link event. - /// - public readonly EventRecord? Link; - - /// - /// Returns the event that was read or which triggered the subscription. - /// - /// If this represents a link event, the - /// will be the , otherwise it will be . - /// - public EventRecord OriginalEvent => Link ?? Event; - - /// - /// Position of the if available. - /// - public readonly Position? OriginalPosition; - - /// - /// The stream name of the . - /// - public string OriginalStreamId => OriginalEvent.EventStreamId; - - /// - /// The in the stream of the . - /// - public StreamPosition OriginalEventNumber => OriginalEvent.EventNumber; - - /// - /// Indicates whether this is a resolved link - /// event. - /// - public bool IsResolved => Link != null && Event != null; - - /// - /// Constructs a new . - /// - /// - /// - /// - public ResolvedEvent(EventRecord @event, EventRecord? link, ulong? commitPosition) { - Event = @event; - Link = link; - OriginalPosition = commitPosition.HasValue - ? new Position(commitPosition.Value, (link ?? @event).Position.PreparePosition) - : new Position?(); - } - } -} diff --git a/src/EventStore.Client/Core/ServerCapabilities.cs b/src/EventStore.Client/Core/ServerCapabilities.cs deleted file mode 100644 index aa63c4e3d..000000000 --- a/src/EventStore.Client/Core/ServerCapabilities.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EventStore.Client { -#pragma warning disable 1591 - public record ServerCapabilities( - bool SupportsBatchAppend = false, - bool SupportsPersistentSubscriptionsToAll = false, - bool SupportsPersistentSubscriptionsGetInfo = false, - bool SupportsPersistentSubscriptionsRestartSubsystem = false, - bool SupportsPersistentSubscriptionsReplayParked = false, - bool SupportsPersistentSubscriptionsList = false); -} diff --git a/src/EventStore.Client/Core/SharingProvider.cs b/src/EventStore.Client/Core/SharingProvider.cs deleted file mode 100644 index 67911a618..000000000 --- a/src/EventStore.Client/Core/SharingProvider.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace EventStore.Client { - internal class SharingProvider { - protected ILogger Log { get; } - - public SharingProvider(ILoggerFactory? loggerFactory) { - Log = loggerFactory?.CreateLogger() ?? - new NullLogger(); - } - } - - // Given a factory for items of type TOutput, where the items: - // - are expensive to produce - // - can be shared by consumers - // - can break - // - can fail to be successfully produced by the factory to begin with. - // - // This class will make minimal use of the factory to provide items to consumers. - // The Factory can produce and return an item, or it can throw an exception. - // We pass the factory a OnBroken callback to be called later if that instance becomes broken. - // the OnBroken callback can be called multiple times, the factory will be called once. - // the argument to the OnBroken callback is the input to construct the next item. - // - // The factory will not be called multiple times concurrently so does not need to be - // thread safe, but it does need to terminate. - // - // This class is thread safe. - - internal class SharingProvider : SharingProvider, IDisposable { - private readonly Func, Task> _factory; - private readonly TimeSpan _factoryRetryDelay; - private readonly TInput _initialInput; - private TaskCompletionSource _currentBox; - private bool _disposed; - - public SharingProvider( - Func, Task> factory, - TimeSpan factoryRetryDelay, - TInput initialInput, - ILoggerFactory? loggerFactory = null) : base(loggerFactory) { - - _factory = factory; - _factoryRetryDelay = factoryRetryDelay; - _initialInput = initialInput; - _currentBox = new(TaskCreationOptions.RunContinuationsAsynchronously); - _ = FillBoxAsync(_currentBox, input: initialInput); - } - - public Task CurrentAsync => _currentBox.Task; - - public void Reset() { - OnBroken(_currentBox, _initialInput); - } - - // Call this to return a box containing a defective item, or indeed no item at all. - // A new box will be produced and filled if necessary. - private void OnBroken(TaskCompletionSource brokenBox, TInput input) { - if (!brokenBox.Task.IsCompleted) { - // factory is still working on this box. don't create a new box to fill - // or we would have to require the factory be thread safe. - Log.LogDebug("{type} returned to factory. Production already in progress.", typeof(TOutput).Name); - return; - } - - // replace _currentBox with a new one, but only if it is the broken one. - var originalBox = Interlocked.CompareExchange( - location1: ref _currentBox, - value: new(TaskCreationOptions.RunContinuationsAsynchronously), - comparand: brokenBox); - - if (originalBox == brokenBox) { - // replaced the _currentBox, call the factory to fill it. - Log.LogDebug("{type} returned to factory. Producing a new one.", typeof(TOutput).Name); - _ = FillBoxAsync(_currentBox, input); - } else { - // did not replace. a new one was created previously. do nothing. - Log.LogDebug("{type} returned to factory. Production already complete.", typeof(TOutput).Name); - } - } - - private async Task FillBoxAsync(TaskCompletionSource box, TInput input) { - if (_disposed) { - Log.LogDebug("{type} will not be produced, factory is closed!", typeof(TOutput).Name); - box.TrySetException(new ObjectDisposedException(GetType().ToString())); - return; - } - - try { - Log.LogDebug("{type} being produced...", typeof(TOutput).Name); - var item = await _factory(input, x => OnBroken(box, x)).ConfigureAwait(false); - box.TrySetResult(item); - Log.LogDebug("{type} produced!", typeof(TOutput).Name); - } catch (Exception ex) { - await Task.Yield(); // avoid risk of stack overflow - Log.LogDebug(ex, "{type} production failed. Retrying in {delay}", typeof(TOutput).Name, _factoryRetryDelay); - await Task.Delay(_factoryRetryDelay).ConfigureAwait(false); - box.TrySetException(ex); - OnBroken(box, _initialInput); - } - } - - public void Dispose() { - _disposed = true; - } - } -} diff --git a/src/EventStore.Client/Core/SingleNodeChannelSelector.cs b/src/EventStore.Client/Core/SingleNodeChannelSelector.cs deleted file mode 100644 index 79c3affb0..000000000 --- a/src/EventStore.Client/Core/SingleNodeChannelSelector.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace EventStore.Client { - internal class SingleNodeChannelSelector : IChannelSelector { - private readonly ILogger _log; - private readonly ChannelCache _channelCache; - private readonly DnsEndPoint _endPoint; - - public SingleNodeChannelSelector( - EventStoreClientSettings settings, - ChannelCache channelCache) { - - _log = settings.LoggerFactory?.CreateLogger() ?? - new NullLogger(); - - _channelCache = channelCache; - - var uri = settings.ConnectivitySettings.ResolvedAddressOrDefault; - _endPoint = new DnsEndPoint(host: uri.Host, port: uri.Port); - } - - public Task SelectChannelAsync(CancellationToken cancellationToken) => - Task.FromResult(SelectChannel(_endPoint)); - - public ChannelBase SelectChannel(DnsEndPoint endPoint) { - _log.LogInformation("Selected {endPoint}.", endPoint); - - return _channelCache.GetChannelInfo(endPoint); - } - } -} diff --git a/src/EventStore.Client/Core/SingleNodeHttpHandler.cs b/src/EventStore.Client/Core/SingleNodeHttpHandler.cs deleted file mode 100644 index b8560152e..000000000 --- a/src/EventStore.Client/Core/SingleNodeHttpHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.Client { - internal class SingleNodeHttpHandler : DelegatingHandler { - private readonly EventStoreClientSettings _settings; - - public SingleNodeHttpHandler(EventStoreClientSettings settings) { - _settings = settings; - } - - protected override Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) { - request.RequestUri = new UriBuilder(request.RequestUri!) { - Scheme = _settings.ConnectivitySettings.ResolvedAddressOrDefault.Scheme - }.Uri; - return base.SendAsync(request, cancellationToken); - } - } -} diff --git a/src/EventStore.Client/Core/StreamFilter.cs b/src/EventStore.Client/Core/StreamFilter.cs deleted file mode 100644 index 8ed70e7bb..000000000 --- a/src/EventStore.Client/Core/StreamFilter.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; - -namespace EventStore.Client { - /// - /// A structure representing a filter on stream names for read operations. - /// - public readonly struct StreamFilter : IEquatable, IEventFilter { - /// - /// An empty . - /// - public static readonly StreamFilter None = default; - - readonly PrefixFilterExpression[] _prefixes; - - /// - public PrefixFilterExpression[] Prefixes => _prefixes ?? Array.Empty(); - - /// - public RegularFilterExpression Regex { get; } - - /// - public uint? MaxSearchWindow { get; } - - /// - /// Creates a from a single prefix. - /// - /// - /// - public static IEventFilter Prefix(string prefix) - => new StreamFilter(new PrefixFilterExpression(prefix)); - - /// - /// Creates a from multiple prefixes. - /// - /// - /// - public static IEventFilter Prefix(params string[] prefixes) - => new StreamFilter(Array.ConvertAll(prefixes, prefix => new PrefixFilterExpression(prefix))); - - /// - /// Creates a from a search window and multiple prefixes. - /// - /// - /// - /// - public static IEventFilter Prefix(uint maxSearchWindow, params string[] prefixes) - => new StreamFilter(maxSearchWindow, - Array.ConvertAll(prefixes, prefix => new PrefixFilterExpression(prefix))); - - /// - /// Creates a from a regular expression and a search window. - /// - /// - /// - /// - public static IEventFilter RegularExpression(string regex, uint maxSearchWindow = 32) - => new StreamFilter(maxSearchWindow, new RegularFilterExpression(regex)); - - /// - /// Creates a from a regular expression and a search window. - /// - /// - /// - /// - public static IEventFilter RegularExpression(Regex regex, uint maxSearchWindow = 32) - => new StreamFilter(maxSearchWindow, new RegularFilterExpression(regex)); - - StreamFilter(RegularFilterExpression regex) : this(default, regex) { } - - StreamFilter(uint maxSearchWindow, RegularFilterExpression regex) { - if (maxSearchWindow == 0) { - throw new ArgumentOutOfRangeException(nameof(maxSearchWindow), - maxSearchWindow, $"{nameof(maxSearchWindow)} must be greater than 0."); - } - - Regex = regex; - _prefixes = Array.Empty(); - MaxSearchWindow = maxSearchWindow; - } - - StreamFilter(params PrefixFilterExpression[] prefixes) : this(32, prefixes) { } - - StreamFilter(uint maxSearchWindow, params PrefixFilterExpression[] prefixes) { - if (prefixes.Length == 0) { - throw new ArgumentException(); - } - - if (maxSearchWindow == 0) { - throw new ArgumentOutOfRangeException(nameof(maxSearchWindow), - maxSearchWindow, $"{nameof(maxSearchWindow)} must be greater than 0."); - } - - _prefixes = prefixes; - Regex = RegularFilterExpression.None; - MaxSearchWindow = maxSearchWindow; - } - - /// - public bool Equals(StreamFilter other) => - Prefixes.SequenceEqual(other.Prefixes) && - Regex.Equals(other.Regex) && - MaxSearchWindow.Equals(other.MaxSearchWindow); - - /// - public override bool Equals(object? obj) => obj is StreamFilter other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(Prefixes).Combine(Regex).Combine(MaxSearchWindow); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(StreamFilter left, StreamFilter right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(StreamFilter left, StreamFilter right) => !left.Equals(right); - - /// - public override string ToString() => - this == None - ? "(none)" - : $"{nameof(StreamFilter)} {(Prefixes.Length == 0 ? Regex.ToString() : $"[{string.Join(", ", Prefixes)}]")}"; - } -} diff --git a/src/EventStore.Client/Core/StreamIdentifier.cs b/src/EventStore.Client/Core/StreamIdentifier.cs deleted file mode 100644 index e77286166..000000000 --- a/src/EventStore.Client/Core/StreamIdentifier.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text; -using Google.Protobuf; - -namespace EventStore.Client { -#pragma warning disable 1591 - internal partial class StreamIdentifier { - private string? _cached; - - public static implicit operator string?(StreamIdentifier? source) { - if (source == null) { - return null; - } - if (source._cached != null || source.StreamName.IsEmpty) return source._cached; - -#if NET - var tmp = Encoding.UTF8.GetString(source.StreamName.Span); -#else - var tmp = Encoding.UTF8.GetString(source.StreamName.ToByteArray()); -#endif - //this doesn't have to be thread safe, its just a cache in case the identifier is turned into a string several times - source._cached = tmp; - return source._cached; - } - - public static implicit operator StreamIdentifier(string source) => - new() {StreamName = ByteString.CopyFromUtf8(source)}; - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/StreamPosition.cs b/src/EventStore.Client/Core/StreamPosition.cs deleted file mode 100644 index 7c196c7da..000000000 --- a/src/EventStore.Client/Core/StreamPosition.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure referring to an 's position within a stream. - /// - public readonly struct StreamPosition : IEquatable, IComparable, IComparable, IPosition { - private readonly ulong _value; - - /// - /// The beginning (i.e., the first event) of a stream. - /// - public static readonly StreamPosition Start = new StreamPosition(0); - - /// - /// The end of a stream. Use this when reading a stream backwards, or subscribing live to a stream. - /// - public static readonly StreamPosition End = new StreamPosition(ulong.MaxValue); - - /// - /// Converts a to a . It is not meant to be used directly from your code. - /// - /// - /// - public static StreamPosition FromInt64(long value) => - value == -1 ? End : new StreamPosition(Convert.ToUInt64(value)); - - /// - /// Creates a from a . - /// - /// - /// - public static StreamPosition FromStreamRevision(StreamRevision revision) => revision.ToUInt64() switch { - ulong.MaxValue => throw new ArgumentOutOfRangeException(nameof(revision)), - _ => new StreamPosition(revision.ToUInt64()) - }; - - /// - /// Constructs a new . - /// - /// - /// - public StreamPosition(ulong value) { - if (value > long.MaxValue && value != ulong.MaxValue) { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _value = value; - } - - /// - /// Advance to the next . - /// - /// - public StreamPosition Next() => this + 1; - - /// - public int CompareTo(StreamPosition other) => _value.CompareTo(other._value); - - /// - public int CompareTo(object? obj) => obj switch { - null => 1, - StreamPosition other => CompareTo(other), - _ => throw new ArgumentException("Object is not a StreamPosition"), - }; - - /// - public bool Equals(StreamPosition other) => _value == other._value; - - /// - public override bool Equals(object? obj) => obj is StreamPosition other && Equals(other); - - /// - public override int GetHashCode() => _value.GetHashCode(); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(StreamPosition left, StreamPosition right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(StreamPosition left, StreamPosition right) => !left.Equals(right); - - /// - /// Adds right to left. - /// - /// - /// - /// - public static StreamPosition operator +(StreamPosition left, ulong right) { - checked { - return new StreamPosition(left._value + right); - } - } - - /// - /// Adds right to left. - /// - /// - /// - /// - public static StreamPosition operator +(ulong left, StreamPosition right) { - checked { - return new StreamPosition(left + right._value); - } - } - - /// - /// Subtracts right from left. - /// - /// - /// - /// - public static StreamPosition operator -(StreamPosition left, ulong right) { - checked { - return new StreamPosition(left._value - right); - } - } - - /// - /// Subtracts right from left. - /// - /// - /// - /// - public static StreamPosition operator -(ulong left, StreamPosition right) { - checked { - return new StreamPosition(left - right._value); - } - } - - /// - /// Compares whether left > right. - /// - /// - /// - /// - public static bool operator >(StreamPosition left, StreamPosition right) => left._value > right._value; - - /// - /// Compares whether left < right. - /// - /// - /// - /// - public static bool operator <(StreamPosition left, StreamPosition right) => left._value < right._value; - - /// - /// Compares whether left >= right. - /// - /// - /// - /// - public static bool operator >=(StreamPosition left, StreamPosition right) => left._value >= right._value; - - /// - /// Compares whether left <= right. - /// - /// - /// - /// - public static bool operator <=(StreamPosition left, StreamPosition right) => left._value <= right._value; - - /// - /// Converts the to a . It is not meant to be used directly from your code. - /// - /// - public long ToInt64() => Equals(End) ? -1 : Convert.ToInt64(_value); - - /// - /// Converts a to a . - /// - /// - /// - public static implicit operator ulong(StreamPosition streamPosition) => streamPosition._value; - - /// - /// Converts a to a . - /// - /// - /// - public static implicit operator StreamPosition(ulong value) => new StreamPosition(value); - - /// - public override string ToString() => this == End ? "End" : _value.ToString(); - - /// - /// Converts the to a . - /// - /// - public ulong ToUInt64() => _value; - } -} diff --git a/src/EventStore.Client/Core/StreamRevision.cs b/src/EventStore.Client/Core/StreamRevision.cs deleted file mode 100644 index 89b553f53..000000000 --- a/src/EventStore.Client/Core/StreamRevision.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure referring to the expected stream revision when writing to a stream. - /// - public readonly struct StreamRevision : IEquatable, IComparable, IComparable { - private readonly ulong _value; - - /// - /// Represents no , i.e., when a stream does not exist. - /// - public static readonly StreamRevision None = new StreamRevision(ulong.MaxValue); - - /// - /// Converts a to a . It is not meant to be used directly from your code. - /// - /// - /// - public static StreamRevision FromInt64(long value) => - value == -1 ? None : new StreamRevision(Convert.ToUInt64(value)); - - /// - /// Creates a new from the given . - /// - /// - /// - public static StreamRevision FromStreamPosition(StreamPosition position) => position.ToUInt64() switch { - ulong.MaxValue => throw new ArgumentOutOfRangeException(nameof(position)), - _ => new StreamRevision(position.ToUInt64()) - }; - - /// - /// Constructs a new . - /// - /// - /// - public StreamRevision(ulong value) { - if (value > long.MaxValue && value != ulong.MaxValue) { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _value = value; - } - - /// - /// Advances the to the next revision. - /// - /// - public StreamRevision Next() => this + 1; - - /// - public int CompareTo(StreamRevision other) => _value.CompareTo(other._value); - - /// - public int CompareTo(object? obj) => obj switch { - null => 1, - StreamRevision other => CompareTo(other), - _ => throw new ArgumentException("Object is not a StreamRevision"), - }; - - /// - public bool Equals(StreamRevision other) => _value == other._value; - - /// - public override bool Equals(object? obj) => obj is StreamRevision other && Equals(other); - - /// - public override int GetHashCode() => _value.GetHashCode(); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(StreamRevision left, StreamRevision right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(StreamRevision left, StreamRevision right) => !left.Equals(right); - - /// - /// Adds right to left. - /// - /// - /// - /// - public static StreamRevision operator +(StreamRevision left, ulong right) { - checked { - return new StreamRevision(left._value + right); - } - } - - /// - /// Adds right to left. - /// - /// - /// - /// - public static StreamRevision operator +(ulong left, StreamRevision right) { - checked { - return new StreamRevision(left + right._value); - } - } - - /// - /// Subtracts right from left. - /// - /// - /// - /// - public static StreamRevision operator -(StreamRevision left, ulong right) { - checked { - return new StreamRevision(left._value - right); - } - } - - /// - /// Subtracts right from left. - /// - /// - /// - /// - public static StreamRevision operator -(ulong left, StreamRevision right) { - checked { - return new StreamRevision(left - right._value); - } - } - - /// - /// Compares whether left > right. - /// - /// - /// - /// - public static bool operator >(StreamRevision left, StreamRevision right) => left._value > right._value; - - /// - /// Compares whether left < right. - /// - /// - /// - /// - public static bool operator <(StreamRevision left, StreamRevision right) => left._value < right._value; - - /// - /// Compares whether left >= right. - /// - /// - /// - /// - public static bool operator >=(StreamRevision left, StreamRevision right) => left._value >= right._value; - - /// - /// Compares whether left <= right. - /// - /// - /// - /// - public static bool operator <=(StreamRevision left, StreamRevision right) => left._value <= right._value; - - /// - /// Converts the to a . It is not meant to be used directly from your code. - /// - /// - public long ToInt64() => Equals(None) ? -1 : Convert.ToInt64(_value); - - /// - /// Converts a to a . - /// - /// - /// - public static implicit operator ulong(StreamRevision streamRevision) => streamRevision._value; - - /// - /// Converts a to a . - /// - /// - /// - public static implicit operator StreamRevision(ulong value) => new StreamRevision(value); - - /// - public override string ToString() => this == None ? nameof(None) : _value.ToString(); - - /// - /// Converts the to a . - /// - /// - public ulong ToUInt64() => _value; - } -} diff --git a/src/EventStore.Client/Core/StreamState.cs b/src/EventStore.Client/Core/StreamState.cs deleted file mode 100644 index a0a021726..000000000 --- a/src/EventStore.Client/Core/StreamState.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure that represents the state the stream should be in when writing. - /// - public readonly struct StreamState : IEquatable { - /// - /// The stream should not exist. - /// - public static readonly StreamState NoStream = new StreamState(Constants.NoStream); - - /// - /// The stream may or may not exist. - /// - public static readonly StreamState Any = new StreamState(Constants.Any); - - /// - /// The stream must exist. - /// - public static readonly StreamState StreamExists = new StreamState(Constants.StreamExists); - - private readonly int _value; - - private static class Constants { - public const int NoStream = 1; - public const int Any = 2; - public const int StreamExists = 4; - } - - internal StreamState(int value) { - switch (value) { - case Constants.NoStream: - case Constants.Any: - case Constants.StreamExists: - _value = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(value)); - } - } - - /// - public bool Equals(StreamState other) => _value == other._value; - - /// - public override bool Equals(object? obj) => obj is StreamState other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(_value); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// Returns True when left and right are equal. - public static bool operator ==(StreamState left, StreamState right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// Returns True when left and right are not equal. - public static bool operator !=(StreamState left, StreamState right) => !left.Equals(right); - - /// - /// Converts the to a . It is not meant to be used directly from your code. - /// - /// - public long ToInt64() => -Convert.ToInt64(_value); - - /// - /// Converts the to an . It is not meant to be used directly from your code. - /// - /// - public static implicit operator int(StreamState streamState) => streamState._value; - - /// - public override string ToString() => _value switch { - Constants.NoStream => nameof(NoStream), - Constants.Any => nameof(Any), - Constants.StreamExists => nameof(StreamExists), - _ => _value.ToString() - }; - } -} diff --git a/src/EventStore.Client/Core/SubscriptionDroppedReason.cs b/src/EventStore.Client/Core/SubscriptionDroppedReason.cs deleted file mode 100644 index 1865a5d44..000000000 --- a/src/EventStore.Client/Core/SubscriptionDroppedReason.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore.Client { - /// - /// Represents the reason subscription was dropped. - /// - public enum SubscriptionDroppedReason { - /// - /// Subscription was dropped because the subscription was disposed. - /// - Disposed, - /// - /// Subscription was dropped because of an error in user code. - /// - SubscriberError, - /// - /// Subscription was dropped because of a server error. - /// - ServerError - } -} diff --git a/src/EventStore.Client/Core/SystemRoles.cs b/src/EventStore.Client/Core/SystemRoles.cs deleted file mode 100644 index b9cb068f7..000000000 --- a/src/EventStore.Client/Core/SystemRoles.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace EventStore.Client { - /// - /// Roles used by the system. - /// - public static class SystemRoles { - /// - /// The $admins role. - /// - public const string Admins = "$admins"; - - /// - /// The $ops role. - /// - public const string Operations = "$ops"; - - /// - /// The $all role. - /// - public const string All = "$all"; - } -} diff --git a/src/EventStore.Client/Core/SystemStreams.cs b/src/EventStore.Client/Core/SystemStreams.cs deleted file mode 100644 index 67899794b..000000000 --- a/src/EventStore.Client/Core/SystemStreams.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace EventStore.Client { - /// - /// A collection of constants and methods to identify streams. - /// - public static class SystemStreams { - /// - /// A stream containing all events in the EventStoreDB transaction file. - /// - public const string AllStream = "$all"; - - /// - /// A stream containing links pointing to each stream in the EventStoreDB. - /// - public const string StreamsStream = "$streams"; - - /// - /// A stream containing system settings. - /// - public const string SettingsStream = "$settings"; - - /// - /// A stream containing statistics. - /// - public const string StatsStreamPrefix = "$stats"; - - /// - /// Returns True if the stream is a system stream. - /// - /// - /// - public static bool IsSystemStream(string streamId) => streamId.Length != 0 && streamId[0] == '$'; - - /// - /// Returns the metadata stream of the stream. - /// - /// - /// - public static string MetastreamOf(string streamId) => "$$" + streamId; - - /// - /// Returns true if the stream is a metadata stream. - /// - /// - /// - public static bool IsMetastream(string streamId) => streamId[..2] == "$$"; - - /// - /// Returns the original stream of the metadata stream. - /// - /// - /// - public static string OriginalStreamOf(string metastreamId) => metastreamId[2..]; - } -} diff --git a/src/EventStore.Client/Core/TaskExtensions.cs b/src/EventStore.Client/Core/TaskExtensions.cs deleted file mode 100644 index 5511b1c20..000000000 --- a/src/EventStore.Client/Core/TaskExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.Client { - internal static class TaskExtensions { - // To give up waiting for the task, cancel the token. - // obvs this wouldn't cancel the task itself. - public static async ValueTask WithCancellation(this Task task, CancellationToken cancellationToken) { - if (task.Status == TaskStatus.RanToCompletion) - return task.Result; - - await Task - .WhenAny( - task, - Task.Delay(-1, cancellationToken)) - .ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - return await task.ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/Core/UserCredentials.cs b/src/EventStore.Client/Core/UserCredentials.cs deleted file mode 100644 index d944d90d7..000000000 --- a/src/EventStore.Client/Core/UserCredentials.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Net.Http.Headers; -using System.Text; -using static System.Convert; - -namespace EventStore.Client { - /// - /// Represents either a username/password pair or a JWT token used for authentication and - /// authorization to perform operations on the EventStoreDB. - /// - public class UserCredentials { - // ReSharper disable once InconsistentNaming - static readonly UTF8Encoding UTF8NoBom = new UTF8Encoding(false); - - /// - /// Constructs a new . - /// - public UserCredentials(string username, string password) { - Username = username; - Password = password; - - Authorization = new( - Constants.Headers.BasicScheme, - ToBase64String(UTF8NoBom.GetBytes($"{username}:{password}")) - ); - } - - /// - /// Constructs a new . - /// - public UserCredentials(string bearerToken) { - Authorization = new(Constants.Headers.BearerScheme, bearerToken); - } - - AuthenticationHeaderValue Authorization { get; } - - /// - /// The username - /// - public string? Username { get; } - - /// - /// The password - /// - public string? Password { get; } - - /// - public override string ToString() => Authorization.ToString(); - - /// - /// Implicitly convert a to a . - /// - public static implicit operator string(UserCredentials self) => self.ToString(); - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/Uuid.cs b/src/EventStore.Client/Core/Uuid.cs deleted file mode 100644 index 0a38cc766..000000000 --- a/src/EventStore.Client/Core/Uuid.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace EventStore.Client { - /// - /// An RFC-4122 compliant v4 UUID. - /// - public readonly struct Uuid : IEquatable { - /// - /// Represents the empty (00000000-0000-0000-0000-000000000000) . - /// - /// - /// This reorders the bits in System.Guid to improve interop with other languages. See: https://stackoverflow.com/a/16722909 - /// - public static readonly Uuid Empty = new Uuid(Guid.Empty); - - private readonly long _lsb; - private readonly long _msb; - - /// - /// Creates a new, randomized . - /// - /// - public static Uuid NewUuid() => new Uuid(Guid.NewGuid()); - - /// - /// Converts a to a . - /// - /// - /// - public static Uuid FromGuid(Guid value) => new Uuid(value); - - /// - /// Parses a into a . - /// - /// - /// - public static Uuid Parse(string value) => new Uuid(value); - - /// - /// Creates a from a pair of . - /// - /// The representing the most significant bits. - /// The representing the least significant bits. - /// - public static Uuid FromInt64(long msb, long lsb) => new Uuid(msb, lsb); - - /// - /// Creates a from the gRPC wire format. - /// - /// - /// - internal static Uuid FromDto(UUID dto) => - dto == null - ? throw new ArgumentNullException(nameof(dto)) - : dto.ValueCase switch { - UUID.ValueOneofCase.String => new Uuid(dto.String), - UUID.ValueOneofCase.Structured => new Uuid(dto.Structured.MostSignificantBits, - dto.Structured.LeastSignificantBits), - _ => throw new ArgumentException($"Invalid argument: {dto.ValueCase}", nameof(dto)) - }; - - private Uuid(Guid value) { - if (!BitConverter.IsLittleEndian) { - throw new NotSupportedException(); - } - - Span data = stackalloc byte[16]; - - if (!TryWriteGuidBytes(value, data)) { - throw new InvalidOperationException(); - } - - data[..8].Reverse(); - data[..2].Reverse(); - data.Slice(2, 2).Reverse(); - data.Slice(4, 4).Reverse(); - data[8..].Reverse(); - - _msb = BitConverterToInt64(data); - _lsb = BitConverterToInt64(data[8..]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long BitConverterToInt64(ReadOnlySpan value) - { -#if NET - return BitConverter.ToInt64(value); -#else - return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(value)); -#endif - } - - private Uuid(string value) : this(value == null - ? throw new ArgumentNullException(nameof(value)) - : Guid.Parse(value)) { - } - - private Uuid(long msb, long lsb) { - _msb = msb; - _lsb = lsb; - } - - /// - /// Converts the to the gRPC wire format. - /// - /// - internal UUID ToDto() => - new UUID { - Structured = new UUID.Types.Structured { - LeastSignificantBits = _lsb, - MostSignificantBits = _msb - } - }; - - - /// - public bool Equals(Uuid other) => _lsb == other._lsb && _msb == other._msb; - - /// - public override bool Equals(object? obj) => obj is Uuid other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(_lsb).Combine(_msb); - - /// - /// Compares left and right for equality. - /// - /// A - /// A - /// True if left is equal to right. - public static bool operator ==(Uuid left, Uuid right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// A - /// A - /// True if left is not equal to right. - public static bool operator !=(Uuid left, Uuid right) => !left.Equals(right); - - /// - public override string ToString() => ToGuid().ToString(); - - /// - /// Converts the to a based on the supplied format. - /// - /// - /// - public string ToString(string format) => ToGuid().ToString(format); - - /// - /// Converts the to a . - /// - /// - public Guid ToGuid() { - if (!BitConverter.IsLittleEndian) { - throw new NotSupportedException(); - } - - Span data = stackalloc byte[16]; - if (!TryWriteBytes(data, _msb) || - !TryWriteBytes(data[8..], _lsb)) { - throw new InvalidOperationException(); - } - - data[..8].Reverse(); - data[..4].Reverse(); - data.Slice(4, 2).Reverse(); - data.Slice(6, 2).Reverse(); - data[8..].Reverse(); - -#if NET - return new Guid(data); -#else - return new Guid(data.ToArray()); -#endif - } - private static bool TryWriteBytes(Span destination, long value) - { - if (destination.Length < sizeof(long)) - return false; - - Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value); - return true; - } - - private bool TryWriteGuidBytes(Guid value, Span destination) - { -#if NET - return value.TryWriteBytes(destination); -#else - if (destination.Length < 16) - return false; - - var bytes = value.ToByteArray(); - bytes.CopyTo(destination); - return true; -#endif - } - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Core/protos/code.proto b/src/EventStore.Client/Core/protos/code.proto deleted file mode 100644 index 98ae0ac18..000000000 --- a/src/EventStore.Client/Core/protos/code.proto +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.rpc; - -option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; -option java_multiple_files = true; -option java_outer_classname = "CodeProto"; -option java_package = "com.google.rpc"; -option objc_class_prefix = "RPC"; - -// The canonical error codes for gRPC APIs. -// -// -// Sometimes multiple error codes may apply. Services should return -// the most specific error code that applies. For example, prefer -// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. -// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. -enum Code { - // Not an error; returned on success - // - // HTTP Mapping: 200 OK - OK = 0; - - // The operation was cancelled, typically by the caller. - // - // HTTP Mapping: 499 Client Closed Request - CANCELLED = 1; - - // Unknown error. For example, this error may be returned when - // a `Status` value received from another address space belongs to - // an error space that is not known in this address space. Also - // errors raised by APIs that do not return enough error information - // may be converted to this error. - // - // HTTP Mapping: 500 Internal Server Error - UNKNOWN = 2; - - // The client specified an invalid argument. Note that this differs - // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments - // that are problematic regardless of the state of the system - // (e.g., a malformed file name). - // - // HTTP Mapping: 400 Bad Request - INVALID_ARGUMENT = 3; - - // The deadline expired before the operation could complete. For operations - // that change the state of the system, this error may be returned - // even if the operation has completed successfully. For example, a - // successful response from a server could have been delayed long - // enough for the deadline to expire. - // - // HTTP Mapping: 504 Gateway Timeout - DEADLINE_EXCEEDED = 4; - - // Some requested entity (e.g., file or directory) was not found. - // - // Note to server developers: if a request is denied for an entire class - // of users, such as gradual feature rollout or undocumented whitelist, - // `NOT_FOUND` may be used. If a request is denied for some users within - // a class of users, such as user-based access control, `PERMISSION_DENIED` - // must be used. - // - // HTTP Mapping: 404 Not Found - NOT_FOUND = 5; - - // The entity that a client attempted to create (e.g., file or directory) - // already exists. - // - // HTTP Mapping: 409 Conflict - ALREADY_EXISTS = 6; - - // The caller does not have permission to execute the specified - // operation. `PERMISSION_DENIED` must not be used for rejections - // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` - // instead for those errors). `PERMISSION_DENIED` must not be - // used if the caller can not be identified (use `UNAUTHENTICATED` - // instead for those errors). This error code does not imply the - // request is valid or the requested entity exists or satisfies - // other pre-conditions. - // - // HTTP Mapping: 403 Forbidden - PERMISSION_DENIED = 7; - - // The request does not have valid authentication credentials for the - // operation. - // - // HTTP Mapping: 401 Unauthorized - UNAUTHENTICATED = 16; - - // Some resource has been exhausted, perhaps a per-user quota, or - // perhaps the entire file system is out of space. - // - // HTTP Mapping: 429 Too Many Requests - RESOURCE_EXHAUSTED = 8; - - // The operation was rejected because the system is not in a state - // required for the operation's execution. For example, the directory - // to be deleted is non-empty, an rmdir operation is applied to - // a non-directory, etc. - // - // Service implementors can use the following guidelines to decide - // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: - // (a) Use `UNAVAILABLE` if the client can retry just the failing call. - // (b) Use `ABORTED` if the client should retry at a higher level - // (e.g., when a client-specified test-and-set fails, indicating the - // client should restart a read-modify-write sequence). - // (c) Use `FAILED_PRECONDITION` if the client should not retry until - // the system state has been explicitly fixed. E.g., if an "rmdir" - // fails because the directory is non-empty, `FAILED_PRECONDITION` - // should be returned since the client should not retry unless - // the files are deleted from the directory. - // - // HTTP Mapping: 400 Bad Request - FAILED_PRECONDITION = 9; - - // The operation was aborted, typically due to a concurrency issue such as - // a sequencer check failure or transaction abort. - // - // See the guidelines above for deciding between `FAILED_PRECONDITION`, - // `ABORTED`, and `UNAVAILABLE`. - // - // HTTP Mapping: 409 Conflict - ABORTED = 10; - - // The operation was attempted past the valid range. E.g., seeking or - // reading past end-of-file. - // - // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may - // be fixed if the system state changes. For example, a 32-bit file - // system will generate `INVALID_ARGUMENT` if asked to read at an - // offset that is not in the range [0,2^32-1], but it will generate - // `OUT_OF_RANGE` if asked to read from an offset past the current - // file size. - // - // There is a fair bit of overlap between `FAILED_PRECONDITION` and - // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific - // error) when it applies so that callers who are iterating through - // a space can easily look for an `OUT_OF_RANGE` error to detect when - // they are done. - // - // HTTP Mapping: 400 Bad Request - OUT_OF_RANGE = 11; - - // The operation is not implemented or is not supported/enabled in this - // service. - // - // HTTP Mapping: 501 Not Implemented - UNIMPLEMENTED = 12; - - // Internal errors. This means that some invariants expected by the - // underlying system have been broken. This error code is reserved - // for serious errors. - // - // HTTP Mapping: 500 Internal Server Error - INTERNAL = 13; - - // The service is currently unavailable. This is most likely a - // transient condition, which can be corrected by retrying with - // a backoff. Note that it is not always safe to retry - // non-idempotent operations. - // - // See the guidelines above for deciding between `FAILED_PRECONDITION`, - // `ABORTED`, and `UNAVAILABLE`. - // - // HTTP Mapping: 503 Service Unavailable - UNAVAILABLE = 14; - - // Unrecoverable data loss or corruption. - // - // HTTP Mapping: 500 Internal Server Error - DATA_LOSS = 15; -} diff --git a/src/EventStore.Client/Core/protos/gossip.proto b/src/EventStore.Client/Core/protos/gossip.proto deleted file mode 100644 index 192b3b0a4..000000000 --- a/src/EventStore.Client/Core/protos/gossip.proto +++ /dev/null @@ -1,44 +0,0 @@ -syntax = "proto3"; -package event_store.client.gossip; -option java_package = "com.eventstore.client.gossip"; - -import "shared.proto"; - -service Gossip { - rpc Read (event_store.client.Empty) returns (ClusterInfo); -} - -message ClusterInfo { - repeated MemberInfo members = 1; -} - -message EndPoint { - string address = 1; - uint32 port = 2; -} - -message MemberInfo { - enum VNodeState { - Initializing = 0; - DiscoverLeader = 1; - Unknown = 2; - PreReplica = 3; - CatchingUp = 4; - Clone = 5; - Follower = 6; - PreLeader = 7; - Leader = 8; - Manager = 9; - ShuttingDown = 10; - Shutdown = 11; - ReadOnlyLeaderless = 12; - PreReadOnlyReplica = 13; - ReadOnlyReplica = 14; - ResigningLeader = 15; - } - event_store.client.UUID instance_id = 1; - int64 time_stamp = 2; - VNodeState state = 3; - bool is_alive = 4; - EndPoint http_end_point = 5; -} diff --git a/src/EventStore.Client/Core/protos/operations.proto b/src/EventStore.Client/Core/protos/operations.proto deleted file mode 100644 index e8d5d2726..000000000 --- a/src/EventStore.Client/Core/protos/operations.proto +++ /dev/null @@ -1,45 +0,0 @@ -syntax = "proto3"; -package event_store.client.operations; -option java_package = "com.eventstore.client.operations"; - -import "shared.proto"; - -service Operations { - rpc StartScavenge (StartScavengeReq) returns (ScavengeResp); - rpc StopScavenge (StopScavengeReq) returns (ScavengeResp); - rpc Shutdown (Empty) returns (Empty); - rpc MergeIndexes (Empty) returns (Empty); - rpc ResignNode (Empty) returns (Empty); - rpc SetNodePriority (SetNodePriorityReq) returns (Empty); - rpc RestartPersistentSubscriptions (Empty) returns (Empty); -} - -message StartScavengeReq { - Options options = 1; - message Options { - int32 thread_count = 1; - int32 start_from_chunk = 2; - } -} - -message StopScavengeReq { - Options options = 1; - message Options { - string scavenge_id = 1; - } -} - -message ScavengeResp { - string scavenge_id = 1; - ScavengeResult scavenge_result = 2; - - enum ScavengeResult { - Started = 0; - InProgress = 1; - Stopped = 2; - } -} - -message SetNodePriorityReq { - int32 priority = 1; -} diff --git a/src/EventStore.Client/Core/protos/persistentsubscriptions.proto b/src/EventStore.Client/Core/protos/persistentsubscriptions.proto deleted file mode 100644 index a4109cad2..000000000 --- a/src/EventStore.Client/Core/protos/persistentsubscriptions.proto +++ /dev/null @@ -1,370 +0,0 @@ -syntax = "proto3"; -package event_store.client.persistent_subscriptions; -option java_package = "com.eventstore.dbclient.proto.persistentsubscriptions"; - -import "shared.proto"; - -service PersistentSubscriptions { - rpc Create (CreateReq) returns (CreateResp); - rpc Update (UpdateReq) returns (UpdateResp); - rpc Delete (DeleteReq) returns (DeleteResp); - rpc Read (stream ReadReq) returns (stream ReadResp); - rpc GetInfo (GetInfoReq) returns (GetInfoResp); - rpc ReplayParked (ReplayParkedReq) returns (ReplayParkedResp); - rpc List (ListReq) returns (ListResp); - rpc RestartSubsystem (event_store.client.Empty) returns (event_store.client.Empty); -} - -message ReadReq { - oneof content { - Options options = 1; - Ack ack = 2; - Nack nack = 3; - } - - message Options { - oneof stream_option { - event_store.client.StreamIdentifier stream_identifier = 1; - event_store.client.Empty all = 5; - } - - string group_name = 2; - int32 buffer_size = 3; - UUIDOption uuid_option = 4; - - message UUIDOption { - oneof content { - event_store.client.Empty structured = 1; - event_store.client.Empty string = 2; - } - } - } - - message Ack { - bytes id = 1; - repeated event_store.client.UUID ids = 2; - } - - message Nack { - bytes id = 1; - repeated event_store.client.UUID ids = 2; - Action action = 3; - string reason = 4; - - enum Action { - Unknown = 0; - Park = 1; - Retry = 2; - Skip = 3; - Stop = 4; - } - } -} - -message ReadResp { - oneof content { - ReadEvent event = 1; - SubscriptionConfirmation subscription_confirmation = 2; - } - message ReadEvent { - RecordedEvent event = 1; - RecordedEvent link = 2; - oneof position { - uint64 commit_position = 3; - event_store.client.Empty no_position = 4; - } - oneof count { - int32 retry_count = 5; - event_store.client.Empty no_retry_count = 6; - } - message RecordedEvent { - event_store.client.UUID id = 1; - event_store.client.StreamIdentifier stream_identifier = 2; - uint64 stream_revision = 3; - uint64 prepare_position = 4; - uint64 commit_position = 5; - map metadata = 6; - bytes custom_metadata = 7; - bytes data = 8; - } - } - message SubscriptionConfirmation { - string subscription_id = 1; - } -} - -message CreateReq { - Options options = 1; - - message Options { - oneof stream_option { - StreamOptions stream = 4; - AllOptions all = 5; - } - event_store.client.StreamIdentifier stream_identifier = 1 [deprecated=true]; - string group_name = 2; - Settings settings = 3; - } - - message StreamOptions { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof revision_option { - uint64 revision = 2; - event_store.client.Empty start = 3; - event_store.client.Empty end = 4; - } - } - - message AllOptions { - oneof all_option { - Position position = 1; - event_store.client.Empty start = 2; - event_store.client.Empty end = 3; - } - oneof filter_option { - FilterOptions filter = 4; - event_store.client.Empty no_filter = 5; - } - message FilterOptions { - oneof filter { - Expression stream_identifier = 1; - Expression event_type = 2; - } - oneof window { - uint32 max = 3; - event_store.client.Empty count = 4; - } - uint32 checkpointIntervalMultiplier = 5; - - message Expression { - string regex = 1; - repeated string prefix = 2; - } - } - } - - message Position { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } - - message Settings { - bool resolve_links = 1; - uint64 revision = 2 [deprecated = true]; - bool extra_statistics = 3; - int32 max_retry_count = 5; - int32 min_checkpoint_count = 7; - int32 max_checkpoint_count = 8; - int32 max_subscriber_count = 9; - int32 live_buffer_size = 10; - int32 read_batch_size = 11; - int32 history_buffer_size = 12; - ConsumerStrategy named_consumer_strategy = 13 [deprecated = true]; - oneof message_timeout { - int64 message_timeout_ticks = 4; - int32 message_timeout_ms = 14; - } - oneof checkpoint_after { - int64 checkpoint_after_ticks = 6; - int32 checkpoint_after_ms = 15; - } - string consumer_strategy = 16; - } - - enum ConsumerStrategy { - DispatchToSingle = 0; - RoundRobin = 1; - Pinned = 2; - } -} - -message CreateResp { -} - -message UpdateReq { - Options options = 1; - - message Options { - oneof stream_option { - StreamOptions stream = 4; - AllOptions all = 5; - } - event_store.client.StreamIdentifier stream_identifier = 1 [deprecated = true]; - string group_name = 2; - Settings settings = 3; - } - - message StreamOptions { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof revision_option { - uint64 revision = 2; - event_store.client.Empty start = 3; - event_store.client.Empty end = 4; - } - } - - message AllOptions { - oneof all_option { - Position position = 1; - event_store.client.Empty start = 2; - event_store.client.Empty end = 3; - } - } - - message Position { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } - - message Settings { - bool resolve_links = 1; - uint64 revision = 2 [deprecated = true]; - bool extra_statistics = 3; - int32 max_retry_count = 5; - int32 min_checkpoint_count = 7; - int32 max_checkpoint_count = 8; - int32 max_subscriber_count = 9; - int32 live_buffer_size = 10; - int32 read_batch_size = 11; - int32 history_buffer_size = 12; - ConsumerStrategy named_consumer_strategy = 13; - oneof message_timeout { - int64 message_timeout_ticks = 4; - int32 message_timeout_ms = 14; - } - oneof checkpoint_after { - int64 checkpoint_after_ticks = 6; - int32 checkpoint_after_ms = 15; - } - } - - enum ConsumerStrategy { - DispatchToSingle = 0; - RoundRobin = 1; - Pinned = 2; - } -} - -message UpdateResp { -} - -message DeleteReq { - Options options = 1; - - message Options { - oneof stream_option { - event_store.client.StreamIdentifier stream_identifier = 1; - event_store.client.Empty all = 3; - } - - string group_name = 2; - } -} - -message DeleteResp { -} - -message GetInfoReq { - Options options = 1; - - message Options { - oneof stream_option { - event_store.client.StreamIdentifier stream_identifier = 1; - event_store.client.Empty all = 2; - } - - string group_name = 3; - } -} - -message GetInfoResp { - SubscriptionInfo subscription_info = 1; -} - -message SubscriptionInfo { - string event_source = 1; - string group_name = 2; - string status = 3; - repeated ConnectionInfo connections = 4; - int32 average_per_second = 5; - int64 total_items = 6; - int64 count_since_last_measurement = 7; - string last_checkpointed_event_position = 8; - string last_known_event_position = 9; - bool resolve_link_tos = 10; - string start_from = 11; - int32 message_timeout_milliseconds = 12; - bool extra_statistics = 13; - int32 max_retry_count = 14; - int32 live_buffer_size = 15; - int32 buffer_size = 16; - int32 read_batch_size = 17; - int32 check_point_after_milliseconds = 18; - int32 min_check_point_count = 19; - int32 max_check_point_count = 20; - int32 read_buffer_count = 21; - int64 live_buffer_count = 22; - int32 retry_buffer_count = 23; - int32 total_in_flight_messages = 24; - int32 outstanding_messages_count = 25; - string named_consumer_strategy = 26; - int32 max_subscriber_count = 27; - int64 parked_message_count = 28; - - message ConnectionInfo { - string from = 1; - string username = 2; - int32 average_items_per_second = 3; - int64 total_items = 4; - int64 count_since_last_measurement = 5; - repeated Measurement observed_measurements = 6; - int32 available_slots = 7; - int32 in_flight_messages = 8; - string connection_name = 9; - } - - message Measurement { - string key = 1; - int64 value = 2; - } -} - -message ReplayParkedReq { - Options options = 1; - - message Options { - string group_name = 1; - oneof stream_option { - event_store.client.StreamIdentifier stream_identifier = 2; - event_store.client.Empty all = 3; - } - oneof stop_at_option { - int64 stop_at = 4; - event_store.client.Empty no_limit = 5; - } - } -} - -message ReplayParkedResp { -} - -message ListReq { - Options options = 1; - - message Options { - oneof list_option { - event_store.client.Empty list_all_subscriptions = 1; - StreamOption list_for_stream = 2; - } - } - message StreamOption { - oneof stream_option { - event_store.client.StreamIdentifier stream = 1; - event_store.client.Empty all = 2; - } - } -} - -message ListResp { - repeated SubscriptionInfo subscriptions = 1; -} diff --git a/src/EventStore.Client/Core/protos/projectionmanagement.proto b/src/EventStore.Client/Core/protos/projectionmanagement.proto deleted file mode 100644 index f1733b55a..000000000 --- a/src/EventStore.Client/Core/protos/projectionmanagement.proto +++ /dev/null @@ -1,174 +0,0 @@ -syntax = "proto3"; -package event_store.client.projections; -option java_package = "com.eventstore.client.projections"; - -import "google/protobuf/struct.proto"; -import "shared.proto"; - -service Projections { - rpc Create (CreateReq) returns (CreateResp); - rpc Update (UpdateReq) returns (UpdateResp); - rpc Delete (DeleteReq) returns (DeleteResp); - rpc Statistics (StatisticsReq) returns (stream StatisticsResp); - rpc Disable (DisableReq) returns (DisableResp); - rpc Enable (EnableReq) returns (EnableResp); - rpc Reset (ResetReq) returns (ResetResp); - rpc State (StateReq) returns (StateResp); - rpc Result (ResultReq) returns (ResultResp); - rpc RestartSubsystem (Empty) returns (Empty); -} - -message CreateReq { - Options options = 1; - - message Options { - oneof mode { - event_store.client.Empty one_time = 1; - Transient transient = 2; - Continuous continuous = 3; - } - string query = 4; - - message Transient { - string name = 1; - } - message Continuous { - string name = 1; - bool track_emitted_streams = 2; - } - } -} - -message CreateResp { -} - -message UpdateReq { - Options options = 1; - - message Options { - string name = 1; - string query = 2; - oneof emit_option { - bool emit_enabled = 3; - event_store.client.Empty no_emit_options = 4; - } - } -} - -message UpdateResp { -} - -message DeleteReq { - Options options = 1; - - message Options { - string name = 1; - bool delete_emitted_streams = 2; - bool delete_state_stream = 3; - bool delete_checkpoint_stream = 4; - } -} - -message DeleteResp { -} - -message StatisticsReq { - Options options = 1; - message Options { - oneof mode { - string name = 1; - event_store.client.Empty all = 2; - event_store.client.Empty transient = 3; - event_store.client.Empty continuous = 4; - event_store.client.Empty one_time = 5; - } - } -} - -message StatisticsResp { - Details details = 1; - - message Details { - int64 coreProcessingTime = 1; - int64 version = 2; - int64 epoch = 3; - string effectiveName = 4; - int32 writesInProgress = 5; - int32 readsInProgress = 6; - int32 partitionsCached = 7; - string status = 8; - string stateReason = 9; - string name = 10; - string mode = 11; - string position = 12; - float progress = 13; - string lastCheckpoint = 14; - int64 eventsProcessedAfterRestart = 15; - string checkpointStatus = 16; - int64 bufferedEvents = 17; - int32 writePendingEventsBeforeCheckpoint = 18; - int32 writePendingEventsAfterCheckpoint = 19; - } -} - -message StateReq { - Options options = 1; - - message Options { - string name = 1; - string partition = 2; - } -} - -message StateResp { - google.protobuf.Value state = 1; -} - -message ResultReq { - Options options = 1; - - message Options { - string name = 1; - string partition = 2; - } -} - -message ResultResp { - google.protobuf.Value result = 1; -} - -message ResetReq { - Options options = 1; - - message Options { - string name = 1; - bool write_checkpoint = 2; - } -} - -message ResetResp { -} - - -message EnableReq { - Options options = 1; - - message Options { - string name = 1; - } -} - -message EnableResp { -} - -message DisableReq { - Options options = 1; - - message Options { - string name = 1; - bool write_checkpoint = 2; - } -} - -message DisableResp { -} diff --git a/src/EventStore.Client/Core/protos/serverfeatures.proto b/src/EventStore.Client/Core/protos/serverfeatures.proto deleted file mode 100644 index cba04f2c6..000000000 --- a/src/EventStore.Client/Core/protos/serverfeatures.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; -package event_store.client.server_features; -option java_package = "com.eventstore.dbclient.proto.serverfeatures"; -import "shared.proto"; - -service ServerFeatures { - rpc GetSupportedMethods (event_store.client.Empty) returns (SupportedMethods); -} - -message SupportedMethods { - repeated SupportedMethod methods = 1; - string event_store_server_version = 2; -} - -message SupportedMethod { - string method_name = 1; - string service_name = 2; - repeated string features = 3; -} diff --git a/src/EventStore.Client/Core/protos/shared.proto b/src/EventStore.Client/Core/protos/shared.proto deleted file mode 100644 index 2b113aed8..000000000 --- a/src/EventStore.Client/Core/protos/shared.proto +++ /dev/null @@ -1,61 +0,0 @@ -syntax = "proto3"; -package event_store.client; -option java_package = "com.eventstore.dbclient.proto.shared"; -import "google/protobuf/empty.proto"; - -message UUID { - oneof value { - Structured structured = 1; - string string = 2; - } - - message Structured { - int64 most_significant_bits = 1; - int64 least_significant_bits = 2; - } -} -message Empty { -} - -message StreamIdentifier { - reserved 1 to 2; - bytes stream_name = 3; -} - -message AllStreamPosition { - uint64 commit_position = 1; - uint64 prepare_position = 2; -} - -message WrongExpectedVersion { - oneof current_stream_revision_option { - uint64 current_stream_revision = 1; - google.protobuf.Empty current_no_stream = 2; - } - oneof expected_stream_position_option { - uint64 expected_stream_position = 3; - google.protobuf.Empty expected_any = 4; - google.protobuf.Empty expected_stream_exists = 5; - google.protobuf.Empty expected_no_stream = 6; - } -} - -message AccessDenied {} - -message StreamDeleted { - StreamIdentifier stream_identifier = 1; -} - -message Timeout {} - -message Unknown {} - -message InvalidTransaction {} - -message MaximumAppendSizeExceeded { - uint32 maxAppendSize = 1; -} - -message BadRequest { - string message = 1; -} diff --git a/src/EventStore.Client/Core/protos/status.proto b/src/EventStore.Client/Core/protos/status.proto deleted file mode 100644 index 65eced268..000000000 --- a/src/EventStore.Client/Core/protos/status.proto +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.rpc; - -import "google/protobuf/any.proto"; -import "code.proto"; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; -option java_multiple_files = true; -option java_outer_classname = "StatusProto"; -option java_package = "com.google.rpc"; -option objc_class_prefix = "RPC"; - -// The `Status` type defines a logical error model that is suitable for -// different programming environments, including REST APIs and RPC APIs. It is -// used by [gRPC](https://github.com/grpc). Each `Status` message contains -// three pieces of data: error code, error message, and error details. -// -// You can find out more about this error model and how to work with it in the -// [API Design Guide](https://cloud.google.com/apis/design/errors). -message Status { - // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - google.rpc.Code code = 1; - - // A developer-facing error message, which should be in English. Any - // user-facing error message should be localized and sent in the - // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - string message = 2; - - // A list of messages that carry the error details. There is a common set of - // message types for APIs to use. - google.protobuf.Any details = 3; -} diff --git a/src/EventStore.Client/Core/protos/streams.proto b/src/EventStore.Client/Core/protos/streams.proto deleted file mode 100644 index ac599fb82..000000000 --- a/src/EventStore.Client/Core/protos/streams.proto +++ /dev/null @@ -1,316 +0,0 @@ -syntax = "proto3"; -package event_store.client.streams; -option java_package = "com.eventstore.dbclient.proto.streams"; - -import "shared.proto"; -import "status.proto"; -import "google/protobuf/duration.proto"; -import "google/protobuf/empty.proto"; -import "google/protobuf/timestamp.proto"; - -service Streams { - rpc Read (ReadReq) returns (stream ReadResp); - rpc Append (stream AppendReq) returns (AppendResp); - rpc Delete (DeleteReq) returns (DeleteResp); - rpc Tombstone (TombstoneReq) returns (TombstoneResp); - rpc BatchAppend (stream BatchAppendReq) returns (stream BatchAppendResp); -} - -message ReadReq { - Options options = 1; - - message Options { - oneof stream_option { - StreamOptions stream = 1; - AllOptions all = 2; - } - ReadDirection read_direction = 3; - bool resolve_links = 4; - oneof count_option { - uint64 count = 5; - SubscriptionOptions subscription = 6; - } - oneof filter_option { - FilterOptions filter = 7; - event_store.client.Empty no_filter = 8; - } - UUIDOption uuid_option = 9; - ControlOption control_option = 10; - - enum ReadDirection { - Forwards = 0; - Backwards = 1; - } - message StreamOptions { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof revision_option { - uint64 revision = 2; - event_store.client.Empty start = 3; - event_store.client.Empty end = 4; - } - } - message AllOptions { - oneof all_option { - Position position = 1; - event_store.client.Empty start = 2; - event_store.client.Empty end = 3; - } - } - message SubscriptionOptions { - } - message Position { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } - message FilterOptions { - oneof filter { - Expression stream_identifier = 1; - Expression event_type = 2; - } - oneof window { - uint32 max = 3; - event_store.client.Empty count = 4; - } - uint32 checkpointIntervalMultiplier = 5; - - message Expression { - string regex = 1; - repeated string prefix = 2; - } - } - message UUIDOption { - oneof content { - event_store.client.Empty structured = 1; - event_store.client.Empty string = 2; - } - } - message ControlOption { - uint32 compatibility = 1; - } - } -} - -message ReadResp { - oneof content { - ReadEvent event = 1; - SubscriptionConfirmation confirmation = 2; - Checkpoint checkpoint = 3; - StreamNotFound stream_not_found = 4; - uint64 first_stream_position = 5; - uint64 last_stream_position = 6; - AllStreamPosition last_all_stream_position = 7; - CaughtUp caught_up = 8; - FellBehind fell_behind = 9; - } - - message CaughtUp {} - - message FellBehind {} - - message ReadEvent { - RecordedEvent event = 1; - RecordedEvent link = 2; - oneof position { - uint64 commit_position = 3; - event_store.client.Empty no_position = 4; - } - - message RecordedEvent { - event_store.client.UUID id = 1; - event_store.client.StreamIdentifier stream_identifier = 2; - uint64 stream_revision = 3; - uint64 prepare_position = 4; - uint64 commit_position = 5; - map metadata = 6; - bytes custom_metadata = 7; - bytes data = 8; - } - } - message SubscriptionConfirmation { - string subscription_id = 1; - } - message Checkpoint { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } - message StreamNotFound { - event_store.client.StreamIdentifier stream_identifier = 1; - } -} - -message AppendReq { - oneof content { - Options options = 1; - ProposedMessage proposed_message = 2; - } - - message Options { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof expected_stream_revision { - uint64 revision = 2; - event_store.client.Empty no_stream = 3; - event_store.client.Empty any = 4; - event_store.client.Empty stream_exists = 5; - } - } - message ProposedMessage { - event_store.client.UUID id = 1; - map metadata = 2; - bytes custom_metadata = 3; - bytes data = 4; - } -} - -message AppendResp { - oneof result { - Success success = 1; - WrongExpectedVersion wrong_expected_version = 2; - } - - message Position { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } - - message Success { - oneof current_revision_option { - uint64 current_revision = 1; - event_store.client.Empty no_stream = 2; - } - oneof position_option { - Position position = 3; - event_store.client.Empty no_position = 4; - } - } - - message WrongExpectedVersion { - oneof current_revision_option_20_6_0 { - uint64 current_revision_20_6_0 = 1; - event_store.client.Empty no_stream_20_6_0 = 2; - } - oneof expected_revision_option_20_6_0 { - uint64 expected_revision_20_6_0 = 3; - event_store.client.Empty any_20_6_0 = 4; - event_store.client.Empty stream_exists_20_6_0 = 5; - } - oneof current_revision_option { - uint64 current_revision = 6; - event_store.client.Empty current_no_stream = 7; - } - oneof expected_revision_option { - uint64 expected_revision = 8; - event_store.client.Empty expected_any = 9; - event_store.client.Empty expected_stream_exists = 10; - event_store.client.Empty expected_no_stream = 11; - } - - } -} - -message BatchAppendReq { - event_store.client.UUID correlation_id = 1; - Options options = 2; - repeated ProposedMessage proposed_messages = 3; - bool is_final = 4; - - message Options { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof expected_stream_position { - uint64 stream_position = 2; - google.protobuf.Empty no_stream = 3; - google.protobuf.Empty any = 4; - google.protobuf.Empty stream_exists = 5; - } - oneof deadline_option { - google.protobuf.Timestamp deadline_21_10_0 = 6; - google.protobuf.Duration deadline = 7; - } - } - - message ProposedMessage { - event_store.client.UUID id = 1; - map metadata = 2; - bytes custom_metadata = 3; - bytes data = 4; - } -} - -message BatchAppendResp { - event_store.client.UUID correlation_id = 1; - oneof result { - google.rpc.Status error = 2; - Success success = 3; - } - - event_store.client.StreamIdentifier stream_identifier = 4; - - oneof expected_stream_position { - uint64 stream_position = 5; - google.protobuf.Empty no_stream = 6; - google.protobuf.Empty any = 7; - google.protobuf.Empty stream_exists = 8; - } - - message Success { - oneof current_revision_option { - uint64 current_revision = 1; - google.protobuf.Empty no_stream = 2; - } - oneof position_option { - event_store.client.AllStreamPosition position = 3; - google.protobuf.Empty no_position = 4; - } - } -} - -message DeleteReq { - Options options = 1; - - message Options { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof expected_stream_revision { - uint64 revision = 2; - event_store.client.Empty no_stream = 3; - event_store.client.Empty any = 4; - event_store.client.Empty stream_exists = 5; - } - } -} - -message DeleteResp { - oneof position_option { - Position position = 1; - event_store.client.Empty no_position = 2; - } - - message Position { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } -} - -message TombstoneReq { - Options options = 1; - - message Options { - event_store.client.StreamIdentifier stream_identifier = 1; - oneof expected_stream_revision { - uint64 revision = 2; - event_store.client.Empty no_stream = 3; - event_store.client.Empty any = 4; - event_store.client.Empty stream_exists = 5; - } - } -} - -message TombstoneResp { - oneof position_option { - Position position = 1; - event_store.client.Empty no_position = 2; - } - - message Position { - uint64 commit_position = 1; - uint64 prepare_position = 2; - } -} diff --git a/src/EventStore.Client/Core/protos/usermanagement.proto b/src/EventStore.Client/Core/protos/usermanagement.proto deleted file mode 100644 index 1fab5a73e..000000000 --- a/src/EventStore.Client/Core/protos/usermanagement.proto +++ /dev/null @@ -1,119 +0,0 @@ -syntax = "proto3"; -package event_store.client.users; -option java_package = "com.eventstore.client.users"; - -service Users { - rpc Create (CreateReq) returns (CreateResp); - rpc Update (UpdateReq) returns (UpdateResp); - rpc Delete (DeleteReq) returns (DeleteResp); - rpc Disable (DisableReq) returns (DisableResp); - rpc Enable (EnableReq) returns (EnableResp); - rpc Details (DetailsReq) returns (stream DetailsResp); - rpc ChangePassword (ChangePasswordReq) returns (ChangePasswordResp); - rpc ResetPassword (ResetPasswordReq) returns (ResetPasswordResp); -} - -message CreateReq { - Options options = 1; - message Options { - string login_name = 1; - string password = 2; - string full_name = 3; - repeated string groups = 4; - } -} - -message CreateResp { - -} - -message UpdateReq { - Options options = 1; - message Options { - string login_name = 1; - string password = 2; - string full_name = 3; - repeated string groups = 4; - } -} - -message UpdateResp { - -} - -message DeleteReq { - Options options = 1; - message Options { - string login_name = 1; - } -} - -message DeleteResp { - -} - -message EnableReq { - Options options = 1; - message Options { - string login_name = 1; - } -} - -message EnableResp { - -} - -message DisableReq { - Options options = 1; - message Options { - string login_name = 1; - } -} - -message DisableResp { -} - -message DetailsReq { - Options options = 1; - message Options { - string login_name = 1; - } -} - -message DetailsResp { - UserDetails user_details = 1; - message UserDetails { - string login_name = 1; - string full_name = 2; - repeated string groups = 3; - DateTime last_updated = 4; - bool disabled = 5; - - message DateTime { - int64 ticks_since_epoch = 1; - } - } -} - -message ChangePasswordReq { - Options options = 1; - message Options { - string login_name = 1; - string current_password = 2; - string new_password = 3; - } -} - -message ChangePasswordResp { -} - -message ResetPasswordReq { - Options options = 1; - message Options { - string login_name = 1; - string new_password = 2; - } -} - -message ResetPasswordResp { -} diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj deleted file mode 100644 index 6aa82bad0..000000000 --- a/src/EventStore.Client/EventStore.Client.csproj +++ /dev/null @@ -1,57 +0,0 @@ - - - - EventStore.Client - The base GRPC client library for Event Store. Get the open source or commercial versions of Event Store server from https://eventstore.com/ - EventStore.Client.Grpc - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/EventStore.Client/EventStore.Client.csproj.DotSettings b/src/EventStore.Client/EventStore.Client.csproj.DotSettings deleted file mode 100644 index ae68911e8..000000000 --- a/src/EventStore.Client/EventStore.Client.csproj.DotSettings +++ /dev/null @@ -1,5 +0,0 @@ - - True - True - True - True \ No newline at end of file diff --git a/src/EventStore.Client/Operations/DatabaseScavengeResult.cs b/src/EventStore.Client/Operations/DatabaseScavengeResult.cs deleted file mode 100644 index 9a09143fa..000000000 --- a/src/EventStore.Client/Operations/DatabaseScavengeResult.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure representing the result of a scavenge operation. - /// - public readonly struct DatabaseScavengeResult : IEquatable { - /// - /// The ID of the scavenge operation. - /// - public string ScavengeId { get; } - - /// - /// The of the scavenge operation. - /// - public ScavengeResult Result { get; } - - /// - /// A scavenge operation that has started. - /// - /// - /// - public static DatabaseScavengeResult Started(string scavengeId) => - new DatabaseScavengeResult(scavengeId, ScavengeResult.Started); - - /// - /// A scavenge operation that has stopped. - /// - /// - /// - public static DatabaseScavengeResult Stopped(string scavengeId) => - new DatabaseScavengeResult(scavengeId, ScavengeResult.Stopped); - - /// - /// A scavenge operation that is currently in progress. - /// - /// - /// - public static DatabaseScavengeResult InProgress(string scavengeId) => - new DatabaseScavengeResult(scavengeId, ScavengeResult.InProgress); - - /// - /// A scavenge operation whose state is unknown. - /// - /// - /// - public static DatabaseScavengeResult Unknown(string scavengeId) => - new DatabaseScavengeResult(scavengeId, ScavengeResult.Unknown); - - private DatabaseScavengeResult(string scavengeId, ScavengeResult result) { - ScavengeId = scavengeId; - Result = result; - } - - /// - public bool Equals(DatabaseScavengeResult other) => ScavengeId == other.ScavengeId && Result == other.Result; - - /// - public override bool Equals(object? obj) => obj is DatabaseScavengeResult other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(ScavengeId).Combine(Result); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(DatabaseScavengeResult left, DatabaseScavengeResult right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(DatabaseScavengeResult left, DatabaseScavengeResult right) => - !left.Equals(right); - } -} diff --git a/src/EventStore.Client/Operations/EventStoreOperationsClient.Admin.cs b/src/EventStore.Client/Operations/EventStoreOperationsClient.Admin.cs deleted file mode 100644 index bfa750145..000000000 --- a/src/EventStore.Client/Operations/EventStoreOperationsClient.Admin.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Operations; - -namespace EventStore.Client { - public partial class EventStoreOperationsClient { - private static readonly Empty EmptyResult = new Empty(); - - /// - /// Shuts down the EventStoreDB node. - /// - /// - /// - /// - /// - public async Task ShutdownAsync( - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).ShutdownAsync(EmptyResult, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Initiates an index merge operation. - /// - /// - /// - /// - /// - public async Task MergeIndexesAsync( - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).MergeIndexesAsync(EmptyResult, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Resigns a node. - /// - /// - /// - /// - /// - public async Task ResignNodeAsync( - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).ResignNodeAsync(EmptyResult, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Sets the node priority. - /// - /// - /// - /// - /// - /// - public async Task SetNodePriorityAsync(int nodePriority, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).SetNodePriorityAsync( - new SetNodePriorityReq {Priority = nodePriority}, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Restart persistent subscriptions - /// - /// - /// - /// - /// - public async Task RestartPersistentSubscriptions( - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).RestartPersistentSubscriptionsAsync( - EmptyResult, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/Operations/EventStoreOperationsClient.Scavenge.cs b/src/EventStore.Client/Operations/EventStoreOperationsClient.Scavenge.cs deleted file mode 100644 index 43dcfc50f..000000000 --- a/src/EventStore.Client/Operations/EventStoreOperationsClient.Scavenge.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Operations; - -namespace EventStore.Client { - public partial class EventStoreOperationsClient { - /// - /// Starts a scavenge operation. - /// - /// - /// - /// - /// - /// - /// - /// - public async Task StartScavengeAsync( - int threadCount = 1, - int startFromChunk = 0, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (threadCount <= 0) { - throw new ArgumentOutOfRangeException(nameof(threadCount)); - } - - if (startFromChunk < 0) { - throw new ArgumentOutOfRangeException(nameof(startFromChunk)); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).StartScavengeAsync( - new StartScavengeReq { - Options = new StartScavengeReq.Types.Options { - ThreadCount = threadCount, - StartFromChunk = startFromChunk - } - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - var result = await call.ResponseAsync.ConfigureAwait(false); - - return result.ScavengeResult switch { - ScavengeResp.Types.ScavengeResult.Started => DatabaseScavengeResult.Started(result.ScavengeId), - ScavengeResp.Types.ScavengeResult.Stopped => DatabaseScavengeResult.Stopped(result.ScavengeId), - ScavengeResp.Types.ScavengeResult.InProgress => DatabaseScavengeResult.InProgress(result.ScavengeId), - _ => DatabaseScavengeResult.Unknown(result.ScavengeId) - }; - } - - /// - /// Stops a scavenge operation. - /// - /// - /// - /// - /// - /// - public async Task StopScavengeAsync( - string scavengeId, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - var result = await new Operations.Operations.OperationsClient( - channelInfo.CallInvoker).StopScavengeAsync(new StopScavengeReq { - Options = new StopScavengeReq.Types.Options { - ScavengeId = scavengeId - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)) - .ResponseAsync.ConfigureAwait(false); - - return result.ScavengeResult switch { - ScavengeResp.Types.ScavengeResult.Started => DatabaseScavengeResult.Started(result.ScavengeId), - ScavengeResp.Types.ScavengeResult.Stopped => DatabaseScavengeResult.Stopped(result.ScavengeId), - ScavengeResp.Types.ScavengeResult.InProgress => DatabaseScavengeResult.InProgress(result.ScavengeId), - _ => DatabaseScavengeResult.Unknown(result.ScavengeId) - }; - } - } -} diff --git a/src/EventStore.Client/Operations/EventStoreOperationsClient.cs b/src/EventStore.Client/Operations/EventStoreOperationsClient.cs deleted file mode 100644 index 672383348..000000000 --- a/src/EventStore.Client/Operations/EventStoreOperationsClient.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; - -namespace EventStore.Client; - -/// -/// The client used to perform maintenance and other administrative tasks on the EventStoreDB. -/// -public sealed partial class EventStoreOperationsClient : EventStoreClientBase { - static readonly Dictionary> ExceptionMap = - new() { - [Constants.Exceptions.ScavengeNotFound] = ex => new ScavengeNotFoundException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.ScavengeId)?.Value - ) - }; - - readonly ILogger _log; - - /// - /// Constructs a new . This method is not intended to be called directly in your code. - /// - /// - public EventStoreOperationsClient(IOptions options) : this(options.Value) { } - - /// - /// Constructs a new . - /// - /// - public EventStoreOperationsClient(EventStoreClientSettings? settings = null) : base(settings, ExceptionMap) => - _log = Settings.LoggerFactory?.CreateLogger() ?? new NullLogger(); -} \ No newline at end of file diff --git a/src/EventStore.Client/Operations/EventStoreOperationsClientServiceCollectionExtensions.cs b/src/EventStore.Client/Operations/EventStoreOperationsClientServiceCollectionExtensions.cs deleted file mode 100644 index 9f664277f..000000000 --- a/src/EventStore.Client/Operations/EventStoreOperationsClientServiceCollectionExtensions.cs +++ /dev/null @@ -1,74 +0,0 @@ -// ReSharper disable CheckNamespace - -using System; -using System.Net.Http; -using EventStore.Client; -using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using EventStoreOperationsClient = EventStore.Client.EventStoreOperationsClient; - -namespace Microsoft.Extensions.DependencyInjection { - /// - /// A set of extension methods for which provide support for an . - /// - public static class EventStoreOperationsClientServiceCollectionExtensions { - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreOperationsClient(this IServiceCollection services, Uri address, - Func? createHttpMessageHandler = null) - => services.AddEventStoreOperationsClient(options => { - options.ConnectivitySettings.Address = address; - options.CreateHttpMessageHandler = createHttpMessageHandler; - }); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreOperationsClient(this IServiceCollection services, - Action? configureOptions = null) => - services.AddEventStoreOperationsClient(new EventStoreClientSettings(), configureOptions); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreOperationsClient(this IServiceCollection services, - string connectionString, Action? configureOptions = null) => - services.AddEventStoreOperationsClient(EventStoreClientSettings.Create(connectionString), configureOptions); - - private static IServiceCollection AddEventStoreOperationsClient(this IServiceCollection services, - EventStoreClientSettings options, Action? configureOptions) { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - configureOptions?.Invoke(options); - - services.TryAddSingleton(provider => { - options.LoggerFactory ??= provider.GetService(); - options.Interceptors ??= provider.GetServices(); - - return new EventStoreOperationsClient(options); - }); - - return services; - } - } -} -// ReSharper restore CheckNamespace diff --git a/src/EventStore.Client/Operations/ScavengeResult.cs b/src/EventStore.Client/Operations/ScavengeResult.cs deleted file mode 100644 index 6b8802d85..000000000 --- a/src/EventStore.Client/Operations/ScavengeResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace EventStore.Client { - /// - /// An enumeration that represents the result of a scavenge operation. - /// - public enum ScavengeResult { - /// - /// The scavenge operation has started. - /// - Started, - /// - /// The scavenge operation is in progress. - /// - InProgress, - - /// - /// The scavenge operation has stopped. - /// - Stopped, - - /// - /// The status of the scavenge operation was unknown. - /// - Unknown - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Create.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Create.cs deleted file mode 100644 index 4cb7acac0..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Create.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.PersistentSubscriptions; - -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - private static readonly IDictionary NamedConsumerStrategyToCreateProto - = new Dictionary { - [SystemConsumerStrategies.DispatchToSingle] = CreateReq.Types.ConsumerStrategy.DispatchToSingle, - [SystemConsumerStrategies.RoundRobin] = CreateReq.Types.ConsumerStrategy.RoundRobin, - [SystemConsumerStrategies.Pinned] = CreateReq.Types.ConsumerStrategy.Pinned, - }; - - private static CreateReq.Types.StreamOptions StreamOptionsForCreateProto(string streamName, StreamPosition position) { - if (position == StreamPosition.Start) { - return new CreateReq.Types.StreamOptions { - StreamIdentifier = streamName, - Start = new Empty() - }; - } - - if (position == StreamPosition.End) { - return new CreateReq.Types.StreamOptions { - StreamIdentifier = streamName, - End = new Empty() - }; - } - - return new CreateReq.Types.StreamOptions { - StreamIdentifier = streamName, - Revision = position.ToUInt64() - }; - } - - private static CreateReq.Types.AllOptions AllOptionsForCreateProto(Position position, IEventFilter? filter) { - var allFilter = GetFilterOptions(filter); - CreateReq.Types.AllOptions allOptions; - if (position == Position.Start) { - allOptions = new CreateReq.Types.AllOptions { - Start = new Empty(), - }; - } else if (position == Position.End) { - allOptions = new CreateReq.Types.AllOptions { - End = new Empty() - }; - } else { - allOptions = new CreateReq.Types.AllOptions { - Position = new CreateReq.Types.Position { - CommitPosition = position.CommitPosition, - PreparePosition = position.PreparePosition - } - }; - } - - if (allFilter is null) { - allOptions.NoFilter = new Empty(); - } else { - allOptions.Filter = allFilter; - } - - return allOptions; - } - - private static CreateReq.Types.AllOptions.Types.FilterOptions? GetFilterOptions(IEventFilter? filter) { - if (filter == null) { - return null; - } - - var options = filter switch { - StreamFilter _ => new CreateReq.Types.AllOptions.Types.FilterOptions { - StreamIdentifier = (filter.Prefixes, filter.Regex) switch { - (PrefixFilterExpression[] _, RegularFilterExpression _) - when (filter.Prefixes?.Length ?? 0) == 0 && - filter.Regex != RegularFilterExpression.None => - new CreateReq.Types.AllOptions.Types.FilterOptions.Types.Expression - {Regex = filter.Regex}, - (PrefixFilterExpression[] _, RegularFilterExpression _) - when (filter.Prefixes?.Length ?? 0) != 0 && - filter.Regex == RegularFilterExpression.None => - new CreateReq.Types.AllOptions.Types.FilterOptions.Types.Expression { - Prefix = {Array.ConvertAll(filter.Prefixes!, e => e.ToString())} - }, - _ => throw new InvalidOperationException() - } - }, - EventTypeFilter _ => new CreateReq.Types.AllOptions.Types.FilterOptions { - EventType = (filter.Prefixes, filter.Regex) switch { - (PrefixFilterExpression[] _, RegularFilterExpression _) - when (filter.Prefixes?.Length ?? 0) == 0 && - filter.Regex != RegularFilterExpression.None => - new CreateReq.Types.AllOptions.Types.FilterOptions.Types.Expression - {Regex = filter.Regex}, - (PrefixFilterExpression[] _, RegularFilterExpression _) - when (filter.Prefixes?.Length ?? 0) != 0 && - filter.Regex == RegularFilterExpression.None => - new CreateReq.Types.AllOptions.Types.FilterOptions.Types.Expression { - Prefix = {Array.ConvertAll(filter.Prefixes!, e => e.ToString())} - }, - _ => throw new InvalidOperationException() - } - }, - _ => throw new InvalidOperationException() - }; - - if (filter.MaxSearchWindow.HasValue) { - options.Max = filter.MaxSearchWindow.Value; - } else { - options.Count = new Empty(); - } - - return options; - } - - - /// - /// Creates a persistent subscription. - /// - /// - public async Task CreateToStreamAsync(string streamName, string groupName, PersistentSubscriptionSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - await CreateInternalAsync(streamName, groupName, null, settings, deadline, userCredentials, - cancellationToken) - .ConfigureAwait(false); - - /// - /// Creates a persistent subscription. - /// - /// - [Obsolete("CreateAsync is no longer supported. Use CreateToStreamAsync instead.", false)] - public async Task CreateAsync(string streamName, string groupName, PersistentSubscriptionSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - await CreateInternalAsync(streamName, groupName, null, settings, deadline, userCredentials, - cancellationToken) - .ConfigureAwait(false); - - /// - /// Creates a filtered persistent subscription to $all. - /// - public async Task CreateToAllAsync(string groupName, IEventFilter eventFilter, - PersistentSubscriptionSettings settings, TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - await CreateInternalAsync(SystemStreams.AllStream, groupName, eventFilter, settings, deadline, - userCredentials, cancellationToken) - .ConfigureAwait(false); - - /// - /// Creates a persistent subscription to $all. - /// - public async Task CreateToAllAsync(string groupName, PersistentSubscriptionSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - await CreateInternalAsync(SystemStreams.AllStream, groupName, null, settings, deadline, userCredentials, - cancellationToken) - .ConfigureAwait(false); - - private async Task CreateInternalAsync(string streamName, string groupName, IEventFilter? eventFilter, - PersistentSubscriptionSettings settings, TimeSpan? deadline, UserCredentials? userCredentials, - CancellationToken cancellationToken) { - if (streamName is null) { - throw new ArgumentNullException(nameof(streamName)); - } - - if (groupName is null) { - throw new ArgumentNullException(nameof(groupName)); - } - - if (settings is null) { - throw new ArgumentNullException(nameof(settings)); - } - - if (settings.ConsumerStrategyName is null) { - throw new ArgumentNullException(nameof(settings.ConsumerStrategyName)); - } - - if (streamName != SystemStreams.AllStream && settings.StartFrom != null && - settings.StartFrom is not StreamPosition) { - throw new ArgumentException( - $"{nameof(settings.StartFrom)} must be of type '{nameof(StreamPosition)}' when subscribing to a stream"); - } - - if (streamName == SystemStreams.AllStream && settings.StartFrom != null && - settings.StartFrom is not Position) { - throw new ArgumentException( - $"{nameof(settings.StartFrom)} must be of type '{nameof(Position)}' when subscribing to {SystemStreams.AllStream}"); - } - - if (eventFilter != null && streamName != SystemStreams.AllStream) { - throw new ArgumentException( - $"Filters are only supported when subscribing to {SystemStreams.AllStream}"); - } - - if (!NamedConsumerStrategyToCreateProto.ContainsKey(settings.ConsumerStrategyName)) { - throw new ArgumentException( - "The specified consumer strategy is not supported, specify one of the SystemConsumerStrategies"); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - - if (streamName == SystemStreams.AllStream && - !channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsToAll) { - throw new InvalidOperationException("The server does not support persistent subscriptions to $all."); - } - - using var call = new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient( - channelInfo.CallInvoker).CreateAsync(new CreateReq { - Options = new CreateReq.Types.Options { - Stream = streamName != SystemStreams.AllStream - ? StreamOptionsForCreateProto(streamName, - (StreamPosition)(settings.StartFrom ?? StreamPosition.End)) - : null, - All = streamName == SystemStreams.AllStream - ? AllOptionsForCreateProto((Position)(settings.StartFrom ?? Position.End), eventFilter) - : null, -#pragma warning disable 612 - StreamIdentifier = - streamName != SystemStreams.AllStream - ? streamName - : string.Empty, /*for backwards compatibility*/ -#pragma warning restore 612 - GroupName = groupName, - Settings = new CreateReq.Types.Settings { -#pragma warning disable 612 - Revision = streamName != SystemStreams.AllStream - ? ((StreamPosition)(settings.StartFrom ?? StreamPosition.End)).ToUInt64() - : default, /*for backwards compatibility*/ -#pragma warning restore 612 - CheckpointAfterMs = (int)settings.CheckPointAfter.TotalMilliseconds, - ExtraStatistics = settings.ExtraStatistics, - MessageTimeoutMs = (int)settings.MessageTimeout.TotalMilliseconds, - ResolveLinks = settings.ResolveLinkTos, - HistoryBufferSize = settings.HistoryBufferSize, - LiveBufferSize = settings.LiveBufferSize, - MaxCheckpointCount = settings.CheckPointUpperBound, - MaxRetryCount = settings.MaxRetryCount, - MaxSubscriberCount = settings.MaxSubscriberCount, - MinCheckpointCount = settings.CheckPointLowerBound, -#pragma warning disable 612 - /*for backwards compatibility*/ - NamedConsumerStrategy = NamedConsumerStrategyToCreateProto[settings.ConsumerStrategyName], -#pragma warning restore 612 - ReadBatchSize = settings.ReadBatchSize - } - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Delete.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Delete.cs deleted file mode 100644 index 40ef522ba..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Delete.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.PersistentSubscriptions; - -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - /// - /// Deletes a persistent subscription. - /// - [Obsolete("DeleteAsync is no longer supported. Use DeleteToStreamAsync instead.", false)] - public Task DeleteAsync(string streamName, string groupName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) => - DeleteToStreamAsync(streamName, groupName, deadline, userCredentials, cancellationToken); - - /// - /// Deletes a persistent subscription. - /// - public async Task DeleteToStreamAsync(string streamName, string groupName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - - if (streamName == SystemStreams.AllStream && - !channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsToAll) { - throw new NotSupportedException("The server does not support persistent subscriptions to $all."); - } - - var deleteOptions = new DeleteReq.Types.Options { - GroupName = groupName - }; - - if (streamName == SystemStreams.AllStream) { - deleteOptions.All = new Empty(); - } else { - deleteOptions.StreamIdentifier = streamName; - } - - using var call = - new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient( - channelInfo.CallInvoker) - .DeleteAsync(new DeleteReq {Options = deleteOptions}, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, - cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Deletes a persistent subscription to $all. - /// - public async Task DeleteToAllAsync(string groupName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) => - await DeleteToStreamAsync(SystemStreams.AllStream, groupName, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Info.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Info.cs deleted file mode 100644 index b1cc4bebf..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Info.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.PersistentSubscriptions; -using Grpc.Core; - -#nullable enable -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - /// - /// Gets the status of a persistent subscription to $all - /// - public async Task GetInfoToAllAsync(string groupName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsGetInfo) { - var req = new GetInfoReq() { - Options = new GetInfoReq.Types.Options{ - GroupName = groupName, - All = new Empty() - } - }; - - return await GetInfoGrpcAsync(req, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - } - - throw new NotSupportedException("The server does not support getting persistent subscription details for $all"); - } - - /// - /// Gets the status of a persistent subscription to a stream - /// - public async Task GetInfoToStreamAsync(string streamName, string groupName, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsGetInfo) { - var req = new GetInfoReq() { - Options = new GetInfoReq.Types.Options { - GroupName = groupName, - StreamIdentifier = streamName - } - }; - - return await GetInfoGrpcAsync(req, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - } - - return await GetInfoHttpAsync(streamName, groupName, channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - } - - private async Task GetInfoGrpcAsync(GetInfoReq req, TimeSpan? deadline, - UserCredentials? userCredentials, CallInvoker callInvoker, CancellationToken cancellationToken) { - - var result = await new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient(callInvoker) - .GetInfoAsync(req, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)) - .ConfigureAwait(false); - - return PersistentSubscriptionInfo.From(result.SubscriptionInfo); - } - - private async Task GetInfoHttpAsync(string streamName, string groupName, - ChannelInfo channelInfo, TimeSpan? deadline, UserCredentials? userCredentials, CancellationToken cancellationToken) { - - var path = $"/subscriptions/{UrlEncode(streamName)}/{UrlEncode(groupName)}/info"; - var result = await HttpGet(path, - onNotFound: () => throw new PersistentSubscriptionNotFoundException(streamName, groupName), - channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - - return PersistentSubscriptionInfo.From(result); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.List.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.List.cs deleted file mode 100644 index ce588e3f7..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.List.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.PersistentSubscriptions; -using Grpc.Core; - -#nullable enable -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - /// - /// Lists persistent subscriptions to $all. - /// - public async Task> ListToAllAsync(TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsList) { - var req = new ListReq() { - Options = new ListReq.Types.Options{ - ListForStream = new ListReq.Types.StreamOption() { - All = new Empty() - } - } - }; - - return await ListGrpcAsync(req, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - } - - throw new NotSupportedException("The server does not support listing the persistent subscriptions."); - } - - /// - /// Lists persistent subscriptions to the specified stream. - /// - public async Task> ListToStreamAsync(string streamName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsList) { - var req = new ListReq() { - Options = new ListReq.Types.Options { - ListForStream = new ListReq.Types.StreamOption() { - Stream = streamName - } - } - }; - - return await ListGrpcAsync(req, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - } - - return await ListHttpAsync(streamName, channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - } - - /// - /// Lists all persistent subscriptions. - /// - public async Task> ListAllAsync(TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsList) { - var req = new ListReq() { - Options = new ListReq.Types.Options { - ListAllSubscriptions = new Empty() - } - }; - - return await ListGrpcAsync(req, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - } - - try { - var result = await HttpGet>("/subscriptions", - onNotFound: () => throw new PersistentSubscriptionNotFoundException(string.Empty, string.Empty), - channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - - return result.Select(PersistentSubscriptionInfo.From); - } catch (AccessDeniedException ex) when (userCredentials != null) { // Required to get same gRPC behavior. - throw new NotAuthenticatedException(ex.Message, ex); - } - } - - private async Task> ListGrpcAsync(ListReq req, TimeSpan? deadline, - UserCredentials? userCredentials, CallInvoker callInvoker, CancellationToken cancellationToken) { - - using var call = new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient(callInvoker) - .ListAsync(req, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - ListResp? response = await call.ResponseAsync.ConfigureAwait(false); - - return response.Subscriptions.Select(PersistentSubscriptionInfo.From); - } - - private async Task> ListHttpAsync(string streamName, - ChannelInfo channelInfo, TimeSpan? deadline, UserCredentials? userCredentials, CancellationToken cancellationToken) { - - var path = $"/subscriptions/{UrlEncode(streamName)}"; - var result = await HttpGet>(path, - onNotFound: () => throw new PersistentSubscriptionNotFoundException(streamName, string.Empty), - channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - return result.Select(PersistentSubscriptionInfo.From); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Read.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Read.cs deleted file mode 100644 index 211ffdbeb..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Read.cs +++ /dev/null @@ -1,478 +0,0 @@ -using System.Threading.Channels; -using EventStore.Client.Diagnostics; -using EventStore.Client.PersistentSubscriptions; -using Grpc.Core; - -using static EventStore.Client.PersistentSubscriptions.PersistentSubscriptions; -using static EventStore.Client.PersistentSubscriptions.ReadResp.ContentOneofCase; - -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - /// - /// Subscribes to a persistent subscription. - /// - /// - /// - /// - [Obsolete("SubscribeAsync is no longer supported. Use SubscribeToStream with manual acks instead.", false)] - public async Task SubscribeAsync( - string streamName, string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, int bufferSize = 10, bool autoAck = true, - CancellationToken cancellationToken = default - ) { - if (autoAck) { - throw new InvalidOperationException( - $"AutoAck is no longer supported. Please use {nameof(SubscribeToStream)} with manual acks instead." - ); - } - - return await PersistentSubscription - .Confirm( - SubscribeToStream(streamName, groupName, bufferSize, userCredentials, cancellationToken), - eventAppeared, - subscriptionDropped ?? delegate { }, - _log, - userCredentials, - cancellationToken - ) - .ConfigureAwait(false); - } - - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged - /// - /// - /// - /// - public async Task SubscribeToStreamAsync( - string streamName, string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, int bufferSize = 10, - CancellationToken cancellationToken = default - ) { - return await PersistentSubscription - .Confirm( - SubscribeToStream(streamName, groupName, bufferSize, userCredentials, cancellationToken), - eventAppeared, - subscriptionDropped ?? delegate { }, - _log, - userCredentials, - cancellationToken - ) - .ConfigureAwait(false); - } - - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged. - /// - /// The name of the stream to read events from. - /// The name of the persistent subscription group. - /// The size of the buffer. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public PersistentSubscriptionResult SubscribeToStream( - string streamName, string groupName, int bufferSize = 10, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) { - if (streamName == null) { - throw new ArgumentNullException(nameof(streamName)); - } - - if (groupName == null) { - throw new ArgumentNullException(nameof(groupName)); - } - - if (streamName == string.Empty) { - throw new ArgumentException($"{nameof(streamName)} may not be empty.", nameof(streamName)); - } - - if (groupName == string.Empty) { - throw new ArgumentException($"{nameof(groupName)} may not be empty.", nameof(groupName)); - } - - if (bufferSize <= 0) { - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - } - - var readOptions = new ReadReq.Types.Options { - BufferSize = bufferSize, - GroupName = groupName, - UuidOption = new ReadReq.Types.Options.Types.UUIDOption { Structured = new Empty() } - }; - - if (streamName == SystemStreams.AllStream) { - readOptions.All = new Empty(); - } else { - readOptions.StreamIdentifier = streamName; - } - - return new PersistentSubscriptionResult( - streamName, - groupName, - async ct => { - var channelInfo = await GetChannelInfo(ct).ConfigureAwait(false); - - if (streamName == SystemStreams.AllStream && - !channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsToAll) { - throw new NotSupportedException( - "The server does not support persistent subscriptions to $all." - ); - } - - return channelInfo; - }, - new() { Options = readOptions }, - Settings, - userCredentials, - cancellationToken - ); - } - - /// - /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged - /// - public async Task SubscribeToAllAsync( - string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, int bufferSize = 10, - CancellationToken cancellationToken = default - ) => - await SubscribeToStreamAsync( - SystemStreams.AllStream, - groupName, - eventAppeared, - subscriptionDropped, - userCredentials, - bufferSize, - cancellationToken - ) - .ConfigureAwait(false); - - /// - /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged. - /// - /// The name of the persistent subscription group. - /// The size of the buffer. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public PersistentSubscriptionResult SubscribeToAll( - string groupName, int bufferSize = 10, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) => - SubscribeToStream(SystemStreams.AllStream, groupName, bufferSize, userCredentials, cancellationToken); - - /// - public class PersistentSubscriptionResult : IAsyncEnumerable, IAsyncDisposable, IDisposable { - const int MaxEventIdLength = 2000; - - readonly ReadReq _request; - readonly Channel _channel; - readonly CancellationTokenSource _cts; - readonly CallOptions _callOptions; - - AsyncDuplexStreamingCall? _call; - int _messagesEnumerated; - - /// - /// The server-generated unique identifier for the subscription. - /// - public string? SubscriptionId { get; private set; } - - /// - /// The name of the stream to read events from. - /// - public string StreamName { get; } - - /// - /// The name of the persistent subscription group. - /// - public string GroupName { get; } - - /// - /// An . Do not enumerate more than once. - /// - public IAsyncEnumerable Messages { - get { - if (Interlocked.Exchange(ref _messagesEnumerated, 1) == 1) - throw new InvalidOperationException("Messages may only be enumerated once."); - - return GetMessages(); - - async IAsyncEnumerable GetMessages() { - try { - await foreach (var message in _channel.Reader.ReadAllAsync(_cts.Token)) { - if (message is PersistentSubscriptionMessage.SubscriptionConfirmation(var subscriptionId)) - SubscriptionId = subscriptionId; - - yield return message; - } - } - finally { - _cts.Cancel(); - } - } - } - } - - internal PersistentSubscriptionResult( - string streamName, string groupName, - Func> selectChannelInfo, - ReadReq request, EventStoreClientSettings settings, UserCredentials? userCredentials, - CancellationToken cancellationToken - ) { - StreamName = streamName; - GroupName = groupName; - - _request = request; - - _callOptions = EventStoreCallOptions.CreateStreaming( - settings, - userCredentials: userCredentials, - cancellationToken: cancellationToken - ); - - _channel = Channel.CreateBounded(ReadBoundedChannelOptions); - - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - _ = PumpMessages(); - - return; - - async Task PumpMessages() { - try { - var channelInfo = await selectChannelInfo(_cts.Token).ConfigureAwait(false); - var client = new PersistentSubscriptionsClient(channelInfo.CallInvoker); - - _call = client.Read(_callOptions); - - await _call.RequestStream.WriteAsync(_request).ConfigureAwait(false); - - await foreach (var response in _call.ResponseStream.ReadAllAsync(_cts.Token).ConfigureAwait(false)) { - PersistentSubscriptionMessage subscriptionMessage = response.ContentCase switch { - SubscriptionConfirmation => new PersistentSubscriptionMessage.SubscriptionConfirmation( - response.SubscriptionConfirmation.SubscriptionId - ), - Event => new PersistentSubscriptionMessage.Event( - ConvertToResolvedEvent(response), - response.Event.CountCase switch { - ReadResp.Types.ReadEvent.CountOneofCase.RetryCount => response.Event.RetryCount, - _ => null - } - ), - _ => PersistentSubscriptionMessage.Unknown.Instance - }; - - if (subscriptionMessage is PersistentSubscriptionMessage.Event evnt) - EventStoreClientDiagnostics.ActivitySource.TraceSubscriptionEvent( - SubscriptionId, - evnt.ResolvedEvent, - channelInfo, - settings, - userCredentials - ); - - await _channel.Writer.WriteAsync(subscriptionMessage, _cts.Token).ConfigureAwait(false); - } - - _channel.Writer.TryComplete(); - } catch (Exception ex) { -#if NET48 - switch (ex) { - // The gRPC client for .NET 48 uses WinHttpHandler under the hood for sending HTTP requests. - // In certain scenarios, this can lead to exceptions of type WinHttpException being thrown. - // One such scenario is when the server abruptly closes the connection, which results in a WinHttpException with the error code 12030. - // Additionally, there are cases where the server response does not include the 'grpc-status' header. - // The absence of this header leads to an RpcException with the status code 'Cancelled' and the message "No grpc-status found on response". - // The switch statement below handles these specific exceptions and translates them into the appropriate - // PersistentSubscriptionDroppedByServerException exception. - case RpcException { StatusCode: StatusCode.Unavailable } rex1 when rex1.Status.Detail.Contains("WinHttpException: Error 12030"): - case RpcException { StatusCode: StatusCode.Cancelled } rex2 - when rex2.Status.Detail.Contains("No grpc-status found on response"): - ex = new PersistentSubscriptionDroppedByServerException(StreamName, GroupName, ex); - break; - } -#endif - if (ex is PersistentSubscriptionNotFoundException) { - await _channel.Writer - .WriteAsync(PersistentSubscriptionMessage.NotFound.Instance, cancellationToken) - .ConfigureAwait(false); - - _channel.Writer.TryComplete(); - return; - } - - _channel.Writer.TryComplete(ex); - } - } - } - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The of the s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(params Uuid[] eventIds) => AckInternal(eventIds); - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The of the s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(IEnumerable eventIds) => Ack(eventIds.ToArray()); - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(params ResolvedEvent[] resolvedEvents) => - Ack(Array.ConvertAll(resolvedEvents, resolvedEvent => resolvedEvent.OriginalEvent.EventId)); - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(IEnumerable resolvedEvents) => - Ack(resolvedEvents.Select(resolvedEvent => resolvedEvent.OriginalEvent.EventId)); - - /// - /// Acknowledge that a message has failed processing (this will tell the server it has not been processed). - /// - /// The to take. - /// A reason given. - /// The of the s to nak. There should not be more than 2000 to nak at a time. - /// The number of eventIds exceeded the limit of 2000. - public Task Nack(PersistentSubscriptionNakEventAction action, string reason, params Uuid[] eventIds) => - NackInternal(eventIds, action, reason); - - /// - /// Acknowledge that a message has failed processing (this will tell the server it has not been processed). - /// - /// The to take. - /// A reason given. - /// The s to nak. There should not be more than 2000 to nak at a time. - /// The number of resolvedEvents exceeded the limit of 2000. - public Task Nack(PersistentSubscriptionNakEventAction action, string reason, params ResolvedEvent[] resolvedEvents) => - Nack(action, reason, Array.ConvertAll(resolvedEvents, re => re.OriginalEvent.EventId)); - - static ResolvedEvent ConvertToResolvedEvent(ReadResp response) => new( - ConvertToEventRecord(response.Event.Event)!, - ConvertToEventRecord(response.Event.Link), - response.Event.PositionCase switch { - ReadResp.Types.ReadEvent.PositionOneofCase.CommitPosition => response.Event.CommitPosition, - _ => null - } - ); - - Task AckInternal(params Uuid[] eventIds) { - if (eventIds.Length > MaxEventIdLength) { - throw new ArgumentException( - $"The number of eventIds exceeds the maximum length of {MaxEventIdLength}.", - nameof(eventIds) - ); - } - - return _call is null - ? throw new InvalidOperationException() - : _call.RequestStream.WriteAsync( - new ReadReq { - Ack = new ReadReq.Types.Ack { - Ids = { - Array.ConvertAll(eventIds, id => id.ToDto()) - } - } - } - ); - } - - Task NackInternal(Uuid[] eventIds, PersistentSubscriptionNakEventAction action, string reason) { - if (eventIds.Length > MaxEventIdLength) { - throw new ArgumentException( - $"The number of eventIds exceeds the maximum length of {MaxEventIdLength}.", - nameof(eventIds) - ); - } - - return _call is null - ? throw new InvalidOperationException() - : _call.RequestStream.WriteAsync( - new ReadReq { - Nack = new ReadReq.Types.Nack { - Ids = { - Array.ConvertAll(eventIds, id => id.ToDto()) - }, - Action = action switch { - PersistentSubscriptionNakEventAction.Park => ReadReq.Types.Nack.Types.Action.Park, - PersistentSubscriptionNakEventAction.Retry => ReadReq.Types.Nack.Types.Action.Retry, - PersistentSubscriptionNakEventAction.Skip => ReadReq.Types.Nack.Types.Action.Skip, - PersistentSubscriptionNakEventAction.Stop => ReadReq.Types.Nack.Types.Action.Stop, - _ => ReadReq.Types.Nack.Types.Action.Unknown - }, - Reason = reason - } - } - ); - } - - static EventRecord? ConvertToEventRecord(ReadResp.Types.ReadEvent.Types.RecordedEvent? e) => - e is null - ? null - : new EventRecord( - e.StreamIdentifier!, - Uuid.FromDto(e.Id), - new StreamPosition(e.StreamRevision), - new Position(e.CommitPosition, e.PreparePosition), - e.Metadata, - e.Data.ToByteArray(), - e.CustomMetadata.ToByteArray() - ); - - /// - public async ValueTask DisposeAsync() { - await CastAndDispose(_cts).ConfigureAwait(false); - await CastAndDispose(_call).ConfigureAwait(false); - - return; - - static async Task CastAndDispose(IDisposable? resource) { - switch (resource) { - case null: - return; - - case IAsyncDisposable resourceAsyncDisposable: - await resourceAsyncDisposable.DisposeAsync().ConfigureAwait(false); - break; - - default: - resource.Dispose(); - break; - } - } - } - - /// - public void Dispose() { - _cts.Dispose(); - _call?.Dispose(); - } - - /// - public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - await foreach (var message in Messages.WithCancellation(cancellationToken)) { - if (message is not PersistentSubscriptionMessage.Event(var resolvedEvent, _)) - continue; - - yield return resolvedEvent; - } - } - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.ReplayParked.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.ReplayParked.cs deleted file mode 100644 index 248ed9143..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.ReplayParked.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.PersistentSubscriptions; -using Grpc.Core; -using NotSupportedException = System.NotSupportedException; - -#nullable enable -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - /// - /// Retry the parked messages of the persistent subscription - /// - public async Task ReplayParkedMessagesToAllAsync(string groupName, long? stopAt = null, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsReplayParked) { - var req = new ReplayParkedReq() { - Options = new ReplayParkedReq.Types.Options{ - GroupName = groupName, - All = new Empty() - }, - }; - - await ReplayParkedGrpcAsync(req, stopAt, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - - return; - } - - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsToAll) { - await ReplayParkedHttpAsync(SystemStreams.AllStream, groupName, stopAt, channelInfo, - deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - - return; - } - - throw new NotSupportedException("The server does not support persistent subscriptions to $all."); - } - - /// - /// Retry the parked messages of the persistent subscription - /// - public async Task ReplayParkedMessagesToStreamAsync(string streamName, string groupName, long? stopAt=null, - TimeSpan? deadline=null, UserCredentials? userCredentials=null, CancellationToken cancellationToken=default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsReplayParked) { - var req = new ReplayParkedReq() { - Options = new ReplayParkedReq.Types.Options { - GroupName = groupName, - StreamIdentifier = streamName - }, - }; - - await ReplayParkedGrpcAsync(req, stopAt, deadline, userCredentials, channelInfo.CallInvoker, cancellationToken) - .ConfigureAwait(false); - - return; - } - - await ReplayParkedHttpAsync(streamName, groupName, stopAt, channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - } - - private async Task ReplayParkedGrpcAsync(ReplayParkedReq req, long? numberOfEvents, TimeSpan? deadline, - UserCredentials? userCredentials, CallInvoker callInvoker, CancellationToken cancellationToken) { - - if (numberOfEvents.HasValue) { - req.Options.StopAt = numberOfEvents.Value; - } else { - req.Options.NoLimit = new Empty(); - } - - await new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient(callInvoker) - .ReplayParkedAsync(req, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)) - .ConfigureAwait(false); - } - - private async Task ReplayParkedHttpAsync(string streamName, string groupName, long? numberOfEvents, - ChannelInfo channelInfo, TimeSpan? deadline, UserCredentials? userCredentials, CancellationToken cancellationToken) { - - var path = $"/subscriptions/{UrlEncode(streamName)}/{UrlEncode(groupName)}/replayParked"; - var query = numberOfEvents.HasValue ? $"stopAt={numberOfEvents.Value}":""; - - await HttpPost(path, query, - onNotFound: () => throw new PersistentSubscriptionNotFoundException(streamName, groupName), - channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.RestartSubsystem.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.RestartSubsystem.cs deleted file mode 100644 index 4d01967d8..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.RestartSubsystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -#nullable enable -namespace EventStore.Client { - partial class EventStorePersistentSubscriptionsClient { - /// - /// Restarts the persistent subscriptions subsystem. - /// - public async Task RestartSubsystemAsync(TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - if (channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsRestartSubsystem) { - await new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient(channelInfo.CallInvoker) - .RestartSubsystemAsync(new Empty(), EventStoreCallOptions - .CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)) - .ConfigureAwait(false); - return; - } - - await HttpPost( - path: "/subscriptions/restart", - query: "", - onNotFound: () => - throw new Exception("Unexpected exception while restarting the persistent subscription subsystem."), - channelInfo, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Update.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Update.cs deleted file mode 100644 index 1eae92a25..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.Update.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.PersistentSubscriptions; - -namespace EventStore.Client { - public partial class EventStorePersistentSubscriptionsClient { - private static readonly IDictionary NamedConsumerStrategyToUpdateProto - = new Dictionary { - [SystemConsumerStrategies.DispatchToSingle] = UpdateReq.Types.ConsumerStrategy.DispatchToSingle, - [SystemConsumerStrategies.RoundRobin] = UpdateReq.Types.ConsumerStrategy.RoundRobin, - [SystemConsumerStrategies.Pinned] = UpdateReq.Types.ConsumerStrategy.Pinned, - }; - - private static UpdateReq.Types.StreamOptions StreamOptionsForUpdateProto(string streamName, - StreamPosition position) { - if (position == StreamPosition.Start) { - return new UpdateReq.Types.StreamOptions { - StreamIdentifier = streamName, - Start = new Empty() - }; - } - - if (position == StreamPosition.End) { - return new UpdateReq.Types.StreamOptions { - StreamIdentifier = streamName, - End = new Empty() - }; - } - - return new UpdateReq.Types.StreamOptions { - StreamIdentifier = streamName, - Revision = position.ToUInt64() - }; - } - - private static UpdateReq.Types.AllOptions AllOptionsForUpdateProto(Position position) { - if (position == Position.Start) { - return new UpdateReq.Types.AllOptions { - Start = new Empty() - }; - } - - if (position == Position.End) { - return new UpdateReq.Types.AllOptions { - End = new Empty() - }; - } - - return new UpdateReq.Types.AllOptions { - Position = new UpdateReq.Types.Position { - CommitPosition = position.CommitPosition, - PreparePosition = position.PreparePosition - } - }; - } - - - /// - /// Updates a persistent subscription. - /// - /// - [Obsolete("UpdateAsync is no longer supported. Use UpdateToStreamAsync instead.", false)] - public Task UpdateAsync(string streamName, string groupName, PersistentSubscriptionSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - UpdateToStreamAsync(streamName, groupName, settings, deadline, userCredentials, cancellationToken); - - /// - /// Updates a persistent subscription. - /// - /// - public async Task UpdateToStreamAsync(string streamName, string groupName, PersistentSubscriptionSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (streamName is null) { - throw new ArgumentNullException(nameof(streamName)); - } - - if (groupName is null) { - throw new ArgumentNullException(nameof(groupName)); - } - - if (settings is null) { - throw new ArgumentNullException(nameof(settings)); - } - - if (settings.ConsumerStrategyName is null) { - throw new ArgumentNullException(nameof(settings.ConsumerStrategyName)); - } - - if (streamName != SystemStreams.AllStream && settings.StartFrom is not null && - settings.StartFrom is not StreamPosition) { - throw new ArgumentException( - $"{nameof(settings.StartFrom)} must be of type '{nameof(StreamPosition)}' when subscribing to a stream"); - } - - if (streamName == SystemStreams.AllStream && settings.StartFrom is not null && - settings.StartFrom is not Position) { - throw new ArgumentException( - $"{nameof(settings.StartFrom)} must be of type '{nameof(Position)}' when subscribing to {SystemStreams.AllStream}"); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - - if (streamName == SystemStreams.AllStream && - !channelInfo.ServerCapabilities.SupportsPersistentSubscriptionsToAll) { - throw new InvalidOperationException("The server does not support persistent subscriptions to $all."); - } - - using var call = new PersistentSubscriptions.PersistentSubscriptions.PersistentSubscriptionsClient(channelInfo.CallInvoker) - .UpdateAsync(new UpdateReq { - Options = new UpdateReq.Types.Options { - GroupName = groupName, - Stream = streamName != SystemStreams.AllStream - ? StreamOptionsForUpdateProto(streamName, - (StreamPosition)(settings.StartFrom ?? StreamPosition.End)) - : null, - All = streamName == SystemStreams.AllStream - ? AllOptionsForUpdateProto((Position)(settings.StartFrom ?? Position.End)) - : null, -#pragma warning disable 612 - StreamIdentifier = - streamName != SystemStreams.AllStream - ? streamName - : string.Empty, /*for backwards compatibility*/ -#pragma warning restore 612 - Settings = new UpdateReq.Types.Settings { -#pragma warning disable 612 - Revision = streamName != SystemStreams.AllStream - ? ((StreamPosition)(settings.StartFrom ?? StreamPosition.End)).ToUInt64() - : default, /*for backwards compatibility*/ -#pragma warning restore 612 - CheckpointAfterMs = (int)settings.CheckPointAfter.TotalMilliseconds, - ExtraStatistics = settings.ExtraStatistics, - MessageTimeoutMs = (int)settings.MessageTimeout.TotalMilliseconds, - ResolveLinks = settings.ResolveLinkTos, - HistoryBufferSize = settings.HistoryBufferSize, - LiveBufferSize = settings.LiveBufferSize, - MaxCheckpointCount = settings.CheckPointUpperBound, - MaxRetryCount = settings.MaxRetryCount, - MaxSubscriberCount = settings.MaxSubscriberCount, - MinCheckpointCount = settings.CheckPointLowerBound, - NamedConsumerStrategy = - NamedConsumerStrategyToUpdateProto[settings.ConsumerStrategyName], - ReadBatchSize = settings.ReadBatchSize - } - } - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Updates a persistent subscription to $all. - /// - public async Task UpdateToAllAsync(string groupName, PersistentSubscriptionSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - await UpdateToStreamAsync(SystemStreams.AllStream, groupName, settings, deadline, userCredentials, - cancellationToken) - .ConfigureAwait(false); - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.cs deleted file mode 100644 index 411de3e70..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClient.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text.Encodings.Web; -using System.Threading.Channels; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace EventStore.Client { - /// - /// The client used to manage persistent subscriptions in the EventStoreDB. - /// - public sealed partial class EventStorePersistentSubscriptionsClient : EventStoreClientBase { - private static BoundedChannelOptions ReadBoundedChannelOptions = new (1) { - SingleReader = true, - SingleWriter = true, - AllowSynchronousContinuations = true - }; - - private readonly ILogger _log; - - /// - /// Constructs a new . - /// - public EventStorePersistentSubscriptionsClient(EventStoreClientSettings? settings) : base(settings, - new Dictionary> { - [Constants.Exceptions.PersistentSubscriptionDoesNotExist] = ex => new - PersistentSubscriptionNotFoundException( - ex.Trailers.First(x => x.Key == Constants.Exceptions.StreamName).Value, - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.GroupName)?.Value ?? "", ex), - [Constants.Exceptions.MaximumSubscribersReached] = ex => new - MaximumSubscribersReachedException( - ex.Trailers.First(x => x.Key == Constants.Exceptions.StreamName).Value, - ex.Trailers.First(x => x.Key == Constants.Exceptions.GroupName).Value, ex), - [Constants.Exceptions.PersistentSubscriptionDropped] = ex => new - PersistentSubscriptionDroppedByServerException( - ex.Trailers.First(x => x.Key == Constants.Exceptions.StreamName).Value, - ex.Trailers.First(x => x.Key == Constants.Exceptions.GroupName).Value, ex) - }) { - _log = Settings.LoggerFactory?.CreateLogger() - ?? new NullLogger(); - } - - private static string UrlEncode(string s) { - return UrlEncoder.Default.Encode(s); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClientCollectionExtensions.cs b/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClientCollectionExtensions.cs deleted file mode 100644 index f8d491dda..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/EventStorePersistentSubscriptionsClientCollectionExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -// ReSharper disable CheckNamespace - -using System; -using System.Net.Http; -using EventStore.Client; -using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; - -namespace Microsoft.Extensions.DependencyInjection { - /// - /// A set of extension methods for which provide support for an . - /// - public static class EventStorePersistentSubscriptionsClientCollectionExtensions { - /// - /// Adds an to the . - /// - /// - public static IServiceCollection AddEventStorePersistentSubscriptionsClient(this IServiceCollection services, - Uri address, Func? createHttpMessageHandler = null) - => services.AddEventStorePersistentSubscriptionsClient(options => { - options.ConnectivitySettings.Address = address; - options.CreateHttpMessageHandler = createHttpMessageHandler; - }); - - /// - /// Adds an to the . - /// - /// - public static IServiceCollection AddEventStorePersistentSubscriptionsClient(this IServiceCollection services, - Action? configureSettings = null) => - services.AddEventStorePersistentSubscriptionsClient(new EventStoreClientSettings(), - configureSettings); - - /// - /// Adds an to the . - /// - /// - public static IServiceCollection AddEventStorePersistentSubscriptionsClient(this IServiceCollection services, - string connectionString, Action? configureSettings = null) => - services.AddEventStorePersistentSubscriptionsClient(EventStoreClientSettings.Create(connectionString), - configureSettings); - - private static IServiceCollection AddEventStorePersistentSubscriptionsClient(this IServiceCollection services, - EventStoreClientSettings settings, Action? configureSettings) { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - configureSettings?.Invoke(settings); - services.TryAddSingleton(provider => { - settings.LoggerFactory ??= provider.GetService(); - settings.Interceptors ??= provider.GetServices(); - - return new EventStorePersistentSubscriptionsClient(settings); - }); - return services; - } - } -} -// ReSharper restore CheckNamespace diff --git a/src/EventStore.Client/PersistentSubscriptions/MaximumSubscribersReachedException.cs b/src/EventStore.Client/PersistentSubscriptions/MaximumSubscribersReachedException.cs deleted file mode 100644 index 879378f85..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/MaximumSubscribersReachedException.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when the maximum number of subscribers on a persistent subscription is exceeded. - /// - public class MaximumSubscribersReachedException : Exception { - /// - /// The stream name. - /// - public readonly string StreamName; - /// - /// The group name. - /// - public readonly string GroupName; - - /// - /// Constructs a new . - /// - /// - public MaximumSubscribersReachedException(string streamName, string groupName, Exception? exception = null) - : base($"Maximum subscriptions reached for subscription group '{groupName}' on stream '{streamName}.'", - exception) { - if (streamName == null) throw new ArgumentNullException(nameof(streamName)); - if (groupName == null) throw new ArgumentNullException(nameof(groupName)); - StreamName = streamName; - GroupName = groupName; - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscription.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscription.cs deleted file mode 100644 index f3d19b42e..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscription.cs +++ /dev/null @@ -1,205 +0,0 @@ -using EventStore.Client.PersistentSubscriptions; -using Grpc.Core; -using Microsoft.Extensions.Logging; - -namespace EventStore.Client { - /// - /// Represents a persistent subscription connection. - /// - public class PersistentSubscription : IDisposable { - private readonly EventStorePersistentSubscriptionsClient.PersistentSubscriptionResult _persistentSubscriptionResult; - private readonly IAsyncEnumerator _enumerator; - private readonly Func _eventAppeared; - private readonly Action _subscriptionDropped; - private readonly ILogger _log; - private readonly CancellationTokenSource _cts; - - private int _subscriptionDroppedInvoked; - - /// - /// The Subscription Id. - /// - public string SubscriptionId { get; } - - internal static async Task Confirm( - EventStorePersistentSubscriptionsClient.PersistentSubscriptionResult persistentSubscriptionResult, - Func eventAppeared, - Action subscriptionDropped, - ILogger log, UserCredentials? userCredentials, CancellationToken cancellationToken = default) { - var enumerator = persistentSubscriptionResult - .Messages - .GetAsyncEnumerator(cancellationToken); - - var result = await enumerator.MoveNextAsync(cancellationToken).ConfigureAwait(false); - - return (result, enumerator.Current) switch { - (true, PersistentSubscriptionMessage.SubscriptionConfirmation (var subscriptionId)) => - new PersistentSubscription(persistentSubscriptionResult, enumerator, subscriptionId, eventAppeared, - subscriptionDropped, log, cancellationToken), - (true, PersistentSubscriptionMessage.NotFound) => - throw new PersistentSubscriptionNotFoundException(persistentSubscriptionResult.StreamName, - persistentSubscriptionResult.GroupName), - _ => throw new InvalidOperationException("Subscription could not be confirmed.") - }; - } - - // PersistentSubscription takes responsibility for disposing the call and the disposable - private PersistentSubscription( - EventStorePersistentSubscriptionsClient.PersistentSubscriptionResult persistentSubscriptionResult, - IAsyncEnumerator enumerator, string subscriptionId, - Func eventAppeared, - Action subscriptionDropped, ILogger log, - CancellationToken cancellationToken) { - _persistentSubscriptionResult = persistentSubscriptionResult; - _enumerator = enumerator; - SubscriptionId = subscriptionId; - _eventAppeared = eventAppeared; - _subscriptionDropped = subscriptionDropped; - _log = log; - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - Task.Run(Subscribe, _cts.Token); - } - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The of the s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(params Uuid[] eventIds) => AckInternal(eventIds); - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The of the s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(IEnumerable eventIds) => Ack(eventIds.ToArray()); - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(params ResolvedEvent[] resolvedEvents) => - Ack(Array.ConvertAll(resolvedEvents, resolvedEvent => resolvedEvent.OriginalEvent.EventId)); - - /// - /// Acknowledge that a message has completed processing (this will tell the server it has been processed). - /// - /// There is no need to ack a message if you have Auto Ack enabled. - /// The s to acknowledge. There should not be more than 2000 to ack at a time. - public Task Ack(IEnumerable resolvedEvents) => - Ack(resolvedEvents.Select(resolvedEvent => resolvedEvent.OriginalEvent.EventId)); - - - /// - /// Acknowledge that a message has failed processing (this will tell the server it has not been processed). - /// - /// The to take. - /// A reason given. - /// The of the s to nak. There should not be more than 2000 to nak at a time. - /// The number of eventIds exceeded the limit of 2000. - public Task Nack(PersistentSubscriptionNakEventAction action, string reason, params Uuid[] eventIds) => NackInternal(eventIds, action, reason); - - /// - /// Acknowledge that a message has failed processing (this will tell the server it has not been processed). - /// - /// The to take. - /// A reason given. - /// The s to nak. There should not be more than 2000 to nak at a time. - /// The number of resolvedEvents exceeded the limit of 2000. - public Task Nack(PersistentSubscriptionNakEventAction action, string reason, - params ResolvedEvent[] resolvedEvents) => - Nack(action, reason, - Array.ConvertAll(resolvedEvents, resolvedEvent => resolvedEvent.OriginalEvent.EventId)); - - /// - public void Dispose() => SubscriptionDropped(SubscriptionDroppedReason.Disposed); - - private async Task Subscribe() { - _log.LogDebug("Persistent Subscription {subscriptionId} confirmed.", SubscriptionId); - - try { - while (await _enumerator.MoveNextAsync(_cts.Token).ConfigureAwait(false)) { - if (_enumerator.Current is not PersistentSubscriptionMessage.Event(var resolvedEvent, var retryCount)) { - continue; - } - - if (_enumerator.Current is PersistentSubscriptionMessage.NotFound) { - if (_subscriptionDroppedInvoked != 0) { - return; - } - SubscriptionDropped(SubscriptionDroppedReason.ServerError, - new PersistentSubscriptionNotFoundException( - _persistentSubscriptionResult.StreamName, _persistentSubscriptionResult.GroupName)); - return; - } - - _log.LogTrace( - "Persistent Subscription {subscriptionId} received event {streamName}@{streamRevision} {position}", - SubscriptionId, resolvedEvent.OriginalEvent.EventStreamId, - resolvedEvent.OriginalEvent.EventNumber, resolvedEvent.OriginalEvent.Position); - - try { - await _eventAppeared( - this, - resolvedEvent, - retryCount, - _cts.Token).ConfigureAwait(false); - } catch (Exception ex) when (ex is ObjectDisposedException or OperationCanceledException) { - if (_subscriptionDroppedInvoked != 0) { - return; - } - - _log.LogWarning(ex, - "Persistent Subscription {subscriptionId} was dropped because cancellation was requested by another caller.", - SubscriptionId); - - SubscriptionDropped(SubscriptionDroppedReason.Disposed); - - return; - } catch (Exception ex) { - _log.LogError(ex, - "Persistent Subscription {subscriptionId} was dropped because the subscriber made an error.", - SubscriptionId); - SubscriptionDropped(SubscriptionDroppedReason.SubscriberError, ex); - - return; - } - } - } catch (Exception ex) { - if (_subscriptionDroppedInvoked == 0) { - _log.LogError(ex, - "Persistent Subscription {subscriptionId} was dropped because an error occurred on the server.", - SubscriptionId); - SubscriptionDropped(SubscriptionDroppedReason.ServerError, ex); - } - } finally { - if (_subscriptionDroppedInvoked == 0) { - _log.LogError( - "Persistent Subscription {subscriptionId} was unexpectedly terminated.", - SubscriptionId); - SubscriptionDropped(SubscriptionDroppedReason.ServerError); - } - } - } - - private void SubscriptionDropped(SubscriptionDroppedReason reason, Exception? ex = null) { - if (Interlocked.CompareExchange(ref _subscriptionDroppedInvoked, 1, 0) == 1) { - return; - } - - try { - _subscriptionDropped.Invoke(this, reason, ex); - } finally { - _persistentSubscriptionResult.Dispose(); - _cts.Dispose(); - } - } - - private Task AckInternal(params Uuid[] ids) => _persistentSubscriptionResult.Ack(ids); - - private Task NackInternal(Uuid[] ids, PersistentSubscriptionNakEventAction action, string reason) => - _persistentSubscriptionResult.Nack(action, reason, ids); - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionDroppedByServerException.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionDroppedByServerException.cs deleted file mode 100644 index 29b42bb49..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionDroppedByServerException.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when the EventStoreDB drops a persistent subscription. - /// - public class PersistentSubscriptionDroppedByServerException : Exception { - /// - /// The stream name. - /// - public readonly string StreamName; - - /// - /// The group name. - /// - public readonly string GroupName; - - /// - /// Constructs a new . - /// - /// - public PersistentSubscriptionDroppedByServerException(string streamName, string groupName, - Exception? exception = null) - : base($"Subscription group '{groupName}' on stream '{streamName}' was dropped.", exception) { - if (streamName == null) throw new ArgumentNullException(nameof(streamName)); - if (groupName == null) throw new ArgumentNullException(nameof(groupName)); - StreamName = streamName; - GroupName = groupName; - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionExtraStatistic.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionExtraStatistic.cs deleted file mode 100644 index 5b41893c4..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionExtraStatistic.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace EventStore.Client; - -/// -/// Provides the definitions of the available extra statistics. -/// -public static class PersistentSubscriptionExtraStatistic { -#pragma warning disable CS1591 - public const string Highest = "Highest"; - public const string Mean = "Mean"; - public const string Median = "Median"; - public const string Fastest = "Fastest"; - public const string Quintile1 = "Quintile 1"; - public const string Quintile2 = "Quintile 2"; - public const string Quintile3 = "Quintile 3"; - public const string Quintile4 = "Quintile 4"; - public const string Quintile5 = "Quintile 5"; - public const string NinetyPercent = "90%"; - public const string NinetyFivePercent = "95%"; - public const string NinetyNinePercent = "99%"; - public const string NinetyNinePointFivePercent = "99.5%"; - public const string NinetyNinePointNinePercent = "99.9%"; -#pragma warning restore CS1591 -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionInfo.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionInfo.cs deleted file mode 100644 index 40af4cdf2..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionInfo.cs +++ /dev/null @@ -1,224 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EventStore.Client.PersistentSubscriptions; -using Google.Protobuf.Collections; - -namespace EventStore.Client { - /// - /// Provides the details for a persistent subscription. - /// - /// The source of events for the subscription. - /// The group name given on creation. - /// The current status of the subscription. - /// Active connections to the subscription. - /// The settings used to create the persistant subscription. - /// Live statistics for the persistent subscription. - public record PersistentSubscriptionInfo(string EventSource, string GroupName, string Status, - IEnumerable Connections, - PersistentSubscriptionSettings? Settings, PersistentSubscriptionStats Stats) { - - internal static PersistentSubscriptionInfo From(SubscriptionInfo info) { - IPosition? startFrom = null; - IPosition? lastCheckpointedEventPosition = null; - IPosition? lastKnownEventPosition = null; - if (info.EventSource == SystemStreams.AllStream) { - if (Position.TryParse(info.StartFrom, out var position)) { - startFrom = position; - } - if (Position.TryParse(info.LastCheckpointedEventPosition, out position)) { - lastCheckpointedEventPosition = position; - } - if (Position.TryParse(info.LastKnownEventPosition, out position)) { - lastKnownEventPosition = position; - } - } else { - if (long.TryParse(info.StartFrom, out var streamPosition)) { - startFrom = StreamPosition.FromInt64(streamPosition); - } - if (ulong.TryParse(info.LastCheckpointedEventPosition, out var position)) { - lastCheckpointedEventPosition = new StreamPosition(position); - } - if (ulong.TryParse(info.LastKnownEventPosition, out position)) { - lastKnownEventPosition = new StreamPosition(position); - } - } - - return new PersistentSubscriptionInfo( - EventSource: info.EventSource, - GroupName: info.GroupName, - Status: info.Status, - Connections: From(info.Connections), - Settings: new PersistentSubscriptionSettings( - resolveLinkTos: info.ResolveLinkTos, - startFrom: startFrom, - extraStatistics: info.ExtraStatistics, - messageTimeout: TimeSpan.FromMilliseconds(info.MessageTimeoutMilliseconds), - maxRetryCount: info.MaxRetryCount, - liveBufferSize: info.LiveBufferSize, - readBatchSize: info.ReadBatchSize, - historyBufferSize: info.BufferSize, - checkPointAfter: TimeSpan.FromMilliseconds(info.CheckPointAfterMilliseconds), - checkPointLowerBound: info.MinCheckPointCount, - checkPointUpperBound: info.MaxCheckPointCount, - maxSubscriberCount: info.MaxSubscriberCount, - consumerStrategyName: info.NamedConsumerStrategy - ), - Stats: new PersistentSubscriptionStats( - AveragePerSecond: info.AveragePerSecond, - TotalItems: info.TotalItems, - CountSinceLastMeasurement: info.CountSinceLastMeasurement, - ReadBufferCount: info.ReadBufferCount, - LiveBufferCount: info.LiveBufferCount, - RetryBufferCount: info.RetryBufferCount, - TotalInFlightMessages: info.TotalInFlightMessages, - OutstandingMessagesCount: info.OutstandingMessagesCount, - ParkedMessageCount: info.ParkedMessageCount, - LastCheckpointedEventPosition: lastCheckpointedEventPosition, - LastKnownEventPosition: lastKnownEventPosition - ) - ); - } - - internal static PersistentSubscriptionInfo From(PersistentSubscriptionDto info) { - PersistentSubscriptionSettings? settings = null; - if (info.Config != null) { - settings = new PersistentSubscriptionSettings( - resolveLinkTos: info.Config.ResolveLinktos, - // we only need to support StreamPosition as $all was never implemented in http api. - startFrom: new StreamPosition(info.Config.StartFrom), - extraStatistics: info.Config.ExtraStatistics, - messageTimeout: TimeSpan.FromMilliseconds(info.Config.MessageTimeoutMilliseconds), - maxRetryCount: info.Config.MaxRetryCount, - liveBufferSize: info.Config.LiveBufferSize, - readBatchSize: info.Config.ReadBatchSize, - historyBufferSize: info.Config.BufferSize, - checkPointAfter: TimeSpan.FromMilliseconds(info.Config.CheckPointAfterMilliseconds), - checkPointLowerBound: info.Config.MinCheckPointCount, - checkPointUpperBound: info.Config.MaxCheckPointCount, - maxSubscriberCount: info.Config.MaxSubscriberCount, - consumerStrategyName: info.Config.NamedConsumerStrategy - ); - } - - return new PersistentSubscriptionInfo( - EventSource: info.EventStreamId, - GroupName: info.GroupName, - Status: info.Status, - Connections: PersistentSubscriptionConnectionInfo.CreateFrom(info.Connections), - Settings: settings, - Stats: new PersistentSubscriptionStats( - AveragePerSecond: (int)info.AverageItemsPerSecond, - TotalItems: info.TotalItemsProcessed, - CountSinceLastMeasurement: info.CountSinceLastMeasurement, - ReadBufferCount: info.ReadBufferCount, - LiveBufferCount: info.LiveBufferCount, - RetryBufferCount: info.RetryBufferCount, - TotalInFlightMessages: info.TotalInFlightMessages, - OutstandingMessagesCount: info.OutstandingMessagesCount, - ParkedMessageCount: info.ParkedMessageCount, - LastCheckpointedEventPosition: StreamPosition.FromInt64(info.LastProcessedEventNumber), - LastKnownEventPosition: StreamPosition.FromInt64(info.LastKnownEventNumber) - ) - ); - } - - private static IEnumerable From( - RepeatedField connections) { - foreach (var conn in connections) { - yield return new PersistentSubscriptionConnectionInfo( - From: conn.From, - Username: conn.Username, - AverageItemsPerSecond: conn.AverageItemsPerSecond, - TotalItems: conn.TotalItems, - CountSinceLastMeasurement: conn.CountSinceLastMeasurement, - AvailableSlots: conn.AvailableSlots, - InFlightMessages: conn.InFlightMessages, - ConnectionName: conn.ConnectionName, - ExtraStatistics: From(conn.ObservedMeasurements) - ); - } - } - - private static IDictionary From(IEnumerable measurements) => - measurements.ToDictionary(k => k.Key, v => v.Value); - } - - /// - /// Provides the statistics of a persistent subscription. - /// - /// Average number of events per second. - /// Total number of events processed by subscription. - /// Number of events seen since last measurement on this connection (used as the basis for ). - /// Number of events in the read buffer. - /// Number of events in the live buffer. - /// Number of events in the retry buffer. - /// Current in flight messages across all connections. - /// Current number of outstanding messages. - /// The current number of parked messages. - /// The of the last checkpoint. This will be null if there are no checkpoints. - /// The of the last known event. This will be undefined if no events have been received yet. - public record PersistentSubscriptionStats( - int AveragePerSecond, long TotalItems, long CountSinceLastMeasurement, int ReadBufferCount, - long LiveBufferCount, int RetryBufferCount, int TotalInFlightMessages, int OutstandingMessagesCount, - long ParkedMessageCount, IPosition? LastCheckpointedEventPosition, IPosition? LastKnownEventPosition); - - /// - /// Provides the details of a persistent subscription connection. - /// - /// Origin of this connection. - /// Connection username. - /// The name of the connection. - /// Average events per second on this connection. - /// Total items on this connection. - /// Number of items seen since last measurement on this connection (used as the basis for averageItemsPerSecond). - /// Number of available slots. - /// Number of in flight messages on this connection. - /// Timing measurements for the connection. Can be enabled with the ExtraStatistics setting. - public record PersistentSubscriptionConnectionInfo(string From, string Username, string ConnectionName, int AverageItemsPerSecond, - long TotalItems, long CountSinceLastMeasurement, int AvailableSlots, int InFlightMessages, - IDictionary ExtraStatistics) { - - internal static IEnumerable CreateFrom( - IEnumerable connections) { - - foreach (var connection in connections) { - yield return CreateFrom(connection); - } - } - - private static PersistentSubscriptionConnectionInfo CreateFrom(PersistentSubscriptionConnectionInfoDto connection) { - return new PersistentSubscriptionConnectionInfo( - From: connection.From, - Username: connection.Username, - ConnectionName: connection.ConnectionName, - AverageItemsPerSecond: (int)connection.AverageItemsPerSecond, - TotalItems: connection.TotalItems, - CountSinceLastMeasurement: connection.CountSinceLastMeasurement, - AvailableSlots: connection.AvailableSlots, - InFlightMessages: connection.InFlightMessages, - ExtraStatistics: CreateFrom(connection.ExtraStatistics) - ); - } - - private static IDictionary CreateFrom(IEnumerable extraStatistics) => - extraStatistics.ToDictionary(k => k.Key, v => v.Value); - } - - internal record PersistentSubscriptionDto(string EventStreamId, string GroupName, - string Status, float AverageItemsPerSecond, long TotalItemsProcessed, long CountSinceLastMeasurement, - long LastProcessedEventNumber, long LastKnownEventNumber, int ReadBufferCount, long LiveBufferCount, - int RetryBufferCount, int TotalInFlightMessages, int OutstandingMessagesCount, int ParkedMessageCount, - PersistentSubscriptionConfig? Config, IEnumerable Connections); - - internal record PersistentSubscriptionConfig(bool ResolveLinktos, ulong StartFrom, string StartPosition, - int MessageTimeoutMilliseconds, bool ExtraStatistics, int MaxRetryCount, int LiveBufferSize, int BufferSize, - int ReadBatchSize, int CheckPointAfterMilliseconds, int MinCheckPointCount, int MaxCheckPointCount, - int MaxSubscriberCount, string NamedConsumerStrategy); - - internal record PersistentSubscriptionConnectionInfoDto(string From, string Username, float AverageItemsPerSecond, - long TotalItems, long CountSinceLastMeasurement, int AvailableSlots, int InFlightMessages, string ConnectionName, - IEnumerable ExtraStatistics); - - internal record PersistentSubscriptionMeasurementInfoDto(string Key, long Value); -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionMessage.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionMessage.cs deleted file mode 100644 index eabde9b62..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionMessage.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace EventStore.Client { - /// - /// The base record of all stream messages. - /// - public abstract record PersistentSubscriptionMessage { - /// - /// A that represents a . - /// - /// The . - /// The number of times the has been retried. - public record Event(ResolvedEvent ResolvedEvent, int? RetryCount) : PersistentSubscriptionMessage; - - /// - /// A representing a stream that was not found. - /// - public record NotFound : PersistentSubscriptionMessage { - internal static readonly NotFound Instance = new(); - } - - /// - /// A indicating that the subscription is ready to send additional messages. - /// - /// The unique identifier of the subscription. - public record SubscriptionConfirmation(string SubscriptionId) : PersistentSubscriptionMessage; - - /// - /// A that could not be identified, usually indicating a lower client compatibility level than the server supports. - /// - public record Unknown : PersistentSubscriptionMessage { - internal static readonly Unknown Instance = new(); - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNakEventAction.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNakEventAction.cs deleted file mode 100644 index ce24b6552..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNakEventAction.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace EventStore.Client { - /// - /// Actions to be taken by server in the case of a client NAK - /// - public enum PersistentSubscriptionNakEventAction { - /// - /// Client unknown on action. Let server decide - /// - Unknown = 0, - - /// - /// Park message do not resend. Put on poison queue - /// - Park = 1, - - /// - /// Explicitly retry the message. - /// - Retry = 2, - - /// - /// Skip this message do not resend do not put in poison queue - /// - Skip = 3, - - /// - /// Stop the subscription. - /// - Stop = 4 - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNotFoundException.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNotFoundException.cs deleted file mode 100644 index 81e023ade..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionNotFoundException.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// The exception that is thrown when a persistent subscription is not found. - /// - public class PersistentSubscriptionNotFoundException : Exception { - /// - /// The stream name. - /// - public readonly string StreamName; - /// - /// The group name. - /// - public readonly string GroupName; - - /// - /// Constructs a new . - /// - /// - public PersistentSubscriptionNotFoundException(string streamName, string groupName, Exception? exception = null) - : base($"Subscription group '{groupName}' on stream '{streamName}' does not exist.", exception) { - if (streamName == null) throw new ArgumentNullException(nameof(streamName)); - if (groupName == null) throw new ArgumentNullException(nameof(groupName)); - StreamName = streamName; - GroupName = groupName; - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionSettings.cs b/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionSettings.cs deleted file mode 100644 index a36bfff87..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/PersistentSubscriptionSettings.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A class representing the settings of a persistent subscription. - /// - public sealed class PersistentSubscriptionSettings { - /// - /// Whether the should resolve linkTo events to their linked events. - /// - public readonly bool ResolveLinkTos; - - /// - /// Which event position in the stream or transaction file the subscription should start from. - /// - public readonly IPosition? StartFrom; - - /// - /// Whether to track latency statistics on this subscription. - /// - public readonly bool ExtraStatistics; - - /// - /// The amount of time after which to consider a message as timed out and retried. - /// - public readonly TimeSpan MessageTimeout; - - /// - /// The maximum number of retries (due to timeout) before a message is considered to be parked. - /// - public readonly int MaxRetryCount; - - /// - /// The size of the buffer (in-memory) listening to live messages as they happen before paging occurs. - /// - public readonly int LiveBufferSize; - - /// - /// The number of events read at a time when paging through history. - /// - public readonly int ReadBatchSize; - - /// - /// The number of events to cache when paging through history. - /// - public readonly int HistoryBufferSize; - - /// - /// The amount of time to try to checkpoint after. - /// - public readonly TimeSpan CheckPointAfter; - - /// - /// The minimum number of messages to process before a checkpoint may be written. - /// - public readonly int CheckPointLowerBound; - - /// - /// The maximum number of messages not checkpointed before forcing a checkpoint. - /// - public readonly int CheckPointUpperBound; - - /// - /// The maximum number of subscribers allowed. - /// - public readonly int MaxSubscriberCount; - - /// - /// The strategy to use for distributing events to client consumers. See for system supported strategies. - /// - public readonly string ConsumerStrategyName; - - /// - /// Constructs a new . - /// - /// - public PersistentSubscriptionSettings(bool resolveLinkTos = false, IPosition? startFrom = null, - bool extraStatistics = false, TimeSpan? messageTimeout = null, int maxRetryCount = 10, - int liveBufferSize = 500, int readBatchSize = 20, int historyBufferSize = 500, - TimeSpan? checkPointAfter = null, int checkPointLowerBound = 10, int checkPointUpperBound = 1000, - int maxSubscriberCount = 0, string consumerStrategyName = SystemConsumerStrategies.RoundRobin) { - messageTimeout ??= TimeSpan.FromSeconds(30); - checkPointAfter ??= TimeSpan.FromSeconds(2); - - if (messageTimeout.Value < TimeSpan.Zero || messageTimeout.Value.TotalMilliseconds > int.MaxValue) { - throw new ArgumentOutOfRangeException( - nameof(messageTimeout), - $"{nameof(messageTimeout)} must be greater than {TimeSpan.Zero} and less than or equal to {TimeSpan.FromMilliseconds(int.MaxValue)}"); - } - - if (checkPointAfter.Value < TimeSpan.Zero || checkPointAfter.Value.TotalMilliseconds > int.MaxValue) { - throw new ArgumentOutOfRangeException( - nameof(checkPointAfter), - $"{nameof(checkPointAfter)} must be greater than {TimeSpan.Zero} and less than or equal to {TimeSpan.FromMilliseconds(int.MaxValue)}"); - } - - ResolveLinkTos = resolveLinkTos; - StartFrom = startFrom; - ExtraStatistics = extraStatistics; - MessageTimeout = messageTimeout.Value; - MaxRetryCount = maxRetryCount; - LiveBufferSize = liveBufferSize; - ReadBatchSize = readBatchSize; - HistoryBufferSize = historyBufferSize; - CheckPointAfter = checkPointAfter.Value; - CheckPointLowerBound = checkPointLowerBound; - CheckPointUpperBound = checkPointUpperBound; - MaxSubscriberCount = maxSubscriberCount; - ConsumerStrategyName = consumerStrategyName; - } - } -} diff --git a/src/EventStore.Client/PersistentSubscriptions/SystemConsumerStrategies.cs b/src/EventStore.Client/PersistentSubscriptions/SystemConsumerStrategies.cs deleted file mode 100644 index 6183461ca..000000000 --- a/src/EventStore.Client/PersistentSubscriptions/SystemConsumerStrategies.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace EventStore.Client { - /// - /// System supported consumer strategies for use with persistent subscriptions. - /// - public static class SystemConsumerStrategies { - /// - /// Distributes events to a single client until it is full. Then round robin to the next client. - /// - public const string DispatchToSingle = nameof(DispatchToSingle); - - /// - /// Distribute events to each client in a round robin fashion. - /// - public const string RoundRobin = nameof(RoundRobin); - - /// - /// Distribute events of the same streamId to the same client until it disconnects on a best efforts basis. - /// Designed to be used with indexes such as the category projection. - /// - public const string Pinned = nameof(Pinned); - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Control.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Control.cs deleted file mode 100644 index e21da92fc..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Control.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Projections; - -namespace EventStore.Client { - public partial class EventStoreProjectionManagementClient { - /// - /// Enables a projection. - /// - /// - /// - /// - /// - /// - public async Task EnableAsync(string name, TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).EnableAsync(new EnableReq { - Options = new EnableReq.Types.Options { - Name = name - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Resets a projection. This will re-emit events. Streams that are written to from the projection will also be soft deleted. - /// - /// - /// - /// - /// - /// - public async Task ResetAsync(string name, TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).ResetAsync(new ResetReq { - Options = new ResetReq.Types.Options { - Name = name, - WriteCheckpoint = true - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Aborts a projection. Does not save the projection's checkpoint. - /// - /// - /// - /// - /// - /// - public Task AbortAsync(string name, TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - DisableInternalAsync(name, false, deadline, userCredentials, cancellationToken); - - /// - /// Disables a projection. Saves the projection's checkpoint. - /// - /// - /// - /// - /// - /// - public Task DisableAsync(string name, TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - DisableInternalAsync(name, true, deadline, userCredentials, cancellationToken); - - /// - /// Restarts the projection subsystem. - /// - /// - /// - /// - /// - public async Task RestartSubsystemAsync(TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).RestartSubsystemAsync(new Empty(), - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - private async Task DisableInternalAsync(string name, bool writeCheckpoint, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).DisableAsync(new DisableReq { - Options = new DisableReq.Types.Options { - Name = name, - WriteCheckpoint = writeCheckpoint - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Create.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Create.cs deleted file mode 100644 index 54498cabf..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Create.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Projections; - -namespace EventStore.Client { - public partial class EventStoreProjectionManagementClient { - /// - /// Creates a one-time projection. - /// - /// - /// - /// - /// - /// - public async Task CreateOneTimeAsync(string query, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).CreateAsync(new CreateReq { - Options = new CreateReq.Types.Options { - OneTime = new Empty(), - Query = query - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Creates a continuous projection. - /// - /// - /// - /// - /// - /// - /// - /// - public async Task CreateContinuousAsync(string name, string query, bool trackEmittedStreams = false, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).CreateAsync(new CreateReq { - Options = new CreateReq.Types.Options { - Continuous = new CreateReq.Types.Options.Types.Continuous { - Name = name, - TrackEmittedStreams = trackEmittedStreams - }, - Query = query - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Creates a transient projection. - /// - /// - /// - /// - /// - /// - /// - public async Task CreateTransientAsync(string name, string query, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).CreateAsync(new CreateReq { - Options = new CreateReq.Types.Options { - Transient = new CreateReq.Types.Options.Types.Transient { - Name = name - }, - Query = query - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.State.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.State.cs deleted file mode 100644 index 64187fe1f..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.State.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.IO; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Projections; -using Google.Protobuf.WellKnownTypes; -using Type = System.Type; - -namespace EventStore.Client { - public partial class EventStoreProjectionManagementClient { - static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new JsonSerializerOptions(); - - /// - /// Gets the result of a projection as an untyped document. - /// - /// - /// - /// - /// - /// - /// - public async Task GetResultAsync(string name, string? partition = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - -#if NET - await using var stream = new MemoryStream(); -#else - using var stream = new MemoryStream(); -#endif - await using var writer = new Utf8JsonWriter(stream); - var serializer = new ValueSerializer(); - serializer.Write(writer, value, DefaultJsonSerializerOptions); - await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - stream.Position = 0; - - return JsonDocument.Parse(stream); - } - - /// - /// Gets the result of a projection. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task GetResultAsync(string name, string? partition = null, - JsonSerializerOptions? serializerOptions = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var value = await GetResultInternalAsync(name, partition, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); -#if NET - await using var stream = new MemoryStream(); -#else - using var stream = new MemoryStream(); -#endif - await using var writer = new Utf8JsonWriter(stream); - var serializer = new ValueSerializer(); - serializer.Write(writer, value, DefaultJsonSerializerOptions); - await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - stream.Position = 0; - - return JsonSerializer.Deserialize(stream.ToArray(), serializerOptions)!; - } - - private async ValueTask GetResultInternalAsync(string name, string? partition, - TimeSpan? deadline, UserCredentials? userCredentials, CancellationToken cancellationToken) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).ResultAsync(new ResultReq { - Options = new ResultReq.Types.Options { - Name = name, - Partition = partition ?? string.Empty - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - var response = await call.ResponseAsync.ConfigureAwait(false); - return response.Result; - } - - /// - /// Gets the state of a projection as an untyped document. - /// - /// - /// - /// - /// - /// - /// - public async Task GetStateAsync(string name, string? partition = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - -#if NET - await using var stream = new MemoryStream(); -#else - using var stream = new MemoryStream(); -#endif - await using var writer = new Utf8JsonWriter(stream); - var serializer = new ValueSerializer(); - serializer.Write(writer, value, DefaultJsonSerializerOptions); - stream.Position = 0; - await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - - return JsonDocument.Parse(stream); - } - - /// - /// Gets the state of a projection. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task GetStateAsync(string name, string? partition = null, - JsonSerializerOptions? serializerOptions = null, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - var value = await GetStateInternalAsync(name, partition, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - -#if NET - await using var stream = new MemoryStream(); -#else - using var stream = new MemoryStream(); -#endif - await using var writer = new Utf8JsonWriter(stream); - var serializer = new ValueSerializer(); - serializer.Write(writer, value, DefaultJsonSerializerOptions); - await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - stream.Position = 0; - - return JsonSerializer.Deserialize(stream.ToArray(), serializerOptions)!; - } - - private async ValueTask GetStateInternalAsync(string name, string? partition, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).StateAsync(new StateReq { - Options = new StateReq.Types.Options { - Name = name, - Partition = partition ?? string.Empty - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - var response = await call.ResponseAsync.ConfigureAwait(false); - return response.State; - } - - private class ValueSerializer : System.Text.Json.Serialization.JsonConverter { - public override Value Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - throw new NotSupportedException(); - - public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOptions options) { - switch (value.KindCase) { - case Value.KindOneofCase.None: - break; - case Value.KindOneofCase.BoolValue: - writer.WriteBooleanValue(value.BoolValue); - break; - case Value.KindOneofCase.NullValue: - writer.WriteNullValue(); - break; - case Value.KindOneofCase.NumberValue: - writer.WriteNumberValue(value.NumberValue); - break; - case Value.KindOneofCase.StringValue: - writer.WriteStringValue(value.StringValue); - break; - case Value.KindOneofCase.ListValue: - writer.WriteStartArray(); - foreach (var item in value.ListValue.Values) { - Write(writer, item, options); - } - - writer.WriteEndArray(); - break; - case Value.KindOneofCase.StructValue: - writer.WriteStartObject(); - foreach (var map in value.StructValue.Fields) { - writer.WritePropertyName(map.Key); - Write(writer, map.Value, options); - } - - writer.WriteEndObject(); - break; - } - } - } - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Statistics.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Statistics.cs deleted file mode 100644 index 0b49b8b80..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Statistics.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Projections; -using Grpc.Core; - -namespace EventStore.Client { - public partial class EventStoreProjectionManagementClient { - /// - /// List the of all one-time projections. - /// - /// - /// - /// - /// - public IAsyncEnumerable ListOneTimeAsync(TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) => - ListInternalAsync(new StatisticsReq.Types.Options { - OneTime = new Empty() - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken), - cancellationToken); - - /// - /// List the of all continuous projections. - /// - /// - /// - /// - /// - public IAsyncEnumerable ListContinuousAsync(TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - ListInternalAsync(new StatisticsReq.Types.Options { - Continuous = new Empty() - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken), - cancellationToken); - - /// - /// Gets the status of a projection. - /// - /// - /// - /// - /// - /// - public Task GetStatusAsync(string name, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => ListInternalAsync(new StatisticsReq.Types.Options { - Name = name - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken), - cancellationToken) - .FirstOrDefaultAsync(cancellationToken).AsTask(); - - /// - /// List the of all projections. - /// - /// - /// - /// - /// - public IAsyncEnumerable ListAllAsync(TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) => - ListInternalAsync(new StatisticsReq.Types.Options { - All = new Empty() - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken), - cancellationToken); - - private async IAsyncEnumerable ListInternalAsync(StatisticsReq.Types.Options options, - CallOptions callOptions, - [EnumeratorCancellation] CancellationToken cancellationToken) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).Statistics(new StatisticsReq { - Options = options - }, callOptions); - - await foreach (var projectionDetails in call.ResponseStream - .ReadAllAsync(cancellationToken) - .Select(ConvertToProjectionDetails) - .WithCancellation(cancellationToken) - .ConfigureAwait(false)) { - yield return projectionDetails; - } - } - - private static ProjectionDetails ConvertToProjectionDetails(StatisticsResp response) { - var details = response.Details; - - return new ProjectionDetails(details.CoreProcessingTime, details.Version, details.Epoch, - details.EffectiveName, details.WritesInProgress, details.ReadsInProgress, details.PartitionsCached, - details.Status, details.StateReason, details.Name, details.Mode, details.Position, details.Progress, - details.LastCheckpoint, details.EventsProcessedAfterRestart, details.CheckpointStatus, - details.BufferedEvents, details.WritePendingEventsBeforeCheckpoint, - details.WritePendingEventsAfterCheckpoint); - } - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Update.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Update.cs deleted file mode 100644 index 8f577e92c..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.Update.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Projections; - -namespace EventStore.Client { - public partial class EventStoreProjectionManagementClient { - /// - /// Updates a projection. - /// - /// - /// - /// - /// - /// - /// - /// - public async Task UpdateAsync(string name, string query, bool? emitEnabled = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var options = new UpdateReq.Types.Options { - Name = name, - Query = query - }; - if (emitEnabled.HasValue) { - options.EmitEnabled = emitEnabled.Value; - } else { - options.NoEmitOptions = new Empty(); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Projections.Projections.ProjectionsClient( - channelInfo.CallInvoker).UpdateAsync(new UpdateReq { - Options = options - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - await call.ResponseAsync.ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.cs deleted file mode 100644 index be0679928..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClient.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; - -namespace EventStore.Client { - /// - ///The client used to manage projections on the EventStoreDB. - /// - public sealed partial class EventStoreProjectionManagementClient : EventStoreClientBase { - private readonly ILogger _log; - - /// - /// Constructs a new . This method is not intended to be called directly from your code. - /// - /// - public EventStoreProjectionManagementClient(IOptions options) : this(options.Value) { - } - - /// - /// Constructs a new . - /// - /// - public EventStoreProjectionManagementClient(EventStoreClientSettings? settings) : base(settings, - new Dictionary>()) { - _log = settings?.LoggerFactory?.CreateLogger() ?? - new NullLogger(); - } - } -} diff --git a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClientCollectionExtensions.cs b/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClientCollectionExtensions.cs deleted file mode 100644 index ea1f634f6..000000000 --- a/src/EventStore.Client/ProjectionManagement/EventStoreProjectionManagementClientCollectionExtensions.cs +++ /dev/null @@ -1,74 +0,0 @@ -// ReSharper disable CheckNamespace - -using System; -using System.Net.Http; -using EventStore.Client; -using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; - -namespace Microsoft.Extensions.DependencyInjection { - /// - /// A set of extension methods for which provide support for an . - /// - public static class EventStoreProjectionManagementClientCollectionExtensions { - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreProjectionManagementClient(this IServiceCollection services, - Uri address, - Func? createHttpMessageHandler = null) - => services.AddEventStoreProjectionManagementClient(options => { - options.ConnectivitySettings.Address = address; - options.CreateHttpMessageHandler = createHttpMessageHandler; - }); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreProjectionManagementClient(this IServiceCollection services, - Action? configureSettings = null) => - services.AddEventStoreProjectionManagementClient(new EventStoreClientSettings(), configureSettings); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreProjectionManagementClient(this IServiceCollection services, - string connectionString, Action? configureSettings = null) => - services.AddEventStoreProjectionManagementClient(EventStoreClientSettings.Create(connectionString), - configureSettings); - - private static IServiceCollection AddEventStoreProjectionManagementClient(this IServiceCollection services, - EventStoreClientSettings settings, Action? configureSettings) { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - configureSettings?.Invoke(settings); - - services.TryAddSingleton(provider => { - settings.LoggerFactory ??= provider.GetService(); - settings.Interceptors ??= provider.GetServices(); - - return new EventStoreProjectionManagementClient(settings); - }); - - return services; - } - } -} -// ReSharper restore CheckNamespace diff --git a/src/EventStore.Client/ProjectionManagement/ProjectionDetails.cs b/src/EventStore.Client/ProjectionManagement/ProjectionDetails.cs deleted file mode 100644 index c604362b1..000000000 --- a/src/EventStore.Client/ProjectionManagement/ProjectionDetails.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace EventStore.Client { - /// - /// Provides the details for a projection. - /// - public sealed class ProjectionDetails { - /// - /// The CoreProcessingTime - /// - public readonly long CoreProcessingTime; - - /// - /// The projection version - /// - public readonly long Version; - - /// - /// The Epoch - /// - public readonly long Epoch; - - /// - /// The projection EffectiveName - /// - public readonly string EffectiveName; - - /// - /// The projection WritesInProgress - /// - public readonly int WritesInProgress; - - /// - /// The projection ReadsInProgress - /// - public readonly int ReadsInProgress; - - /// - /// The projection PartitionsCached - /// - public readonly int PartitionsCached; - - /// - /// The projection Status - /// - public readonly string Status; - - /// - /// The projection StateReason - /// - public readonly string StateReason; - - /// - /// The projection Name - /// - public readonly string Name; - - /// - /// The projection Mode - /// - public readonly string Mode; - - /// - /// The projection Position - /// - public readonly string Position; - - /// - /// The projection Progress - /// - public readonly float Progress; - - /// - /// LastCheckpoint - /// - public readonly string LastCheckpoint; - - /// - /// The projection EventsProcessedAfterRestart - /// - public readonly long EventsProcessedAfterRestart; - - /// - /// The projection CheckpointStatus - /// - public readonly string CheckpointStatus; - - /// - /// The projection BufferedEvents - /// - public readonly long BufferedEvents; - - /// - /// The projection WritePendingEventsBeforeCheckpoint - /// - public readonly int WritePendingEventsBeforeCheckpoint; - - /// - /// The projection WritePendingEventsAfterCheckpoint - /// - public readonly int WritePendingEventsAfterCheckpoint; - - /// - /// create a new class. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ProjectionDetails( - long coreProcessingTime, - long version, - long epoch, - string effectiveName, - int writesInProgress, - int readsInProgress, - int partitionsCached, - string status, - string stateReason, - string name, - string mode, - string position, - float progress, - string lastCheckpoint, - long eventsProcessedAfterRestart, - string checkpointStatus, - long bufferedEvents, - int writePendingEventsBeforeCheckpoint, - int writePendingEventsAfterCheckpoint) { - CoreProcessingTime = coreProcessingTime; - Version = version; - Epoch = epoch; - EffectiveName = effectiveName; - WritesInProgress = writesInProgress; - ReadsInProgress = readsInProgress; - PartitionsCached = partitionsCached; - Status = status; - StateReason = stateReason; - Name = name; - Mode = mode; - Position = position; - Progress = progress; - LastCheckpoint = lastCheckpoint; - EventsProcessedAfterRestart = eventsProcessedAfterRestart; - CheckpointStatus = checkpointStatus; - BufferedEvents = bufferedEvents; - WritePendingEventsBeforeCheckpoint = writePendingEventsBeforeCheckpoint; - WritePendingEventsAfterCheckpoint = writePendingEventsAfterCheckpoint; - } - } -} diff --git a/src/EventStore.Client/Streams/ConditionalWriteResult.cs b/src/EventStore.Client/Streams/ConditionalWriteResult.cs deleted file mode 100644 index f51ef577b..000000000 --- a/src/EventStore.Client/Streams/ConditionalWriteResult.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure that represents the result of a conditional write. - /// - public readonly struct ConditionalWriteResult : IEquatable { - /// - /// Indicates that the stream the operation is targeting was deleted. - /// - public static readonly ConditionalWriteResult StreamDeleted = - new ConditionalWriteResult(StreamRevision.None, Position.End, ConditionalWriteStatus.StreamDeleted); - - /// - /// The correct expected version to use when writing to the stream next. - /// - public long NextExpectedVersion { get; } - - /// - /// The of the write in the transaction file. - /// - public Position LogPosition { get; } - - /// - /// The . - /// - public ConditionalWriteStatus Status { get; } - - /// - /// The correct to use when writing to the stream next. - /// - public StreamRevision NextExpectedStreamRevision { get; } - - private ConditionalWriteResult(StreamRevision nextExpectedStreamRevision, Position logPosition, - ConditionalWriteStatus status = ConditionalWriteStatus.Succeeded) { - NextExpectedStreamRevision = nextExpectedStreamRevision; - NextExpectedVersion = nextExpectedStreamRevision.ToInt64(); - LogPosition = logPosition; - Status = status; - } - - internal static ConditionalWriteResult FromWriteResult(IWriteResult writeResult) - => writeResult switch { - WrongExpectedVersionResult wrongExpectedVersion => - new ConditionalWriteResult(wrongExpectedVersion.NextExpectedStreamRevision, Position.End, - ConditionalWriteStatus.VersionMismatch), - _ => new ConditionalWriteResult(writeResult.NextExpectedStreamRevision, writeResult.LogPosition) - }; - - internal static ConditionalWriteResult FromWrongExpectedVersion(WrongExpectedVersionException ex) - => new ConditionalWriteResult(ex.ExpectedStreamRevision, Position.End, - ConditionalWriteStatus.VersionMismatch); - - /// - public bool Equals(ConditionalWriteResult other) => - NextExpectedStreamRevision == other.NextExpectedStreamRevision && - LogPosition.Equals(other.LogPosition) && - Status == other.Status; - - /// - public override bool Equals(object? obj) => obj is ConditionalWriteResult other && Equals(other); - - /// - public override int GetHashCode() => - HashCode.Hash.Combine(NextExpectedVersion).Combine(LogPosition).Combine(Status); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(ConditionalWriteResult left, ConditionalWriteResult right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(ConditionalWriteResult left, ConditionalWriteResult right) => - !left.Equals(right); - - /// - public override string ToString() => $"{Status}:{NextExpectedVersion}:{LogPosition}"; - } -} diff --git a/src/EventStore.Client/Streams/ConditionalWriteStatus.cs b/src/EventStore.Client/Streams/ConditionalWriteStatus.cs deleted file mode 100644 index 93bf82fe7..000000000 --- a/src/EventStore.Client/Streams/ConditionalWriteStatus.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace EventStore.Client { - /// - /// The reason why a conditional write fails - /// - public enum ConditionalWriteStatus { - /// - /// The write operation succeeded - /// - Succeeded = 0, - - /// - /// The expected version does not match actual stream version - /// - VersionMismatch = 1, - - /// - /// The stream has been deleted - /// - StreamDeleted = 2 - } -} diff --git a/src/EventStore.Client/Streams/DeadLine.cs b/src/EventStore.Client/Streams/DeadLine.cs deleted file mode 100644 index b4ba29b49..000000000 --- a/src/EventStore.Client/Streams/DeadLine.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace EventStore.Client { -#pragma warning disable CS1591 - public static class DeadLine { -#pragma warning restore CS1591 - /// - /// Represents no deadline (i.e., wait infinitely) - /// - public static TimeSpan? None = null; - } -} diff --git a/src/EventStore.Client/Streams/DeleteResult.cs b/src/EventStore.Client/Streams/DeleteResult.cs deleted file mode 100644 index ea5195b1d..000000000 --- a/src/EventStore.Client/Streams/DeleteResult.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// A structure that represents the result of a delete operation. - /// - public readonly struct DeleteResult : IEquatable { - /// - public bool Equals(DeleteResult other) => LogPosition.Equals(other.LogPosition); - - /// - public override bool Equals(object? obj) => obj is DeleteResult other && Equals(other); - - /// - public override int GetHashCode() => LogPosition.GetHashCode(); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(DeleteResult left, DeleteResult right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(DeleteResult left, DeleteResult right) => !left.Equals(right); - - /// - /// The of the delete in the transaction file. - /// - public readonly Position LogPosition; - - /// - /// Constructs a new . - /// - /// - public DeleteResult(Position logPosition) { - LogPosition = logPosition; - } - } -} diff --git a/src/EventStore.Client/Streams/Direction.cs b/src/EventStore.Client/Streams/Direction.cs deleted file mode 100644 index 40e9489da..000000000 --- a/src/EventStore.Client/Streams/Direction.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace EventStore.Client { - /// - /// An enumeration that indicates the direction of the read operation. - /// - public enum Direction { - /// - /// Read backwards. - /// - Backwards, - - /// - /// Read forwards. - /// - Forwards - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClient.Append.cs b/src/EventStore.Client/Streams/EventStoreClient.Append.cs deleted file mode 100644 index 9b244b3a4..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.Append.cs +++ /dev/null @@ -1,430 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Threading.Channels; -using Google.Protobuf; -using EventStore.Client.Streams; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using EventStore.Client.Diagnostics; -using EventStore.Diagnostics; -using EventStore.Diagnostics.Telemetry; -using EventStore.Diagnostics.Tracing; -using static EventStore.Client.Streams.AppendResp.Types.WrongExpectedVersion; -using static EventStore.Client.Streams.Streams; - -namespace EventStore.Client { - public partial class EventStoreClient { - /// - /// Appends events asynchronously to a stream. - /// - /// The name of the stream to append events to. - /// The expected of the stream to append to. - /// An to append to the stream. - /// An to configure the operation's options. - /// - /// The for the operation. - /// The optional . - /// - public async Task AppendToStreamAsync( - string streamName, - StreamRevision expectedRevision, - IEnumerable eventData, - Action? configureOperationOptions = null, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - var options = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(options); - - _log.LogDebug("Append to stream - {streamName}@{expectedRevision}.", streamName, expectedRevision); - - var task = userCredentials is null && await BatchAppender.IsUsable().ConfigureAwait(false) - ? BatchAppender.Append(streamName, expectedRevision, eventData, deadline, cancellationToken) - : AppendToStreamInternal( - await GetChannelInfo(cancellationToken).ConfigureAwait(false), - new AppendReq { - Options = new() { - StreamIdentifier = streamName, - Revision = expectedRevision - } - }, - eventData, - options, - deadline, - userCredentials, - cancellationToken - ); - - return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(options); - } - - /// - /// Appends events asynchronously to a stream. - /// - /// The name of the stream to append events to. - /// The expected of the stream to append to. - /// An to append to the stream. - /// An to configure the operation's options. - /// - /// The for the operation. - /// The optional . - /// - public async Task AppendToStreamAsync( - string streamName, - StreamState expectedState, - IEnumerable eventData, - Action? configureOperationOptions = null, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - var operationOptions = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(operationOptions); - - _log.LogDebug("Append to stream - {streamName}@{expectedState}.", streamName, expectedState); - - var task = - userCredentials == null && await BatchAppender.IsUsable().ConfigureAwait(false) - ? BatchAppender.Append(streamName, expectedState, eventData, deadline, cancellationToken) - : AppendToStreamInternal( - await GetChannelInfo(cancellationToken).ConfigureAwait(false), - new AppendReq { - Options = new() { - StreamIdentifier = streamName - } - }.WithAnyStreamRevision(expectedState), - eventData, - operationOptions, - deadline, - userCredentials, - cancellationToken - ); - - return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(operationOptions); - } - - ValueTask AppendToStreamInternal( - ChannelInfo channelInfo, - AppendReq header, - IEnumerable eventData, - EventStoreClientOperationOptions operationOptions, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken - ) { - return EventStoreClientDiagnostics.ActivitySource.TraceClientOperation(Operation, TracingConstants.Operations.Append, AppendTags); - - async ValueTask Operation() { - using var call = new StreamsClient(channelInfo.CallInvoker) - .Append(EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - await call.RequestStream - .WriteAsync(header) - .ConfigureAwait(false); - - foreach (var e in eventData) { - var appendReq = new AppendReq { - ProposedMessage = new() { - Id = e.EventId.ToDto(), - Data = ByteString.CopyFrom(e.Data.Span), - CustomMetadata = ByteString.CopyFrom(e.Metadata.InjectTracingContext(Activity.Current)), - Metadata = { - { Constants.Metadata.Type, e.Type }, - { Constants.Metadata.ContentType, e.ContentType } - } - } - }; - - await call.RequestStream.WriteAsync(appendReq).ConfigureAwait(false); - } - - await call.RequestStream.CompleteAsync().ConfigureAwait(false); - - var response = await call.ResponseAsync.ConfigureAwait(false); - - if (response.Success is not null) - return HandleSuccessAppend(response, header); - - if (response.WrongExpectedVersion is null) - throw new InvalidOperationException("The operation completed with an unexpected result."); - - return HandleWrongExpectedRevision(response, header, operationOptions); - } - - ActivityTagsCollection AppendTags() => new ActivityTagsCollection() - .WithRequiredTag(TelemetryTags.EventStore.Stream, header.Options.StreamIdentifier.StreamName.ToStringUtf8()) - .WithGrpcChannelServerTags(Settings, channelInfo) - .WithClientSettingsServerTags(Settings) - .WithOptionalTag(TelemetryTags.Database.User, userCredentials?.Username ?? Settings.DefaultCredentials?.Username); - } - - IWriteResult HandleSuccessAppend(AppendResp response, AppendReq header) { - var currentRevision = response.Success.CurrentRevisionOptionCase == AppendResp.Types.Success.CurrentRevisionOptionOneofCase.NoStream - ? StreamRevision.None - : new StreamRevision(response.Success.CurrentRevision); - - var position = response.Success.PositionOptionCase == AppendResp.Types.Success.PositionOptionOneofCase.Position - ? new Position(response.Success.Position.CommitPosition, response.Success.Position.PreparePosition) - : default; - - _log.LogDebug( - "Append to stream succeeded - {streamName}@{logPosition}/{nextExpectedVersion}.", - header.Options.StreamIdentifier, - position, - currentRevision - ); - - return new SuccessResult(currentRevision, position); - } - - IWriteResult HandleWrongExpectedRevision( - AppendResp response, AppendReq header, EventStoreClientOperationOptions operationOptions - ) { - var actualStreamRevision = response.WrongExpectedVersion.CurrentRevisionOptionCase == CurrentRevisionOptionOneofCase.CurrentRevision - ? new StreamRevision(response.WrongExpectedVersion.CurrentRevision) - : StreamRevision.None; - - _log.LogDebug( - "Append to stream failed with Wrong Expected Version - {streamName}/{expectedRevision}/{currentRevision}", - header.Options.StreamIdentifier, - new StreamRevision(header.Options.Revision), - actualStreamRevision - ); - - if (operationOptions.ThrowOnAppendFailure) { - if (response.WrongExpectedVersion.ExpectedRevisionOptionCase == ExpectedRevisionOptionOneofCase.ExpectedRevision) { - throw new WrongExpectedVersionException( - header.Options.StreamIdentifier!, - new StreamRevision(response.WrongExpectedVersion.ExpectedRevision), - actualStreamRevision - ); - } - - var expectedStreamState = response.WrongExpectedVersion.ExpectedRevisionOptionCase switch { - ExpectedRevisionOptionOneofCase.ExpectedAny => StreamState.Any, - ExpectedRevisionOptionOneofCase.ExpectedNoStream => StreamState.NoStream, - ExpectedRevisionOptionOneofCase.ExpectedStreamExists => StreamState.StreamExists, - _ => StreamState.Any - }; - - throw new WrongExpectedVersionException( - header.Options.StreamIdentifier!, - expectedStreamState, - actualStreamRevision - ); - } - - var expectedRevision = response.WrongExpectedVersion.ExpectedRevisionOptionCase == ExpectedRevisionOptionOneofCase.ExpectedRevision - ? new StreamRevision(response.WrongExpectedVersion.ExpectedRevision) - : StreamRevision.None; - - return new WrongExpectedVersionResult( - header.Options.StreamIdentifier!, - expectedRevision, - actualStreamRevision - ); - } - - class StreamAppender : IDisposable { - readonly EventStoreClientSettings _settings; - readonly CancellationToken _cancellationToken; - readonly Action _onException; - readonly Channel _channel; - readonly ConcurrentDictionary> _pendingRequests; - readonly TaskCompletionSource _isUsable; - - ChannelInfo? _channelInfo; - AsyncDuplexStreamingCall? _call; - - public StreamAppender( - EventStoreClientSettings settings, - ValueTask channelInfoTask, - CancellationToken cancellationToken, - Action onException - ) { - _settings = settings; - _cancellationToken = cancellationToken; - _onException = onException; - _channel = Channel.CreateBounded(10000); - _pendingRequests = new ConcurrentDictionary>(); - _isUsable = new TaskCompletionSource(); - - _ = Task.Run(() => Duplex(channelInfoTask), cancellationToken); - } - - public ValueTask Append( - string streamName, StreamRevision expectedStreamPosition, - IEnumerable events, TimeSpan? timeoutAfter, - CancellationToken cancellationToken = default - ) => - AppendInternal( - BatchAppendReq.Types.Options.Create(streamName, expectedStreamPosition, timeoutAfter), - events, - cancellationToken - ); - - public ValueTask Append( - string streamName, StreamState expectedStreamState, - IEnumerable events, TimeSpan? timeoutAfter, - CancellationToken cancellationToken = default - ) => - AppendInternal( - BatchAppendReq.Types.Options.Create(streamName, expectedStreamState, timeoutAfter), - events, - cancellationToken - ); - - public Task IsUsable() => _isUsable.Task; - - ValueTask AppendInternal( - BatchAppendReq.Types.Options options, - IEnumerable events, - CancellationToken cancellationToken - ) { - return EventStoreClientDiagnostics.ActivitySource.TraceClientOperation( - Operation, - TracingConstants.Operations.Append, - AppendTags - ); - - async ValueTask Operation() { - var correlationId = Uuid.NewUuid(); - - var complete = _pendingRequests.GetOrAdd(correlationId, new TaskCompletionSource()); - - try { - foreach (var appendRequest in GetRequests(events, options, correlationId)) - await _channel.Writer.WriteAsync(appendRequest, cancellationToken).ConfigureAwait(false); - } - catch (ChannelClosedException ex) { - // channel is closed, our tcs won't necessarily get completed, don't wait for it. - throw ex.InnerException ?? ex; - } - - return await complete.Task.ConfigureAwait(false); - } - - ActivityTagsCollection AppendTags() => new ActivityTagsCollection() - .WithRequiredTag(TelemetryTags.EventStore.Stream, options.StreamIdentifier.StreamName.ToStringUtf8()) - .WithGrpcChannelServerTags(_settings, _channelInfo) - .WithClientSettingsServerTags(_settings) - .WithOptionalTag(TelemetryTags.Database.User, _settings.DefaultCredentials?.Username); - } - - async Task Duplex(ValueTask channelInfoTask) { - try { - _channelInfo = await channelInfoTask.ConfigureAwait(false); - if (!_channelInfo.ServerCapabilities.SupportsBatchAppend) { - _channel.Writer.TryComplete(new NotSupportedException("Server does not support batch append")); - _isUsable.TrySetResult(false); - return; - } - - _call = new StreamsClient(_channelInfo.CallInvoker).BatchAppend( - EventStoreCallOptions.CreateStreaming( - _settings, - userCredentials: _settings.DefaultCredentials, - cancellationToken: _cancellationToken - ) - ); - - _ = Task.Run(Send, _cancellationToken); - _ = Task.Run(Receive, _cancellationToken); - - _isUsable.TrySetResult(true); - } - catch (Exception ex) { - _isUsable.TrySetException(ex); - _onException(ex); - } - - return; - - async Task Send() { - if (_call is null) return; - - await foreach (var appendRequest in _channel.Reader.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) - await _call.RequestStream.WriteAsync(appendRequest).ConfigureAwait(false); - - await _call.RequestStream.CompleteAsync().ConfigureAwait(false); - } - - async Task Receive() { - if (_call is null) return; - - try { - await foreach (var response in _call.ResponseStream.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) { - if (!_pendingRequests.TryRemove(Uuid.FromDto(response.CorrelationId), out var writeResult)) { - continue; // TODO: Log? - } - - try { - writeResult.TrySetResult(response.ToWriteResult()); - } - catch (Exception ex) { - writeResult.TrySetException(ex); - } - } - } - catch (Exception ex) { - // signal that no tcs added to _pendingRequests after this point will necessarily complete - _channel.Writer.TryComplete(ex); - - // complete whatever tcs's we have - foreach (var request in _pendingRequests) - request.Value.TrySetException(ex); - - _onException(ex); - } - } - } - - IEnumerable GetRequests(IEnumerable events, BatchAppendReq.Types.Options options, Uuid correlationId) { - var batchSize = 0; - var first = true; - var correlationIdDto = correlationId.ToDto(); - var proposedMessages = new List(); - - foreach (var eventData in events) { - var proposedMessage = new BatchAppendReq.Types.ProposedMessage { - Data = ByteString.CopyFrom(eventData.Data.Span), - CustomMetadata = ByteString.CopyFrom(eventData.Metadata.InjectTracingContext(Activity.Current)), - Id = eventData.EventId.ToDto(), - Metadata = { - { Constants.Metadata.Type, eventData.Type }, - { Constants.Metadata.ContentType, eventData.ContentType } - } - }; - - proposedMessages.Add(proposedMessage); - - if ((batchSize += proposedMessage.CalculateSize()) < _settings.OperationOptions.BatchAppendSize) - continue; - - yield return new BatchAppendReq { - ProposedMessages = { proposedMessages }, - CorrelationId = correlationIdDto, - Options = first ? options : null - }; - - first = false; - proposedMessages.Clear(); - batchSize = 0; - } - - yield return new BatchAppendReq { - ProposedMessages = { proposedMessages }, - IsFinal = true, - CorrelationId = correlationIdDto, - Options = first ? options : null - }; - } - - public void Dispose() { - _channel.Writer.TryComplete(); - _call?.Dispose(); - } - } - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClient.Delete.cs b/src/EventStore.Client/Streams/EventStoreClient.Delete.cs deleted file mode 100644 index dfaac235f..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.Delete.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Streams; -using Microsoft.Extensions.Logging; - -namespace EventStore.Client { - public partial class EventStoreClient { - /// - /// Deletes a stream asynchronously. - /// - /// The name of the stream to delete. - /// The expected of the stream being deleted. - /// The maximum time to wait before terminating the call. - /// The optional to perform operation with. - /// The optional . - /// - public Task DeleteAsync( - string streamName, - StreamRevision expectedRevision, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => - DeleteInternal(new DeleteReq { - Options = new DeleteReq.Types.Options { - StreamIdentifier = streamName, - Revision = expectedRevision - } - }, deadline, userCredentials, cancellationToken); - - /// - /// Deletes a stream asynchronously. - /// - /// The name of the stream to delete. - /// The expected of the stream being deleted. - /// The maximum time to wait before terminating the call. - /// The optional to perform operation with. - /// The optional . - /// - public Task DeleteAsync( - string streamName, - StreamState expectedState, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => DeleteInternal(new DeleteReq { - Options = new DeleteReq.Types.Options { - StreamIdentifier = streamName - } - }.WithAnyStreamRevision(expectedState), deadline, userCredentials, cancellationToken); - - private async Task DeleteInternal(DeleteReq request, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken) { - _log.LogDebug("Deleting stream {streamName}.", request.Options.StreamIdentifier); - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Streams.Streams.StreamsClient( - channelInfo.CallInvoker).DeleteAsync(request, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - var result = await call.ResponseAsync.ConfigureAwait(false); - - return new DeleteResult(new Position(result.Position.CommitPosition, result.Position.PreparePosition)); - } - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClient.Metadata.cs b/src/EventStore.Client/Streams/EventStoreClient.Metadata.cs deleted file mode 100644 index 19de629e7..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.Metadata.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Streams; -using Microsoft.Extensions.Logging; - -namespace EventStore.Client { - public partial class EventStoreClient { - /// - /// Asynchronously reads the metadata for a stream - /// - /// The name of the stream to read the metadata for. - /// - /// The optional to perform operation with. - /// The optional . - /// - public async Task GetStreamMetadataAsync(string streamName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - _log.LogDebug("Read stream metadata for {streamName}.", streamName); - - try { - var result = ReadStreamAsync(Direction.Backwards, SystemStreams.MetastreamOf(streamName), - StreamPosition.End, 1, false, deadline, userCredentials, cancellationToken); - await foreach (var message in result.Messages.ConfigureAwait(false)) { - if (message is not StreamMessage.Event(var resolvedEvent)) { - continue; - } - - return StreamMetadataResult.Create(streamName, resolvedEvent.OriginalEventNumber, - JsonSerializer.Deserialize(resolvedEvent.Event.Data.Span, - StreamMetadataJsonSerializerOptions)); - } - - } catch (StreamNotFoundException) { - } - _log.LogWarning("Stream metadata for {streamName} not found.", streamName); - return StreamMetadataResult.None(streamName); - } - - /// - /// Asynchronously sets the metadata for a stream. - /// - /// The name of the stream to set metadata for. - /// The of the stream to append to. - /// A representing the new metadata. - /// An to configure the operation's options. - /// - /// The optional to perform operation with. - /// The optional . - /// - public Task SetStreamMetadataAsync(string streamName, StreamState expectedState, - StreamMetadata metadata, Action? configureOperationOptions = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var options = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(options); - - return SetStreamMetadataInternal(metadata, new AppendReq { - Options = new AppendReq.Types.Options { - StreamIdentifier = SystemStreams.MetastreamOf(streamName) - } - }.WithAnyStreamRevision(expectedState), options, deadline, userCredentials, cancellationToken); - } - - /// - /// Asynchronously sets the metadata for a stream. - /// - /// The name of the stream to set metadata for. - /// The of the stream to append to. - /// A representing the new metadata. - /// An to configure the operation's options. - /// - /// The optional to perform operation with. - /// The optional . - /// - public Task SetStreamMetadataAsync(string streamName, StreamRevision expectedRevision, - StreamMetadata metadata, Action? configureOperationOptions = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var options = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(options); - - return SetStreamMetadataInternal(metadata, new AppendReq { - Options = new AppendReq.Types.Options { - StreamIdentifier = SystemStreams.MetastreamOf(streamName), - Revision = expectedRevision - } - }, options, deadline, userCredentials, cancellationToken); - } - - private async Task SetStreamMetadataInternal(StreamMetadata metadata, - AppendReq appendReq, - EventStoreClientOperationOptions operationOptions, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken) { - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - return await AppendToStreamInternal(channelInfo, appendReq, new[] { - new EventData(Uuid.NewUuid(), SystemEventTypes.StreamMetadata, - JsonSerializer.SerializeToUtf8Bytes(metadata, StreamMetadataJsonSerializerOptions)), - }, operationOptions, deadline, userCredentials, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClient.Read.cs b/src/EventStore.Client/Streams/EventStoreClient.Read.cs deleted file mode 100644 index 7960622ea..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.Read.cs +++ /dev/null @@ -1,468 +0,0 @@ -using System.Threading.Channels; -using EventStore.Client.Streams; -using Grpc.Core; -using static EventStore.Client.Streams.ReadResp; -using static EventStore.Client.Streams.ReadResp.ContentOneofCase; - -namespace EventStore.Client { - public partial class EventStoreClient { - /// - /// Asynchronously reads all events. - /// - /// The in which to read. - /// The to start reading from. - /// The maximum count to read. - /// Whether to resolve LinkTo events automatically. - /// - /// The optional to perform operation with. - /// The optional . - /// - public ReadAllStreamResult ReadAllAsync( - Direction direction, - Position position, - long maxCount = long.MaxValue, - bool resolveLinkTos = false, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => ReadAllAsync( - direction, - position, - eventFilter: null, - maxCount, - resolveLinkTos, - deadline, - userCredentials, - cancellationToken - ); - - /// - /// Asynchronously reads all events with filtering. - /// - /// The in which to read. - /// The to start reading from. - /// The to apply. - /// The maximum count to read. - /// Whether to resolve LinkTo events automatically. - /// - /// The optional to perform operation with. - /// The optional . - /// - public ReadAllStreamResult ReadAllAsync( - Direction direction, - Position position, - IEventFilter? eventFilter, - long maxCount = long.MaxValue, - bool resolveLinkTos = false, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - if (maxCount <= 0) - throw new ArgumentOutOfRangeException(nameof(maxCount)); - - var readReq = new ReadReq { - Options = new() { - ReadDirection = direction switch { - Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, - Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - _ => throw InvalidOption(direction) - }, - ResolveLinks = resolveLinkTos, - All = new() { - Position = new() { - CommitPosition = position.CommitPosition, - PreparePosition = position.PreparePosition - } - }, - Count = (ulong)maxCount, - UuidOption = new() { Structured = new() }, - ControlOption = new() { Compatibility = 1 }, - Filter = GetFilterOptions(eventFilter) - } - }; - - return new ReadAllStreamResult( - async _ => { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - return channelInfo.CallInvoker; - }, - readReq, - Settings, - deadline, - userCredentials, - cancellationToken - ); - } - - /// - /// A class that represents the result of a read operation on the $all stream. You may either enumerate this instance directly or . Do not enumerate more than once. - /// - public class ReadAllStreamResult : IAsyncEnumerable { - readonly Channel _channel; - readonly CancellationTokenSource _cts; - - int _messagesEnumerated; - - /// - /// The last of the $all stream, if available. - /// - public Position? LastPosition { get; private set; } - - /// - /// An . Do not enumerate more than once. - /// - public IAsyncEnumerable Messages { - get { - return GetMessages(); - - async IAsyncEnumerable GetMessages() { - if (Interlocked.Exchange(ref _messagesEnumerated, 1) == 1) { - throw new InvalidOperationException("Messages may only be enumerated once."); - } - - try { - await foreach (var message in _channel.Reader.ReadAllAsync(_cts.Token) - .ConfigureAwait(false)) { - if (message is StreamMessage.LastAllStreamPosition(var position)) { - LastPosition = position; - } - - yield return message; - } - } - finally { - _cts.Cancel(); - } - } - } - } - - internal ReadAllStreamResult( - Func> selectCallInvoker, ReadReq request, - EventStoreClientSettings settings, TimeSpan? deadline, UserCredentials? userCredentials, - CancellationToken cancellationToken - ) { - var callOptions = EventStoreCallOptions.CreateStreaming( - settings, - deadline, - userCredentials, - cancellationToken - ); - - _channel = Channel.CreateBounded(ReadBoundedChannelOptions); - - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var linkedCancellationToken = _cts.Token; - - if (request.Options.FilterOptionCase == ReadReq.Types.Options.FilterOptionOneofCase.None) - request.Options.NoFilter = new(); - - _ = PumpMessages(); - - return; - - async Task PumpMessages() { - try { - var callInvoker = await selectCallInvoker(linkedCancellationToken).ConfigureAwait(false); - var client = new Streams.Streams.StreamsClient(callInvoker); - using var call = client.Read(request, callOptions); - await foreach (var response in call.ResponseStream.ReadAllAsync(linkedCancellationToken) - .ConfigureAwait(false)) { - await _channel.Writer.WriteAsync( - response.ContentCase switch { - StreamNotFound => StreamMessage.NotFound.Instance, - Event => new StreamMessage.Event(ConvertToResolvedEvent(response.Event)), - FirstStreamPosition => new StreamMessage.FirstStreamPosition(new StreamPosition(response.FirstStreamPosition)), - LastStreamPosition => new StreamMessage.LastStreamPosition(new StreamPosition(response.LastStreamPosition)), - LastAllStreamPosition => new StreamMessage.LastAllStreamPosition( - new Position( - response.LastAllStreamPosition.CommitPosition, - response.LastAllStreamPosition.PreparePosition - ) - ), - _ => StreamMessage.Unknown.Instance - }, - linkedCancellationToken - ).ConfigureAwait(false); - } - - _channel.Writer.Complete(); - } - catch (Exception ex) { - _channel.Writer.TryComplete(ex); - } - } - } - - /// - public async IAsyncEnumerator GetAsyncEnumerator( - CancellationToken cancellationToken = default - ) { - try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { - if (message is not StreamMessage.Event e) { - continue; - } - - yield return e.ResolvedEvent; - } - } - finally { - _cts.Cancel(); - } - } - } - - /// - /// Asynchronously reads all the events from a stream. - /// - /// The result could also be inspected as a means to avoid handling exceptions as the would indicate whether or not the stream is readable./> - /// - /// The in which to read. - /// The name of the stream to read. - /// The to start reading from. - /// The number of events to read from the stream. - /// Whether to resolve LinkTo events automatically. - /// - /// The optional to perform operation with. - /// The optional . - /// - public ReadStreamResult ReadStreamAsync( - Direction direction, - string streamName, - StreamPosition revision, - long maxCount = long.MaxValue, - bool resolveLinkTos = false, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - if (maxCount <= 0) - throw new ArgumentOutOfRangeException(nameof(maxCount)); - - return new ReadStreamResult( - async _ => { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - return channelInfo.CallInvoker; - }, - new ReadReq { - Options = new() { - ReadDirection = direction switch { - Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, - Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - _ => throw InvalidOption(direction) - }, - ResolveLinks = resolveLinkTos, - Stream = ReadReq.Types.Options.Types.StreamOptions.FromStreamNameAndRevision( - streamName, - revision - ), - Count = (ulong)maxCount, - UuidOption = new() { Structured = new() }, - NoFilter = new(), - ControlOption = new() { Compatibility = 1 } - } - }, - Settings, - deadline, - userCredentials, - cancellationToken - ); - } - - /// - /// A class that represents the result of a read operation on a stream. You may either enumerate this instance directly or . Do not enumerate more than once. - /// - public class ReadStreamResult : IAsyncEnumerable { - readonly Channel _channel; - readonly CancellationTokenSource _cts; - - int _messagesEnumerated; - - /// - /// The name of the stream. - /// - public string StreamName { get; } - - /// - /// The of the first message in this stream. Will only be filled once has been enumerated. - /// - public StreamPosition? FirstStreamPosition { get; private set; } - - /// - /// The of the last message in this stream. Will only be filled once has been enumerated. - /// - public StreamPosition? LastStreamPosition { get; private set; } - - /// - /// An . Do not enumerate more than once. - /// - public IAsyncEnumerable Messages { - get { - return GetMessages(); - - async IAsyncEnumerable GetMessages() { - if (Interlocked.Exchange(ref _messagesEnumerated, 1) == 1) { - throw new InvalidOperationException("Messages may only be enumerated once."); - } - - try { - await foreach (var message in _channel.Reader.ReadAllAsync(_cts.Token).ConfigureAwait(false)) { - switch (message) { - case StreamMessage.FirstStreamPosition(var streamPosition): - FirstStreamPosition = streamPosition; - break; - - case StreamMessage.LastStreamPosition(var lastStreamPosition): - LastStreamPosition = lastStreamPosition; - break; - - default: - break; - } - - yield return message; - } - } - finally { - _cts.Cancel(); - } - } - } - } - - /// - /// The . - /// - public Task ReadState { get; } - - internal ReadStreamResult( - Func> selectCallInvoker, ReadReq request, - EventStoreClientSettings settings, TimeSpan? deadline, UserCredentials? userCredentials, - CancellationToken cancellationToken - ) { - var callOptions = EventStoreCallOptions.CreateStreaming( - settings, - deadline, - userCredentials, - cancellationToken - ); - - _channel = Channel.CreateBounded(ReadBoundedChannelOptions); - - StreamName = request.Options.Stream.StreamIdentifier!; - - var tcs = new TaskCompletionSource(); - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var linkedCancellationToken = _cts.Token; -#pragma warning disable CS0612 - ReadState = tcs.Task; -#pragma warning restore CS0612 - - _ = PumpMessages(); - - return; - - async Task PumpMessages() { - var firstMessageRead = false; - - try { - var callInvoker = await selectCallInvoker(linkedCancellationToken).ConfigureAwait(false); - var client = new Streams.Streams.StreamsClient(callInvoker); - using var call = client.Read(request, callOptions); - - await foreach (var response in call.ResponseStream.ReadAllAsync(linkedCancellationToken) - .ConfigureAwait(false)) { - if (!firstMessageRead) { - firstMessageRead = true; - - if (response.ContentCase != StreamNotFound || request.Options.Stream == null) { - await _channel.Writer.WriteAsync(StreamMessage.Ok.Instance, linkedCancellationToken) - .ConfigureAwait(false); - - tcs.SetResult(Client.ReadState.Ok); - } - else { - tcs.SetResult(Client.ReadState.StreamNotFound); - } - } - - await _channel.Writer.WriteAsync( - response.ContentCase switch { - StreamNotFound => StreamMessage.NotFound.Instance, - Event => new StreamMessage.Event(ConvertToResolvedEvent(response.Event)), - ContentOneofCase.FirstStreamPosition => new StreamMessage.FirstStreamPosition( - new StreamPosition(response.FirstStreamPosition) - ), - ContentOneofCase.LastStreamPosition => new StreamMessage.LastStreamPosition( - new StreamPosition(response.LastStreamPosition) - ), - LastAllStreamPosition => new StreamMessage.LastAllStreamPosition( - new Position( - response.LastAllStreamPosition.CommitPosition, - response.LastAllStreamPosition.PreparePosition - ) - ), - _ => StreamMessage.Unknown.Instance - }, - linkedCancellationToken - ).ConfigureAwait(false); - } - - _channel.Writer.Complete(); - } - catch (Exception ex) { - tcs.TrySetException(ex); - _channel.Writer.TryComplete(ex); - } - } - } - - /// - public async IAsyncEnumerator GetAsyncEnumerator( - CancellationToken cancellationToken = default - ) { - try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { - if (message is StreamMessage.NotFound) { - throw new StreamNotFoundException(StreamName); - } - - if (message is not StreamMessage.Event e) { - continue; - } - - yield return e.ResolvedEvent; - } - } - finally { - _cts.Cancel(); - } - } - } - - static ResolvedEvent ConvertToResolvedEvent(ReadResp.Types.ReadEvent readEvent) => - new ResolvedEvent( - ConvertToEventRecord(readEvent.Event)!, - ConvertToEventRecord(readEvent.Link), - readEvent.PositionCase switch { - ReadResp.Types.ReadEvent.PositionOneofCase.CommitPosition => readEvent.CommitPosition, - _ => null - } - ); - - static EventRecord? ConvertToEventRecord(ReadResp.Types.ReadEvent.Types.RecordedEvent? e) => - e == null - ? null - : new EventRecord( - e.StreamIdentifier!, - Uuid.FromDto(e.Id), - new StreamPosition(e.StreamRevision), - new Position(e.CommitPosition, e.PreparePosition), - e.Metadata, - e.Data.ToByteArray(), - e.CustomMetadata.ToByteArray() - ); - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Streams/EventStoreClient.Subscriptions.cs b/src/EventStore.Client/Streams/EventStoreClient.Subscriptions.cs deleted file mode 100644 index 6f3b2f2c6..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.Subscriptions.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System.Threading.Channels; -using EventStore.Client.Diagnostics; -using EventStore.Client.Streams; -using Grpc.Core; - -using static EventStore.Client.Streams.ReadResp.ContentOneofCase; - -namespace EventStore.Client { - public partial class EventStoreClient { - /// - /// Subscribes to all events. - /// - /// A (exclusive of) to start the subscription from. - /// A Task invoked and awaited when a new event is received over the subscription. - /// Whether to resolve LinkTo events automatically. - /// An action invoked if the subscription is dropped. - /// The optional to apply. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public Task SubscribeToAllAsync( - FromAll start, - Func eventAppeared, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - SubscriptionFilterOptions? filterOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => StreamSubscription.Confirm( - SubscribeToAll(start, resolveLinkTos, filterOptions, userCredentials, cancellationToken), - eventAppeared, - subscriptionDropped, - _log, - filterOptions?.CheckpointReached, - cancellationToken: cancellationToken - ); - - /// - /// Subscribes to all events. - /// - /// A (exclusive of) to start the subscription from. - /// Whether to resolve LinkTo events automatically. - /// The optional to apply. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public StreamSubscriptionResult SubscribeToAll( - FromAll start, - bool resolveLinkTos = false, - SubscriptionFilterOptions? filterOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => new( - async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), - new ReadReq { - Options = new ReadReq.Types.Options { - ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = resolveLinkTos, - All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(start), - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - Filter = GetFilterOptions(filterOptions)!, - UuidOption = new() { Structured = new() } - } - }, - Settings, - userCredentials, - cancellationToken - ); - - /// - /// Subscribes to a stream from a checkpoint. - /// - /// A (exclusive of) to start the subscription from. - /// The name of the stream to read events from. - /// A Task invoked and awaited when a new event is received over the subscription. - /// Whether to resolve LinkTo events automatically. - /// An action invoked if the subscription is dropped. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public Task SubscribeToStreamAsync( - string streamName, - FromStream start, - Func eventAppeared, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => StreamSubscription.Confirm( - SubscribeToStream(streamName, start, resolveLinkTos, userCredentials, cancellationToken), - eventAppeared, - subscriptionDropped, - _log, - cancellationToken: cancellationToken - ); - - /// - /// Subscribes to a stream from a checkpoint. - /// - /// A (exclusive of) to start the subscription from. - /// The name of the stream to read events from. - /// Whether to resolve LinkTo events automatically. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public StreamSubscriptionResult SubscribeToStream( - string streamName, - FromStream start, - bool resolveLinkTos = false, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => new( - async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), - new ReadReq { - Options = new ReadReq.Types.Options { - ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = resolveLinkTos, - Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition(streamName, start), - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - UuidOption = new() { Structured = new() } - } - }, - Settings, - userCredentials, - cancellationToken - ); - - /// - /// A class that represents the result of a subscription operation. You may either enumerate this instance directly or . Do not enumerate more than once. - /// - public class StreamSubscriptionResult : IAsyncEnumerable, IAsyncDisposable, IDisposable { - private readonly ReadReq _request; - private readonly Channel _channel; - private readonly CancellationTokenSource _cts; - private readonly CallOptions _callOptions; - private readonly EventStoreClientSettings _settings; - private AsyncServerStreamingCall? _call; - - private int _messagesEnumerated; - - /// - /// The server-generated unique identifier for the subscription. - /// - public string? SubscriptionId { get; private set; } - - /// - /// An . Do not enumerate more than once. - /// - public IAsyncEnumerable Messages { - get { - if (Interlocked.Exchange(ref _messagesEnumerated, 1) == 1) - throw new InvalidOperationException("Messages may only be enumerated once."); - - return GetMessages(); - - async IAsyncEnumerable GetMessages() { - try { - await foreach (var message in _channel.Reader.ReadAllAsync(_cts.Token)) { - if (message is StreamMessage.SubscriptionConfirmation(var subscriptionId)) - SubscriptionId = subscriptionId; - - yield return message; - } - } - finally { -#if NET8_0_OR_GREATER - await _cts.CancelAsync().ConfigureAwait(false); -#else - _cts.Cancel(); -#endif - } - } - } - } - - internal StreamSubscriptionResult( - Func> selectChannelInfo, - ReadReq request, EventStoreClientSettings settings, UserCredentials? userCredentials, - CancellationToken cancellationToken - ) { - _request = request; - _settings = settings; - - _callOptions = EventStoreCallOptions.CreateStreaming( - settings, - userCredentials: userCredentials, - cancellationToken: cancellationToken - ); - - _channel = Channel.CreateBounded(ReadBoundedChannelOptions); - - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - if (_request.Options.FilterOptionCase == ReadReq.Types.Options.FilterOptionOneofCase.None) { - _request.Options.NoFilter = new(); - } - - _ = PumpMessages(); - - return; - - async Task PumpMessages() { - try { - var channelInfo = await selectChannelInfo(_cts.Token).ConfigureAwait(false); - var client = new Streams.Streams.StreamsClient(channelInfo.CallInvoker); - _call = client.Read(_request, _callOptions); - await foreach (var response in _call.ResponseStream.ReadAllAsync(_cts.Token).ConfigureAwait(false)) { - StreamMessage subscriptionMessage = - response.ContentCase switch { - Confirmation => new StreamMessage.SubscriptionConfirmation(response.Confirmation.SubscriptionId), - Event => new StreamMessage.Event(ConvertToResolvedEvent(response.Event)), - FirstStreamPosition => new StreamMessage.FirstStreamPosition(new StreamPosition(response.FirstStreamPosition)), - LastStreamPosition => new StreamMessage.LastStreamPosition(new StreamPosition(response.LastStreamPosition)), - LastAllStreamPosition => new StreamMessage.LastAllStreamPosition( - new Position( - response.LastAllStreamPosition.CommitPosition, - response.LastAllStreamPosition.PreparePosition - ) - ), - Checkpoint => new StreamMessage.AllStreamCheckpointReached( - new Position( - response.Checkpoint.CommitPosition, - response.Checkpoint.PreparePosition - ) - ), - CaughtUp => StreamMessage.CaughtUp.Instance, - FellBehind => StreamMessage.FellBehind.Instance, - _ => StreamMessage.Unknown.Instance - }; - - if (subscriptionMessage is StreamMessage.Event evt) - EventStoreClientDiagnostics.ActivitySource.TraceSubscriptionEvent( - SubscriptionId, - evt.ResolvedEvent, - channelInfo, - _settings, - userCredentials - ); - - await _channel.Writer - .WriteAsync(subscriptionMessage, _cts.Token) - .ConfigureAwait(false); - } - - _channel.Writer.Complete(); - } catch (Exception ex) { - _channel.Writer.TryComplete(ex); - } - } - } - - /// - public async ValueTask DisposeAsync() { - //TODO SS: Check if `CastAndDispose` is still relevant - await CastAndDispose(_cts).ConfigureAwait(false); - await CastAndDispose(_call).ConfigureAwait(false); - - return; - - static async ValueTask CastAndDispose(IDisposable? resource) { - switch (resource) { - case null: - return; - - case IAsyncDisposable disposable: - await disposable.DisposeAsync().ConfigureAwait(false); - break; - - default: - resource.Dispose(); - break; - } - } - } - - /// - public void Dispose() { - _cts.Dispose(); - _call?.Dispose(); - } - - /// - public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - try { - await foreach (var message in _channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { - if (message is not StreamMessage.Event e) - continue; - - yield return e.ResolvedEvent; - } - } - finally { -#if NET8_0_OR_GREATER - await _cts.CancelAsync().ConfigureAwait(false); -#else - _cts.Cancel(); -#endif - } - } - } - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClient.Tombstone.cs b/src/EventStore.Client/Streams/EventStoreClient.Tombstone.cs deleted file mode 100644 index 9fcddfeb4..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.Tombstone.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client.Streams; -using Microsoft.Extensions.Logging; - -namespace EventStore.Client { - public partial class EventStoreClient { - /// - /// Tombstones a stream asynchronously. Note: Tombstoned streams can never be recreated. - /// - /// The name of the stream to tombstone. - /// The expected of the stream being deleted. - /// - /// The optional to perform operation with. - /// The optional . - /// - public Task TombstoneAsync( - string streamName, - StreamRevision expectedRevision, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => TombstoneInternal(new TombstoneReq { - Options = new TombstoneReq.Types.Options { - StreamIdentifier = streamName, - Revision = expectedRevision - } - }, deadline, userCredentials, cancellationToken); - - /// - /// Tombstones a stream asynchronously. Note: Tombstoned streams can never be recreated. - /// - /// The name of the stream to tombstone. - /// The expected of the stream being deleted. - /// - /// The optional to perform operation with. - /// The optional . - /// - public Task TombstoneAsync( - string streamName, - StreamState expectedState, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => TombstoneInternal(new TombstoneReq { - Options = new TombstoneReq.Types.Options { - StreamIdentifier = streamName - } - }.WithAnyStreamRevision(expectedState), deadline, userCredentials, cancellationToken); - - private async Task TombstoneInternal(TombstoneReq request, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken) { - _log.LogDebug("Tombstoning stream {streamName}.", request.Options.StreamIdentifier); - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Streams.Streams.StreamsClient( - channelInfo.CallInvoker).TombstoneAsync(request, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - var result = await call.ResponseAsync.ConfigureAwait(false); - - return new DeleteResult(new Position(result.Position.CommitPosition, result.Position.PreparePosition)); - } - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClient.cs b/src/EventStore.Client/Streams/EventStoreClient.cs deleted file mode 100644 index 9474ff653..000000000 --- a/src/EventStore.Client/Streams/EventStoreClient.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Text.Json; -using System.Threading.Channels; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using ReadReq = EventStore.Client.Streams.ReadReq; - -namespace EventStore.Client { - /// - /// The client used for operations on streams. - /// - public sealed partial class EventStoreClient : EventStoreClientBase { - static readonly JsonSerializerOptions StreamMetadataJsonSerializerOptions = new() { - Converters = { - StreamMetadataJsonConverter.Instance - }, - }; - - static BoundedChannelOptions ReadBoundedChannelOptions = new(1) { - SingleReader = true, - SingleWriter = true, - AllowSynchronousContinuations = true - }; - - readonly ILogger _log; - Lazy _batchAppenderLazy; - StreamAppender BatchAppender => _batchAppenderLazy.Value; - readonly CancellationTokenSource _disposedTokenSource; - - static readonly Dictionary> ExceptionMap = new() { - [Constants.Exceptions.InvalidTransaction] = ex => new InvalidTransactionException(ex.Message, ex), - [Constants.Exceptions.StreamDeleted] = ex => new StreamDeletedException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value ?? "", - ex - ), - [Constants.Exceptions.WrongExpectedVersion] = ex => new WrongExpectedVersionException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value!, - ex.Trailers.GetStreamRevision(Constants.Exceptions.ExpectedVersion), - ex.Trailers.GetStreamRevision(Constants.Exceptions.ActualVersion), - ex, - ex.Message - ), - [Constants.Exceptions.MaximumAppendSizeExceeded] = ex => new MaximumAppendSizeExceededException( - ex.Trailers.GetIntValueOrDefault(Constants.Exceptions.MaximumAppendSize), - ex - ), - [Constants.Exceptions.StreamNotFound] = ex => new StreamNotFoundException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value!, - ex - ), - [Constants.Exceptions.MissingRequiredMetadataProperty] = ex => new RequiredMetadataPropertyMissingException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.MissingRequiredMetadataProperty)?.Value!, - ex - ), - }; - - /// - /// Constructs a new . This is not intended to be called directly from your code. - /// - /// - public EventStoreClient(IOptions options) : this(options.Value) { } - - /// - /// Constructs a new . - /// - /// - public EventStoreClient(EventStoreClientSettings? settings = null) : base(settings, ExceptionMap) { - _log = Settings.LoggerFactory?.CreateLogger() ?? new NullLogger(); - _disposedTokenSource = new CancellationTokenSource(); - _batchAppenderLazy = new Lazy(CreateStreamAppender); - } - - void SwapStreamAppender(Exception ex) => - Interlocked.Exchange(ref _batchAppenderLazy, new Lazy(CreateStreamAppender)).Value - .Dispose(); - - // todo: might be nice to have two different kinds of appenders and we decide which to instantiate according to the server caps. - StreamAppender CreateStreamAppender() => new StreamAppender( - Settings, - GetChannelInfo(_disposedTokenSource.Token), - _disposedTokenSource.Token, - SwapStreamAppender - ); - - static ReadReq.Types.Options.Types.FilterOptions? GetFilterOptions( - IEventFilter? filter, uint checkpointInterval = 0 - ) { - if (filter == null - || filter.Equals(StreamFilter.None) - || filter.Equals(EventTypeFilter.None)) - return null; - - var options = filter switch { - StreamFilter => new ReadReq.Types.Options.Types.FilterOptions { - StreamIdentifier = (filter.Prefixes, filter.Regex) switch { - (_, _) - when (filter.Prefixes?.Length ?? 0) == 0 && - filter.Regex != RegularFilterExpression.None => - new ReadReq.Types.Options.Types.FilterOptions.Types.Expression - { Regex = filter.Regex }, - (_, _) - when (filter.Prefixes?.Length ?? 0) != 0 && - filter.Regex == RegularFilterExpression.None => - new ReadReq.Types.Options.Types.FilterOptions.Types.Expression { - Prefix = { Array.ConvertAll(filter.Prefixes!, e => e.ToString()) } - }, - _ => throw new InvalidOperationException() - } - }, - EventTypeFilter => new ReadReq.Types.Options.Types.FilterOptions { - EventType = (filter.Prefixes, filter.Regex) switch { - (_, _) - when (filter.Prefixes?.Length ?? 0) == 0 && - filter.Regex != RegularFilterExpression.None => - new ReadReq.Types.Options.Types.FilterOptions.Types.Expression - { Regex = filter.Regex }, - (_, _) - when (filter.Prefixes?.Length ?? 0) != 0 && - filter.Regex == RegularFilterExpression.None => - new ReadReq.Types.Options.Types.FilterOptions.Types.Expression { - Prefix = { Array.ConvertAll(filter.Prefixes!, e => e.ToString()) } - }, - _ => throw new InvalidOperationException() - } - }, - _ => null - }; - - if (options == null) - return null; - - if (filter.MaxSearchWindow.HasValue) - options.Max = filter.MaxSearchWindow.Value; - else - options.Count = new Empty(); - - options.CheckpointIntervalMultiplier = checkpointInterval; - - return options; - } - - static ReadReq.Types.Options.Types.FilterOptions? GetFilterOptions( - SubscriptionFilterOptions? filterOptions - ) - => filterOptions == null ? null : GetFilterOptions(filterOptions.Filter, filterOptions.CheckpointInterval); - - /// - public override void Dispose() { - if (_batchAppenderLazy.IsValueCreated) - _batchAppenderLazy.Value.Dispose(); - - _disposedTokenSource.Dispose(); - base.Dispose(); - } - - /// - public override async ValueTask DisposeAsync() { - if (_batchAppenderLazy.IsValueCreated) - _batchAppenderLazy.Value.Dispose(); - - _disposedTokenSource.Dispose(); - await base.DisposeAsync().ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/EventStore.Client/Streams/EventStoreClientExtensions.cs b/src/EventStore.Client/Streams/EventStoreClientExtensions.cs deleted file mode 100644 index 85a78dbe5..000000000 --- a/src/EventStore.Client/Streams/EventStoreClientExtensions.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.Client { - /// - /// A set of extension methods for an . - /// - public static class EventStoreClientExtensions { - private static readonly JsonSerializerOptions SystemSettingsJsonSerializerOptions = new JsonSerializerOptions { - Converters = { - SystemSettingsJsonConverter.Instance - }, - }; - - /// - /// Writes to the $settings stream. - /// - /// - /// - /// - /// - /// - /// - /// - public static Task SetSystemSettingsAsync( - this EventStoreClient client, - SystemSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (client == null) throw new ArgumentNullException(nameof(client)); - return client.AppendToStreamAsync(SystemStreams.SettingsStream, StreamState.Any, - new[] { - new EventData(Uuid.NewUuid(), SystemEventTypes.Settings, - JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions)) - }, deadline: deadline, userCredentials: userCredentials, cancellationToken: cancellationToken); - } - - /// - /// Appends to a stream conditionally. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task ConditionalAppendToStreamAsync( - this EventStoreClient client, - string streamName, - StreamRevision expectedRevision, - IEnumerable eventData, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (client == null) { - throw new ArgumentNullException(nameof(client)); - } - try { - var result = await client.AppendToStreamAsync(streamName, expectedRevision, eventData, - options => options.ThrowOnAppendFailure = false, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - return ConditionalWriteResult.FromWriteResult(result); - } catch (StreamDeletedException) { - return ConditionalWriteResult.StreamDeleted; - } - } - - /// - /// Appends to a stream conditionally. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task ConditionalAppendToStreamAsync( - this EventStoreClient client, - string streamName, - StreamState expectedState, - IEnumerable eventData, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (client == null) { - throw new ArgumentNullException(nameof(client)); - } - try { - var result = await client.AppendToStreamAsync(streamName, expectedState, eventData, - options => options.ThrowOnAppendFailure = false, deadline, userCredentials, cancellationToken) - .ConfigureAwait(false); - return ConditionalWriteResult.FromWriteResult(result); - } catch (StreamDeletedException) { - return ConditionalWriteResult.StreamDeleted; - } catch (WrongExpectedVersionException ex) { - return ConditionalWriteResult.FromWrongExpectedVersion(ex); - } - } - } -} diff --git a/src/EventStore.Client/Streams/EventStoreClientServiceCollectionExtensions.cs b/src/EventStore.Client/Streams/EventStoreClientServiceCollectionExtensions.cs deleted file mode 100644 index 5278f4a6d..000000000 --- a/src/EventStore.Client/Streams/EventStoreClientServiceCollectionExtensions.cs +++ /dev/null @@ -1,153 +0,0 @@ -// ReSharper disable CheckNamespace - -using System; -using System.Net.Http; -using EventStore.Client; -using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; - -namespace Microsoft.Extensions.DependencyInjection { - /// - /// A set of extension methods for which provide support for an . - /// - public static class EventStoreClientServiceCollectionExtensions { - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreClient(this IServiceCollection services, Uri address, - Func? createHttpMessageHandler = null) - => services.AddEventStoreClient(options => { - options.ConnectivitySettings.Address = address; - options.CreateHttpMessageHandler = createHttpMessageHandler; - }); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreClient(this IServiceCollection services, - Func addressFactory, - Func? createHttpMessageHandler = null) - => services.AddEventStoreClient(provider => options => { - options.ConnectivitySettings.Address = addressFactory(provider); - options.CreateHttpMessageHandler = createHttpMessageHandler; - }); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreClient(this IServiceCollection services, - Action? configureSettings = null) => - services.AddEventStoreClient(new EventStoreClientSettings(), configureSettings); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreClient(this IServiceCollection services, - Func> configureSettings) => - services.AddEventStoreClient(new EventStoreClientSettings(), - configureSettings); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreClient(this IServiceCollection services, - string connectionString, Action? configureSettings = null) { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - return services.AddEventStoreClient(EventStoreClientSettings.Create(connectionString), configureSettings); - } - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreClient(this IServiceCollection services, - Func connectionStringFactory, - Action? configureSettings = null) { - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - return services.AddEventStoreClient(provider => EventStoreClientSettings.Create(connectionStringFactory(provider)), configureSettings); - } - - private static IServiceCollection AddEventStoreClient(this IServiceCollection services, - EventStoreClientSettings settings, - Action? configureSettings) { - configureSettings?.Invoke(settings); - - services.TryAddSingleton(provider => { - settings.LoggerFactory ??= provider.GetService(); - settings.Interceptors ??= provider.GetServices(); - - return new EventStoreClient(settings); - }); - - return services; - } - - private static IServiceCollection AddEventStoreClient(this IServiceCollection services, - Func settingsFactory, - Action? configureSettings = null) { - - services.TryAddSingleton(provider => { - var settings = settingsFactory(provider); - configureSettings?.Invoke(settings); - - settings.LoggerFactory ??= provider.GetService(); - settings.Interceptors ??= provider.GetServices(); - - return new EventStoreClient(settings); - }); - - return services; - } - - private static IServiceCollection AddEventStoreClient(this IServiceCollection services, - EventStoreClientSettings settings, - Func> configureSettingsFactory) { - - services.TryAddSingleton(provider => { - configureSettingsFactory(provider).Invoke(settings); - - settings.LoggerFactory ??= provider.GetService(); - settings.Interceptors ??= provider.GetServices(); - - return new EventStoreClient(settings); - }); - - return services; - } - } -} -// ReSharper restore CheckNamespace diff --git a/src/EventStore.Client/Streams/IWriteResult.cs b/src/EventStore.Client/Streams/IWriteResult.cs deleted file mode 100644 index 8fe9d530c..000000000 --- a/src/EventStore.Client/Streams/IWriteResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// An interface representing the result of a write operation. - /// - public interface IWriteResult { - /// - /// The version the stream is currently at. - /// - [Obsolete("Please use NextExpectedStreamRevision instead. This property will be removed in a future version.", - true)] - long NextExpectedVersion { get; } - /// - /// The of the in the transaction file. - /// - Position LogPosition { get; } - /// - /// The the stream is currently at. - /// - StreamRevision NextExpectedStreamRevision { get; } - } -} diff --git a/src/EventStore.Client/Streams/InvalidTransactionException.cs b/src/EventStore.Client/Streams/InvalidTransactionException.cs deleted file mode 100644 index 17933b654..000000000 --- a/src/EventStore.Client/Streams/InvalidTransactionException.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace EventStore.Client; - -/// -/// Exception thrown if there is an attempt to operate inside a -/// transaction which does not exist. -/// -public class InvalidTransactionException : Exception { - /// - /// Constructs a new . - /// - public InvalidTransactionException() { } - - /// - /// Constructs a new . - /// - public InvalidTransactionException(string message) : base(message) { } - - /// - /// Constructs a new . - /// - public InvalidTransactionException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// Constructs a new . - /// - [Obsolete("Obsolete")] - protected InvalidTransactionException(SerializationInfo info, StreamingContext context) : base(info, context) { } -} \ No newline at end of file diff --git a/src/EventStore.Client/Streams/MaximumAppendSizeExceededException.cs b/src/EventStore.Client/Streams/MaximumAppendSizeExceededException.cs deleted file mode 100644 index 7c785b951..000000000 --- a/src/EventStore.Client/Streams/MaximumAppendSizeExceededException.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// Exception thrown when an append exceeds the maximum size set by the server. - /// - public class MaximumAppendSizeExceededException : Exception { - /// - /// The configured maximum append size. - /// - public uint MaxAppendSize { get; } - - /// - /// Constructs a new . - /// - /// - /// - public MaximumAppendSizeExceededException(uint maxAppendSize, Exception? innerException = null) : - base($"Maximum Append Size of {maxAppendSize} Exceeded.", innerException) { - MaxAppendSize = maxAppendSize; - } - - /// - /// Constructs a new . - /// - /// - /// - public MaximumAppendSizeExceededException(int maxAppendSize, Exception? innerException = null) : this( - (uint)maxAppendSize, innerException) { - - } - } -} diff --git a/src/EventStore.Client/Streams/ReadState.cs b/src/EventStore.Client/Streams/ReadState.cs deleted file mode 100644 index 6f0497081..000000000 --- a/src/EventStore.Client/Streams/ReadState.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace EventStore.Client { - /// - /// An enumeration representing the state of a read operation. - /// - public enum ReadState { - /// - /// The stream does not exist. - /// - StreamNotFound, - /// - /// The stream exists. - /// - Ok - } -} diff --git a/src/EventStore.Client/Streams/StreamAcl.cs b/src/EventStore.Client/Streams/StreamAcl.cs deleted file mode 100644 index 60f70a669..000000000 --- a/src/EventStore.Client/Streams/StreamAcl.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Linq; - -namespace EventStore.Client { - /// - /// Represents an access control list for a stream - /// - public sealed class StreamAcl { - /// - /// Roles and users permitted to read the stream - /// - public string[]? ReadRoles { get; } - - /// - /// Roles and users permitted to write to the stream - /// - public string[]? WriteRoles { get; } - - /// - /// Roles and users permitted to delete the stream - /// - public string[]? DeleteRoles { get; } - - /// - /// Roles and users permitted to read stream metadata - /// - public string[]? MetaReadRoles { get; } - - /// - /// Roles and users permitted to write stream metadata - /// - public string[]? MetaWriteRoles { get; } - - - /// - /// Creates a new Stream Access Control List - /// - /// Role and user permitted to read the stream - /// Role and user permitted to write to the stream - /// Role and user permitted to delete the stream - /// Role and user permitted to read stream metadata - /// Role and user permitted to write stream metadata - public StreamAcl(string? readRole = null, string? writeRole = null, string? deleteRole = null, - string? metaReadRole = null, string? metaWriteRole = null) - : this(readRole == null ? null : new[] {readRole}, - writeRole == null ? null : new[] {writeRole}, - deleteRole == null ? null : new[] {deleteRole}, - metaReadRole == null ? null : new[] {metaReadRole}, - metaWriteRole == null ? null : new[] {metaWriteRole}) { - } - - /// - /// - /// - /// Roles and users permitted to read the stream - /// Roles and users permitted to write to the stream - /// Roles and users permitted to delete the stream - /// Roles and users permitted to read stream metadata - /// Roles and users permitted to write stream metadata - public StreamAcl(string[]? readRoles = null, string[]? writeRoles = null, string[]? deleteRoles = null, - string[]? metaReadRoles = null, string[]? metaWriteRoles = null) { - ReadRoles = readRoles; - WriteRoles = writeRoles; - DeleteRoles = deleteRoles; - MetaReadRoles = metaReadRoles; - MetaWriteRoles = metaWriteRoles; - } - - private bool Equals(StreamAcl other) => - (ReadRoles ?? Array.Empty()).SequenceEqual(other.ReadRoles ?? Array.Empty()) && - (WriteRoles ?? Array.Empty()).SequenceEqual(other.WriteRoles ?? Array.Empty()) && - (DeleteRoles ?? Array.Empty()).SequenceEqual(other.DeleteRoles ?? Array.Empty()) && - (MetaReadRoles ?? Array.Empty()).SequenceEqual(other.MetaReadRoles ?? Array.Empty()) && - (MetaWriteRoles ?? Array.Empty()).SequenceEqual(other.MetaWriteRoles ?? Array.Empty()); - - /// - public override bool Equals(object? obj) => - !ReferenceEquals(null, obj) && - (ReferenceEquals(this, obj) || obj.GetType() == GetType() && Equals((StreamAcl)obj)); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(StreamAcl? left, StreamAcl? right) => Equals(left, right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(StreamAcl? left, StreamAcl? right) => !Equals(left, right); - - /// - public override int GetHashCode() => - HashCode.Hash.Combine(ReadRoles).Combine(WriteRoles).Combine(DeleteRoles).Combine(MetaReadRoles) - .Combine(MetaWriteRoles); - - - /// - public override string ToString() => - $"Read: {(ReadRoles == null ? "" : "[" + string.Join(",", ReadRoles) + "]")}, Write: {(WriteRoles == null ? "" : "[" + string.Join(",", WriteRoles) + "]")}, Delete: {(DeleteRoles == null ? "" : "[" + string.Join(",", DeleteRoles) + "]")}, MetaRead: {(MetaReadRoles == null ? "" : "[" + string.Join(",", MetaReadRoles) + "]")}, MetaWrite: {(MetaWriteRoles == null ? "" : "[" + string.Join(",", MetaWriteRoles) + "]")}"; - } -} diff --git a/src/EventStore.Client/Streams/StreamAclJsonConverter.cs b/src/EventStore.Client/Streams/StreamAclJsonConverter.cs deleted file mode 100644 index a487ddee7..000000000 --- a/src/EventStore.Client/Streams/StreamAclJsonConverter.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace EventStore.Client { - internal class StreamAclJsonConverter : JsonConverter { - public static readonly StreamAclJsonConverter Instance = new StreamAclJsonConverter(); - - public override StreamAcl Read(ref Utf8JsonReader reader, Type typeToConvert, - JsonSerializerOptions options) { - string[]? read = null, - write = default, - delete = default, - metaRead = default, - metaWrite = default; - if (reader.TokenType != JsonTokenType.StartObject) { - throw new InvalidOperationException(); - } - - while (reader.Read()) { - if (reader.TokenType == JsonTokenType.EndObject) { - break; - } - - if (reader.TokenType != JsonTokenType.PropertyName) { - throw new InvalidOperationException(); - } - - switch (reader.GetString()) { - case SystemMetadata.AclRead: - read = ReadRoles(ref reader); - break; - case SystemMetadata.AclWrite: - write = ReadRoles(ref reader); - break; - case SystemMetadata.AclDelete: - delete = ReadRoles(ref reader); - break; - case SystemMetadata.AclMetaRead: - metaRead = ReadRoles(ref reader); - break; - case SystemMetadata.AclMetaWrite: - metaWrite = ReadRoles(ref reader); - break; - } - } - - return new StreamAcl(read, write, delete, metaRead, metaWrite); - } - - private static string[]? ReadRoles(ref Utf8JsonReader reader) { - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - if (reader.TokenType == JsonTokenType.Null) { - return null; - } - - if (reader.TokenType == JsonTokenType.String) { - return new[] {reader.GetString()!}; - } - - if (reader.TokenType != JsonTokenType.StartArray) { - throw new InvalidOperationException(); - } - - var roles = new List(); - - while (reader.Read()) { - if (reader.TokenType == JsonTokenType.EndArray) { - return roles.Count == 0 ? Array.Empty() : roles.ToArray(); - } - - if (reader.TokenType != JsonTokenType.String) { - throw new InvalidOperationException(); - } - - roles.Add(reader.GetString()!); - } - - return roles.ToArray(); - } - - public override void Write(Utf8JsonWriter writer, StreamAcl value, JsonSerializerOptions options) { - writer.WriteStartObject(); - - WriteRoles(writer, SystemMetadata.AclRead, value.ReadRoles); - WriteRoles(writer, SystemMetadata.AclWrite, value.WriteRoles); - WriteRoles(writer, SystemMetadata.AclDelete, value.DeleteRoles); - WriteRoles(writer, SystemMetadata.AclMetaRead, value.MetaReadRoles); - WriteRoles(writer, SystemMetadata.AclMetaWrite, value.MetaWriteRoles); - - writer.WriteEndObject(); - } - - private static void WriteRoles(Utf8JsonWriter writer, string name, string[]? roles) { - if (roles == null) { - return; - } - writer.WritePropertyName(name); - writer.WriteStartArray(); - foreach (var role in roles) { - writer.WriteStringValue(role); - } - - writer.WriteEndArray(); - } - } -} diff --git a/src/EventStore.Client/Streams/StreamMessage.cs b/src/EventStore.Client/Streams/StreamMessage.cs deleted file mode 100644 index 4f1a87559..000000000 --- a/src/EventStore.Client/Streams/StreamMessage.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace EventStore.Client { - /// - /// The base record of all stream messages. - /// - public abstract record StreamMessage { - /// - /// A that represents a . - /// - /// The . - public record Event(ResolvedEvent ResolvedEvent) : StreamMessage; - - /// - /// A representing a stream that was not found. - /// - public record NotFound : StreamMessage { - internal static readonly NotFound Instance = new(); - } - - /// - /// A representing a successful read operation. - /// - public record Ok : StreamMessage { - internal static readonly Ok Instance = new(); - }; - - /// - /// A indicating the first position of a stream. - /// - /// The . - public record FirstStreamPosition(StreamPosition StreamPosition) : StreamMessage; - - /// - /// A indicating the last position of a stream. - /// - /// The . - public record LastStreamPosition(StreamPosition StreamPosition) : StreamMessage; - - /// - /// A indicating the last position of the $all stream. - /// - /// The . - public record LastAllStreamPosition(Position Position) : StreamMessage; - - /// - /// A indicating that the subscription is ready to send additional messages. - /// - /// The unique identifier of the subscription. - public record SubscriptionConfirmation(string SubscriptionId) : StreamMessage; - - /// - /// A indicating that a checkpoint has been reached. - /// - /// The . - public record AllStreamCheckpointReached(Position Position) : StreamMessage; - - /// - /// A indicating that a checkpoint has been reached. - /// - /// The . - public record StreamCheckpointReached(StreamPosition StreamPosition) : StreamMessage; - - /// - /// A indicating that the subscription is live. - /// - public record CaughtUp : StreamMessage { - internal static readonly CaughtUp Instance = new(); - } - - /// - /// A indicating that the subscription has switched to catch up mode. - /// - public record FellBehind : StreamMessage { - internal static readonly FellBehind Instance = new(); - } - - /// - /// A that could not be identified, usually indicating a lower client compatibility level than the server supports. - /// - public record Unknown : StreamMessage { - internal static readonly Unknown Instance = new(); - } - } -} diff --git a/src/EventStore.Client/Streams/StreamMetadata.cs b/src/EventStore.Client/Streams/StreamMetadata.cs deleted file mode 100644 index 2b53dd977..000000000 --- a/src/EventStore.Client/Streams/StreamMetadata.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Text.Json; - -namespace EventStore.Client { - /// - /// A structure representing a stream's custom metadata with strongly typed properties - /// for system values and a dictionary-like interface for custom values. - /// - public readonly struct StreamMetadata : IEquatable { - /// - /// The optional maximum age of events allowed in the stream. - /// - public TimeSpan? MaxAge { get; } - - /// - /// The optional from which previous events can be scavenged. - /// This is used to implement soft-deletion of streams. - /// - - public StreamPosition? TruncateBefore { get; } - - /// - /// The optional amount of time for which the stream head is cacheable. - /// - public TimeSpan? CacheControl { get; } - - /// - /// The optional for the stream. - /// - public StreamAcl? Acl { get; } - - /// - /// The optional maximum number of events allowed in the stream. - /// - public int? MaxCount { get; } - - /// - /// The optional of user provided metadata. - /// - public JsonDocument? CustomMetadata { get; } - - /// - /// Constructs a new . - /// - /// - /// - /// - /// - /// - /// - /// - public StreamMetadata( - int? maxCount = null, - TimeSpan? maxAge = null, - StreamPosition? truncateBefore = null, - TimeSpan? cacheControl = null, - StreamAcl? acl = null, - JsonDocument? customMetadata = null) : this() { - if (maxCount <= 0) { - throw new ArgumentOutOfRangeException(nameof(maxCount)); - } - - if (maxAge <= TimeSpan.Zero) { - throw new ArgumentOutOfRangeException(nameof(maxAge)); - } - - if (cacheControl <= TimeSpan.Zero) { - throw new ArgumentOutOfRangeException(nameof(cacheControl)); - } - - MaxAge = maxAge; - TruncateBefore = truncateBefore; - CacheControl = cacheControl; - Acl = acl; - MaxCount = maxCount; - CustomMetadata = customMetadata ?? JsonDocument.Parse("{}"); - } - - /// - public bool Equals(StreamMetadata other) => Nullable.Equals(MaxAge, other.MaxAge) && - Nullable.Equals(TruncateBefore, other.TruncateBefore) && - Nullable.Equals(CacheControl, other.CacheControl) && - Equals(Acl, other.Acl) && MaxCount == other.MaxCount && - string.Equals( - CustomMetadata?.RootElement.GetRawText(), - other.CustomMetadata?.RootElement.GetRawText()); - - /// - public override bool Equals(object? obj) => obj is StreamMetadata other && other.Equals(this); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(MaxAge).Combine(TruncateBefore).Combine(CacheControl) - .Combine(Acl?.GetHashCode()).Combine(MaxCount); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(StreamMetadata left, StreamMetadata right) => Equals(left, right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(StreamMetadata left, StreamMetadata right) => !Equals(left, right); - } -} diff --git a/src/EventStore.Client/Streams/StreamMetadataJsonConverter.cs b/src/EventStore.Client/Streams/StreamMetadataJsonConverter.cs deleted file mode 100644 index 68757a27b..000000000 --- a/src/EventStore.Client/Streams/StreamMetadataJsonConverter.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace EventStore.Client { - internal class StreamMetadataJsonConverter : JsonConverter { - public static readonly StreamMetadataJsonConverter Instance = new StreamMetadataJsonConverter(); - - public override StreamMetadata Read(ref Utf8JsonReader reader, Type typeToConvert, - JsonSerializerOptions options) { - int? maxCount = null; - TimeSpan? maxAge = null, cacheControl = null; - StreamPosition? truncateBefore = null; - StreamAcl? acl = null; - using var stream = new MemoryStream(); - using var customMetadataWriter = new Utf8JsonWriter(stream); - - if (reader.TokenType != JsonTokenType.StartObject) { - throw new InvalidOperationException(); - } - - customMetadataWriter.WriteStartObject(); - - while (reader.Read()) { - if (reader.TokenType == JsonTokenType.EndObject) { - break; - } - - if (reader.TokenType != JsonTokenType.PropertyName) { - throw new InvalidOperationException(); - } - - switch (reader.GetString()) { - case SystemMetadata.MaxCount: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - maxCount = reader.GetInt32(); - break; - case SystemMetadata.MaxAge: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - var int64 = reader.GetInt64(); - maxAge = TimeSpan.FromSeconds(int64); - break; - case SystemMetadata.CacheControl: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - cacheControl = TimeSpan.FromSeconds(reader.GetInt64()); - break; - case SystemMetadata.TruncateBefore: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - var value = reader.GetInt64(); - truncateBefore = value == long.MaxValue - ? StreamPosition.End - : StreamPosition.FromInt64(value); - break; - case SystemMetadata.Acl: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - acl = StreamAclJsonConverter.Instance.Read(ref reader, typeof(StreamAcl), options); - break; - default: - customMetadataWriter.WritePropertyName(reader.GetString()!); - reader.Read(); - switch (reader.TokenType) { - case JsonTokenType.Comment: - customMetadataWriter.WriteCommentValue(reader.GetComment()); - break; - case JsonTokenType.String: - customMetadataWriter.WriteStringValue(reader.GetString()); - break; - case JsonTokenType.Number: - customMetadataWriter.WriteNumberValue(reader.GetDouble()); - break; - case JsonTokenType.True: - case JsonTokenType.False: - customMetadataWriter.WriteBooleanValue(reader.GetBoolean()); - break; - case JsonTokenType.Null: - reader.Read(); - customMetadataWriter.WriteNullValue(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - break; - } - } - - customMetadataWriter.WriteEndObject(); - customMetadataWriter.Flush(); - - stream.Position = 0; - - return new StreamMetadata(maxCount, maxAge, truncateBefore, cacheControl, acl, - JsonDocument.Parse(stream)); - } - - public override void Write(Utf8JsonWriter writer, StreamMetadata value, JsonSerializerOptions options) { - writer.WriteStartObject(); - - if (value.MaxCount.HasValue) { - writer.WriteNumber(SystemMetadata.MaxCount, value.MaxCount.Value); - } - - if (value.MaxAge.HasValue) { - writer.WriteNumber(SystemMetadata.MaxAge, (long)value.MaxAge.Value.TotalSeconds); - } - - if (value.TruncateBefore.HasValue) { - writer.WriteNumber(SystemMetadata.TruncateBefore, value.TruncateBefore.Value.ToInt64()); - } - - if (value.CacheControl.HasValue) { - writer.WriteNumber(SystemMetadata.CacheControl, (long)value.CacheControl.Value.TotalSeconds); - } - - if (value.Acl != null) { - writer.WritePropertyName(SystemMetadata.Acl); - StreamAclJsonConverter.Instance.Write(writer, value.Acl, options); - } - - if (value.CustomMetadata != null) { - foreach (var property in value.CustomMetadata.RootElement.EnumerateObject()) { - property.WriteTo(writer); - } - } - - writer.WriteEndObject(); - } - } -} diff --git a/src/EventStore.Client/Streams/StreamMetadataResult.cs b/src/EventStore.Client/Streams/StreamMetadataResult.cs deleted file mode 100644 index 080ba15ef..000000000 --- a/src/EventStore.Client/Streams/StreamMetadataResult.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// Represents stream metadata as a series of properties for system - /// data (e.g., MaxAge) and a object for user metadata. - /// - public struct StreamMetadataResult : IEquatable { - /// - /// The name of the stream. - /// - public readonly string StreamName; - - /// - /// True if the stream is deleted. - /// - public readonly bool StreamDeleted; - - /// - /// A containing user-specified metadata. - /// - public readonly StreamMetadata Metadata; - - /// - /// A of the version of the metadata. - /// - public readonly StreamPosition? MetastreamRevision; - - /// - public override int GetHashCode() => - HashCode.Hash.Combine(StreamName).Combine(Metadata).Combine(MetastreamRevision); - - /// - public bool Equals(StreamMetadataResult other) => - StreamName == other.StreamName && StreamDeleted == other.StreamDeleted && - Equals(Metadata, other.Metadata) && Nullable.Equals(MetastreamRevision, other.MetastreamRevision); - - /// - public override bool Equals(object? obj) => obj is StreamMetadataResult other && Equals(other); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(StreamMetadataResult left, StreamMetadataResult right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(StreamMetadataResult left, StreamMetadataResult right) => !left.Equals(right); - - /// - /// A representing no metadata. - /// - /// - /// - public static StreamMetadataResult None(string streamName) => new StreamMetadataResult(streamName); - - /// - /// A factory method to create a new . - /// - /// - /// - /// - /// - /// - public static StreamMetadataResult Create(string streamName, StreamPosition revision, - StreamMetadata metadata) => new StreamMetadataResult(streamName, revision, metadata); - - private StreamMetadataResult(string streamName, StreamPosition? metastreamRevision = null, - StreamMetadata metadata = default, bool streamDeleted = false) { - StreamName = streamName; - StreamDeleted = streamDeleted; - Metadata = metadata; - MetastreamRevision = metastreamRevision; - } - } -} diff --git a/src/EventStore.Client/Streams/StreamSubscription.cs b/src/EventStore.Client/Streams/StreamSubscription.cs deleted file mode 100644 index f70080c17..000000000 --- a/src/EventStore.Client/Streams/StreamSubscription.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Grpc.Core; -using Microsoft.Extensions.Logging; - -namespace EventStore.Client { - /// - /// A class representing a . - /// - public class StreamSubscription : IDisposable { - private readonly EventStoreClient.StreamSubscriptionResult _subscription; - private readonly IAsyncEnumerator _messages; - private readonly Func _eventAppeared; - private readonly Func _checkpointReached; - private readonly Action? _subscriptionDropped; - private readonly ILogger _log; - private readonly CancellationTokenSource _cts; - private int _subscriptionDroppedInvoked; - - /// - /// The id of the set by the server. - /// - public string SubscriptionId { get; } - - internal static async Task Confirm( - EventStoreClient.StreamSubscriptionResult subscription, - Func eventAppeared, - Action? subscriptionDropped, - ILogger log, - Func? checkpointReached = null, - CancellationToken cancellationToken = default - ) { - var messages = subscription.Messages; - - var enumerator = messages.GetAsyncEnumerator(cancellationToken); - if (!await enumerator.MoveNextAsync().ConfigureAwait(false) || - enumerator.Current is not StreamMessage.SubscriptionConfirmation(var subscriptionId)) { - throw new InvalidOperationException($"Subscription to {enumerator} could not be confirmed."); - } - - return new StreamSubscription( - subscription, - enumerator, - subscriptionId, - eventAppeared, - subscriptionDropped, - log, - checkpointReached, - cancellationToken - ); - } - - private StreamSubscription( - EventStoreClient.StreamSubscriptionResult subscription, - IAsyncEnumerator messages, string subscriptionId, - Func eventAppeared, - Action? subscriptionDropped, - ILogger log, - Func? checkpointReached, - CancellationToken cancellationToken = default - ) { - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - _subscription = subscription; - _messages = messages; - _eventAppeared = eventAppeared; - _checkpointReached = checkpointReached ?? ((_, _, _) => Task.CompletedTask); - _subscriptionDropped = subscriptionDropped; - _log = log; - _subscriptionDroppedInvoked = 0; - SubscriptionId = subscriptionId; - - _log.LogDebug("Subscription {subscriptionId} confirmed.", SubscriptionId); - - Task.Run(Subscribe, cancellationToken); - } - - private async Task Subscribe() { - using var _ = _cts; - - try { - while (await _messages.MoveNextAsync().ConfigureAwait(false)) { - var message = _messages.Current; - try { - switch (message) { - case StreamMessage.Event(var resolvedEvent): - _log.LogTrace( - "Subscription {subscriptionId} received event {streamName}@{streamRevision} {position}", - SubscriptionId, - resolvedEvent.OriginalEvent.EventStreamId, - resolvedEvent.OriginalEvent.EventNumber, - resolvedEvent.OriginalEvent.Position - ); - - await _eventAppeared(this, resolvedEvent, _cts.Token).ConfigureAwait(false); - break; - - case StreamMessage.AllStreamCheckpointReached (var position): - await _checkpointReached(this, position, _cts.Token) - .ConfigureAwait(false); - - break; - } - } catch (Exception ex) when - (ex is ObjectDisposedException or OperationCanceledException) { - if (_subscriptionDroppedInvoked != 0) { - return; - } - - _log.LogWarning( - ex, - "Subscription {subscriptionId} was dropped because cancellation was requested by another caller.", - SubscriptionId - ); - - SubscriptionDropped(SubscriptionDroppedReason.Disposed); - - return; - } catch (Exception ex) { - _log.LogError( - ex, - "Subscription {subscriptionId} was dropped because the subscriber made an error.", - SubscriptionId - ); - - SubscriptionDropped(SubscriptionDroppedReason.SubscriberError, ex); - - return; - } - } - } catch (RpcException ex) when (ex.Status.StatusCode == StatusCode.Cancelled && - ex.Status.Detail.Contains("Call canceled by the client.")) { - _log.LogInformation( - "Subscription {subscriptionId} was dropped because cancellation was requested by the client.", - SubscriptionId - ); - - SubscriptionDropped(SubscriptionDroppedReason.Disposed, ex); - } catch (Exception ex) { - if (_subscriptionDroppedInvoked == 0) { - _log.LogError( - ex, - "Subscription {subscriptionId} was dropped because an error occurred on the server.", - SubscriptionId - ); - - SubscriptionDropped(SubscriptionDroppedReason.ServerError, ex); - } - } - } - - /// - public void Dispose() => SubscriptionDropped(SubscriptionDroppedReason.Disposed); - - private void SubscriptionDropped(SubscriptionDroppedReason reason, Exception? ex = null) { - if (Interlocked.CompareExchange(ref _subscriptionDroppedInvoked, 1, 0) == 1) { - return; - } - - try { - _subscriptionDropped?.Invoke(this, reason, ex); - } finally { - _subscription.Dispose(); - _cts.Dispose(); - } - } - } -} diff --git a/src/EventStore.Client/Streams/Streams/AppendReq.cs b/src/EventStore.Client/Streams/Streams/AppendReq.cs deleted file mode 100644 index f32611530..000000000 --- a/src/EventStore.Client/Streams/Streams/AppendReq.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace EventStore.Client.Streams { - partial class AppendReq { - public AppendReq WithAnyStreamRevision(StreamState expectedState) { - if (expectedState == StreamState.Any) { - Options.Any = new Empty(); - } else if (expectedState == StreamState.NoStream) { - Options.NoStream = new Empty(); - } else if (expectedState == StreamState.StreamExists) { - Options.StreamExists = new Empty(); - } - - return this; - } - } -} diff --git a/src/EventStore.Client/Streams/Streams/BatchAppendReq.cs b/src/EventStore.Client/Streams/Streams/BatchAppendReq.cs deleted file mode 100644 index f9e1146c0..000000000 --- a/src/EventStore.Client/Streams/Streams/BatchAppendReq.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Google.Protobuf.WellKnownTypes; - -namespace EventStore.Client.Streams { - partial class BatchAppendReq { - partial class Types { - partial class Options { - public static Options Create(StreamIdentifier streamIdentifier, - StreamRevision expectedStreamRevision, TimeSpan? timeoutAfter) => new() { - StreamIdentifier = streamIdentifier, - StreamPosition = expectedStreamRevision.ToUInt64(), - Deadline21100 = Timestamp.FromDateTime(timeoutAfter.HasValue - ? DateTime.UtcNow + timeoutAfter.Value - : DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc)) - }; - public static Options Create(StreamIdentifier streamIdentifier, StreamState expectedState, - TimeSpan? timeoutAfter) => new() { - StreamIdentifier = streamIdentifier, - expectedStreamPositionCase_ = expectedState switch { - { } when expectedState == StreamState.Any => ExpectedStreamPositionOneofCase.Any, - { } when expectedState == StreamState.NoStream => ExpectedStreamPositionOneofCase.NoStream, - { } when expectedState == StreamState.StreamExists => ExpectedStreamPositionOneofCase - .StreamExists, - _ => ExpectedStreamPositionOneofCase.None - }, - expectedStreamPosition_ = new Google.Protobuf.WellKnownTypes.Empty(), - Deadline21100 = Timestamp.FromDateTime(timeoutAfter.HasValue - ? DateTime.UtcNow + timeoutAfter.Value - : DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc)) - }; - } - } - } -} diff --git a/src/EventStore.Client/Streams/Streams/BatchAppendResp.cs b/src/EventStore.Client/Streams/Streams/BatchAppendResp.cs deleted file mode 100644 index 926dcee25..000000000 --- a/src/EventStore.Client/Streams/Streams/BatchAppendResp.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using Grpc.Core; -using static EventStore.Client.WrongExpectedVersion.CurrentStreamRevisionOptionOneofCase; -using static EventStore.Client.WrongExpectedVersion.ExpectedStreamPositionOptionOneofCase; - -namespace EventStore.Client.Streams { - partial class BatchAppendResp { - public IWriteResult ToWriteResult() => ResultCase switch { - ResultOneofCase.Success => new SuccessResult( - Success.CurrentRevisionOptionCase switch { - Types.Success.CurrentRevisionOptionOneofCase.CurrentRevision => - new StreamRevision(Success.CurrentRevision), - _ => StreamRevision.None - }, Success.PositionOptionCase switch { - Types.Success.PositionOptionOneofCase.Position => new Position( - Success.Position.CommitPosition, - Success.Position.PreparePosition), - _ => Position.End - }), - ResultOneofCase.Error => Error.Details switch { - { } when Error.Details.Is(WrongExpectedVersion.Descriptor) => - FromWrongExpectedVersion(StreamIdentifier, Error.Details.Unpack()), - { } when Error.Details.Is(StreamDeleted.Descriptor) => - throw new StreamDeletedException(StreamIdentifier!), - { } when Error.Details.Is(AccessDenied.Descriptor) => throw new AccessDeniedException(), - { } when Error.Details.Is(Timeout.Descriptor) => throw new RpcException( - new Status(StatusCode.DeadlineExceeded, Error.Message)), - { } when Error.Details.Is(Unknown.Descriptor) => throw new InvalidOperationException(Error.Message), - { } when Error.Details.Is(MaximumAppendSizeExceeded.Descriptor) => - throw new MaximumAppendSizeExceededException( - Error.Details.Unpack().MaxAppendSize), - { } when Error.Details.Is(BadRequest.Descriptor) => throw new InvalidOperationException(Error.Details - .Unpack().Message), - _ => throw new InvalidOperationException($"Could not recognize {Error.Message}") - }, - _ => throw new InvalidOperationException() - }; - - private static WrongExpectedVersionResult FromWrongExpectedVersion(StreamIdentifier streamIdentifier, - WrongExpectedVersion wrongExpectedVersion) => new(streamIdentifier!, - wrongExpectedVersion.ExpectedStreamPositionOptionCase switch { - ExpectedStreamPosition => wrongExpectedVersion.ExpectedStreamPosition, - _ => StreamRevision.None - }, wrongExpectedVersion.CurrentStreamRevisionOptionCase switch { - CurrentStreamRevision => wrongExpectedVersion.CurrentStreamRevision, - _ => StreamRevision.None - }); - } -} diff --git a/src/EventStore.Client/Streams/Streams/DeleteReq.cs b/src/EventStore.Client/Streams/Streams/DeleteReq.cs deleted file mode 100644 index 94600138c..000000000 --- a/src/EventStore.Client/Streams/Streams/DeleteReq.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace EventStore.Client.Streams { - partial class DeleteReq { - public DeleteReq WithAnyStreamRevision(StreamState expectedState) { - if (expectedState == StreamState.Any) { - Options.Any = new Empty(); - } else if (expectedState == StreamState.NoStream) { - Options.NoStream = new Empty(); - } else if (expectedState == StreamState.StreamExists) { - Options.StreamExists = new Empty(); - } - - return this; - } - } -} diff --git a/src/EventStore.Client/Streams/Streams/ReadReq.cs b/src/EventStore.Client/Streams/Streams/ReadReq.cs deleted file mode 100644 index ed5ca3af7..000000000 --- a/src/EventStore.Client/Streams/Streams/ReadReq.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; - -namespace EventStore.Client.Streams { - partial class ReadReq { - partial class Types { - partial class Options { - partial class Types { - partial class StreamOptions { - public static StreamOptions FromSubscriptionPosition(string streamName, - FromStream fromStream) { - if (fromStream == FromStream.End) { - return new StreamOptions { - StreamIdentifier = streamName, - End = new Empty() - }; - } - - if (fromStream == FromStream.Start) { - return new StreamOptions { - StreamIdentifier = streamName, - Start = new Empty() - }; - } - - return new StreamOptions { - StreamIdentifier = streamName, - Revision = fromStream.ToUInt64() - }; - } - public static StreamOptions FromStreamNameAndRevision( - string streamName, - StreamPosition streamRevision) { - if (streamName == null) { - throw new ArgumentNullException(nameof(streamName)); - } - - if (streamRevision == StreamPosition.End) { - return new StreamOptions { - StreamIdentifier = streamName, - End = new Empty() - }; - } - - if (streamRevision == StreamPosition.Start) { - return new StreamOptions { - StreamIdentifier = streamName, - Start = new Empty() - }; - } - - return new StreamOptions { - StreamIdentifier = streamName, - Revision = streamRevision - }; - } - } - - partial class AllOptions { - public static AllOptions FromSubscriptionPosition(FromAll position) { - if (position == FromAll.End) { - return new AllOptions { - End = new Empty() - }; - } - - if (position == FromAll.Start) { - return new AllOptions { - Start = new Empty() - }; - } - - var (c, p) = position.ToUInt64(); - - return new AllOptions { - Position = new Position { - CommitPosition = c, - PreparePosition = p - } - }; - } - } - } - } - } - } -} diff --git a/src/EventStore.Client/Streams/Streams/TombstoneReq.cs b/src/EventStore.Client/Streams/Streams/TombstoneReq.cs deleted file mode 100644 index cbe1ef0a0..000000000 --- a/src/EventStore.Client/Streams/Streams/TombstoneReq.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace EventStore.Client.Streams { - partial class TombstoneReq { - public TombstoneReq WithAnyStreamRevision(StreamState expectedState) { - if (expectedState == StreamState.Any) { - Options.Any = new Empty(); - } else if (expectedState == StreamState.NoStream) { - Options.NoStream = new Empty(); - } else if (expectedState == StreamState.StreamExists) { - Options.StreamExists = new Empty(); - } - - return this; - } - } -} diff --git a/src/EventStore.Client/Streams/SubscriptionFilterOptions.cs b/src/EventStore.Client/Streams/SubscriptionFilterOptions.cs deleted file mode 100644 index b6eb22dd0..000000000 --- a/src/EventStore.Client/Streams/SubscriptionFilterOptions.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace EventStore.Client { - /// - /// A class representing the options to use when filtering read operations. - /// - public class SubscriptionFilterOptions { - /// - /// The to apply. - /// - public IEventFilter Filter { get; } - - /// - /// Sets how often the checkpointReached callback is called. - /// - public uint CheckpointInterval { get; } - - /// - /// A Task invoked and await when a checkpoint is reached. - /// Set the checkpointInterval to define how often this method is called. - /// - public Func CheckpointReached { get; } = null!; - - /// - /// - /// - /// The to apply. - /// Sets how often the checkpointReached callback is called. - /// - /// A Task invoked and await when a checkpoint is reached. - /// Set the checkpointInterval to define how often this method is called. - /// - /// - public SubscriptionFilterOptions(IEventFilter filter, uint checkpointInterval, - Func? checkpointReached) - : this(filter, checkpointInterval) { - CheckpointReached = checkpointReached ?? ((_, __, ct) => Task.CompletedTask); - } - - /// - /// - /// - /// The to apply. - /// Sets how often the checkpointReached callback is called. - /// - public SubscriptionFilterOptions(IEventFilter filter, uint checkpointInterval = 1) { - if (checkpointInterval == 0) { - throw new ArgumentOutOfRangeException(nameof(checkpointInterval), - checkpointInterval, $"{nameof(checkpointInterval)} must be greater than 0."); - } - - Filter = filter; - CheckpointInterval = checkpointInterval; - } - } -} diff --git a/src/EventStore.Client/Streams/SuccessResult.cs b/src/EventStore.Client/Streams/SuccessResult.cs deleted file mode 100644 index 0624d6fcc..000000000 --- a/src/EventStore.Client/Streams/SuccessResult.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; - -namespace EventStore.Client { - /// - /// An that indicates a successful append to a stream. - /// - public readonly struct SuccessResult : IWriteResult, IEquatable { - /// - public long NextExpectedVersion { get; } - - /// - public Position LogPosition { get; } - - /// - public StreamRevision NextExpectedStreamRevision { get; } - - /// - /// Constructs a new . - /// - /// - /// - public SuccessResult(StreamRevision nextExpectedStreamRevision, Position logPosition) { - NextExpectedStreamRevision = nextExpectedStreamRevision; - LogPosition = logPosition; - NextExpectedVersion = nextExpectedStreamRevision.ToInt64(); - } - - /// - public bool Equals(SuccessResult other) => - NextExpectedStreamRevision == other.NextExpectedStreamRevision && LogPosition.Equals(other.LogPosition); - - /// - public override bool Equals(object? obj) => obj is SuccessResult other && Equals(other); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(SuccessResult left, SuccessResult right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is equal not to right. - public static bool operator !=(SuccessResult left, SuccessResult right) => !left.Equals(right); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(NextExpectedVersion).Combine(LogPosition); - - /// - public override string ToString() => $"{NextExpectedStreamRevision}:{LogPosition}"; - } -} diff --git a/src/EventStore.Client/Streams/SystemEventTypes.cs b/src/EventStore.Client/Streams/SystemEventTypes.cs deleted file mode 100644 index 48e585160..000000000 --- a/src/EventStore.Client/Streams/SystemEventTypes.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace EventStore.Client { - /// - ///Constants for System event types - /// - public static class SystemEventTypes { - /// - /// event type for stream deleted - /// - public const string StreamDeleted = "$streamDeleted"; - - /// - /// event type for statistics - /// - public const string StatsCollection = "$statsCollected"; - - /// - /// event type for linkTo - /// - public const string LinkTo = "$>"; - - /// - /// event type for stream metadata - /// - public const string StreamMetadata = "$metadata"; - - /// - /// event type for the system settings - /// - public const string Settings = "$settings"; - } -} diff --git a/src/EventStore.Client/Streams/SystemMetadata.cs b/src/EventStore.Client/Streams/SystemMetadata.cs deleted file mode 100644 index 7cce81c90..000000000 --- a/src/EventStore.Client/Streams/SystemMetadata.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace EventStore.Client { - /// - ///Constants for information in stream metadata - /// - internal static class SystemMetadata { - /// - ///The definition of the MaxAge value assigned to stream metadata - ///Setting this allows all events older than the limit to be deleted - /// - public const string MaxAge = "$maxAge"; - - /// - ///The definition of the MaxCount value assigned to stream metadata - ///setting this allows all events with a sequence less than current -maxcount to be deleted - /// - public const string MaxCount = "$maxCount"; - - /// - ///The definition of the Truncate Before value assigned to stream metadata - ///setting this allows all events prior to the integer value to be deleted - /// - public const string TruncateBefore = "$tb"; - - /// - /// Sets the cache control in seconds for the head of the stream. - /// - public const string CacheControl = "$cacheControl"; - - - /// - /// The acl definition in metadata - /// - public const string Acl = "$acl"; - - /// - /// to read from a stream - /// - public const string AclRead = "$r"; - - /// - /// to write to a stream - /// - public const string AclWrite = "$w"; - - /// - /// to delete a stream - /// - public const string AclDelete = "$d"; - - /// - /// to read metadata - /// - public const string AclMetaRead = "$mr"; - - /// - /// to write metadata - /// - public const string AclMetaWrite = "$mw"; - - - /// - /// The user default acl stream - /// - public const string UserStreamAcl = "$userStreamAcl"; - - /// - /// the system stream defaults acl stream - /// - public const string SystemStreamAcl = "$systemStreamAcl"; - } -} diff --git a/src/EventStore.Client/Streams/SystemSettings.cs b/src/EventStore.Client/Streams/SystemSettings.cs deleted file mode 100644 index 37ccd661e..000000000 --- a/src/EventStore.Client/Streams/SystemSettings.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace EventStore.Client { - /// - /// A class representing default access control lists. - /// - public sealed class SystemSettings { - /// - /// Default access control list for new user streams. - /// - public StreamAcl? UserStreamAcl { get; } - - /// - /// Default access control list for new system streams. - /// - public StreamAcl? SystemStreamAcl { get; } - - /// - /// Constructs a new . - /// - /// - /// - public SystemSettings(StreamAcl? userStreamAcl = null, StreamAcl? systemStreamAcl = null) { - UserStreamAcl = userStreamAcl; - SystemStreamAcl = systemStreamAcl; - } - - private bool Equals(SystemSettings other) - => Equals(UserStreamAcl, other.UserStreamAcl) && Equals(SystemStreamAcl, other.SystemStreamAcl); - - /// - public override bool Equals(object? obj) - => ReferenceEquals(this, obj) || obj is SystemSettings other && Equals(other); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(SystemSettings? left, SystemSettings? right) => Equals(left, right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(SystemSettings? left, SystemSettings? right) => !Equals(left, right); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(UserStreamAcl?.GetHashCode()) - .Combine(SystemStreamAcl?.GetHashCode()); - } -} diff --git a/src/EventStore.Client/Streams/SystemSettingsJsonConverter.cs b/src/EventStore.Client/Streams/SystemSettingsJsonConverter.cs deleted file mode 100644 index 03d7e7b9c..000000000 --- a/src/EventStore.Client/Streams/SystemSettingsJsonConverter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace EventStore.Client { - internal class SystemSettingsJsonConverter : JsonConverter { - public static readonly SystemSettingsJsonConverter Instance = new SystemSettingsJsonConverter(); - - public override SystemSettings Read(ref Utf8JsonReader reader, Type typeToConvert, - JsonSerializerOptions options) { - if (reader.TokenType != JsonTokenType.StartObject) { - throw new InvalidOperationException(); - } - - StreamAcl? system = null, user = null; - - while (reader.Read()) { - if (reader.TokenType == JsonTokenType.EndObject) { - break; - } - - if (reader.TokenType != JsonTokenType.PropertyName) { - throw new InvalidOperationException(); - } - - switch (reader.GetString()) { - case SystemMetadata.SystemStreamAcl: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - system = StreamAclJsonConverter.Instance.Read(ref reader, typeof(StreamAcl), options); - break; - case SystemMetadata.UserStreamAcl: - if (!reader.Read()) { - throw new InvalidOperationException(); - } - - user = StreamAclJsonConverter.Instance.Read(ref reader, typeof(StreamAcl), options); - break; - } - } - - return new SystemSettings(user, system); - } - - public override void Write(Utf8JsonWriter writer, SystemSettings value, JsonSerializerOptions options) { - writer.WriteStartObject(); - if (value.UserStreamAcl != null) { - writer.WritePropertyName(SystemMetadata.UserStreamAcl); - StreamAclJsonConverter.Instance.Write(writer, value.UserStreamAcl, options); - } - - if (value.SystemStreamAcl != null) { - writer.WritePropertyName(SystemMetadata.SystemStreamAcl); - StreamAclJsonConverter.Instance.Write(writer, value.SystemStreamAcl, options); - } - - writer.WriteEndObject(); - } - } -} diff --git a/src/EventStore.Client/Streams/WriteResultExtensions.cs b/src/EventStore.Client/Streams/WriteResultExtensions.cs deleted file mode 100644 index ed83c532e..000000000 --- a/src/EventStore.Client/Streams/WriteResultExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EventStore.Client { - internal static class WriteResultExtensions { - public static IWriteResult OptionallyThrowWrongExpectedVersionException(this IWriteResult writeResult, - EventStoreClientOperationOptions options) => - (options.ThrowOnAppendFailure, writeResult) switch { - (true, WrongExpectedVersionResult wrongExpectedVersionResult) - => throw new WrongExpectedVersionException(wrongExpectedVersionResult.StreamName, - writeResult.NextExpectedStreamRevision, wrongExpectedVersionResult.ActualStreamRevision), - _ => writeResult - }; - } -} diff --git a/src/EventStore.Client/Streams/WrongExpectedVersionResult.cs b/src/EventStore.Client/Streams/WrongExpectedVersionResult.cs deleted file mode 100644 index 7dd4887ca..000000000 --- a/src/EventStore.Client/Streams/WrongExpectedVersionResult.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace EventStore.Client { - /// - /// An that indicates a failed append to a stream. - /// - public readonly struct WrongExpectedVersionResult : IWriteResult { - /// - /// The name of the stream. - /// - public string StreamName { get; } - - /// - public long NextExpectedVersion { get; } - - /// - /// The version the stream is at. - /// - public long ActualVersion { get; } - - /// - /// The the stream is at. - /// - public StreamRevision ActualStreamRevision { get; } - - /// - public Position LogPosition { get; } - - /// - public StreamRevision NextExpectedStreamRevision { get; } - - /// - /// Construct a new . - /// - /// - /// - public WrongExpectedVersionResult(string streamName, StreamRevision nextExpectedStreamRevision) { - StreamName = streamName; - ActualVersion = NextExpectedVersion = nextExpectedStreamRevision.ToInt64(); - ActualStreamRevision = NextExpectedStreamRevision = nextExpectedStreamRevision; - LogPosition = default; - } - - /// - /// Construct a new . - /// - /// - /// - /// - public WrongExpectedVersionResult(string streamName, StreamRevision nextExpectedStreamRevision, - StreamRevision actualStreamRevision) { - StreamName = streamName; - ActualVersion = actualStreamRevision.ToInt64(); - ActualStreamRevision = actualStreamRevision; - NextExpectedVersion = nextExpectedStreamRevision.ToInt64(); - NextExpectedStreamRevision = nextExpectedStreamRevision; - LogPosition = default; - } - } -} diff --git a/src/EventStore.Client/UserManagement/EventStoreUserManagementClient.cs b/src/EventStore.Client/UserManagement/EventStoreUserManagementClient.cs deleted file mode 100644 index 6b86e81b4..000000000 --- a/src/EventStore.Client/UserManagement/EventStoreUserManagementClient.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System.Runtime.CompilerServices; -using EventStore.Client.Users; -using Grpc.Core; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace EventStore.Client { - /// - /// The client used for operations on internal users. - /// - public sealed class EventStoreUserManagementClient : EventStoreClientBase { - private readonly ILogger _log; - - /// - /// Constructs a new . - /// - /// - public EventStoreUserManagementClient(EventStoreClientSettings? settings = null) : - base(settings, ExceptionMap) { - _log = Settings.LoggerFactory?.CreateLogger() ?? - new NullLogger(); - } - - /// - /// Creates an internal user. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task CreateUserAsync(string loginName, string fullName, string[] groups, string password, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (loginName == null) throw new ArgumentNullException(nameof(loginName)); - if (fullName == null) throw new ArgumentNullException(nameof(fullName)); - if (groups == null) throw new ArgumentNullException(nameof(groups)); - if (password == null) throw new ArgumentNullException(nameof(password)); - if (loginName == string.Empty) throw new ArgumentOutOfRangeException(nameof(loginName)); - if (fullName == string.Empty) throw new ArgumentOutOfRangeException(nameof(fullName)); - if (password == string.Empty) throw new ArgumentOutOfRangeException(nameof(password)); - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).CreateAsync(new CreateReq { - Options = new CreateReq.Types.Options { - LoginName = loginName, - FullName = fullName, - Password = password, - Groups = {groups} - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Gets the of an internal user. - /// - /// - /// - /// - /// - /// - /// - /// - public async Task GetUserAsync(string loginName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - if (loginName == null) { - throw new ArgumentNullException(nameof(loginName)); - } - - if (loginName == string.Empty) { - throw new ArgumentOutOfRangeException(nameof(loginName)); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).Details(new DetailsReq { - Options = new DetailsReq.Types.Options { - LoginName = loginName - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - await call.ResponseStream.MoveNext().ConfigureAwait(false); - var userDetails = call.ResponseStream.Current.UserDetails; - return ConvertUserDetails(userDetails); - } - - private static UserDetails ConvertUserDetails(DetailsResp.Types.UserDetails userDetails) => - new UserDetails(userDetails.LoginName, userDetails.FullName, userDetails.Groups.ToArray(), - userDetails.Disabled, userDetails.LastUpdated?.TicksSinceEpoch.FromTicksSinceEpoch()); - - /// - /// Deletes an internal user. - /// - /// - /// - /// - /// - /// - /// - /// - public async Task DeleteUserAsync(string loginName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - if (loginName == null) { - throw new ArgumentNullException(nameof(loginName)); - } - - if (loginName == string.Empty) { - throw new ArgumentOutOfRangeException(nameof(loginName)); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).DeleteAsync(new DeleteReq { - Options = new DeleteReq.Types.Options { - LoginName = loginName - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Enables a previously disabled internal user. - /// - /// - /// - /// - /// - /// - /// - /// - public async Task EnableUserAsync(string loginName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - if (loginName == null) { - throw new ArgumentNullException(nameof(loginName)); - } - - if (loginName == string.Empty) { - throw new ArgumentOutOfRangeException(nameof(loginName)); - } - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).EnableAsync(new EnableReq { - Options = new EnableReq.Types.Options { - LoginName = loginName - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Disables an internal user. - /// - /// - /// - /// - /// - /// - /// - public async Task DisableUserAsync(string loginName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { - if (loginName == string.Empty) throw new ArgumentOutOfRangeException(nameof(loginName)); - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).DisableAsync(new DisableReq { - Options = new DisableReq.Types.Options { - LoginName = loginName - } - }, EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Lists the of all internal users. - /// - /// - /// - /// - /// - public async IAsyncEnumerable ListAllAsync(TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).Details(new DetailsReq(), - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - - await foreach (var userDetail in call.ResponseStream - .ReadAllAsync(cancellationToken) - .Select(x => ConvertUserDetails(x.UserDetails)) - .WithCancellation(cancellationToken) - .ConfigureAwait(false)) { - yield return userDetail; - } - } - - /// - /// Changes the password of an internal user. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task ChangePasswordAsync(string loginName, string currentPassword, string newPassword, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (loginName == null) throw new ArgumentNullException(nameof(loginName)); - if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - if (newPassword == null) throw new ArgumentNullException(nameof(newPassword)); - if (loginName == string.Empty) throw new ArgumentOutOfRangeException(nameof(loginName)); - if (currentPassword == string.Empty) throw new ArgumentOutOfRangeException(nameof(currentPassword)); - if (newPassword == string.Empty) throw new ArgumentOutOfRangeException(nameof(newPassword)); - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).ChangePasswordAsync( - new ChangePasswordReq { - Options = new ChangePasswordReq.Types.Options { - CurrentPassword = currentPassword, - NewPassword = newPassword, - LoginName = loginName - } - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - /// - /// Resets the password of an internal user. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task ResetPasswordAsync(string loginName, string newPassword, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - if (loginName == null) throw new ArgumentNullException(nameof(loginName)); - if (newPassword == null) throw new ArgumentNullException(nameof(newPassword)); - if (loginName == string.Empty) throw new ArgumentOutOfRangeException(nameof(loginName)); - if (newPassword == string.Empty) throw new ArgumentOutOfRangeException(nameof(newPassword)); - - var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - var call = new Users.Users.UsersClient( - channelInfo.CallInvoker).ResetPasswordAsync( - new ResetPasswordReq { - Options = new ResetPasswordReq.Types.Options { - NewPassword = newPassword, - LoginName = loginName - } - }, - EventStoreCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); - await call.ResponseAsync.ConfigureAwait(false); - } - - private static readonly Dictionary> ExceptionMap = - new Dictionary> { - [Constants.Exceptions.UserNotFound] = ex => new UserNotFoundException( - ex.Trailers.First(x => x.Key == Constants.Exceptions.LoginName).Value), - }; - } -} diff --git a/src/EventStore.Client/UserManagement/EventStoreUserManagementClientCollectionExtensions.cs b/src/EventStore.Client/UserManagement/EventStoreUserManagementClientCollectionExtensions.cs deleted file mode 100644 index 2b25f816c..000000000 --- a/src/EventStore.Client/UserManagement/EventStoreUserManagementClientCollectionExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ReSharper disable CheckNamespace - -using System.Net.Http; -using EventStore.Client; -using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; - -namespace Microsoft.Extensions.DependencyInjection { - /// - /// A set of extension methods for which provide support for an . - /// - public static class EventStoreUserManagementClientCollectionExtensions { - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreUserManagementClient(this IServiceCollection services, - Uri address, Func? createHttpMessageHandler = null) - => services.AddEventStoreUserManagementClient(options => { - options.ConnectivitySettings.Address = address; - options.CreateHttpMessageHandler = createHttpMessageHandler; - }); - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreUserManagementClient(this IServiceCollection services, - string connectionString, Action? configureSettings = null) - => services.AddEventStoreUserManagementClient(EventStoreClientSettings.Create(connectionString), - configureSettings); - - - /// - /// Adds an to the . - /// - /// - /// - /// - /// - public static IServiceCollection AddEventStoreUserManagementClient(this IServiceCollection services, - Action? configureSettings = null) => - services.AddEventStoreUserManagementClient(new EventStoreClientSettings(), configureSettings); - - private static IServiceCollection AddEventStoreUserManagementClient(this IServiceCollection services, - EventStoreClientSettings settings, Action? configureSettings = null) { - configureSettings?.Invoke(settings); - if (services == null) { - throw new ArgumentNullException(nameof(services)); - } - - services.TryAddSingleton(provider => { - settings.LoggerFactory ??= provider.GetService(); - settings.Interceptors ??= provider.GetServices(); - - return new EventStoreUserManagementClient(settings); - }); - - return services; - } - } -} -// ReSharper restore CheckNamespace diff --git a/src/EventStore.Client/UserManagement/EventStoreUserManagerClientExtensions.cs b/src/EventStore.Client/UserManagement/EventStoreUserManagerClientExtensions.cs deleted file mode 100644 index a43ae43d7..000000000 --- a/src/EventStore.Client/UserManagement/EventStoreUserManagerClientExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace EventStore.Client; - -/// -/// A set of extension methods for an . -/// -public static class EventStoreUserManagerClientExtensions { - /// - /// Gets the of the internal user specified by the supplied . - /// - /// - /// - /// - /// - /// - public static Task GetCurrentUserAsync( - this EventStoreUserManagementClient users, - UserCredentials userCredentials, TimeSpan? deadline = null, CancellationToken cancellationToken = default - ) => - users.GetUserAsync( - userCredentials.Username!, deadline, userCredentials, - cancellationToken - ); -} \ No newline at end of file diff --git a/src/EventStore.Client/UserManagement/UserDetails.cs b/src/EventStore.Client/UserManagement/UserDetails.cs deleted file mode 100644 index 92c345eea..000000000 --- a/src/EventStore.Client/UserManagement/UserDetails.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace EventStore.Client; - -/// -/// Provides the details for a user. -/// -public readonly struct UserDetails : IEquatable { - /// - /// The users login name. - /// - public readonly string LoginName; - - /// - /// The full name of the user. - /// - public readonly string FullName; - - /// - /// The groups the user is a member of. - /// - public readonly string[] Groups; - - /// - /// The date/time the user was updated in UTC format. - /// - public readonly DateTimeOffset? DateLastUpdated; - - /// - /// Whether the user disable or not. - /// - public readonly bool Disabled; - - /// - /// create a new class. - /// - /// The login name of the user. - /// The users full name. - /// The groups this user is a member if. - /// Is this user disabled or not. - /// The datt/time this user was last updated in UTC format. - public UserDetails( - string loginName, string fullName, string[] groups, bool disabled, DateTimeOffset? dateLastUpdated) { - if (loginName == null) { - throw new ArgumentNullException(nameof(loginName)); - } - - if (fullName == null) { - throw new ArgumentNullException(nameof(fullName)); - } - - if (groups == null) { - throw new ArgumentNullException(nameof(groups)); - } - - LoginName = loginName; - FullName = fullName; - Groups = groups; - Disabled = disabled; - DateLastUpdated = dateLastUpdated; - } - - /// - public bool Equals(UserDetails other) => - LoginName == other.LoginName && FullName == other.FullName && Groups.SequenceEqual(other.Groups) && - Nullable.Equals(DateLastUpdated, other.DateLastUpdated) && Disabled == other.Disabled; - - /// - public override bool Equals(object? obj) => obj is UserDetails other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Hash.Combine(LoginName).Combine(FullName).Combine(Groups) - .Combine(Disabled).Combine(DateLastUpdated); - - /// - /// Compares left and right for equality. - /// - /// - /// - /// True if left is equal to right. - public static bool operator ==(UserDetails left, UserDetails right) => left.Equals(right); - - /// - /// Compares left and right for inequality. - /// - /// - /// - /// True if left is not equal to right. - public static bool operator !=(UserDetails left, UserDetails right) => !left.Equals(right); - - /// - public override string ToString() => - new { - Disabled, - FullName, - LoginName, - Groups = string.Join(",", Groups) - }?.ToString()!; -} \ No newline at end of file diff --git a/src/Kurrent.Client/OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/Kurrent.Client/OpenTelemetry/TracerProviderBuilderExtensions.cs index dd1478b18..19885375b 100644 --- a/src/Kurrent.Client/OpenTelemetry/TracerProviderBuilderExtensions.cs +++ b/src/Kurrent.Client/OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -14,6 +14,6 @@ public static class TracerProviderBuilderExtensions { /// /// being configured. /// The instance of to chain configuration. - public static TracerProviderBuilder AddEventStoreClientInstrumentation(this TracerProviderBuilder builder) => + public static TracerProviderBuilder AddKurrentClientInstrumentation(this TracerProviderBuilder builder) => builder.AddSource(KurrentClientDiagnostics.InstrumentationName); } diff --git a/test/Kurrent.Client.Tests.Common/Fixtures/KurrentFixtureOptions.cs b/test/Kurrent.Client.Tests.Common/Fixtures/KurrentFixtureOptions.cs index 51e1e4f78..db5ce022e 100644 --- a/test/Kurrent.Client.Tests.Common/Fixtures/KurrentFixtureOptions.cs +++ b/test/Kurrent.Client.Tests.Common/Fixtures/KurrentFixtureOptions.cs @@ -6,6 +6,9 @@ public record KurrentFixtureOptions( KurrentClientSettings ClientSettings, IDictionary Environment ) { + public KurrentFixtureOptions RunInMemory(bool runInMemory = true) => + this with { Environment = Environment.With(x => x["EVENTSTORE_MEM_DB"] = runInMemory.ToString()) }; + public KurrentFixtureOptions WithoutDefaultCredentials() => this with { ClientSettings = ClientSettings.With(x => x.DefaultCredentials = null) }; public KurrentFixtureOptions RunProjections(bool runProjections = true) => diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs new file mode 100644 index 000000000..1eb96f88a --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs @@ -0,0 +1,51 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task connect_to_existing_with_start_from_not_set() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + TaskCompletionSource firstNonSystemEventSource = new(); + + foreach (var @event in Fixture.CreateTestEvents(10)) + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.Any, + new[] { + @event + } + ); + + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Assert.ThrowsAsync(() => firstNonSystemEventSource.Task.WithTimeout()); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs new file mode 100644 index 000000000..2706e0669 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs @@ -0,0 +1,46 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task connect_to_existing_with_start_from_set_to_end_position() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + TaskCompletionSource firstNonSystemEventSource = new(); + + foreach (var @event in Fixture.CreateTestEvents(10)) { + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.Any, + [@event] + ); + } + + await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Assert.ThrowsAsync(() => firstNonSystemEventSource.Task.WithTimeout()); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs new file mode 100644 index 000000000..f7004b890 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs @@ -0,0 +1,34 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllConnectWithoutReadPermissionsObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task connect_to_existing_without_read_all_permissions() { + var group = Fixture.GetGroupName(); + var user = Fixture.GetUserCredentials(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await Fixture.Users.CreateUserWithRetry( + user.Username!, + user.Username!, + [], + user.Password!, + TestCredentials.Root + ); + + await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; }, + userCredentials: user + ); + } + ); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs new file mode 100644 index 000000000..95c3aba2b --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs @@ -0,0 +1,131 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; +using Kurrent.Client.Tests; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllFilterObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryTheory] + [MemberData(nameof(FilterCases))] + public async Task happy_case_filtered_reads_all_existing_filtered_events(string filterName) { + var streamPrefix = $"{filterName}-{Fixture.GetStreamName()}"; + var group = Fixture.GetGroupName(); + var (getFilter, prepareEvent) = Filters.GetFilter(filterName); + var filter = getFilter(streamPrefix); + + await Fixture.Streams.AppendToStreamAsync( + Guid.NewGuid().ToString(), + StreamState.NoStream, + Fixture.CreateTestEvents(256) + ); + + await Fixture.Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.Any, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + var appearedEvents = new List(); + var events = Fixture.CreateTestEvents(20).Select(e => prepareEvent(streamPrefix, e)).ToArray(); + + foreach (var e in events) { + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}_{Guid.NewGuid():n}", + StreamState.NoStream, + [e] + ); + } + + await Fixture.Subscriptions.CreateToAllAsync( + group, + filter, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); + + await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + + await subscription.Messages + .OfType() + .Take(events.Length) + .ForEachAwaitAsync( + async e => { + var (resolvedEvent, _) = e; + appearedEvents.Add(resolvedEvent.Event); + await subscription.Ack(resolvedEvent); + } + ) + .WithTimeout(); + + Assert.Equal(events.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + } + + [RetryTheory] + [MemberData(nameof(FilterCases))] + public async Task happy_case_filtered_with_start_from_set(string filterName) { + var group = Fixture.GetGroupName(); + var streamPrefix = $"{filterName}-{Fixture.GetStreamName()}"; + var (getFilter, prepareEvent) = Filters.GetFilter(filterName); + var filter = getFilter(streamPrefix); + + var events = Fixture.CreateTestEvents(20).Select(e => prepareEvent(streamPrefix, e)).ToArray(); + var eventsToSkip = events.Take(10).ToArray(); + var eventsToCapture = events.Skip(10).ToArray(); + + IWriteResult? eventToCaptureResult = null; + + await Fixture.Streams.AppendToStreamAsync( + Guid.NewGuid().ToString(), + StreamState.NoStream, + Fixture.CreateTestEvents(256) + ); + + await Fixture.Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.Any, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + foreach (var e in eventsToSkip) { + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}_{Guid.NewGuid():n}", + StreamState.NoStream, + new[] { e } + ); + } + + foreach (var e in eventsToCapture) { + var result = await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}_{Guid.NewGuid():n}", + StreamState.NoStream, + new[] { e } + ); + + eventToCaptureResult ??= result; + } + + await Fixture.Subscriptions.CreateToAllAsync( + group, + filter, + new(startFrom: eventToCaptureResult!.LogPosition), + userCredentials: TestCredentials.Root + ); + + await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + + var appearedEvents = await subscription.Messages.OfType() + .Take(10) + .Select(e => e.ResolvedEvent.Event) + .ToArrayAsync() + .AsTask() + .WithTimeout(); + + Assert.Equal(eventsToCapture.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + } + + public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs new file mode 100644 index 000000000..2b8532ad4 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs @@ -0,0 +1,97 @@ +// ReSharper disable InconsistentNaming + +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllGetInfoObsoleteTests(SubscribeToAllGetInfoObsoleteTests.CustomFixture fixture) + : IClassFixture { + static readonly PersistentSubscriptionSettings Settings = new( + resolveLinkTos: true, + startFrom: Position.Start, + extraStatistics: true, + messageTimeout: TimeSpan.FromSeconds(9), + maxRetryCount: 11, + liveBufferSize: 303, + readBatchSize: 30, + historyBufferSize: 909, + checkPointAfter: TimeSpan.FromSeconds(1), + checkPointLowerBound: 1, + checkPointUpperBound: 1, + maxSubscriberCount: 500, + consumerStrategyName: SystemConsumerStrategies.Pinned + ); + + [RetryFact] + public async Task throws_with_non_existing_subscription() { + var group = $"NonExisting-{fixture.GetGroupName()}"; + + await Assert.ThrowsAsync( + async () => await fixture.Subscriptions.GetInfoToAllAsync(group, userCredentials: TestCredentials.Root) + ); + } + + [RetryFact] + public async Task throws_with_no_credentials() { + var group = $"NonExisting-{fixture.GetGroupName()}"; + + await Assert.ThrowsAsync( + async () => + await fixture.Subscriptions.GetInfoToAllAsync(group) + ); + } + + [RetryFact] + public async Task throws_with_non_existing_user() { + var group = $"NonExisting-{fixture.GetGroupName()}"; + + await Assert.ThrowsAsync( + async () => + await fixture.Subscriptions.GetInfoToAllAsync(group, userCredentials: TestCredentials.TestBadUser) + ); + } + + [RetryFact] + public async Task returns_result_with_normal_user_credentials() { + var result = await fixture.Subscriptions.GetInfoToAllAsync(fixture.Group, userCredentials: TestCredentials.Root); + + Assert.Equal("$all", result.EventSource); + } + + public class CustomFixture : KurrentTemporaryFixture { + public string Group { get; } + + public CustomFixture() : base(x => x.WithoutDefaultCredentials()) { + Group = GetGroupName(); + + OnSetup += async () => { + await Subscriptions.CreateToAllAsync(Group, Settings, userCredentials: TestCredentials.Root); + + var counter = 0; + var tcs = new TaskCompletionSource(); + + await Subscriptions.SubscribeToAllAsync( + Group, + (s, e, r, ct) => { + counter++; + + switch (counter) { + case 1: + s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + break; + + case > 10: + tcs.TrySetResult(); + break; + } + + return Task.CompletedTask; + }, + userCredentials: TestCredentials.Root + ); + }; + } + }; +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllListWithIncorrectCredentialsObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllListWithIncorrectCredentialsObsoleteTests.cs new file mode 100644 index 000000000..7c1426073 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllListWithIncorrectCredentialsObsoleteTests.cs @@ -0,0 +1,64 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllListWithIncorrectCredentialsObsoleteTests(ITestOutputHelper output, SubscribeToAllListWithIncorrectCredentialsObsoleteTests.CustomFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task throws_with_no_credentials() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + const int streamSubscriptionCount = 4; + const int allStreamSubscriptionCount = 3; + + for (var i = 0; i < streamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + for (var i = 0; i < allStreamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToAllAsync( + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + await Assert.ThrowsAsync(async () => await Fixture.Subscriptions.ListToAllAsync()); + } + + [RetryFact] + public async Task throws_with_non_existing_user() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + const int streamSubscriptionCount = 4; + const int allStreamSubscriptionCount = 3; + + for (var i = 0; i < streamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + for (var i = 0; i < allStreamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToAllAsync( + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + await Assert.ThrowsAsync( + async () => await Fixture.Subscriptions.ListToAllAsync(userCredentials: TestCredentials.TestBadUser) + ); + } + + public class CustomFixture() : KurrentTemporaryFixture(x => x.WithoutDefaultCredentials()); +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs new file mode 100644 index 000000000..36685373f --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs @@ -0,0 +1,103 @@ +using EventStore.Client; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllNoDefaultCredentialsObsoleteTests(ITestOutputHelper output, SubscribeToAllNoDefaultCredentialsObsoleteTests.CustomFixture fixture) + : KurrentPermanentTests(output, fixture) { + [RetryFact] + public async Task connect_to_existing_without_permissions() { + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; } + ); + } + ); + } + + [RetryFact] + public async Task throws_persistent_subscription_not_found() { + var group = Fixture.GetGroupName(); + + var ex = await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; }, + userCredentials: TestCredentials.Root + ); + } + ).WithTimeout(); + + Assert.Equal(SystemStreams.AllStream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task deleting_without_permissions() { + await Assert.ThrowsAsync(() => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString())); + } + + [RetryFact] + public async Task create_without_permissions() { + var group = Fixture.GetGroupName(); + + await Assert.ThrowsAsync( + () => + Fixture.Subscriptions.CreateToAllAsync( + group, + new() + ) + ); + } + + [RetryFact] + public async Task update_existing_without_permissions() { + var group = Fixture.GetGroupName(); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(), + userCredentials: TestCredentials.Root + ); + + await Assert.ThrowsAsync( + () => Fixture.Subscriptions.UpdateToAllAsync( + group, + new() + ) + ); + } + + [RetryFact] + public async Task update_non_existent() { + var group = Fixture.GetGroupName(); + await Assert.ThrowsAsync( + () => Fixture.Subscriptions.UpdateToAllAsync( + group, + new(), + userCredentials: TestCredentials.Root + ) + ); + } + + [RetryFact] + public Task update_with_prepare_position_larger_than_commit_position() { + var group = Fixture.GetGroupName(); + return Assert.ThrowsAsync( + () => + Fixture.Subscriptions.UpdateToAllAsync( + group, + new(startFrom: new Position(0, 1)), + userCredentials: TestCredentials.Root + ) + ); + } + + public class CustomFixture() : KurrentPermanentFixture(x => x.WithoutDefaultCredentials()); +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs new file mode 100644 index 000000000..957b42c69 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs @@ -0,0 +1,786 @@ +using System.Text; +using EventStore.Client; +using Grpc.Core; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllObsoleteTests(ITestOutputHelper output, KurrentPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { + [RetryFact] + public async Task connect_to_existing_with_max_one_client() { + // Arrange + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(maxSubscriberCount: 1), userCredentials: TestCredentials.Root); + + using var first = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; }, + userCredentials: TestCredentials.Root + ).WithTimeout(); + + var ex = await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; }, + userCredentials: TestCredentials.Root + ); + } + ).WithTimeout(); + + Assert.Equal(SystemStreams.AllStream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task connect_to_existing_with_permissions() { + // Arrange + var group = Fixture.GetGroupName(); + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; }, + (s, reason, ex) => dropped.TrySetResult((reason, ex)), + TestCredentials.Root + ).WithTimeout(); + + Assert.NotNull(subscription); + + await Assert.ThrowsAsync(() => dropped.Task.WithTimeout()); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_beginning() { + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + // append 10 events to random streams to make sure we have at least 10 events in the transaction file + foreach (var @event in Fixture.CreateTestEvents(10)) { + await Fixture.Streams.AppendToStreamAsync( + Guid.NewGuid().ToString(), + StreamState.NoStream, + [@event] + ); + } + + var events = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ToArrayAsync(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + var resolvedEvent = await firstEventSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.Equal(events![0].Event.EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_not_set_then_event_written() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + var expectedStreamId = Guid.NewGuid().ToString(); + var expectedEvent = Fixture.CreateTestEvents(1).First(); + + TaskCompletionSource firstNonSystemEventSource = new(); + + foreach (var @event in Fixture.CreateTestEvents(10)) { + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.Any, + [@event] + ); + } + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); + + var resolvedEvent = await firstNonSystemEventSource.Task.WithTimeout(); + Assert.Equal(expectedEvent!.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_set_to_end_position_then_event_written() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + var expectedStreamId = Fixture.GetStreamName(); + var expectedEvent = Fixture.CreateTestEvents().First(); + + TaskCompletionSource firstNonSystemEventSource = new(); + + foreach (var @event in Fixture.CreateTestEvents(10)) { + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.Any, + [@event] + ); + } + + await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); + + var resolvedEvent = await firstNonSystemEventSource.Task.WithTimeout(); + Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_set_to_invalid_middle_position() { + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> dropped = new(); + + var invalidPosition = new Position(1L, 1L); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: invalidPosition), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => await subscription.Ack(e), + (subscription, reason, ex) => { dropped.TrySetResult((reason, ex)); }, + TestCredentials.Root + ); + + var (reason, exception) = await dropped.Task.WithTimeout(); + Assert.Equal(SubscriptionDroppedReason.ServerError, reason); + Assert.IsType(exception); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_set_to_valid_middle_position() { + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ToArrayAsync(); + + var expectedEvent = events[events.Length / 2]; //just a random event in the middle of the results + + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: expectedEvent.OriginalPosition), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + var resolvedEvent = await firstEventSource.Task.WithTimeout(); + Assert.Equal(expectedEvent.OriginalPosition, resolvedEvent.Event.Position); + Assert.Equal(expectedEvent.Event.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.Event.EventStreamId, resolvedEvent.Event.EventStreamId); + } + + [RetryFact] + public async Task connect_with_retries() { + // Arrange + var group = Fixture.GetGroupName(); + + TaskCompletionSource retryCountSource = new(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + + // Act + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, r, ct) => { + if (r > 4) { + retryCountSource.TrySetResult(r.Value); + await subscription.Ack(e.Event.EventId); + } else { + await subscription.Nack( + PersistentSubscriptionNakEventAction.Retry, + "Not yet tried enough times", + e + ); + } + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + retryCountSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + // Assert + Assert.Equal(5, await retryCountSource.Task.WithTimeout()); + } + + [RetryFact] + public async Task deleting_existing_with_subscriber() { + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> dropped = new(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (s, e, i, ct) => await s.Ack(e), + (s, r, e) => dropped.TrySetResult((r, e)), + TestCredentials.Root + ); + + // todo: investigate why this test is flaky without this delay + await Task.Delay(500); + + await Fixture.Subscriptions.DeleteToAllAsync(group, userCredentials: TestCredentials.Root); + + var (reason, exception) = await dropped.Task.WithTimeout(); + Assert.Equal(SubscriptionDroppedReason.ServerError, reason); + var ex = Assert.IsType(exception); + + Assert.Equal(SystemStreams.AllStream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task happy_case_catching_up_to_link_to_events_manual_ack() { + var group = Fixture.GetGroupName(); + var bufferCount = 10; + var eventWriteCount = bufferCount * 2; + TaskCompletionSource eventsReceived = new(); + int eventReceivedCount = 0; + + var events = Fixture.CreateTestEvents(eventWriteCount) + .Select( + (e, i) => new EventData( + e.EventId, + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"{i}@test"), + contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream + ) + ) + .ToArray(); + + foreach (var e in events) { + await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] { e }); + } + + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + + if (e.OriginalStreamId.StartsWith("test-") + && Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + await eventsReceived.Task.WithTimeout(); + } + + [RetryFact] + public async Task happy_case_catching_up_to_normal_events_manual_ack() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + var bufferCount = 10; + var eventWriteCount = bufferCount * 2; + int eventReceivedCount = 0; + + TaskCompletionSource eventsReceived = new(); + + var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); + + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + + if (e.OriginalStreamId.StartsWith("test-") + && Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + await eventsReceived.Task.WithTimeout(); + } + + [RetryFact] + public async Task happy_case_writing_and_subscribing_to_normal_events_manual_ack() { + var group = Fixture.GetGroupName(); + var bufferCount = 10; + var eventWriteCount = bufferCount * 2; + int eventReceivedCount = 0; + + TaskCompletionSource eventsReceived = new(); + + var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); + + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + if (e.OriginalStreamId.StartsWith("test-") + && Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + foreach (var e in events) { + await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] { e }); + } + + await eventsReceived.Task.WithTimeout(); + } + + [RetryFact] + public async Task update_existing_with_check_point() { + var group = Fixture.GetGroupName(); + var events = Fixture.CreateTestEvents(5).ToArray(); + var appearedEvents = new List(); + + TaskCompletionSource appeared = new(); + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + TaskCompletionSource resumedSource = new(); + Position checkPoint = default; + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, [e]); + + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(checkPointLowerBound: 5, checkPointAfter: TimeSpan.FromSeconds(1), startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); + + using var firstSubscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (s, e, r, ct) => { + appearedEvents.Add(e); + + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); + + await s.Ack(e); + }, + (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), + TestCredentials.Root + ); + + await Task.WhenAll(appeared.Task, WaitForCheckpoint().WithTimeout()); + + // Force restart of the subscription + await Fixture.Subscriptions.UpdateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await droppedSource.Task.WithTimeout(); + + using var secondSubscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (s, e, r, ct) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + s.Dispose(); + }, + (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + }, + TestCredentials.Root + ); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, new[] { e }); + + var resumedEvent = await resumedSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.True(resumedEvent.Event.Position > checkPoint); + + return; + + async Task WaitForCheckpoint() { + await using var subscription = Fixture.Streams.SubscribeToStream( + $"$persistentsubscription-$all::{group}-checkpoint", + FromStream.Start, + userCredentials: TestCredentials.Root + ); + + await foreach (var message in subscription.Messages) { + if (message is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + checkPoint = resolvedEvent.Event.Data.ParsePosition(); + return; + } + } + } + + [RetryFact] + public async Task update_existing_with_check_point_filtered() { + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + TaskCompletionSource resumedSource = new(); + TaskCompletionSource appeared = new(); + + List appearedEvents = []; + + EventData[] events = Fixture.CreateTestEvents(5).ToArray(); + + Position checkPoint = default; + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, [e]); + + await Fixture.Subscriptions.CreateToAllAsync( + group, + StreamFilter.Prefix("test"), + new( + checkPointLowerBound: 5, + checkPointAfter: TimeSpan.FromSeconds(1), + startFrom: Position.Start + ), + userCredentials: TestCredentials.Root + ); + + await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (s, e, r, ct) => { + appearedEvents.Add(e); + + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); + + await s.Ack(e); + }, + (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), + TestCredentials.Root + ); + + await Task.WhenAll(appeared.Task, Checkpointed()).WithTimeout(); + + // Force restart of the subscription + await Fixture.Subscriptions.UpdateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await droppedSource.Task.WithTimeout(); + + await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (s, e, r, ct) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + s.Dispose(); + }, + (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + }, + TestCredentials.Root + ); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, new[] { e }); + + Task resumed = resumedSource.Task; + + var resumedEvent = await resumed.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.True(resumedEvent.Event.Position > checkPoint); + + return; + + async Task Checkpointed() { + await using var subscription = Fixture.Streams.SubscribeToStream( + $"$persistentsubscription-$all::{group}-checkpoint", + FromStream.Start, + userCredentials: TestCredentials.Root + ); + + await foreach (var message in subscription.Messages) { + if (message is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + checkPoint = resolvedEvent.Event.Data.ParsePosition(); + return; + } + } + } + + [RetryFact] + public async Task update_existing_with_subscribers() { + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + + using var subscription = Fixture.Subscriptions.SubscribeToAllAsync( + group, + delegate { return Task.CompletedTask; }, + (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), + TestCredentials.Root + ); + + // todo: investigate why this test is flaky without this delay + await Task.Delay(500); + + await Fixture.Subscriptions.UpdateToAllAsync( + group, + new(), + userCredentials: TestCredentials.Root + ); + + var (reason, exception) = await droppedSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.Equal(SubscriptionDroppedReason.ServerError, reason); + var ex = Assert.IsType(exception); + + Assert.Equal(SystemStreams.AllStream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task when_writing_and_filtering_out_events() { + var events = Fixture.CreateTestEvents(10).ToArray(); + var group = Fixture.GetGroupName(); + var prefix = Guid.NewGuid().ToString("N"); + + TaskCompletionSource appeared = new(); + + Position firstCheckPoint = default; + Position secondCheckPoint = default; + List appearedEvents = []; + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync(prefix + Guid.NewGuid(), StreamState.Any, [e]); + + await Fixture.Subscriptions.CreateToAllAsync( + group, + StreamFilter.Prefix(prefix), + new( + checkPointLowerBound: 1, + checkPointUpperBound: 5, + checkPointAfter: TimeSpan.FromSeconds(1), + startFrom: Position.Start + ), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( + group, + async (s, e, r, ct) => { + appearedEvents.Add(e); + + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); + + await s.Ack(e); + }, + userCredentials: TestCredentials.Root + ); + + await Task.WhenAll(appeared.Task, WaitForCheckpoints().WithTimeout()); + + foreach (var e in events) { + await Fixture.Streams.AppendToStreamAsync( + "filtered-out-stream-" + Guid.NewGuid(), + StreamState.Any, + [e] + ); + } + + Assert.True(secondCheckPoint > firstCheckPoint); + Assert.Equal(events.Select(e => e.EventId), appearedEvents.Select(e => e.Event.EventId)); + + return; + + async Task WaitForCheckpoints() { + bool firstCheckpointSet = false; + await using var subscription = Fixture.Streams.SubscribeToStream( + $"$persistentsubscription-$all::{group}-checkpoint", + FromStream.Start, + userCredentials: TestCredentials.Root + ); + + await foreach (var message in subscription.Messages) { + if (message is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + if (!firstCheckpointSet) { + firstCheckPoint = resolvedEvent.Event.Data.ParsePosition(); + firstCheckpointSet = true; + } else { + secondCheckPoint = resolvedEvent.Event.Data.ParsePosition(); + return; + } + } + } + } + + // [RetryFact] + // public async Task when_writing_and_subscribing_to_normal_events_manual_nack() { + // var group = Fixture.GetGroupName(); + // var bufferCount = 10; + // var eventWriteCount = bufferCount * 2; + // var prefix = $"{Guid.NewGuid():N}-"; + // + // var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); + // + // await Fixture.Subscriptions.CreateToAllAsync( + // group, + // new(startFrom: Position.Start, resolveLinkTos: true), + // userCredentials: TestCredentials.Root + // ); + // + // var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + // + // foreach (var e in events) + // await Fixture.Streams.AppendToStreamAsync(prefix + Guid.NewGuid(), StreamState.Any, [e]); + // + // await subscription.Messages.OfType() + // .SelectAwait( + // async e => { + // await subscription.Nack(PersistentSubscriptionNakEventAction.Park, "fail", e.ResolvedEvent); + // return e; + // } + // ) + // .Where(e => e.ResolvedEvent.OriginalStreamId.StartsWith(prefix)) + // .Take(events.Length) + // .ToArrayAsync() + // .AsTask() + // .WithTimeout(); + // } + + // [RetryFact] + // public async Task update_existing_with_commit_position_larger_than_last_indexed_position() { + // var group = Fixture.GetGroupName(); + // + // await Fixture.Subscriptions.CreateToAllAsync( + // group, + // new(), + // userCredentials: TestCredentials.Root + // ); + // + // var lastEvent = await Fixture.Streams.ReadAllAsync( + // Direction.Backwards, + // Position.End, + // 1, + // userCredentials: TestCredentials.Root + // ).FirstAsync(); + // + // var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); + // var ex = await Assert.ThrowsAsync( + // () => + // Fixture.Subscriptions.UpdateToAllAsync( + // group, + // new(startFrom: new Position(lastCommitPosition + 1, lastCommitPosition)), + // userCredentials: TestCredentials.Root + // ) + // ); + // + // Assert.Equal(StatusCode.Internal, ex.StatusCode); + // } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReplayParkedObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReplayParkedObsoleteTests.cs new file mode 100644 index 000000000..7e1f2366d --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReplayParkedObsoleteTests.cs @@ -0,0 +1,87 @@ +using EventStore.Client; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllReplayParkedObsoleteTests(ITestOutputHelper output, SubscribeToAllReplayParkedTests.CustomFixture fixture) + : KurrentPermanentTests(output, fixture) { + [RetryFact] + public async Task does_not_throw() { + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await Fixture.Subscriptions.ReplayParkedMessagesToAllAsync( + group, + userCredentials: TestCredentials.Root + ); + + await Fixture.Subscriptions.ReplayParkedMessagesToAllAsync( + group, + 100, + userCredentials: TestCredentials.Root + ); + } + + [RetryFact] + public async Task throws_when_given_non_existing_subscription() { + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + var nonExistingGroup = Fixture.GetGroupName(); + await Assert.ThrowsAsync( + () => + Fixture.Subscriptions.ReplayParkedMessagesToAllAsync( + nonExistingGroup, + userCredentials: TestCredentials.Root + ) + ); + } + + [RetryFact] + public async Task throws_with_no_credentials() { + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await Assert.ThrowsAsync( + () => + Fixture.Subscriptions.ReplayParkedMessagesToAllAsync(group) + ); + } + + [RetryFact] + public async Task throws_with_non_existing_user() { + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); + + await Assert.ThrowsAsync( + () => + Fixture.Subscriptions.ReplayParkedMessagesToAllAsync( + group, + userCredentials: TestCredentials.TestBadUser + ) + ); + } + + [RetryFact] + public async Task throws_with_normal_user_credentials() { + var user = Fixture.GetUserCredentials(); + + await Fixture.Users + .CreateUserWithRetry(user.Username!, user.Username!, [], user.Password!, TestCredentials.Root) + .WithTimeout(); + + await Assert.ThrowsAsync( + () => + Fixture.Subscriptions.ReplayParkedMessagesToAllAsync( + Fixture.GetGroupName(), + userCredentials: user + ) + ); + } + + public class CustomFixture() : KurrentPermanentFixture(x => x.WithoutDefaultCredentials()); +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllResultWithNormalUserCredentialsObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllResultWithNormalUserCredentialsObsoleteTests.cs new file mode 100644 index 000000000..e1815bf9b --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllResultWithNormalUserCredentialsObsoleteTests.cs @@ -0,0 +1,35 @@ +using Kurrent.Client.Tests.TestNode; +using Kurrent.Client.Tests; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllResultWithNormalUserCredentialsObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task returns_result_with_normal_user_credentials() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + const int streamSubscriptionCount = 4; + const int allStreamSubscriptionCount = 3; + + for (var i = 0; i < streamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + for (var i = 0; i < allStreamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToAllAsync( + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + var result = await Fixture.Subscriptions.ListToAllAsync(userCredentials: TestCredentials.Root); + Assert.Equal(allStreamSubscriptionCount, result.Count()); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsAllSubscriptionsObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsAllSubscriptionsObsoleteTests.cs new file mode 100644 index 000000000..07035b60b --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsAllSubscriptionsObsoleteTests.cs @@ -0,0 +1,42 @@ +using Kurrent.Client.Tests.TestNode; +using Kurrent.Client.Tests; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllReturnsAllSubscriptionsObsoleteTests(ITestOutputHelper output, SubscribeToAllReturnsAllSubscriptions.CustomFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task returns_all_subscriptions() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + const int streamSubscriptionCount = 4; + const int allStreamSubscriptionCount = 3; + const int totalSubscriptionCount = streamSubscriptionCount + allStreamSubscriptionCount; + + for (var i = 0; i < streamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + for (var i = 0; i < allStreamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToAllAsync( + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + var result = (await Fixture.Subscriptions.ListAllAsync(userCredentials: TestCredentials.Root)).ToList(); + Assert.Equal(totalSubscriptionCount, result.Count); + } + + public class CustomFixture : KurrentTemporaryFixture { + public CustomFixture() { + SkipPsWarmUp = true; + } + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsSubscriptionsToAllStreamObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsSubscriptionsToAllStreamObsoleteTests.cs new file mode 100644 index 000000000..e0c1f647b --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllReturnsSubscriptionsToAllStreamObsoleteTests.cs @@ -0,0 +1,36 @@ +using Kurrent.Client.Tests.TestNode; +using Kurrent.Client.Tests; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllReturnsSubscriptionsToAllStreamObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task returns_subscriptions_to_all_stream() { + var group = Fixture.GetGroupName(); + var stream = Fixture.GetStreamName(); + + const int streamSubscriptionCount = 4; + const int allStreamSubscriptionCount = 3; + + for (var i = 0; i < streamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + for (var i = 0; i < allStreamSubscriptionCount; i++) + await Fixture.Subscriptions.CreateToAllAsync( + group + i, + new(), + userCredentials: TestCredentials.Root + ); + + var result = (await Fixture.Subscriptions.ListToAllAsync(userCredentials: TestCredentials.Root)).ToList(); + Assert.Equal(allStreamSubscriptionCount, result.Count); + Assert.All(result, s => Assert.Equal("$all", s.EventSource)); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs new file mode 100644 index 000000000..844b24644 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs @@ -0,0 +1,88 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; +using Kurrent.Client.Tests; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllUpdateExistingWithCheckpointObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task update_existing_with_check_point_should_resumes_from_check_point() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + StreamPosition checkPoint = default; + + var events = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(checkPointLowerBound: 5, checkPointAfter: TimeSpan.FromSeconds(1), startFrom: StreamPosition.Start), + userCredentials: TestCredentials.Root + ); + + await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + await enumerator.MoveNextAsync(); + + await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoint().WithTimeout()); + + // Force restart of the subscription + await Fixture.Subscriptions.UpdateToStreamAsync(stream, group, new(), userCredentials: TestCredentials.Root); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); + + await using var sub = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + + var resolvedEvent = await sub.Messages + .OfType() + .Select(e => e.ResolvedEvent) + .FirstAsync() + .AsTask() + .WithTimeout(); + + Assert.Equal(checkPoint.Next(), resolvedEvent.Event.EventNumber); + + return; + + async Task Subscribe() { + var count = 0; + + while (await enumerator.MoveNextAsync()) { + if (enumerator.Current is not PersistentSubscriptionMessage.Event(var resolvedEvent, _)) { + continue; + } + + count++; + + await subscription.Ack(resolvedEvent); + if (count >= events.Length) { + break; + } + } + } + + async Task WaitForCheckpoint() { + await using var subscription = Fixture.Streams.SubscribeToStream( + $"$persistentsubscription-{stream}::{group}-checkpoint", + FromStream.Start, + userCredentials: TestCredentials.Root + ); + + await foreach (var message in subscription.Messages) { + if (message is not StreamMessage.Event (var resolvedEvent)) { + continue; + } + + checkPoint = resolvedEvent.Event.Data.ParseStreamPosition(); + return; + } + } + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllWithoutPSObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllWithoutPSObsoleteTests.cs new file mode 100644 index 000000000..f676c9cb0 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllWithoutPSObsoleteTests.cs @@ -0,0 +1,17 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; +using Kurrent.Client.Tests; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToAllWithoutPSObsoleteTests(ITestOutputHelper output, KurrentTemporaryFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [RetryFact] + public async Task list_without_persistent_subscriptions() { + await Assert.ThrowsAsync( + async () => + await Fixture.Subscriptions.ListToAllAsync(userCredentials: TestCredentials.Root) + ); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs similarity index 90% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs index b6a05c5cc..d69349f3d 100644 --- a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs @@ -19,7 +19,7 @@ await Fixture.Streams.AppendToStreamAsync( ); await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); await Assert.ThrowsAsync( () => subscription.Messages diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectWithoutReadPermissionsTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllConnectWithoutReadPermissionsTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllFilterTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllFilterTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllGetInfoTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllGetInfoTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllListWithIncorrectCredentialsTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllListWithIncorrectCredentialsTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllListWithIncorrectCredentialsTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllListWithIncorrectCredentialsTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllNoDefaultCredentialsTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllNoDefaultCredentialsTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllReplayParkedTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllReplayParkedTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllReplayParkedTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllReplayParkedTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllResultWithNormalUserCredentialsTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllResultWithNormalUserCredentialsTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllResultWithNormalUserCredentialsTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllResultWithNormalUserCredentialsTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllReturnsAllSubscriptions.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllReturnsAllSubscriptions.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllReturnsAllSubscriptions.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllReturnsAllSubscriptions.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllReturnsSubscriptionsToAllStreamTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllReturnsSubscriptionsToAllStreamTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllReturnsSubscriptionsToAllStreamTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllReturnsSubscriptionsToAllStreamTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllUpdateExistingWithCheckpointTest.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllUpdateExistingWithCheckpointTest.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllWithoutPSTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllWithoutPSTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAllWithoutPSTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllWithoutPSTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests.cs new file mode 100644 index 000000000..fa2ca1a32 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests.cs @@ -0,0 +1,36 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests( + ITestOutputHelper output, + SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests.CustomFixture fixture +) + : KurrentTemporaryTests(output, fixture) { + [Fact] + public async Task connect_to_existing_without_permissions() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + delegate { return Task.CompletedTask; } + ); + } + ).WithTimeout(); + } + + public class CustomFixture() : KurrentTemporaryFixture(x => x.WithoutDefaultCredentials()); +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs new file mode 100644 index 000000000..5381dc013 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs @@ -0,0 +1,201 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToStreamGetInfoObsoleteTests(SubscribeToStreamGetInfoObsoleteTests.CustomFixture fixture) + : IClassFixture { + static readonly PersistentSubscriptionSettings Settings = new( + true, + StreamPosition.Start, + true, + TimeSpan.FromSeconds(9), + 11, + 303, + 30, + 909, + TimeSpan.FromSeconds(1), + 1, + 1, + 500, + SystemConsumerStrategies.RoundRobin + ); + + public static IEnumerable AllowedUsers() { + yield return new object[] { TestCredentials.Root }; + } + + [Theory] + [MemberData(nameof(AllowedUsers))] + public async Task returns_expected_result(UserCredentials credentials) { + var result = await fixture.Subscriptions.GetInfoToStreamAsync(fixture.Stream, fixture.Group, userCredentials: credentials); + + Assert.Equal(fixture.Stream, result.EventSource); + Assert.Equal(fixture.Group, result.GroupName); + Assert.NotNull(Settings.StartFrom); + Assert.True(result.Stats.TotalItems > 0); + Assert.True(result.Stats.OutstandingMessagesCount > 0); + Assert.True(result.Stats.AveragePerSecond >= 0); + Assert.True(result.Stats.ParkedMessageCount >= 0); + Assert.True(result.Stats.AveragePerSecond >= 0); + Assert.True(result.Stats.ReadBufferCount >= 0); + Assert.True(result.Stats.RetryBufferCount >= 0); + Assert.True(result.Stats.CountSinceLastMeasurement >= 0); + Assert.True(result.Stats.TotalInFlightMessages >= 0); + Assert.NotNull(result.Stats.LastKnownEventPosition); + Assert.NotNull(result.Stats.LastCheckpointedEventPosition); + Assert.True(result.Stats.LiveBufferCount >= 0); + + Assert.NotNull(result.Connections); + Assert.NotEmpty(result.Connections); + var connection = result.Connections.First(); + Assert.NotNull(connection.From); + Assert.Equal(TestCredentials.Root.Username, connection.Username); + Assert.NotEmpty(connection.ConnectionName); + Assert.True(connection.AverageItemsPerSecond >= 0); + Assert.True(connection.TotalItems >= 0); + Assert.True(connection.CountSinceLastMeasurement >= 0); + Assert.True(connection.AvailableSlots >= 0); + Assert.True(connection.InFlightMessages >= 0); + Assert.NotNull(connection.ExtraStatistics); + Assert.NotEmpty(connection.ExtraStatistics); + + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Highest); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Mean); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Median); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Fastest); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Quintile1); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Quintile2); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Quintile3); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Quintile4); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.Quintile5); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.NinetyPercent); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.NinetyFivePercent); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.NinetyNinePercent); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.NinetyNinePointFivePercent); + AssertKeyAndValue(connection.ExtraStatistics, PersistentSubscriptionExtraStatistic.NinetyNinePointNinePercent); + + Assert.NotNull(result.Settings); + Assert.Equal(Settings.StartFrom, result.Settings!.StartFrom); + Assert.Equal(Settings.ResolveLinkTos, result.Settings!.ResolveLinkTos); + Assert.Equal(Settings.ExtraStatistics, result.Settings!.ExtraStatistics); + Assert.Equal(Settings.MessageTimeout, result.Settings!.MessageTimeout); + Assert.Equal(Settings.MaxRetryCount, result.Settings!.MaxRetryCount); + Assert.Equal(Settings.LiveBufferSize, result.Settings!.LiveBufferSize); + Assert.Equal(Settings.ReadBatchSize, result.Settings!.ReadBatchSize); + Assert.Equal(Settings.HistoryBufferSize, result.Settings!.HistoryBufferSize); + Assert.Equal(Settings.CheckPointAfter, result.Settings!.CheckPointAfter); + Assert.Equal(Settings.CheckPointLowerBound, result.Settings!.CheckPointLowerBound); + Assert.Equal(Settings.CheckPointUpperBound, result.Settings!.CheckPointUpperBound); + Assert.Equal(Settings.MaxSubscriberCount, result.Settings!.MaxSubscriberCount); + Assert.Equal(Settings.ConsumerStrategyName, result.Settings!.ConsumerStrategyName); + } + + [RetryFact] + public async Task throws_when_given_non_existing_subscription() => + await Assert.ThrowsAsync( + async () => { + await fixture.Subscriptions.GetInfoToStreamAsync( + "NonExisting", + "NonExisting", + userCredentials: TestCredentials.Root + ); + } + ); + + [Fact(Skip = "Unable to produce same behavior with HTTP fallback!")] + public async Task throws_with_non_existing_user() { + var group = $"NonExisting-{fixture.GetGroupName()}"; + var stream = $"NonExisting-{fixture.GetStreamName()}"; + + await Assert.ThrowsAsync( + async () => { + await fixture.Subscriptions.GetInfoToStreamAsync( + stream, + group, + userCredentials: TestCredentials.TestBadUser + ); + } + ); + } + + [RetryFact] + public async Task throws_with_no_credentials() { + var group = $"NonExisting-{fixture.GetGroupName()}"; + var stream = $"NonExisting-{fixture.GetStreamName()}"; + + await Assert.ThrowsAsync( + async () => { + await fixture.Subscriptions.GetInfoToStreamAsync( + stream, + group + ); + } + ); + } + + [RetryFact] + public async Task returns_result_for_normal_user() { + var result = await fixture.Subscriptions.GetInfoToStreamAsync( + fixture.Stream, + fixture.Group, + userCredentials: TestCredentials.Root + ); + + Assert.NotNull(result); + } + + public class CustomFixture : KurrentTemporaryFixture { + public string Group { get; set; } + public string Stream { get; set; } + + public CustomFixture() : base(x => x.WithoutDefaultCredentials()) { + Group = GetGroupName(); + Stream = GetStreamName(); + + OnSetup += async () => { + await Subscriptions.CreateToStreamAsync( + groupName: Group, + streamName: Stream, + settings: Settings, + userCredentials: TestCredentials.Root + ); + + var counter = 0; + var tcs = new TaskCompletionSource(); + + await Subscriptions.SubscribeToStreamAsync( + Stream, + Group, + (s, e, r, ct) => { + counter++; + + if (counter == 1) + s.Nack(PersistentSubscriptionNakEventAction.Park, "Test", e); + + if (counter > 10) + tcs.TrySetResult(); + + return Task.CompletedTask; + }, + userCredentials: TestCredentials.Root + ); + + for (var i = 0; i < 15; i++) { + await Streams.AppendToStreamAsync( + Stream, + StreamState.Any, + [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], + userCredentials: TestCredentials.Root + ); + } + }; + } + }; + + void AssertKeyAndValue(IDictionary items, string key) { + Assert.True(items.ContainsKey(key)); + Assert.True(items[key] > 0); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs new file mode 100644 index 000000000..2f192c426 --- /dev/null +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -0,0 +1,948 @@ +using System.Text; +using EventStore.Client; + +namespace Kurrent.Client.Tests.PersistentSubscriptions; + +[Trait("Category", "Target:PersistentSubscriptions")] +public class SubscribeToStreamObsoleteTests(ITestOutputHelper output, KurrentPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { + [RetryFact] + public async Task connect_to_existing_with_max_one_client() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(maxSubscriberCount: 1), + userCredentials: TestCredentials.Root + ); + + using var first = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + delegate { return Task.CompletedTask; }, + userCredentials: TestCredentials.Root + ).WithTimeout(); + + var ex = await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + delegate { return Task.CompletedTask; }, + userCredentials: TestCredentials.Root + ); + } + ).WithTimeout(); + + Assert.Equal(stream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task connect_to_existing_with_permissions() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + delegate { return Task.CompletedTask; }, + (s, reason, ex) => dropped.TrySetResult((reason, ex)), + TestCredentials.Root + ).WithTimeout(); + + Assert.NotNull(subscription); + + await Assert.ThrowsAsync(() => dropped.Task.WithTimeout()); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_beginning_and_events_in_it() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.Start), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + var firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); + Assert.Equal(events[0].EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_beginning_and_no_stream() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents().ToArray(); + + var eventId = events.Single().EventId; + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var firstEvent = await firstEventSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.Equal(StreamPosition.Start, firstEvent.Event.EventNumber); + Assert.Equal(eventId, firstEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_not_set_and_events_in_it() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + var firstEvent = firstEventSource.Task; + + await Assert.ThrowsAsync(() => firstEvent.WithTimeout()); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_not_set_and_events_in_it_then_event_written() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(11).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(10)); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(9), events.Skip(10)); + + var firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(); + Assert.Equal(new(10), resolvedEvent.Event.EventNumber); + Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_set_to_end_position_and_events_in_it() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.End), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + var firstEvent = firstEventSource.Task; + await Assert.ThrowsAsync(() => firstEvent.WithTimeout()); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_set_to_end_position_and_events_in_it_then_event_written() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(11).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(10)); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.End), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(9), events.Skip(10)); + + var firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(); + Assert.Equal(new(10), resolvedEvent.Event.EventNumber); + Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_two_and_no_stream() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(3).ToArray(); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: new StreamPosition(2)), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var firstEvent = firstEventSource.Task; + var resolvedEvent = await firstEvent.WithTimeout(TimeSpan.FromSeconds(10)); + var eventId = events.Last().EventId; + + Assert.Equal(new(2), resolvedEvent.Event.EventNumber); + Assert.Equal(eventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_x_set_and_events_in_it() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(10)); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: new StreamPosition(4)), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(9), events.Skip(10)); + + var firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(); + Assert.Equal(new(4), resolvedEvent.Event.EventNumber); + Assert.Equal(events.Skip(4).First().EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_x_set_and_events_in_it_then_event_written() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(11).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(10)); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: new StreamPosition(10)), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(9), events.Skip(10)); + + var firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(); + Assert.Equal(new(10), resolvedEvent.Event.EventNumber); + Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_existing_with_start_from_x_set_higher_than_x_and_events_in_it_then_event_written() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(12).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(11)); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: new StreamPosition(11)), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(10), events.Skip(11)); + + var firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(); + Assert.Equal(new(11), resolvedEvent.Event.EventNumber); + Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task connect_to_non_existing_with_permissions() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + var ex = await Assert.ThrowsAsync( + async () => { + using var _ = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + delegate { return Task.CompletedTask; }, + userCredentials: TestCredentials.Root + ); + } + ).WithTimeout(); + + Assert.Equal(stream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task connect_with_retries() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource retryCountSource = new(); + + var events = Fixture.CreateTestEvents().ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.Start), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + if (r > 4) { + retryCountSource.TrySetResult(r.Value); + await subscription.Ack(e.Event.EventId); + } else { + await subscription.Nack( + PersistentSubscriptionNakEventAction.Retry, + "Not yet tried enough times", + e + ); + } + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + retryCountSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + Task retryCount = retryCountSource.Task; + Assert.Equal(5, await retryCount.WithTimeout()); + } + + [RetryFact] + public async Task connecting_to_a_persistent_subscription() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource firstEventSource = new(); + + var events = Fixture.CreateTestEvents(12).ToArray(); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(11)); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: new StreamPosition(11)), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(10), events.Skip(11)); + + Task firstEvent = firstEventSource.Task; + + var resolvedEvent = await firstEvent.WithTimeout(); + Assert.Equal(new(11), resolvedEvent.Event.EventNumber); + Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + } + + [RetryFact] + public async Task deleting_existing_with_subscriber_the_subscription_is_dropped() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> dropped = new(); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + (_, _, _, _) => Task.CompletedTask, + (_, r, e) => dropped.TrySetResult((r, e)), + TestCredentials.Root + ); + + await Fixture.Subscriptions.DeleteToStreamAsync( + stream, + group, + userCredentials: TestCredentials.Root + ); + + var (reason, exception) = await dropped.Task.WithTimeout(); + Assert.Equal(SubscriptionDroppedReason.ServerError, reason); + var ex = Assert.IsType(exception); + + Assert.Equal(stream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [Fact(Skip = "Isn't this how it should work?")] + public async Task deleting_existing_with_subscriber_the_subscription_is_dropped_with_not_found() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> _dropped = new(); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + (_, _, _, _) => Task.CompletedTask, + (_, r, e) => _dropped.TrySetResult((r, e)), + TestCredentials.Root + ); + + await Fixture.Subscriptions.DeleteToStreamAsync( + stream, + group, + userCredentials: TestCredentials.Root + ); + + Task<(SubscriptionDroppedReason, Exception?)> dropped = _dropped.Task; + + var (reason, exception) = await dropped.WithTimeout(); + Assert.Equal(SubscriptionDroppedReason.ServerError, reason); + var ex = Assert.IsType(exception); + Assert.Equal(stream, ex.StreamName); + Assert.Equal("groupname123", ex.GroupName); + } + + [RetryFact] + public async Task happy_case_catching_up_to_link_to_events_manual_ack() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + const int bufferCount = 10; + const int eventWriteCount = bufferCount * 2; + + EventData[] events; + TaskCompletionSource eventsReceived = new(); + int eventReceivedCount = 0; + + events = Fixture.CreateTestEvents(eventWriteCount) + .Select( + (e, i) => new EventData( + e.EventId, + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"{i}@{stream}"), + contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream + ) + ) + .ToArray(); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { e }); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.Start, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + await eventsReceived.Task.WithTimeout(); + } + + [RetryFact] + public async Task happy_case_catching_up_to_normal_events_manual_ack() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + const int bufferCount = 10; + const int eventWriteCount = bufferCount * 2; + + int eventReceivedCount = 0; + + var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); + TaskCompletionSource eventsReceived = new(); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { e }); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.Start, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + await eventsReceived.Task.WithTimeout(); + } + + [RetryFact] + public async Task happy_case_writing_and_subscribing_to_normal_events_manual_ack() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + const int bufferCount = 10; + const int eventWriteCount = bufferCount * 2; + + int eventReceivedCount = 0; + + var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); + TaskCompletionSource eventsReceived = new(); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.End, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); + + await eventsReceived.Task.WithTimeout(); + } + + [RetryFact] + public async Task update_existing_with_check_point() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + StreamPosition checkPoint = default; + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + TaskCompletionSource resumedSource = new(); + TaskCompletionSource appeared = new(); + List appearedEvents = []; + EventData[] events = Fixture.CreateTestEvents(5).ToArray(); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new( + checkPointLowerBound: 5, + checkPointAfter: TimeSpan.FromSeconds(1), + startFrom: StreamPosition.Start + ), + userCredentials: TestCredentials.Root + ); + + var checkPointStream = $"$persistentsubscription-{stream}::{group}-checkpoint"; + + await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (s, e, _, _) => { + appearedEvents.Add(e); + await s.Ack(e); + + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); + }, + (_, reason, ex) => droppedSource.TrySetResult((reason, ex)), + TestCredentials.Root + ); + + await Task.WhenAll(appeared.Task.WithTimeout(), Checkpointed()); + + // Force restart of the subscription + await Fixture.Subscriptions.UpdateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + await droppedSource.Task.WithTimeout(); + + await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (s, e, _, _) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + }, + (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + }, + TestCredentials.Root + ); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); + + var resumedEvent = await resumedSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.Equal(checkPoint.Next(), resumedEvent.Event.EventNumber); + + return; + + async Task Checkpointed() { + await using var subscription = Fixture.Streams.SubscribeToStream( + checkPointStream, + FromStream.Start, + userCredentials: TestCredentials.Root + ); + + await foreach (var message in subscription.Messages) { + if (message is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + checkPoint = resolvedEvent.Event.Data.ParseStreamPosition(); + return; + } + + throw new InvalidOperationException(); + } + } + + [RetryFact] + public async Task update_existing_with_subscribers() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + delegate { return Task.CompletedTask; }, + (_, reason, ex) => droppedSource.TrySetResult((reason, ex)), + TestCredentials.Root + ); + + await Fixture.Subscriptions.UpdateToStreamAsync( + stream, + group, + new(), + userCredentials: TestCredentials.Root + ); + + var dropped = droppedSource.Task; + + var (reason, exception) = await dropped.WithTimeout(TimeSpan.FromSeconds(10)); + Assert.Equal(SubscriptionDroppedReason.ServerError, reason); + var ex = Assert.IsType(exception); + + Assert.Equal(stream, ex.StreamName); + Assert.Equal(group, ex.GroupName); + } + + [RetryFact] + public async Task when_writing_and_subscribing_to_normal_events_manual_nack() { + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + + const int bufferCount = 10; + const int eventWriteCount = bufferCount * 2; + + int eventReceivedCount = 0; + + EventData[] events = Fixture.CreateTestEvents(eventWriteCount) + .ToArray(); + + TaskCompletionSource eventsReceived = new(); + + await Fixture.Subscriptions.CreateToStreamAsync( + stream, + group, + new(startFrom: StreamPosition.Start, resolveLinkTos: true), + userCredentials: TestCredentials.Root + ); + + using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( + stream, + group, + async (subscription, e, retryCount, ct) => { + await subscription.Nack(PersistentSubscriptionNakEventAction.Park, "fail", e); + + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + }, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + + foreach (var e in events) + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); + + await eventsReceived.Task.WithTimeout(); + } +} diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamGetInfoTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs similarity index 96% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamGetInfoTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs index 52efa2647..d0c593078 100644 --- a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamGetInfoTests.cs +++ b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs @@ -6,8 +6,8 @@ namespace Kurrent.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToStreamGetInfoTests(SubscribeToStreamGetInfoTests.NoDefaultCredentialsFixture fixture) - : IClassFixture { +public class SubscribeToStreamGetInfoTests(SubscribeToStreamGetInfoTests.CustomFixture fixture) + : IClassFixture { static readonly PersistentSubscriptionSettings Settings = new( true, StreamPosition.Start, @@ -148,14 +148,14 @@ public async Task returns_result_for_normal_user() { Assert.NotNull(result); } - public class NoDefaultCredentialsFixture : KurrentTemporaryFixture { + public class CustomFixture : KurrentTemporaryFixture { public string Group { get; set; } public string Stream { get; set; } KurrentPersistentSubscriptionsClient.PersistentSubscriptionResult? Subscription; - IAsyncEnumerator? Enumerator; + IAsyncEnumerator? Enumerator; - public NoDefaultCredentialsFixture() : base(x => x.WithoutDefaultCredentials()) { + public CustomFixture() : base(x => x.WithoutDefaultCredentials()) { Group = GetGroupName(); Stream = GetStreamName(); diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamListTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamListTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamListTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamListTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamNoDefaultCredentialsTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamNoDefaultCredentialsTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamReplayParkedTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamReplayParkedTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamReplayParkedTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamReplayParkedTests.cs diff --git a/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamTests.cs b/test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStreamTests.cs rename to test/Kurrent.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs diff --git a/test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs b/test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs new file mode 100644 index 000000000..b42cf782e --- /dev/null +++ b/test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs @@ -0,0 +1,616 @@ +using EventStore.Client; +using Kurrent.Client.Tests.Streams; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests; + +[Trait("Category", "Target:Streams")] +[Trait("Category", "Operation:Subscriptions")] +[Obsolete("Will be removed in future release when older subscriptions APIs are removed from the client")] +public class SubscribeToAllObsoleteTests(ITestOutputHelper output, SubscribeToAllObsoleteTests.CustomFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [Fact] + public async Task receives_all_events_from_start() { + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, false, OnDropped) + .WithTimeout(); + + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_end() { + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.End, OnReceived, false, OnDropped) + .WithTimeout(); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_position() { + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + IWriteResult writeResult = new SuccessResult(); + foreach (var evt in seedEvents.Take(pageSize)) + writeResult = await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var position = FromAll.After(writeResult.LogPosition); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(position, OnReceived, false, OnDropped) + .WithTimeout(); + + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, true, OnDropped) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + var hasResolvedLink = re.OriginalEvent.EventStreamId.StartsWith($"$et-{KurrentTemporaryFixture.TestEventType}"); + if (availableEvents.RemoveWhere(x => x == re.Event.EventId && hasResolvedLink) == 0) { + Fixture.Log.Debug("Received unexpected event {EventId} from stream {StreamId}", re.Event.EventId, re.OriginalEvent.EventStreamId); + return Task.CompletedTask; + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType = typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_start(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_start)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + var checkpointReached = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // Debugging: + // await foreach (var evt in Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start)) + // Fixture.Log.Debug("Read event {EventId} from {StreamId}.", evt.OriginalEvent.EventId, evt.OriginalEvent.EventStreamId); + + // add some of the events we want to see before we start the subscription + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, false, OnDropped, filterOptions) + .WithTimeout(); + + // add some of the events we want to see after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + // wait until all events were received and at least one checkpoint was reached? + await receivedAllEvents.Task.WithTimeout(); + await checkpointReached.Task.WithTimeout(); + + // await Task.WhenAll(receivedAllEvents.Task, checkpointReached.Task).WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId) == 0) { + Fixture.Log.Error( + "Received unexpected event {EventId} from {StreamId}", + re.OriginalEvent.EventId, + re.OriginalEvent.EventStreamId + ); + + receivedAllEvents.TrySetException( + new InvalidOperationException($"Received unexpected event {re.OriginalEvent.EventId} from stream {re.OriginalEvent.EventStreamId}") + ); + } else { + Fixture.Log.Verbose("Received expected event {EventId} from {StreamId}.", re.OriginalEvent.EventId, re.OriginalEvent.EventStreamId); + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events.", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) { + subscriptionDropped.SetResult(new(reason, ex)); + if (reason != SubscriptionDroppedReason.Disposed) { + receivedAllEvents.TrySetException(ex!); + checkpointReached.TrySetException(ex!); + } + } + + Task CheckpointReached(StreamSubscription sub, Position position, CancellationToken ct) { + Fixture.Log.Verbose( + "Checkpoint reached {Position}. Received {ReceivedEventsCount}/{TotalEventsCount} events", + position, + seedEvents.Length - availableEvents.Count, + seedEvents.Length + ); + + checkpointReached.TrySetResult(true); + return Task.CompletedTask; + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType = typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_end(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_end)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + var checkpointReached = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // add some of the events that are a match to the filter but will not be received + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.End, OnReceived, false, OnDropped, filterOptions) + .WithTimeout(); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + // wait until all events were received and at least one checkpoint was reached? + await receivedAllEvents.Task.WithTimeout(); + await checkpointReached.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId) == 0) { + Fixture.Log.Error( + "Received unexpected event {EventId} from {StreamId}", + re.OriginalEvent.EventId, + re.OriginalEvent.EventStreamId + ); + + receivedAllEvents.TrySetException( + new InvalidOperationException($"Received unexpected event {re.OriginalEvent.EventId} from stream {re.OriginalEvent.EventStreamId}") + ); + } else { + Fixture.Log.Verbose("Received expected event {EventId} from {StreamId}", re.OriginalEvent.EventId, re.OriginalEvent.EventStreamId); + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) { + subscriptionDropped.SetResult(new(reason, ex)); + if (reason != SubscriptionDroppedReason.Disposed) { + receivedAllEvents.TrySetException(ex!); + checkpointReached.TrySetException(ex!); + } + } + + Task CheckpointReached(StreamSubscription sub, Position position, CancellationToken ct) { + Fixture.Log.Verbose( + "Checkpoint reached {Position}. Received {ReceivedEventsCount}/{TotalEventsCount} events", + position, + pageSize - availableEvents.Count, + pageSize + ); + + checkpointReached.TrySetResult(true); + return Task.CompletedTask; + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType = typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_position(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_position)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + var checkpointReached = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // add some of the events that are a match to the filter but will not be received + IWriteResult writeResult = new SuccessResult(); + foreach (var evt in seedEvents.Take(pageSize)) + writeResult = await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var position = FromAll.After(writeResult.LogPosition); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(position, OnReceived, false, OnDropped, filterOptions) + .WithTimeout(); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + // wait until all events were received and at least one checkpoint was reached? + await receivedAllEvents.Task.WithTimeout(); + await checkpointReached.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId) == 0) { + Fixture.Log.Error( + "Received unexpected event {EventId} from {StreamId}", + re.OriginalEvent.EventId, + re.OriginalEvent.EventStreamId + ); + + receivedAllEvents.TrySetException( + new InvalidOperationException($"Received unexpected event {re.OriginalEvent.EventId} from stream {re.OriginalEvent.EventStreamId}") + ); + } else { + Fixture.Log.Verbose("Received expected event {EventId} from {StreamId}", re.OriginalEvent.EventId, re.OriginalEvent.EventStreamId); + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) { + subscriptionDropped.SetResult(new(reason, ex)); + if (reason != SubscriptionDroppedReason.Disposed) { + receivedAllEvents.TrySetException(ex!); + checkpointReached.TrySetException(ex!); + } + } + + Task CheckpointReached(StreamSubscription sub, Position position, CancellationToken ct) { + Fixture.Log.Verbose( + "Checkpoint reached {Position}. Received {ReceivedEventsCount}/{TotalEventsCount} events", + position, + pageSize - availableEvents.Count, + pageSize + ); + + checkpointReached.TrySetResult(true); + return Task.CompletedTask; + } + } + + [Fact] + public async Task receives_all_filtered_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + var options = new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentTemporaryFixture.TestEventType}")); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, true, OnDropped, options) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + var hasResolvedLink = re.OriginalEvent.EventStreamId.StartsWith($"$et-{KurrentTemporaryFixture.TestEventType}"); + if (availableEvents.RemoveWhere(x => x == re.Event.EventId && hasResolvedLink) == 0) { + Fixture.Log.Debug("Received unexpected event {EventId} from stream {StreamId}", re.Event.EventId, re.OriginalEvent.EventStreamId); + return Task.CompletedTask; + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task drops_when_disposed() { + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync( + FromAll.Start, + (sub, re, ct) => Task.CompletedTask, + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + } + + [Fact] + public async Task drops_when_subscriber_error() { + var expectedResult = SubscriptionDroppedResult.SubscriberError(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync( + FromAll.Start, + (sub, re, ct) => expectedResult.Throw(), + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents()); + + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(expectedResult); + } + + public class CustomFixture : KurrentTemporaryFixture { + public CustomFixture() : base(x => x.RunProjections()) { + OnSetup = async () => { + await Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.NoStream, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + }; + } + } +} diff --git a/test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs new file mode 100644 index 000000000..2a2c3f10f --- /dev/null +++ b/test/Kurrent.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -0,0 +1,322 @@ +using EventStore.Client; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests; + +[Trait("Category", "Target:Streams")] +[Trait("Category", "Operation:Subscriptions")] +[Obsolete("Will be removed in future release when older subscriptions APIs are removed from the client")] +public class SubscribeToStreamObsoleteTests(ITestOutputHelper output, SubscribeToStreamObsoleteTests.CustomFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [Fact] + public async Task receives_all_events_from_start() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived, false, OnDropped) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.StreamExists, seedEvents.Skip(pageSize)); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_position() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); + var streamPosition = StreamPosition.FromStreamRevision(writeResult.NextExpectedStreamRevision); + var checkpoint = FromStream.After(streamPosition); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync(streamName, checkpoint, OnReceived, false, OnDropped) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, writeResult.NextExpectedStreamRevision, seedEvents.Skip(pageSize)); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_non_existing_stream() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived, false, OnDropped) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task allow_multiple_subscriptions_to_same_stream() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(5).ToArray(); + + var targetEventsCount = seedEvents.Length * 2; + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + using var subscription1 = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived) + .WithTimeout(); + + using var subscription2 = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (--targetEventsCount == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + } + + [Fact] + public async Task drops_when_disposed() { + var streamName = Fixture.GetStreamName(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync( + streamName, + FromStream.Start, + (sub, re, ct) => Task.CompletedTask, + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + } + + [Fact] + public async Task drops_when_subscriber_error() { + var streamName = Fixture.GetStreamName(); + + var expectedResult = SubscriptionDroppedResult.SubscriberError(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync( + streamName, + FromStream.Start, + (sub, re, ct) => expectedResult.Throw(), + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, Fixture.CreateTestEvents()); + + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(expectedResult); + } + + [Fact] + public async Task drops_when_stream_tombstoned() { + var streamName = Fixture.GetStreamName(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync( + streamName, + FromStream.Start, + (sub, re, ct) => Task.CompletedTask, + false, + (sub, reason, ex) => { subscriptionDropped.SetResult(new(reason, ex)); } + ) + .WithTimeout(); + + // rest in peace + await Fixture.Streams.TombstoneAsync(streamName, StreamState.NoStream); + + var result = await subscriptionDropped.Task.WithTimeout(); + result.Error.ShouldBeOfType().Stream.ShouldBe(streamName); + } + + [Fact] + public async Task receives_all_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync($"$et-{KurrentTemporaryFixture.TestEventType}", FromStream.Start, OnReceived, true, OnDropped) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + var hasResolvedLink = re.OriginalEvent.EventStreamId.StartsWith($"$et-{KurrentTemporaryFixture.TestEventType}"); + if (availableEvents.RemoveWhere(x => x == re.Event.EventId && hasResolvedLink) == 0) { + Fixture.Log.Debug("Received unexpected event {EventId} from stream {StreamId}", re.Event.EventId, re.OriginalEvent.EventStreamId); + return Task.CompletedTask; + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + public class CustomFixture : KurrentTemporaryFixture { + public CustomFixture() : base(x => x.RunProjections()) { + OnSetup = async () => { + await Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.NoStream, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + }; + } + } +} diff --git a/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs b/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs new file mode 100644 index 000000000..33d63b280 --- /dev/null +++ b/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs @@ -0,0 +1,497 @@ +using EventStore.Client; +using Kurrent.Client.Tests.Streams; +using Kurrent.Client.Tests.TestNode; + +namespace Kurrent.Client.Tests; + +[Trait("Category", "Subscriptions")] +[Trait("Category", "Target:Streams")] +public class SubscribeToAllTests(ITestOutputHelper output, SubscribeToAllTests.CustomFixture fixture) + : KurrentTemporaryTests(output, fixture) { + [Fact] + public async Task receives_all_events_from_start() { + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"stream-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"stream-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + await Subscribe().WithTimeout(); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + if (enumerator.Current is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + } + } + } + + [Fact] + public async Task receives_all_events_from_end() { + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents) + await Fixture.Streams.AppendToStreamAsync( + $"stream-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + await Subscribe().WithTimeout(); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + if (enumerator.Current is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + availableEvents.Remove(resolvedEvent.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + return; + } + } + } + } + + [Fact] + public async Task receives_all_events_from_position() { + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + IWriteResult writeResult = new SuccessResult(); + foreach (var evt in seedEvents.Take(pageSize)) + writeResult = await Fixture.Streams.AppendToStreamAsync( + $"stream-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + var position = FromAll.After(writeResult.LogPosition); + + await using var subscription = Fixture.Streams.SubscribeToAll(position); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"stream-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + await Subscribe().WithTimeout(); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + if (enumerator.Current is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + } + } + } + + [Fact] + public async Task receives_all_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, true); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + await Subscribe().WithTimeout(); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + if (enumerator.Current is not StreamMessage.Event(var resolvedEvent)) { + continue; + } + + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + } + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType = typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_start(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_start)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync( + Fixture.GetStreamName(), + StreamState.NoStream, + Fixture.CreateTestEvents(3) + ); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + .Messages.CountAsync(); + + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // Debugging: + // await foreach (var evt in Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start)) + // Fixture.Log.Debug("Read event {EventId} from {StreamId}.", evt.OriginalEvent.EventId, evt.OriginalEvent.EventStreamId); + + // add some of the events we want to see before we start the subscription + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); + + await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + // add some of the events we want to see after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + bool checkpointReached = false; + + await Subscribe().WithTimeout(); + + Assert.True(checkpointReached); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + switch (enumerator.Current) { + case StreamMessage.AllStreamCheckpointReached: + checkpointReached = true; + + break; + + case StreamMessage.Event(var resolvedEvent): { + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + + break; + } + } + } + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType = typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_end(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_end)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync( + Fixture.GetStreamName(), + StreamState.NoStream, + Fixture.CreateTestEvents(3) + ); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + .Messages.CountAsync(); + + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // Debugging: + // await foreach (var evt in Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start)) + // Fixture.Log.Debug("Read event {EventId} from {StreamId}.", evt.OriginalEvent.EventId, evt.OriginalEvent.EventStreamId); + + // add some of the events we want to see before we start the subscription + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); + + await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End, filterOptions: filterOptions); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + // add some of the events we want to see after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + bool checkpointReached = false; + + await Subscribe().WithTimeout(); + + Assert.True(checkpointReached); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + switch (enumerator.Current) { + case StreamMessage.AllStreamCheckpointReached: + checkpointReached = true; + + break; + + case StreamMessage.Event(var resolvedEvent): { + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + + break; + } + } + } + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType = typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_position(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_position)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync( + Fixture.GetStreamName(), + StreamState.NoStream, + Fixture.CreateTestEvents(3) + ); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + .Messages.CountAsync(); + + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // add some of the events that are a match to the filter but will not be received + IWriteResult writeResult = new SuccessResult(); + foreach (var evt in seedEvents.Take(pageSize)) + writeResult = await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + var position = FromAll.After(writeResult.LogPosition); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); + + await using var subscription = Fixture.Streams.SubscribeToAll(position, filterOptions: filterOptions); + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync( + $"{streamPrefix}-{evt.EventId.ToGuid():N}", + StreamState.NoStream, + new[] { evt } + ); + + bool checkpointReached = false; + + await Subscribe().WithTimeout(); + + Assert.True(checkpointReached); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + switch (enumerator.Current) { + case StreamMessage.AllStreamCheckpointReached: + checkpointReached = true; + + break; + + case StreamMessage.Event(var resolvedEvent): { + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + + break; + } + } + } + } + } + + [Fact] + public async Task receives_all_filtered_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + var filterOptions = new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentPermanentFixture.TestEventType}")); + + await using var subscription = + Fixture.Streams.SubscribeToAll(FromAll.Start, true, filterOptions: filterOptions); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + + Assert.True(await enumerator.MoveNextAsync()); + + Assert.IsType(enumerator.Current); + + await Subscribe().WithTimeout(); + + return; + + async Task Subscribe() { + while (await enumerator.MoveNextAsync()) { + if (enumerator.Current is not StreamMessage.Event(var resolvedEvent) || + !resolvedEvent.OriginalEvent.EventStreamId.StartsWith($"$et-{KurrentPermanentFixture.TestEventType}")) { + continue; + } + + availableEvents.Remove(resolvedEvent.Event.EventId); + + if (availableEvents.Count == 0) { + return; + } + } + } + } + + public class CustomFixture : KurrentTemporaryFixture { + public CustomFixture() : base(x => x.RunProjections()) { + OnSetup = async () => { + await Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.NoStream, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + }; + } + } +} diff --git a/test/Kurrent.Client.Tests/Streams/SubscribeToStreamTests.cs b/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs similarity index 100% rename from test/Kurrent.Client.Tests/Streams/SubscribeToStreamTests.cs rename to test/Kurrent.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs diff --git a/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscriptionDroppedResult.cs b/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscriptionDroppedResult.cs new file mode 100644 index 000000000..6157c6cb5 --- /dev/null +++ b/test/Kurrent.Client.Tests/Streams/Subscriptions/SubscriptionDroppedResult.cs @@ -0,0 +1,18 @@ +using EventStore.Client; + +namespace Kurrent.Client.Tests; + +public record SubscriptionDroppedResult(SubscriptionDroppedReason Reason, Exception? Error) { + public Task Throw() => Task.FromException(Error!); + + public static SubscriptionDroppedResult ServerError(Exception? error = null) => + new(SubscriptionDroppedReason.ServerError, error ?? new Exception("Server error")); + + public static SubscriptionDroppedResult SubscriberError(Exception? error = null) => + new(SubscriptionDroppedReason.SubscriberError, error ?? new Exception("Subscriber error")); + + public static SubscriptionDroppedResult Disposed(Exception? error = null) => + new(SubscriptionDroppedReason.Disposed, error); + + public override string ToString() => $"{Reason} {Error?.Message ?? string.Empty}".Trim(); +}