From bb30aa39cf7c124225e35429999b1ded03958c3d Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Wed, 12 Mar 2025 10:14:14 +0100 Subject: [PATCH 01/23] [DEVEX-222] Fixed the missing application of custom serialization setting in AppendToStreamAsync method --- .../Streams/KurrentDBClient.Append.cs | 85 +++++++++++++------ 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index 0408dbc04..a12715031 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -34,47 +34,40 @@ public Task AppendToStreamAsync( Settings.Serialization.DefaultContentType ); - var eventsData = _messageSerializer.Serialize(messages, serializationContext); + var messageSerializer = _messageSerializer.With(Settings.Serialization, options.SerializationSettings); - return AppendToStreamAsync( - streamName, - options.ExpectedStreamState ?? StreamState.Any, - eventsData, - options.ConfigureOperationOptions, - options.Deadline, - options.UserCredentials, - cancellationToken - ); + var eventsData = messageSerializer.Serialize(messages, serializationContext); + + return AppendToStreamAsync(streamName, eventsData, options, cancellationToken); } /// - /// Appends events asynchronously to a stream. + /// Appends events asynchronously to a stream using raw message data. + /// If you want to use auto-serialization, use overload with .. + /// This method intends to cover low-level scenarios in which you want to have full control of the serialization mechanism. /// /// 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. + /// Raw message data to append to the stream. + /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check /// The optional . /// public async Task AppendToStreamAsync( string streamName, - StreamState expectedState, - IEnumerable eventData, - Action? configureOperationOptions = null, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, + IEnumerable eventsData, + AppendToStreamRawOptions options, CancellationToken cancellationToken = default ) { + var expectedState = options.ExpectedStreamState ?? StreamState.Any; + var userCredentials = options.UserCredentials; + var deadline = options.Deadline; var operationOptions = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(operationOptions); + options.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) + ? BatchAppender.Append(streamName, expectedState, eventsData, deadline, cancellationToken) : AppendToStreamInternal( await GetChannelInfo(cancellationToken).ConfigureAwait(false), new AppendReq { @@ -82,7 +75,7 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), StreamIdentifier = streamName } }.WithAnyStreamRevision(expectedState), - eventData, + eventsData, operationOptions, deadline, userCredentials, @@ -92,6 +85,41 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(operationOptions); } + /// + /// 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 . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with Message for auto-serialization or method with options parameter for raw serialization" + )] + public Task AppendToStreamAsync( + string streamName, + StreamState expectedState, + IEnumerable eventData, + Action? configureOperationOptions = null, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + AppendToStreamAsync( + streamName, + eventData, + new AppendToStreamRawOptions { + ExpectedStreamState = expectedState, + ConfigureOperationOptions = configureOperationOptions, + Deadline = deadline, + UserCredentials = userCredentials, + }, + cancellationToken + ); + ValueTask AppendToStreamInternal( ChannelInfo channelInfo, AppendReq header, @@ -232,7 +260,7 @@ IWriteResult HandleWrongExpectedRevision( } class StreamAppender : IDisposable { - readonly KurrentDBClientSettings _settings; + readonly KurrentDBClientSettings _settings; readonly CancellationToken _cancellationToken; readonly Action _onException; readonly Channel _channel; @@ -467,7 +495,7 @@ public static Task AppendToStreamAsync( new AppendToStreamOptions(), cancellationToken ); - + /// /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through /// @@ -492,8 +520,7 @@ public static Task AppendToStreamAsync( ); } - // TODO: In the follow up PR merge StreamState and StreamRevision into a one thing - public class AppendToStreamOptions { + public class AppendToStreamRawOptions { /// /// The expected of the stream to append to. /// @@ -513,7 +540,9 @@ public class AppendToStreamOptions { /// The for the operation. /// public UserCredentials? UserCredentials { get; set; } + } + public class AppendToStreamOptions : AppendToStreamRawOptions { /// /// Allows to customize or disable the automatic deserialization /// From bcfc61de76247b994eab0bfe8f0614718a2946fb Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 13 Mar 2025 09:09:16 +0100 Subject: [PATCH 02/23] [DEVEX-222] Added stream state as a mandatory parameter in append to stream Added also override for raw event append that has options and made other methods obsolete. Introduced OperationOptions that compose default client operation settings plus credentials and deadlines. --- src/KurrentDB.Client/Core/OperationOptions.cs | 36 ++++ .../Streams/KurrentDBClient.Append.cs | 161 ++++++------------ .../Streams/KurrentDBClient.Metadata.cs | 88 +++++++--- .../Streams/KurrentDBClientExtensions.cs | 77 +++++++-- .../Streams/WriteResultExtensions.cs | 15 +- .../KurrentDBClientWarmupExtensions.cs | 31 ++-- .../SubscribeToAllGetInfoTests.cs | 2 +- .../SubscribeToStreamGetInfoObsoleteTests.cs | 8 +- .../SubscribeToStreamGetInfoTests.cs | 10 +- ...scribeToStreamNoDefaultCredentialsTests.cs | 2 +- .../Security/SecurityFixture.cs | 30 ++-- .../Streams/AppendTests.cs | 36 ++-- ...ializationTests.PersistentSubscriptions.cs | 51 +++--- .../SerializationTests.Subscriptions.cs | 21 ++- .../Serialization/SerializationTests.cs | 29 +++- .../Streams/SoftDeleteTests.cs | 15 +- 16 files changed, 374 insertions(+), 238 deletions(-) create mode 100644 src/KurrentDB.Client/Core/OperationOptions.cs diff --git a/src/KurrentDB.Client/Core/OperationOptions.cs b/src/KurrentDB.Client/Core/OperationOptions.cs new file mode 100644 index 000000000..34c41f7a9 --- /dev/null +++ b/src/KurrentDB.Client/Core/OperationOptions.cs @@ -0,0 +1,36 @@ +namespace KurrentDB.Client; + +/// +/// A class representing the options to apply to an individual operation. +/// +public record OperationOptions { + /// + /// 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; } + + /// + /// Maximum time that the operation will be run + /// + public TimeSpan? Deadline { get; set; } + + /// + /// The for the operation. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Clones a copy of the current . + /// + /// + public OperationOptions With(KurrentDBClientOperationOptions clientOperationOptions) => + new() { + ThrowOnAppendFailure = ThrowOnAppendFailure ?? clientOperationOptions.ThrowOnAppendFailure, + BatchAppendSize = BatchAppendSize ?? clientOperationOptions.BatchAppendSize + }; +} diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index a12715031..3ded2ace7 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -19,14 +19,16 @@ public partial class KurrentDBClient { /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through /// /// The name of the stream to append events to. + /// The expected of the stream to append to. /// Messages to append to the stream. - /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check + /// Optional settings for the append operation, e.g. deadline, user credentials etc. /// The optional . /// public Task AppendToStreamAsync( string streamName, + StreamState expectedState, IEnumerable messages, - AppendToStreamOptions options, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { var serializationContext = new MessageSerializationContext( @@ -34,11 +36,11 @@ public Task AppendToStreamAsync( Settings.Serialization.DefaultContentType ); - var messageSerializer = _messageSerializer.With(Settings.Serialization, options.SerializationSettings); + var messageSerializer = _messageSerializer.With(Settings.Serialization, options?.SerializationSettings); var eventsData = messageSerializer.Serialize(messages, serializationContext); - return AppendToStreamAsync(streamName, eventsData, options, cancellationToken); + return AppendToStreamAsync(streamName, expectedState, eventsData, options, cancellationToken); } /// @@ -47,21 +49,21 @@ public Task AppendToStreamAsync( /// This method intends to cover low-level scenarios in which you want to have full control of the serialization mechanism. /// /// The name of the stream to append events to. + /// The expected of the stream to append to. /// Raw message data to append to the stream. - /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check + /// Optional settings for the append operation, e.g. deadline, user credentials etc. /// The optional . /// public async Task AppendToStreamAsync( string streamName, + StreamState expectedState, IEnumerable eventsData, - AppendToStreamRawOptions options, + OperationOptions? options = null, CancellationToken cancellationToken = default ) { - var expectedState = options.ExpectedStreamState ?? StreamState.Any; - var userCredentials = options.UserCredentials; - var deadline = options.Deadline; - var operationOptions = Settings.OperationOptions.Clone(); - options.ConfigureOperationOptions?.Invoke(operationOptions); + var operationOptions = (options ?? new OperationOptions()).With(Settings.OperationOptions); + var userCredentials = options?.UserCredentials; + var deadline = options?.Deadline; _log.LogDebug("Append to stream - {streamName}@{expectedState}.", streamName, expectedState); @@ -77,58 +79,22 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), }.WithAnyStreamRevision(expectedState), eventsData, operationOptions, - deadline, - userCredentials, cancellationToken ); return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(operationOptions); } - /// - /// 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 . - /// - [Obsolete( - "This method may be removed in future releases. Use the overload with Message for auto-serialization or method with options parameter for raw serialization" - )] - public Task AppendToStreamAsync( - string streamName, - StreamState expectedState, - IEnumerable eventData, - Action? configureOperationOptions = null, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => - AppendToStreamAsync( - streamName, - eventData, - new AppendToStreamRawOptions { - ExpectedStreamState = expectedState, - ConfigureOperationOptions = configureOperationOptions, - Deadline = deadline, - UserCredentials = userCredentials, - }, - cancellationToken - ); - ValueTask AppendToStreamInternal( ChannelInfo channelInfo, AppendReq header, IEnumerable eventData, - KurrentDBClientOperationOptions operationOptions, - TimeSpan? deadline, - UserCredentials? userCredentials, + OperationOptions operationOptions, CancellationToken cancellationToken ) { + var userCredentials = operationOptions.UserCredentials; + var deadline = operationOptions.Deadline; + var tags = new ActivityTagsCollection() .WithRequiredTag( TelemetryTags.Kurrent.Stream, @@ -209,7 +175,7 @@ IWriteResult HandleSuccessAppend(AppendResp response, AppendReq header) { } IWriteResult HandleWrongExpectedRevision( - AppendResp response, AppendReq header, KurrentDBClientOperationOptions operationOptions + AppendResp response, AppendReq header, OperationOptions operationOptions ) { var actualStreamRevision = response.WrongExpectedVersion.CurrentRevisionOptionCase == CurrentRevisionOptionOneofCase.CurrentRevision @@ -223,7 +189,7 @@ IWriteResult HandleWrongExpectedRevision( actualStreamRevision ); - if (operationOptions.ThrowOnAppendFailure) { + if (operationOptions.ThrowOnAppendFailure == true) { if (response.WrongExpectedVersion.ExpectedRevisionOptionCase == ExpectedRevisionOptionOneofCase.ExpectedRevision) { throw new WrongExpectedVersionException( @@ -459,90 +425,69 @@ public static class KurrentDBClientAppendToStreamExtensions { /// /// /// The name of the stream to append events to. + /// The expected of the stream to append to. /// Messages to append to the stream. + /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check /// The optional . /// public static Task AppendToStreamAsync( this KurrentDBClient dbClient, string streamName, - IEnumerable messages, - CancellationToken cancellationToken = default - ) - => dbClient.AppendToStreamAsync( - streamName, - messages, - new AppendToStreamOptions(), - cancellationToken - ); - - /// - /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through - /// - /// - /// The name of the stream to append events to. - /// Messages to append to the stream. - /// The optional . - /// - public static Task AppendToStreamAsync( - this KurrentDBClient dbClient, - string streamName, + StreamState expectedState, IEnumerable messages, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) => dbClient.AppendToStreamAsync( streamName, + expectedState, messages.Select(m => Message.From(m)), - new AppendToStreamOptions(), + options, cancellationToken ); /// - /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through + /// Appends events asynchronously to a stream. /// /// - /// The name of the stream to append events to. - /// Messages to append to the stream. - /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check + /// 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 . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with Message for auto-serialization or method with options parameter for raw serialization" + )] public static Task AppendToStreamAsync( this KurrentDBClient dbClient, string streamName, - IEnumerable messages, - AppendToStreamOptions options, + StreamState expectedState, + IEnumerable eventData, + Action? configureOperationOptions = null, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) - => dbClient.AppendToStreamAsync( + ) { + var operationOptions = new OperationOptions { + Deadline = deadline, + UserCredentials = userCredentials, + }; + configureOperationOptions?.Invoke(operationOptions); + + return dbClient.AppendToStreamAsync( streamName, - messages.Select(m => Message.From(m)), - options, + expectedState, + eventData, + operationOptions, cancellationToken ); + } } - public class AppendToStreamRawOptions { - /// - /// The expected of the stream to append to. - /// - public StreamState? ExpectedStreamState { get; set; } - - /// - /// An to configure the operation's options. - /// - public Action? ConfigureOperationOptions { get; set; } - - /// - /// Maximum time that the operation will be run - /// - public TimeSpan? Deadline { get; set; } - - /// - /// The for the operation. - /// - public UserCredentials? UserCredentials { get; set; } - } - - public class AppendToStreamOptions : AppendToStreamRawOptions { + public record AppendToStreamOptions : OperationOptions { /// /// Allows to customize or disable the automatic deserialization /// diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs index 629d12ced..ac0d4d115 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs @@ -12,25 +12,40 @@ public partial class KurrentDBClient { /// The optional to perform operation with. /// The optional . /// - public async Task GetStreamMetadataAsync(string streamName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { + 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); + 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)); + return StreamMetadataResult.Create( + streamName, + resolvedEvent.OriginalEventNumber, + JsonSerializer.Deserialize( + resolvedEvent.Event.Data.Span, + StreamMetadataJsonSerializerOptions + ) + ); } + } catch (StreamNotFoundException) { } - } catch (StreamNotFoundException) { - } _log.LogWarning("Stream metadata for {streamName} not found.", streamName); return StreamMetadataResult.None(streamName); } @@ -46,32 +61,51 @@ public async Task GetStreamMetadataAsync(string streamName /// The optional to perform operation with. /// The optional . /// - public Task SetStreamMetadataAsync(string streamName, StreamState expectedState, + public Task SetStreamMetadataAsync( + string streamName, StreamState expectedState, StreamMetadata metadata, Action? configureOperationOptions = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { + 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); + var operationOptions = + new OperationOptions { Deadline = deadline, UserCredentials = userCredentials }.With(options); + + return SetStreamMetadataInternal( + metadata, + new AppendReq { + Options = new AppendReq.Types.Options { + StreamIdentifier = SystemStreams.MetastreamOf(streamName) + } + }.WithAnyStreamRevision(expectedState), + operationOptions, + cancellationToken + ); } - private async Task SetStreamMetadataInternal(StreamMetadata metadata, + async Task SetStreamMetadataInternal( + StreamMetadata metadata, AppendReq appendReq, - KurrentDBClientOperationOptions operationOptions, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken) { - + OperationOptions operationOptions, + 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); + return await AppendToStreamInternal( + channelInfo, + appendReq, + [ + new EventData( + Uuid.NewUuid(), + SystemEventTypes.StreamMetadata, + JsonSerializer.SerializeToUtf8Bytes(metadata, StreamMetadataJsonSerializerOptions) + ) + ], + operationOptions, + cancellationToken + ).ConfigureAwait(false); } } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs index e677d189c..14af2ba5a 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs @@ -9,12 +9,44 @@ namespace KurrentDB.Client { /// A set of extension methods for an . /// public static class KurrentDBClientExtensions { - private static readonly JsonSerializerOptions SystemSettingsJsonSerializerOptions = new JsonSerializerOptions { + static readonly JsonSerializerOptions SystemSettingsJsonSerializerOptions = new JsonSerializerOptions { Converters = { SystemSettingsJsonConverter.Instance }, }; + /// + /// Writes to the $settings stream. + /// + /// + /// + /// Optional settings for the append operation, e.g. deadline, user credentials etc. + /// + /// + /// + public static Task SetSystemSettingsAsync( + this KurrentDBClient dbClient, + SystemSettings settings, + OperationOptions? options = null, + CancellationToken cancellationToken = default + ) { + if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); + + return dbClient.AppendToStreamAsync( + SystemStreams.SettingsStream, + StreamState.Any, + [ + new EventData( + Uuid.NewUuid(), + SystemEventTypes.Settings, + JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions) + ) + ], + options, + cancellationToken + ); + } + /// /// Writes to the $settings stream. /// @@ -28,14 +60,25 @@ public static class KurrentDBClientExtensions { public static Task SetSystemSettingsAsync( this KurrentDBClient dbClient, SystemSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); - return dbClient.AppendToStreamAsync(SystemStreams.SettingsStream, StreamState.Any, - new[] { - new EventData(Uuid.NewUuid(), SystemEventTypes.Settings, - JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions)) - }, deadline: deadline, userCredentials: userCredentials, cancellationToken: cancellationToken); + + return dbClient.AppendToStreamAsync( + SystemStreams.SettingsStream, + StreamState.Any, + [ + new EventData( + Uuid.NewUuid(), + SystemEventTypes.Settings, + JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions) + ) + ], + new OperationOptions { Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken: cancellationToken + ); } /// @@ -57,14 +100,26 @@ public static async Task ConditionalAppendToStreamAsync( IEnumerable eventData, TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { + CancellationToken cancellationToken = default + ) { if (dbClient == null) { throw new ArgumentNullException(nameof(dbClient)); } + try { - var result = await dbClient.AppendToStreamAsync(streamName, expectedState, eventData, - options => options.ThrowOnAppendFailure = false, deadline, userCredentials, cancellationToken) + var result = await dbClient.AppendToStreamAsync( + streamName, + expectedState, + eventData, + new OperationOptions { + ThrowOnAppendFailure = false, + Deadline = deadline, + UserCredentials = userCredentials + }, + cancellationToken + ) .ConfigureAwait(false); + return ConditionalWriteResult.FromWriteResult(result); } catch (StreamDeletedException) { return ConditionalWriteResult.StreamDeleted; diff --git a/src/KurrentDB.Client/Streams/WriteResultExtensions.cs b/src/KurrentDB.Client/Streams/WriteResultExtensions.cs index 52f98b334..5d2385d21 100644 --- a/src/KurrentDB.Client/Streams/WriteResultExtensions.cs +++ b/src/KurrentDB.Client/Streams/WriteResultExtensions.cs @@ -1,11 +1,16 @@ namespace KurrentDB.Client { - internal static class WriteResultExtensions { - public static IWriteResult OptionallyThrowWrongExpectedVersionException(this IWriteResult writeResult, - KurrentDBClientOperationOptions options) => + static class WriteResultExtensions { + public static IWriteResult OptionallyThrowWrongExpectedVersionException( + this IWriteResult writeResult, + OperationOptions options + ) => (options.ThrowOnAppendFailure, writeResult) switch { (true, WrongExpectedVersionResult wrongExpectedVersionResult) - => throw new WrongExpectedVersionException(wrongExpectedVersionResult.StreamName, - writeResult.NextExpectedStreamState, wrongExpectedVersionResult.ActualStreamState), + => throw new WrongExpectedVersionException( + wrongExpectedVersionResult.StreamName, + writeResult.NextExpectedStreamState, + wrongExpectedVersionResult.ActualStreamState + ), _ => writeResult }; } diff --git a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs index a47f8ae85..ab27e4339 100644 --- a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs +++ b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs @@ -14,7 +14,9 @@ public static class KurrentDBClientWarmupExtensions { /// static readonly IEnumerable DefaultBackoffDelay = Backoff.ConstantBackoff(FromMilliseconds(100), 300); - static async Task TryWarmUp(T client, Func action, CancellationToken cancellationToken = default) + static async Task TryWarmUp( + T client, Func action, CancellationToken cancellationToken = default + ) where T : KurrentDBClientBase { await Policy .Handle(ex => ex.StatusCode != StatusCode.Unimplemented) @@ -25,8 +27,7 @@ await Policy async ct => { try { await action(ct).ConfigureAwait(false); - } - catch (Exception ex) when (ex is not OperationCanceledException) { + } catch (Exception ex) when (ex is not OperationCanceledException) { // grpc throws a rpcexception when you cancel the token (which we convert into // invalid operation) - but polly expects operationcancelledexception or it wont // call onTimeoutAsync. so raise that here. @@ -40,7 +41,9 @@ await Policy return client; } - public static Task WarmUp(this KurrentDBClient dbClient, CancellationToken cancellationToken = default) => + public static Task WarmUp( + this KurrentDBClient dbClient, CancellationToken cancellationToken = default + ) => TryWarmUp( dbClient, async ct => { @@ -63,17 +66,19 @@ public static Task WarmUp(this KurrentDBClient dbClient, Cancel // the read from leader above is not enough to guarantee the next write goes to leader _ = await dbClient.AppendToStreamAsync( - streamName: "warmup", - expectedState: StreamState.Any, - eventData: Enumerable.Empty(), - userCredentials: TestCredentials.Root, - cancellationToken: ct + "warmup", + StreamState.Any, + [], + new OperationOptions { UserCredentials = TestCredentials.Root }, + ct ); }, cancellationToken ); - public static Task WarmUp(this KurrentDBOperationsClient client, CancellationToken cancellationToken = default) => + public static Task WarmUp( + this KurrentDBOperationsClient client, CancellationToken cancellationToken = default + ) => TryWarmUp( client, async ct => { @@ -81,7 +86,7 @@ await client.RestartPersistentSubscriptions( userCredentials: TestCredentials.Root, cancellationToken: ct ); - }, + }, cancellationToken ); @@ -119,7 +124,9 @@ public static Task WarmUp( cancellationToken ); - public static Task WarmUp(this KurrentDBUserManagementClient client, CancellationToken cancellationToken = default) => + public static Task WarmUp( + this KurrentDBUserManagementClient client, CancellationToken cancellationToken = default + ) => TryWarmUp( client, async ct => { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs index ca66c5e9d..0942fcebd 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs @@ -74,7 +74,7 @@ await Streams.AppendToStreamAsync( $"test-{Guid.NewGuid():n}", StreamState.NoStream, [eventData], - userCredentials: TestCredentials.Root + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs index 01e2aa457..af9014258 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs @@ -29,7 +29,11 @@ public static IEnumerable AllowedUsers() { [Theory] [MemberData(nameof(AllowedUsers))] public async Task returns_expected_result(UserCredentials credentials) { - var result = await fixture.Subscriptions.GetInfoToStreamAsync(fixture.Stream, fixture.Group, 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); @@ -187,7 +191,7 @@ await Streams.AppendToStreamAsync( Stream, StreamState.Any, [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], - userCredentials: TestCredentials.Root + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } }; diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs index 100ff2e97..b19f8e8af 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs @@ -31,7 +31,11 @@ public static IEnumerable AllowedUsers() { [Theory] [MemberData(nameof(AllowedUsers))] public async Task returns_expected_result(UserCredentials credentials) { - var result = await fixture.Subscriptions.GetInfoToStreamAsync(fixture.Stream, fixture.Group, 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); @@ -153,7 +157,7 @@ public class CustomFixture : KurrentDBTemporaryFixture { public string Stream { get; set; } KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult? Subscription; - IAsyncEnumerator? Enumerator; + IAsyncEnumerator? Enumerator; public CustomFixture() : base(x => x.WithoutDefaultCredentials()) { Group = GetGroupName(); @@ -176,7 +180,7 @@ await Streams.AppendToStreamAsync( Stream, StreamState.Any, [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], - userCredentials: TestCredentials.Root + new OperationOptions { UserCredentials = TestCredentials.Root } ); } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs index 3893c5d56..39a42258c 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs @@ -57,7 +57,7 @@ await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, Fixture.CreateTestEvents(), - userCredentials: TestCredentials.Root + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.CreateToStreamAsync( diff --git a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs index 1d3982520..15b124560 100644 --- a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs +++ b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs @@ -149,7 +149,7 @@ await Streams.SetStreamMetadataAsync( protected virtual Task When() => Task.CompletedTask; - public Task ReadEvent(string streamId, UserCredentials? userCredentials = default) => + public Task ReadEvent(string streamId, UserCredentials? userCredentials = null) => Streams.ReadStreamAsync( Direction.Forwards, streamId, @@ -162,7 +162,7 @@ public Task ReadEvent(string streamId, UserCredentials? userCredentials = defaul .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadStreamForward(string streamId, UserCredentials? userCredentials = default) => + public Task ReadStreamForward(string streamId, UserCredentials? userCredentials = null) => Streams.ReadStreamAsync( Direction.Forwards, streamId, @@ -175,7 +175,7 @@ public Task ReadStreamForward(string streamId, UserCredentials? userCredentials .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials = default) => + public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials = null) => Streams.ReadStreamAsync( Direction.Backwards, streamId, @@ -188,16 +188,16 @@ public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task AppendStream(string streamId, UserCredentials? userCredentials = default) => + public Task AppendStream(string streamId, UserCredentials? userCredentials = null) => Streams.AppendToStreamAsync( streamId, StreamState.Any, CreateTestEvents(3), - userCredentials: userCredentials + new AppendToStreamOptions { UserCredentials = userCredentials } ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadAllForward(UserCredentials? userCredentials = default) => + public Task ReadAllForward(UserCredentials? userCredentials = null) => Streams.ReadAllAsync( Direction.Forwards, Position.Start, @@ -209,7 +209,7 @@ public Task ReadAllForward(UserCredentials? userCredentials = default) => .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadAllBackward(UserCredentials? userCredentials = default) => + public Task ReadAllBackward(UserCredentials? userCredentials = null) => Streams .ReadAllAsync( Direction.Backwards, @@ -222,11 +222,13 @@ public Task ReadAllBackward(UserCredentials? userCredentials = default) => .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadMeta(string streamId, UserCredentials? userCredentials = default) => + public Task ReadMeta(string streamId, UserCredentials? userCredentials = null) => Streams.GetStreamMetadataAsync(streamId, userCredentials: userCredentials) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task WriteMeta(string streamId, UserCredentials? userCredentials = default, string? role = default) => + public Task WriteMeta( + string streamId, UserCredentials? userCredentials = null, string? role = null + ) => Streams.SetStreamMetadataAsync( streamId, StreamState.Any, @@ -242,7 +244,7 @@ public Task WriteMeta(string streamId, UserCredentials? userCreden ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public async Task SubscribeToStream(string streamId, UserCredentials? userCredentials = default) { + public async Task SubscribeToStream(string streamId, UserCredentials? userCredentials = null) { await using var subscription = Streams.SubscribeToStream(streamId, FromStream.Start, userCredentials: userCredentials); @@ -251,7 +253,7 @@ await subscription .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } - public async Task SubscribeToAll(UserCredentials? userCredentials = default) { + public async Task SubscribeToAll(UserCredentials? userCredentials = null) { await using var subscription = Streams.SubscribeToAll(FromAll.Start, userCredentials: userCredentials); @@ -260,7 +262,9 @@ await subscription .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } - public async Task CreateStreamWithMeta(StreamMetadata metadataPermanent, [CallerMemberName] string streamId = "") { + public async Task CreateStreamWithMeta( + StreamMetadata metadataPermanent, [CallerMemberName] string streamId = "" + ) { await Streams.SetStreamMetadataAsync( streamId, StreamState.NoStream, @@ -272,7 +276,7 @@ await Streams.SetStreamMetadataAsync( return streamId; } - public Task DeleteStream(string streamId, UserCredentials? userCredentials = default) => + public Task DeleteStream(string streamId, UserCredentials? userCredentials = null) => Streams.TombstoneAsync(streamId, StreamState.Any, userCredentials: userCredentials) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } diff --git a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs index 3dc716e18..3040968d3 100644 --- a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs @@ -6,7 +6,8 @@ namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Append")] -public class AppendTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class AppendTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Theory, ExpectedVersionCreateStreamTestCases] public async Task appending_zero_events(StreamState expectedStreamState) { var stream = $"{Fixture.GetStreamName()}_{expectedStreamState}"; @@ -232,7 +233,7 @@ public async Task appending_with_wrong_expected_version_to_existing_stream_retur stream, StreamState.StreamRevision(1), Fixture.CreateTestEvents(), - options => { options.ThrowOnAppendFailure = false; } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); var wrongExpectedVersionResult = (WrongExpectedVersionResult)writeResult; @@ -305,7 +306,7 @@ public async Task stream, StreamState.StreamExists, Fixture.CreateTestEvents(), - options => { options.ThrowOnAppendFailure = false; } + new OperationOptions { ThrowOnAppendFailure = false } ); var wrongExpectedVersionResult = Assert.IsType(writeResult); @@ -361,7 +362,9 @@ public async Task returns_failure_status_when_conditionally_appending_with_versi ); Assert.Equal( - ConditionalWriteResult.FromWrongExpectedVersion(new(stream, StreamState.StreamRevision(7), StreamState.NoStream)), + ConditionalWriteResult.FromWrongExpectedVersion( + new(stream, StreamState.StreamRevision(7), StreamState.NoStream) + ), result ); } @@ -429,7 +432,7 @@ public async Task with_timeout_any_stream_revision_fails_when_operation_expired( stream, StreamState.Any, Fixture.CreateTestEvents(100), - deadline: TimeSpan.FromTicks(1) + new OperationOptions { Deadline = TimeSpan.FromTicks(1) } ).ShouldThrowAsync(); ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); @@ -445,7 +448,7 @@ public async Task with_timeout_stream_revision_fails_when_operation_expired() { stream, StreamState.StreamRevision(0), Fixture.CreateTestEvents(10), - deadline: TimeSpan.Zero + new OperationOptions { Deadline = TimeSpan.Zero } ).ShouldThrowAsync(); ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); @@ -460,7 +463,12 @@ await Fixture.Streams streamName, StreamState.Any, Fixture.CreateTestEventsThatThrowsException(), - userCredentials: new UserCredentials(TestCredentials.Root.Username!, TestCredentials.Root.Password!) + new OperationOptions { + UserCredentials = new UserCredentials( + TestCredentials.Root.Username!, + TestCredentials.Root.Password! + ) + } ) .ShouldThrowAsync(); @@ -556,7 +564,9 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_throws_wev() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(6), events.Take(1))); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(6), events.Take(1)) + ); } [RetryFact] @@ -571,7 +581,7 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_returns_wev() { stream, StreamState.StreamRevision(6), events.Take(1), - options => options.ThrowOnAppendFailure = false + new OperationOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -585,7 +595,9 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_throws_wev() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(4), events.Take(1))); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(4), events.Take(1)) + ); } [RetryFact] @@ -600,7 +612,7 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_returns_wev() { stream, StreamState.StreamRevision(4), events.Take(1), - options => options.ThrowOnAppendFailure = false + new OperationOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -758,7 +770,7 @@ public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_ret stream, StreamState.NoStream, events, - options => options.ThrowOnAppendFailure = false + new OperationOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 2d3223d18..4bf7e6f93 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -18,7 +18,7 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s List expected = GenerateMessages(); //When - await Fixture.Streams.AppendToStreamAsync(stream, expected); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var group = await CreateToStreamSubscription(stream); @@ -42,7 +42,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await client.AppendToStreamAsync(stream, messagesWithMetadata); + await client.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var group = await CreateToStreamSubscription(stream, subscriptionsClient); @@ -62,10 +62,10 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await Fixture.Streams.AppendToStreamAsync(stream, messagesWithMetadata); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -80,7 +80,7 @@ public async Task subscribe_to_stream_without_options_does_NOT_deserialize_resol var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions .SubscribeToStream(stream, group, int.MaxValue).Take(2) .ToListAsync(); @@ -95,9 +95,9 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When - var group = await CreateToAllSubscription(stream); + var group = await CreateToAllSubscription(stream); var resolvedEvents = await Fixture.Subscriptions - .SubscribeToAll(group, int.MaxValue) + .SubscribeToAll(group, int.MaxValue) .Take(2) .ToListAsync(); @@ -130,7 +130,7 @@ Action customTypeMapping var (stream, expected) = await AppendEventsUsingAutoSerialization(client); // Then - var group = await CreateToStreamSubscription(stream, subscriptionsClient); + var group = await CreateToStreamSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -153,7 +153,7 @@ Action customTypeMapping var (stream, expected) = await AppendEventsUsingAutoSerialization(client); // Then - var group = await CreateToAllSubscription(stream, subscriptionsClient); + var group = await CreateToAllSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -180,7 +180,7 @@ public async Task automatic_serialization_custom_json_settings_are_applied() { var (stream, expected) = await AppendEventsUsingAutoSerialization(client); // Then - var group = await CreateToStreamSubscription(stream, subscriptionsClient); + var group = await CreateToStreamSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -197,7 +197,7 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(string messageTypeName, out Type? type) { #else public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { #endif @@ -208,7 +208,7 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { #else public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { #endif @@ -233,8 +233,8 @@ public async Task append_and_subscribe_to_stream_uses_custom_message_type_naming var (stream, expected) = await AppendEventsUsingAutoSerialization(client); //Then - var group = await CreateToStreamSubscription(stream, subscriptionsClient); - var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream,group).Take(2) + var group = await CreateToStreamSubscription(stream, subscriptionsClient); + var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream, group).Take(2) .ToListAsync(); Assert.All( @@ -261,7 +261,7 @@ public async Task append_and_subscribe_to_all_uses_custom_message_type_naming_st var (stream, expected) = await AppendEventsUsingAutoSerialization(client); //Then - var group = await CreateToAllSubscription(stream, subscriptionsClient); + var group = await CreateToAllSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -285,7 +285,7 @@ public async Task ); // When - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -301,7 +301,7 @@ public async Task subscribe_to_all_deserializes_resolved_message_appended_with_m ); // When - var group = await CreateToAllSubscription(stream); + var group = await CreateToAllSubscription(stream); var resolvedEvents = await Fixture.Subscriptions .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -319,7 +319,7 @@ public async Task var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); // When - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -334,7 +334,7 @@ public async Task var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); // When - var group = await CreateToAllSubscription(stream); + var group = await CreateToAllSubscription(stream); var resolvedEvents = await Fixture.Subscriptions .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -375,11 +375,14 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv ); } - async Task<(string, List)> AppendEventsUsingAutoSerialization(KurrentDBClient? kurrentDbClient = null) { + async Task<(string, List)> AppendEventsUsingAutoSerialization( + KurrentDBClient? kurrentDbClient = null + ) { var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, messages); + var writeResult = + await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.Any, messages); Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); @@ -437,7 +440,9 @@ Action customizeSerialization return new KurrentDBPersistentSubscriptionsClient(settings); } - async Task CreateToStreamSubscription(string stream, KurrentDBPersistentSubscriptionsClient? client = null) { + async Task CreateToStreamSubscription( + string stream, KurrentDBPersistentSubscriptionsClient? client = null + ) { string group = Fixture.GetGroupName(); await (client ?? Fixture.Subscriptions).CreateToStreamAsync( @@ -455,7 +460,7 @@ async Task CreateToAllSubscription(string stream, KurrentDBPersistentSub await (client ?? Fixture.Subscriptions).CreateToAllAsync( group, - StreamFilter.Prefix(stream), + StreamFilter.Prefix(stream), new(startFrom: Position.Start), userCredentials: TestCredentials.Root ); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 8092c44d3..564e5e600 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -18,7 +18,7 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s List expected = GenerateMessages(); //When - await Fixture.Streams.AppendToStreamAsync(stream, expected); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); //Then var resolvedEvents = await Fixture.Streams.SubscribeToStream(stream).Take(2).ToListAsync(); @@ -38,7 +38,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await client.AppendToStreamAsync(stream, messagesWithMetadata); + await client.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await client.SubscribeToStream(stream).Take(2).ToListAsync(); @@ -57,7 +57,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await Fixture.Streams.AppendToStreamAsync(stream, messagesWithMetadata); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await Fixture.Streams.SubscribeToStream(stream).Take(2).ToListAsync(); @@ -172,7 +172,7 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(string messageTypeName, out Type? type) { #else public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { #endif @@ -183,7 +183,7 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { #else public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { #endif @@ -267,7 +267,8 @@ public async Task subscribe_to_all_deserializes_resolved_message_appended_with_m } [RetryFact] - public async Task subscribe_to_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { + public async Task + subscribe_to_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { // Given var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); @@ -323,11 +324,15 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv ); } - async Task<(string, List)> AppendEventsUsingAutoSerialization(KurrentDBClient? kurrentDbClient = null) { + async Task<(string, List)> AppendEventsUsingAutoSerialization( + KurrentDBClient? kurrentDbClient = null + ) { var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, messages); + var writeResult = + await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.Any, messages); + Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index edc0be143..2167444c1 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -18,7 +18,7 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s List expected = GenerateMessages(); //When - await Fixture.Streams.AppendToStreamAsync(stream, expected); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); //Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); @@ -38,7 +38,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await client.AppendToStreamAsync(stream, messagesWithMetadata); + await client.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await client.ReadStreamAsync(stream).ToListAsync(); @@ -57,7 +57,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await Fixture.Streams.AppendToStreamAsync(stream, messagesWithMetadata); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); @@ -203,7 +203,11 @@ public async Task append_and_read_stream_uses_custom_message_type_naming_strateg //Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); - Assert.All(resolvedEvents, resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType)); + Assert.All( + resolvedEvents, + resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType) + ); + AssertThatMessages(AreDeserialized, expected, resolvedEvents); } @@ -222,7 +226,11 @@ public async Task append_and_read_all_uses_custom_message_type_naming_strategy() .ReadAllAsync(new ReadAllOptions { Filter = StreamFilter.Prefix(stream) }) .ToListAsync(); - Assert.All(resolvedEvents, resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType)); + Assert.All( + resolvedEvents, + resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType) + ); + AssertThatMessages(AreDeserialized, expected, resolvedEvents); } @@ -259,7 +267,8 @@ public async Task read_all_deserializes_resolved_message_appended_with_manual_co } [RetryFact] - public async Task read_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { + public async Task + read_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { // Given var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); @@ -316,11 +325,15 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv ); } - async Task<(string, List)> AppendEventsUsingAutoSerialization(KurrentDBClient? kurrentDbClient = null) { + async Task<(string, List)> AppendEventsUsingAutoSerialization( + KurrentDBClient? kurrentDbClient = null + ) { var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, messages); + var writeResult = + await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.NoStream, messages); + Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); diff --git a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs index 0510450c4..40ad0f5a4 100644 --- a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs @@ -5,7 +5,8 @@ namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Delete")] -public class SoftDeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class SoftDeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { static JsonDocument CustomMetadata { get; } static SoftDeleteTests() { @@ -209,7 +210,9 @@ await Fixture.Streams.AppendToStreamAsync( Assert.Equal(SystemStreams.MetastreamOf(stream), ex.Stream); - await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents())); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents()) + ); } [Fact] @@ -247,7 +250,11 @@ await Assert.ThrowsAsync( public async Task allows_recreating_for_first_write_only_returns_wrong_expected_version() { var stream = Fixture.GetStreamName(); - var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents(2)); + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents(2) + ); Assert.Equal(new(1), writeResult.NextExpectedStreamState); @@ -265,7 +272,7 @@ public async Task allows_recreating_for_first_write_only_returns_wrong_expected_ stream, StreamState.NoStream, Fixture.CreateTestEvents(), - options => options.ThrowOnAppendFailure = false + new OperationOptions { ThrowOnAppendFailure = false } ); Assert.IsType(wrongExpectedVersionResult); From 32f5c7c05ebd894a2dae1926b26be299a74f7be7 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 13 Mar 2025 09:22:17 +0100 Subject: [PATCH 03/23] [DEVEX-222] Ensured that metadata is serialized with auto-serialization as regular system metadata --- src/KurrentDB.Client/Core/OperationOptions.cs | 4 +++- .../Core/Serialization/MessageSerializer.cs | 12 +++++++----- src/KurrentDB.Client/Streams/KurrentDBClient.cs | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/KurrentDB.Client/Core/OperationOptions.cs b/src/KurrentDB.Client/Core/OperationOptions.cs index 34c41f7a9..a8bef2553 100644 --- a/src/KurrentDB.Client/Core/OperationOptions.cs +++ b/src/KurrentDB.Client/Core/OperationOptions.cs @@ -31,6 +31,8 @@ public record OperationOptions { public OperationOptions With(KurrentDBClientOperationOptions clientOperationOptions) => new() { ThrowOnAppendFailure = ThrowOnAppendFailure ?? clientOperationOptions.ThrowOnAppendFailure, - BatchAppendSize = BatchAppendSize ?? clientOperationOptions.BatchAppendSize + BatchAppendSize = BatchAppendSize ?? clientOperationOptions.BatchAppendSize, + Deadline = Deadline, + UserCredentials = UserCredentials, }; } diff --git a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs index aff4a6dc2..3594e00d0 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs @@ -54,8 +54,10 @@ public static IMessageSerializer With( } class MessageSerializer(SchemaRegistry schemaRegistry) : IMessageSerializer { - readonly ISerializer _jsonSerializer = - schemaRegistry.GetSerializer(ContentType.Json); + readonly SystemTextJsonSerializer _metadataSerializer = + new SystemTextJsonSerializer( + new SystemTextJsonSerializationSettings { Options = KurrentDBClient.StreamMetadataJsonSerializerOptions } + ); readonly IMessageTypeNamingStrategy _messageTypeNamingStrategy = schemaRegistry.MessageTypeNamingStrategy; @@ -74,7 +76,7 @@ public EventData Serialize(Message message, MessageSerializationContext serializ .Serialize(data); var serializedMetadata = metadata != null - ? _jsonSerializer.Serialize(metadata) + ? _metadataSerializer.Serialize(metadata) : ReadOnlyMemory.Empty; return new EventData( @@ -106,8 +108,8 @@ public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? } object? metadata = record.Metadata.Length > 0 && TryResolveClrMetadataType(record, out var clrMetadataType) - ? _jsonSerializer.Deserialize(record.Metadata, clrMetadataType!) - : null; + ? _metadataSerializer.Deserialize(record.Metadata, clrMetadataType!) + : null; deserialized = Message.From(data, metadata, record.EventId); return true; diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.cs index 4e9bc36c2..728897871 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.cs @@ -13,7 +13,7 @@ namespace KurrentDB.Client { /// The client used for operations on streams. /// public sealed partial class KurrentDBClient : KurrentDBClientBase { - static readonly JsonSerializerOptions StreamMetadataJsonSerializerOptions = new() { + internal static readonly JsonSerializerOptions StreamMetadataJsonSerializerOptions = new() { Converters = { StreamMetadataJsonConverter.Instance }, From dace25c167711e5990e26b0e10fe004d635b1be9 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 13 Mar 2025 10:29:10 +0100 Subject: [PATCH 04/23] [DEVEX-222] Adjusted samples to match the append api changes Added also factor for event data that's not requiring to provide id immediately --- samples/appending-events/Program.cs | 43 +++++-------- samples/diagnostics/Program.cs | 3 +- samples/projection-management/Program.cs | 3 +- samples/quick-start/Program.cs | 11 ++-- samples/reading-events/Program.cs | 3 +- samples/secure-with-tls/Program.cs | 2 +- samples/server-side-filtering/Program.cs | 3 +- samples/subscribing-to-streams/Program.cs | 5 +- src/KurrentDB.Client/Core/EventData.cs | 62 ++++++++++++++++--- .../Core/Serialization/Message.cs | 8 +-- 10 files changed, 83 insertions(+), 60 deletions(-) diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index a9fedf2a1..96593b3b1 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -18,8 +18,7 @@ static async Task AppendToStream(KurrentDBClient client) { #region append-to-stream - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = EventData.For( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -27,9 +26,7 @@ static async Task AppendToStream(KurrentDBClient client) { await client.AppendToStreamAsync( "some-stream", StreamState.NoStream, - new List { - eventData - } + [eventData] ); #endregion append-to-stream @@ -38,8 +35,7 @@ await client.AppendToStreamAsync( static async Task AppendWithSameId(KurrentDBClient client) { #region append-duplicate-event - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = EventData.For( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -47,18 +43,14 @@ static async Task AppendWithSameId(KurrentDBClient client) { await client.AppendToStreamAsync( "same-event-stream", StreamState.Any, - new List { - eventData - } + [eventData] ); // attempt to append the same event again await client.AppendToStreamAsync( "same-event-stream", StreamState.Any, - new List { - eventData - } + [eventData] ); #endregion append-duplicate-event @@ -67,14 +59,12 @@ await client.AppendToStreamAsync( static async Task AppendWithNoStream(KurrentDBClient client) { #region append-with-no-stream - var eventDataOne = new EventData( - Uuid.NewUuid(), + var eventDataOne = EventData.For( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); - var eventDataTwo = new EventData( - Uuid.NewUuid(), + var eventDataTwo = EventData.For( "some-event", "{\"id\": \"2\" \"value\": \"some other value\"}"u8.ToArray() ); @@ -82,18 +72,14 @@ static async Task AppendWithNoStream(KurrentDBClient client) { await client.AppendToStreamAsync( "no-stream-stream", StreamState.NoStream, - new List { - eventDataOne - } + [eventDataOne] ); // attempt to append the same event again await client.AppendToStreamAsync( "no-stream-stream", StreamState.NoStream, - new List { - eventDataTwo - } + [eventDataTwo] ); #endregion append-with-no-stream @@ -103,7 +89,7 @@ static async Task AppendWithConcurrencyCheck(KurrentDBClient client) { await client.AppendToStreamAsync( "concurrency-stream", StreamState.Any, - new[] { new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) } + [EventData.For("-", ReadOnlyMemory.Empty)] ); #region append-with-concurrency-check @@ -119,8 +105,7 @@ await client.AppendToStreamAsync( var clientTwoRead = client.ReadStreamAsync(Direction.Forwards, "concurrency-stream", StreamPosition.Start); var clientTwoRevision = (await clientTwoRead.LastAsync()).Event.EventNumber.ToUInt64(); - var clientOneData = new EventData( - Uuid.NewUuid(), + var clientOneData = EventData.For( "some-event", "{\"id\": \"1\" \"value\": \"clientOne\"}"u8.ToArray() ); @@ -162,9 +147,9 @@ static async Task AppendOverridingUserCredentials(KurrentDBClient client, Cancel await client.AppendToStreamAsync( "some-stream", StreamState.Any, - new[] { eventData }, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken + [eventData], + new OperationOptions { UserCredentials = new UserCredentials("admin", "changeit") }, + cancellationToken ); #endregion overriding-user-credentials diff --git a/samples/diagnostics/Program.cs b/samples/diagnostics/Program.cs index 0310858bf..e015fd6bd 100644 --- a/samples/diagnostics/Program.cs +++ b/samples/diagnostics/Program.cs @@ -55,8 +55,7 @@ static async Task TraceAppendToStream(KurrentDBClient client) { host.Start(); - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = EventData.For( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); diff --git a/samples/projection-management/Program.cs b/samples/projection-management/Program.cs index a868dfb11..1129dbd69 100644 --- a/samples/projection-management/Program.cs +++ b/samples/projection-management/Program.cs @@ -439,8 +439,7 @@ static async Task Populate(string connection, int numberOfEvents) { var client = new KurrentDBClient(settings); var messages = Enumerable.Range(0, numberOfEvents).Select( number => - new EventData( - Uuid.NewUuid(), + EventData.For( "eventtype", Encoding.UTF8.GetBytes($@"{{ ""Id"":{number} }}") ) diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 7b9588b75..7aa3c306a 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -21,8 +21,7 @@ ImportantData = "I wrote my first event!" }; -var eventData = new EventData( - Uuid.NewUuid(), +var eventData = EventData.For( "TestEvent", JsonSerializer.SerializeToUtf8Bytes(evt) ); @@ -34,7 +33,7 @@ await client.AppendToStreamAsync( "some-stream", StreamState.Any, - new[] { eventData }, + [eventData], cancellationToken: cancellationToken ); @@ -45,9 +44,9 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "some-stream", StreamState.Any, - new[] { eventData }, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken + [eventData], + new OperationOptions{ UserCredentials = new UserCredentials("admin", "changeit") }, + cancellationToken ); #endregion overriding-user-credentials diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index 394fe5ee3..1c58489a5 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -6,8 +6,7 @@ var events = Enumerable.Range(0, 20) .Select( - r => new EventData( - Uuid.NewUuid(), + r => EventData.For( "some-event", Encoding.UTF8.GetBytes($"{{\"id\": \"{r}\" \"value\": \"some value\"}}") ) diff --git a/samples/secure-with-tls/Program.cs b/samples/secure-with-tls/Program.cs index e82f1f548..48a72875f 100644 --- a/samples/secure-with-tls/Program.cs +++ b/samples/secure-with-tls/Program.cs @@ -9,7 +9,7 @@ await using var client = new KurrentDBClient(KurrentDBClientSettings.Create(connectionString)); -var eventData = new EventData(Uuid.NewUuid(), "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); +var eventData = EventData.For("some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); try { var appendResult = await client.AppendToStreamAsync( diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index 720d5c2f5..ae2fcd9a9 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -30,8 +30,7 @@ await Task.Delay(2000); for (var i = 0; i < eventCount; i++) { - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = EventData.For( i % 2 == 0 ? "some-event" : "other-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index f27f07efd..dbf9186da 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -165,9 +165,8 @@ static async Task SubscribeToAllFromPosition(KurrentDBClient client, Cancellatio var result = await client.AppendToStreamAsync( "subscribe-to-all-from-position", StreamState.NoStream, - new[] { - new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) - }); + [EventData.For("-", ReadOnlyMemory.Empty)] + ); await using var subscription = client.SubscribeToAll( FromAll.After(result.LogPosition), diff --git a/src/KurrentDB.Client/Core/EventData.cs b/src/KurrentDB.Client/Core/EventData.cs index 5568ed11b..963a77682 100644 --- a/src/KurrentDB.Client/Core/EventData.cs +++ b/src/KurrentDB.Client/Core/EventData.cs @@ -41,8 +41,10 @@ public sealed class EventData { /// 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) { + 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)); } @@ -51,15 +53,59 @@ public EventData(Uuid eventId, string type, ReadOnlyMemory data, ReadOnlyM 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."); + 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(); + EventId = eventId; + Type = type; + Data = data; + Metadata = metadata ?? Array.Empty(); ContentType = contentType; } + + /// + /// Creates a new with the specified event type, id, binary data and metadata + /// + /// 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. + /// Optional metadata providing additional context about the event, such as correlation IDs, timestamps, or user information. + /// Unique identifier for this specific event instance. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// A new immutable EventData instance with the specified properties. + /// Thrown when eventId is explicitly set to Uuid.Empty, which is an invalid identifier. + /// + /// + /// // Create an event with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new EventMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(UserRegistered).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var event = EventData.From(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = EventData.From(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public static EventData For( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? eventId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) => + new(eventId ?? Uuid.NewUuid(), type, data, metadata, contentType); } } diff --git a/src/KurrentDB.Client/Core/Serialization/Message.cs b/src/KurrentDB.Client/Core/Serialization/Message.cs index de553c556..a388c742e 100644 --- a/src/KurrentDB.Client/Core/Serialization/Message.cs +++ b/src/KurrentDB.Client/Core/Serialization/Message.cs @@ -1,6 +1,4 @@ -using KurrentDB.Client; - -namespace KurrentDB.Client.Core.Serialization; +namespace KurrentDB.Client; /// /// Represents a message wrapper in the KurrentDB system, containing both domain data and optional metadata. @@ -20,9 +18,9 @@ public record Message(object Data, object? Metadata, Uuid? MessageId = null) { /// /// /// // Create a message with a specific ID - /// var userCreated = new UserCreated { Id = "123", Name = "Alice" }; + /// var UserRegistered = new UserRegistered { Id = "123", Name = "Alice" }; /// var messageId = Uuid.NewUuid(); - /// var message = Message.From(userCreated, messageId); + /// var message = Message.From(UserRegistered, messageId); /// /// public static Message From(object data, Uuid messageId) => From b7dd8e28ba12f537888fcda8a8a7be171178efad Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 13 Mar 2025 11:27:29 +0100 Subject: [PATCH 05/23] [DEVEX-222] Added MessageData type as an easier to use replacement for EventData, and better aligned with general usage of messages instead of just events --- samples/appending-events/Program.cs | 26 +-- samples/diagnostics/Program.cs | 6 +- samples/projection-management/Program.cs | 2 +- samples/quick-start/Program.cs | 2 +- samples/reading-events/Program.cs | 2 +- samples/secure-with-tls/Program.cs | 4 +- samples/server-side-filtering/Program.cs | 4 +- samples/subscribing-to-streams/Program.cs | 2 +- src/KurrentDB.Client/Core/EventData.cs | 209 ++++++++++-------- src/KurrentDB.Client/Core/MessageData.cs | 137 ++++++++++++ .../Core/Serialization/Message.cs | 2 +- .../Core/Serialization/MessageSerializer.cs | 18 +- .../Streams/KurrentDBClient.Append.cs | 46 ++-- .../Streams/KurrentDBClient.Metadata.cs | 3 +- .../Streams/KurrentDBClientExtensions.cs | 77 ++++--- .../KurrentDBPermanentFixture.Helpers.cs | 15 +- .../KurrentDBTemporaryFixture.Helpers.cs | 15 +- .../MessageSerializerExtensionsTests.cs | 8 +- .../Serialization/MessageSerializerTests.cs | 20 +- .../FilterTestCases.cs | 10 +- .../SubscribeToAllFilterObsoleteTests.cs | 4 +- .../Obsolete/SubscribeToAllObsoleteTests.cs | 8 +- .../SubscribeToAllFilterTests.cs | 4 +- .../SubscribeToAll/SubscribeToAllTests.cs | 10 +- .../SubscribeToStreamGetInfoObsoleteTests.cs | 2 +- .../SubscribeToStreamObsoleteTests.cs | 30 +-- .../SubscribeToStreamGetInfoTests.cs | 2 +- .../SubscribeToStreamTests.cs | 24 +- .../Streams/AppendTests.cs | 4 +- .../Streams/Read/EventBinaryData.cs | 35 --- .../Streams/Read/MessageBinaryData.cs | 33 +++ ...DataComparer.cs => MessageDataComparer.cs} | 8 +- .../Streams/Read/ReadAllEventsFixture.cs | 10 +- .../Streams/Read/ReadAllEventsForwardTests.cs | 8 +- .../Streams/Read/ReadStreamBackwardTests.cs | 10 +- ...dStreamEventsLinkedToDeletedStreamTests.cs | 9 +- .../Streams/Read/ReadStreamForwardTests.cs | 8 +- ...reamWhenHavingMaxCountSetForStreamTests.cs | 12 +- ...ializationTests.PersistentSubscriptions.cs | 3 +- .../SerializationTests.Subscriptions.cs | 3 +- .../Serialization/SerializationTests.cs | 3 +- .../Streams/SoftDeleteTests.cs | 8 +- .../Streams/SubscriptionFilter.cs | 6 +- .../Obsolete/SubscribeToAllObsoleteTests.cs | 38 ++-- .../SubscribeToStreamObsoleteTests.cs | 8 +- .../Subscriptions/SubscribeToAllTests.cs | 40 ++-- .../Subscriptions/SubscribeToStreamTests.cs | 8 +- 47 files changed, 548 insertions(+), 398 deletions(-) create mode 100644 src/KurrentDB.Client/Core/MessageData.cs delete mode 100644 test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs create mode 100644 test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs rename test/KurrentDB.Client.Tests/Streams/Read/{EventDataComparer.cs => MessageDataComparer.cs} (65%) diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index 96593b3b1..1572169a5 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -18,7 +18,7 @@ static async Task AppendToStream(KurrentDBClient client) { #region append-to-stream - var eventData = EventData.For( + var eventData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -35,7 +35,7 @@ await client.AppendToStreamAsync( static async Task AppendWithSameId(KurrentDBClient client) { #region append-duplicate-event - var eventData = EventData.For( + var eventData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -59,12 +59,12 @@ await client.AppendToStreamAsync( static async Task AppendWithNoStream(KurrentDBClient client) { #region append-with-no-stream - var eventDataOne = EventData.For( + var eventDataOne = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); - var eventDataTwo = EventData.For( + var eventDataTwo = MessageData.From( "some-event", "{\"id\": \"2\" \"value\": \"some other value\"}"u8.ToArray() ); @@ -89,7 +89,7 @@ static async Task AppendWithConcurrencyCheck(KurrentDBClient client) { await client.AppendToStreamAsync( "concurrency-stream", StreamState.Any, - [EventData.For("-", ReadOnlyMemory.Empty)] + [MessageData.From("-", ReadOnlyMemory.Empty)] ); #region append-with-concurrency-check @@ -105,7 +105,7 @@ await client.AppendToStreamAsync( var clientTwoRead = client.ReadStreamAsync(Direction.Forwards, "concurrency-stream", StreamPosition.Start); var clientTwoRevision = (await clientTwoRead.LastAsync()).Event.EventNumber.ToUInt64(); - var clientOneData = EventData.For( + var clientOneData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"clientOne\"}"u8.ToArray() ); @@ -113,13 +113,10 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "no-stream-stream", clientOneRevision, - new List { - clientOneData - } + [clientOneData] ); - var clientTwoData = new EventData( - Uuid.NewUuid(), + var clientTwoData = MessageData.From( "some-event", "{\"id\": \"2\" \"value\": \"clientTwo\"}"u8.ToArray() ); @@ -127,17 +124,14 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "no-stream-stream", clientTwoRevision, - new List { - clientTwoData - } + [clientTwoData] ); #endregion append-with-concurrency-check } static async Task AppendOverridingUserCredentials(KurrentDBClient client, CancellationToken cancellationToken) { - var eventData = new EventData( - Uuid.NewUuid(), + var eventData =MessageData.From( "TestEvent", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); diff --git a/samples/diagnostics/Program.cs b/samples/diagnostics/Program.cs index e015fd6bd..5bdb96dcf 100644 --- a/samples/diagnostics/Program.cs +++ b/samples/diagnostics/Program.cs @@ -55,7 +55,7 @@ static async Task TraceAppendToStream(KurrentDBClient client) { host.Start(); - var eventData = EventData.For( + var eventData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -63,9 +63,7 @@ static async Task TraceAppendToStream(KurrentDBClient client) { await client.AppendToStreamAsync( Uuid.NewUuid().ToString(), StreamState.Any, - new List { - eventData - } + [eventData] ); # endregion setup-client-for-tracing diff --git a/samples/projection-management/Program.cs b/samples/projection-management/Program.cs index 1129dbd69..01b88c1b5 100644 --- a/samples/projection-management/Program.cs +++ b/samples/projection-management/Program.cs @@ -439,7 +439,7 @@ static async Task Populate(string connection, int numberOfEvents) { var client = new KurrentDBClient(settings); var messages = Enumerable.Range(0, numberOfEvents).Select( number => - EventData.For( + MessageData.From( "eventtype", Encoding.UTF8.GetBytes($@"{{ ""Id"":{number} }}") ) diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 7aa3c306a..31332c661 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -21,7 +21,7 @@ ImportantData = "I wrote my first event!" }; -var eventData = EventData.For( +var eventData = MessageData.From( "TestEvent", JsonSerializer.SerializeToUtf8Bytes(evt) ); diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index 1c58489a5..f5dea1890 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -6,7 +6,7 @@ var events = Enumerable.Range(0, 20) .Select( - r => EventData.For( + r => MessageData.From( "some-event", Encoding.UTF8.GetBytes($"{{\"id\": \"{r}\" \"value\": \"some value\"}}") ) diff --git a/samples/secure-with-tls/Program.cs b/samples/secure-with-tls/Program.cs index 48a72875f..82de15db5 100644 --- a/samples/secure-with-tls/Program.cs +++ b/samples/secure-with-tls/Program.cs @@ -9,11 +9,11 @@ await using var client = new KurrentDBClient(KurrentDBClientSettings.Create(connectionString)); -var eventData = EventData.For("some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); +var eventData = MessageData.From("some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); try { var appendResult = await client.AppendToStreamAsync( - "some-stream", StreamState.Any, new List { eventData } + "some-stream", StreamState.Any, [eventData] ); Console.WriteLine($"SUCCESS! Append result: {appendResult.LogPosition}"); diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index ae2fcd9a9..b50ca0339 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -30,7 +30,7 @@ await Task.Delay(2000); for (var i = 0; i < eventCount; i++) { - var eventData = EventData.For( + var eventData = MessageData.From( i % 2 == 0 ? "some-event" : "other-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -38,7 +38,7 @@ await client.AppendToStreamAsync( Guid.NewGuid().ToString("N"), StreamState.Any, - new List { eventData } + [eventData] ); } diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index dbf9186da..09ac19559 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -165,7 +165,7 @@ static async Task SubscribeToAllFromPosition(KurrentDBClient client, Cancellatio var result = await client.AppendToStreamAsync( "subscribe-to-all-from-position", StreamState.NoStream, - [EventData.For("-", ReadOnlyMemory.Empty)] + [MessageData.From("-", ReadOnlyMemory.Empty)] ); await using var subscription = client.SubscribeToAll( diff --git a/src/KurrentDB.Client/Core/EventData.cs b/src/KurrentDB.Client/Core/EventData.cs index 963a77682..e02022c7d 100644 --- a/src/KurrentDB.Client/Core/EventData.cs +++ b/src/KurrentDB.Client/Core/EventData.cs @@ -1,111 +1,126 @@ -using System; using System.Net.Http.Headers; -namespace KurrentDB.Client { +namespace KurrentDB.Client; + +/// +/// Represents an event to be written. +/// +[Obsolete("Use MessageData instead.", false)] +public sealed class EventData { /// - /// Represents an event to be written. + /// The raw bytes of the event data. /// - 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; + public readonly ReadOnlyMemory Data; - /// - /// The raw bytes of the event metadata. - /// - public readonly ReadOnlyMemory Metadata; + /// + /// The of the event, used as part of the idempotent write check. + /// + public readonly Uuid EventId; - /// - /// 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 raw bytes of the event metadata. + /// + public readonly ReadOnlyMemory Metadata; - /// - /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. - /// - public readonly string ContentType; + /// + /// The name of the event type. It is strongly recommended that these + /// use lowerCamelCase if projections are to be used. + /// + public readonly string Type; - /// - /// 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)); - } + /// + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// + public readonly string ContentType; - MediaTypeHeaderValue.Parse(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)); + } - 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." - ); - } + MediaTypeHeaderValue.Parse(contentType); - EventId = eventId; - Type = type; - Data = data; - Metadata = metadata ?? Array.Empty(); - ContentType = 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." + ); } - - /// - /// Creates a new with the specified event type, id, binary data and metadata - /// - /// 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. - /// Optional metadata providing additional context about the event, such as correlation IDs, timestamps, or user information. - /// Unique identifier for this specific event instance. - /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. - /// A new immutable EventData instance with the specified properties. - /// Thrown when eventId is explicitly set to Uuid.Empty, which is an invalid identifier. - /// - /// - /// // Create an event with data and metadata - /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; - /// - /// var metadata = new EventMetadata { - /// UserId = "user-456", - /// Timestamp = DateTimeOffset.UtcNow, - /// CorrelationId = correlationId - /// }; - /// - /// var type = typeof(UserRegistered).FullName!; - /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); - /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); - /// - /// // Let the system assign an ID automatically - /// var event = EventData.From(type, dataBytes, metadataBytes); - /// - /// // Or specify a custom ID - /// var messageWithId = EventData.From(type, dataBytes, metadataBytes, Uuid.NewUuid()); - /// - /// - public static EventData For( - string type, - ReadOnlyMemory data, - ReadOnlyMemory? metadata = null, - Uuid? eventId = null, - string contentType = Constants.Metadata.ContentTypes.ApplicationJson - ) => - new(eventId ?? Uuid.NewUuid(), type, data, metadata, contentType); + + EventId = eventId; + Type = type; + Data = data; + Metadata = metadata ?? Array.Empty(); + ContentType = contentType; } + + /// + /// Creates a new with the specified event type, id, binary data and metadata + /// + /// 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. + /// Optional metadata providing additional context about the event, such as correlation IDs, timestamps, or user information. + /// Unique identifier for this specific event instance. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// A new immutable EventData instance with the specified properties. + /// Thrown when eventId is explicitly set to Uuid.Empty, which is an invalid identifier. + /// + /// + /// // Create an event with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new EventMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(OrderPlaced).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var event = EventData.From(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = EventData.From(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public static EventData From( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? eventId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) => + new(eventId ?? Uuid.NewUuid(), type, data, metadata, contentType); + + /// + /// Implicitly converts an instance to a instance. + /// The EventId becomes the MessageId, and all other properties are directly mapped. + /// + /// The event data to convert to message data. + /// A new instance with properties copied from the event data. + public static implicit operator MessageData(EventData eventData) => + new MessageData( + eventData.Type, + eventData.Data, + eventData.Metadata, + eventData.EventId, + eventData.ContentType + ); } diff --git a/src/KurrentDB.Client/Core/MessageData.cs b/src/KurrentDB.Client/Core/MessageData.cs new file mode 100644 index 000000000..1b7c424d0 --- /dev/null +++ b/src/KurrentDB.Client/Core/MessageData.cs @@ -0,0 +1,137 @@ +using System.Net.Http.Headers; + +namespace KurrentDB.Client { + /// + /// Represents a message to be written. + /// + public sealed class MessageData { + /// + /// The raw bytes of the message data. + /// + public readonly ReadOnlyMemory Data; + + /// + /// The of the message, used as part of the idempotent write check. + /// + public readonly Uuid MessageId; + + /// + /// The raw bytes of the message metadata. + /// + public readonly ReadOnlyMemory Metadata; + + /// + /// The name of the message 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 name of the message type. It is strongly recommended that these use lowerCamelCase if projections are to be used. + /// The raw bytes of the message data. + /// The raw bytes of the message metadata. + /// The of the message, used as part of the idempotent write check. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// + /// + /// + /// // Create an message with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new MessageMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(OrderPlaced).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var message = new MessageData(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = new MessageData(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public MessageData( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? messageId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) { + messageId ??= Uuid.NewUuid(); + + if (messageId == Uuid.Empty) { + throw new ArgumentOutOfRangeException(nameof(messageId)); + } + + 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." + ); + } + + MessageId = messageId.Value; + Type = type; + Data = data; + Metadata = metadata ?? Array.Empty(); + ContentType = contentType; + } + + /// + /// Creates a new with the specified message type, id, binary data and metadata + /// + /// The name of the message type. It is strongly recommended that these use lowerCamelCase if projections are to be used. + /// The raw bytes of the message data. + /// Optional metadata providing additional context about the message, such as correlation IDs, timestamps, or user information. + /// Unique identifier for this specific message instance. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// A new immutable instance with the specified properties. + /// Thrown when messageId is explicitly set to Uuid.Empty, which is an invalid identifier. + /// + /// + /// // Create an message with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new MessageMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(OrderPlaced).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var message = MessageData.From(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = MessageData.From(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public static MessageData From( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? messageId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) => + new(type, data, metadata, messageId, contentType); + } +} diff --git a/src/KurrentDB.Client/Core/Serialization/Message.cs b/src/KurrentDB.Client/Core/Serialization/Message.cs index a388c742e..c87e3d967 100644 --- a/src/KurrentDB.Client/Core/Serialization/Message.cs +++ b/src/KurrentDB.Client/Core/Serialization/Message.cs @@ -7,7 +7,7 @@ namespace KurrentDB.Client; /// The message domain data. /// Optional metadata providing additional context about the message, such as correlation IDs, timestamps, or user information. /// Unique identifier for this specific message instance. When null, the system will auto-generate an ID. -public record Message(object Data, object? Metadata, Uuid? MessageId = null) { +public sealed record Message(object Data, object? Metadata, Uuid? MessageId = null) { /// /// Creates a new Message with the specified domain data and message ID, but without metadata. /// This factory method is a convenient shorthand when working with systems that don't require metadata. diff --git a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs index 3594e00d0..802182994 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs @@ -1,12 +1,10 @@ -using System.Diagnostics.CodeAnalysis; -using KurrentDB.Client; - namespace KurrentDB.Client.Core.Serialization; +using System.Diagnostics.CodeAnalysis; using static ContentTypeExtensions; interface IMessageSerializer { - public EventData Serialize(Message value, MessageSerializationContext context); + public MessageData Serialize(Message value, MessageSerializationContext context); #if NET48 public bool TryDeserialize(EventRecord record, out Message? deserialized); @@ -24,7 +22,7 @@ ContentType ContentType } static class MessageSerializerExtensions { - public static EventData[] Serialize( + public static MessageData[] Serialize( this IMessageSerializer serializer, IEnumerable messages, MessageSerializationContext context @@ -62,8 +60,8 @@ class MessageSerializer(SchemaRegistry schemaRegistry) : IMessageSerializer { readonly IMessageTypeNamingStrategy _messageTypeNamingStrategy = schemaRegistry.MessageTypeNamingStrategy; - public EventData Serialize(Message message, MessageSerializationContext serializationContext) { - var (data, metadata, eventId) = message; + public MessageData Serialize(Message message, MessageSerializationContext serializationContext) { + var (data, metadata, messageId) = message; var eventType = _messageTypeNamingStrategy .ResolveTypeName( @@ -79,11 +77,11 @@ public EventData Serialize(Message message, MessageSerializationContext serializ ? _metadataSerializer.Serialize(metadata) : ReadOnlyMemory.Empty; - return new EventData( - eventId ?? Uuid.NewUuid(), + return new MessageData( eventType, serializedData, serializedMetadata, + messageId, serializationContext.ContentType.ToMessageContentType() ); } @@ -135,7 +133,7 @@ bool TryResolveClrMetadataType(EventRecord record, out Type? clrMetadataType) => class NullMessageSerializer : IMessageSerializer { public static readonly NullMessageSerializer Instance = new NullMessageSerializer(); - public EventData Serialize(Message value, MessageSerializationContext context) { + public MessageData Serialize(Message value, MessageSerializationContext context) { throw new InvalidOperationException("Cannot serialize, automatic deserialization is disabled"); } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index 3ded2ace7..cbfe8be8f 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -38,9 +38,9 @@ public Task AppendToStreamAsync( var messageSerializer = _messageSerializer.With(Settings.Serialization, options?.SerializationSettings); - var eventsData = messageSerializer.Serialize(messages, serializationContext); + var messageData = messageSerializer.Serialize(messages, serializationContext); - return AppendToStreamAsync(streamName, expectedState, eventsData, options, cancellationToken); + return AppendToStreamAsync(streamName, expectedState, messageData, options, cancellationToken); } /// @@ -50,14 +50,14 @@ public Task AppendToStreamAsync( /// /// The name of the stream to append events to. /// The expected of the stream to append to. - /// Raw message data to append to the stream. + /// Raw message data to append to the stream. /// Optional settings for the append operation, e.g. deadline, user credentials etc. /// The optional . /// public async Task AppendToStreamAsync( string streamName, StreamState expectedState, - IEnumerable eventsData, + IEnumerable messageData, OperationOptions? options = null, CancellationToken cancellationToken = default ) { @@ -69,7 +69,7 @@ public async Task AppendToStreamAsync( var task = userCredentials == null && await BatchAppender.IsUsable().ConfigureAwait(false) - ? BatchAppender.Append(streamName, expectedState, eventsData, deadline, cancellationToken) + ? BatchAppender.Append(streamName, expectedState, messageData, deadline, cancellationToken) : AppendToStreamInternal( await GetChannelInfo(cancellationToken).ConfigureAwait(false), new AppendReq { @@ -77,7 +77,7 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), StreamIdentifier = streamName } }.WithAnyStreamRevision(expectedState), - eventsData, + messageData, operationOptions, cancellationToken ); @@ -88,13 +88,13 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), ValueTask AppendToStreamInternal( ChannelInfo channelInfo, AppendReq header, - IEnumerable eventData, + IEnumerable messageData, OperationOptions operationOptions, CancellationToken cancellationToken ) { var userCredentials = operationOptions.UserCredentials; var deadline = operationOptions.Deadline; - + var tags = new ActivityTagsCollection() .WithRequiredTag( TelemetryTags.Kurrent.Stream, @@ -123,10 +123,10 @@ await call.RequestStream .WriteAsync(header) .ConfigureAwait(false); - foreach (var e in eventData) { + foreach (var e in messageData) { var appendReq = new AppendReq { ProposedMessage = new() { - Id = e.EventId.ToDto(), + Id = e.MessageId.ToDto(), Data = ByteString.CopyFrom(e.Data.Span), CustomMetadata = ByteString.CopyFrom(e.Metadata.InjectTracingContext(Activity.Current)), Metadata = { @@ -253,8 +253,10 @@ Action onException } public ValueTask Append( - string streamName, StreamState expectedStreamState, - IEnumerable events, TimeSpan? timeoutAfter, + string streamName, + StreamState expectedStreamState, + IEnumerable events, + TimeSpan? timeoutAfter, CancellationToken cancellationToken = default ) => AppendInternal( @@ -267,7 +269,7 @@ public ValueTask Append( ValueTask AppendInternal( BatchAppendReq.Types.Options options, - IEnumerable events, + IEnumerable events, CancellationToken cancellationToken ) { var tags = new ActivityTagsCollection() @@ -370,7 +372,9 @@ out var writeResult } IEnumerable GetRequests( - IEnumerable events, BatchAppendReq.Types.Options options, Uuid correlationId + IEnumerable events, + BatchAppendReq.Types.Options options, + Uuid correlationId ) { var batchSize = 0; var first = true; @@ -381,7 +385,7 @@ IEnumerable GetRequests( var proposedMessage = new BatchAppendReq.Types.ProposedMessage { Data = ByteString.CopyFrom(eventData.Data.Span), CustomMetadata = ByteString.CopyFrom(eventData.Metadata.InjectTracingContext(Activity.Current)), - Id = eventData.EventId.ToDto(), + Id = eventData.MessageId.ToDto(), Metadata = { { Constants.Metadata.Type, eventData.Type }, { Constants.Metadata.ContentType, eventData.ContentType } @@ -459,7 +463,8 @@ public static Task AppendToStreamAsync( /// The optional . /// [Obsolete( - "This method may be removed in future releases. Use the overload with Message for auto-serialization or method with options parameter for raw serialization" + "This method may be removed in future releases. Use the overload with Message for auto-serialization or method with options parameter for raw serialization", + false )] public static Task AppendToStreamAsync( this KurrentDBClient dbClient, @@ -472,15 +477,16 @@ public static Task AppendToStreamAsync( CancellationToken cancellationToken = default ) { var operationOptions = new OperationOptions { - Deadline = deadline, - UserCredentials = userCredentials, + Deadline = deadline, + UserCredentials = userCredentials, }; + configureOperationOptions?.Invoke(operationOptions); - + return dbClient.AppendToStreamAsync( streamName, expectedState, - eventData, + eventData.Select(e => (MessageData)e), operationOptions, cancellationToken ); diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs index ac0d4d115..c9dbb7a2f 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs @@ -97,8 +97,7 @@ CancellationToken cancellationToken channelInfo, appendReq, [ - new EventData( - Uuid.NewUuid(), + new MessageData( SystemEventTypes.StreamMetadata, JsonSerializer.SerializeToUtf8Bytes(metadata, StreamMetadataJsonSerializerOptions) ) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs index 14af2ba5a..de438e7c4 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; namespace KurrentDB.Client { /// @@ -36,8 +32,7 @@ public static Task SetSystemSettingsAsync( SystemStreams.SettingsStream, StreamState.Any, [ - new EventData( - Uuid.NewUuid(), + new MessageData( SystemEventTypes.Settings, JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions) ) @@ -63,23 +58,12 @@ public static Task SetSystemSettingsAsync( TimeSpan? deadline = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) { - if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); - - return dbClient.AppendToStreamAsync( - SystemStreams.SettingsStream, - StreamState.Any, - [ - new EventData( - Uuid.NewUuid(), - SystemEventTypes.Settings, - JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions) - ) - ], + ) => + dbClient.SetSystemSettingsAsync( + settings, new OperationOptions { Deadline = deadline, UserCredentials = userCredentials }, cancellationToken: cancellationToken ); - } /// /// Appends to a stream conditionally. @@ -87,9 +71,8 @@ public static Task SetSystemSettingsAsync( /// /// /// - /// - /// - /// + /// + /// /// /// /// @@ -97,9 +80,8 @@ public static async Task ConditionalAppendToStreamAsync( this KurrentDBClient dbClient, string streamName, StreamState expectedState, - IEnumerable eventData, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, + IEnumerable messageData, + OperationOptions? options = null, CancellationToken cancellationToken = default ) { if (dbClient == null) { @@ -110,12 +92,8 @@ public static async Task ConditionalAppendToStreamAsync( var result = await dbClient.AppendToStreamAsync( streamName, expectedState, - eventData, - new OperationOptions { - ThrowOnAppendFailure = false, - Deadline = deadline, - UserCredentials = userCredentials - }, + messageData, + options, cancellationToken ) .ConfigureAwait(false); @@ -127,5 +105,40 @@ public static async Task ConditionalAppendToStreamAsync( return ConditionalWriteResult.FromWrongExpectedVersion(ex); } } + + /// + /// Appends to a stream conditionally. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Task ConditionalAppendToStreamAsync( + this KurrentDBClient dbClient, + string streamName, + StreamState expectedState, +#pragma warning disable CS0618 // Type or member is obsolete + IEnumerable eventData, +#pragma warning restore CS0618 // Type or member is obsolete + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.ConditionalAppendToStreamAsync( + streamName, + expectedState, + eventData.Select(e => (MessageData)e), + new OperationOptions { + ThrowOnAppendFailure = false, + Deadline = deadline, + UserCredentials = userCredentials + }, + cancellationToken + ); } } diff --git a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs index 2a9b80c05..9eb2d0293 100644 --- a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs +++ b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs @@ -32,7 +32,7 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => public ReadOnlyMemory CreateTestNonJsonMetadata() => "non-json-metadata"u8.ToArray(); - public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { + public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { var size = 0; var events = CreateTestEvents(int.MaxValue) @@ -42,7 +42,7 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => return (events, (uint)size); } - public IEnumerable CreateTestEvents( + public IEnumerable CreateTestEvents( int count = 1, string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => Enumerable.Range(0, count) @@ -53,12 +53,12 @@ public IEnumerable CreateTestMessages(int count = 1, object? metadata = Enumerable.Range(0, count) .Select(index => CreateTestMessage(index, metadata)); - public EventData CreateTestEvent( + public MessageData CreateTestEvent( string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => CreateTestEvent(0, type ?? TestEventType, metadata, contentType); - public IEnumerable CreateTestEventsThatThrowsException() { + public IEnumerable CreateTestEventsThatThrowsException() { // Ensure initial IEnumerator.Current does not throw yield return CreateTestEvent(1); @@ -66,7 +66,7 @@ public IEnumerable CreateTestEventsThatThrowsException() { throw new Exception(); } - protected static EventData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); + protected static MessageData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); protected static Message CreateTestMessage(int index, object? metadata = null) => Message.From( @@ -75,15 +75,14 @@ protected static Message CreateTestMessage(int index, object? metadata = null) = Uuid.NewUuid() ); - protected static EventData CreateTestEvent( + protected static MessageData CreateTestEvent( int index, string type, ReadOnlyMemory? metadata = null, string? contentType = null ) => new( - Uuid.NewUuid(), type, Encoding.UTF8.GetBytes($$"""{"x":{{index}}}"""), metadata, - contentType ?? "application/json" + contentType: contentType ?? "application/json" ); public async Task CreateTestUser(bool withoutGroups = true, bool useUserCredentials = false) { diff --git a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs index ff491e4da..1fe14f9d5 100644 --- a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs +++ b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs @@ -32,7 +32,7 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => public ReadOnlyMemory CreateTestNonJsonMetadata() => "non-json-metadata"u8.ToArray(); - public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { + public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { var size = 0; var events = CreateTestEvents(int.MaxValue) @@ -42,18 +42,18 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => return (events, (uint)size); } - public IEnumerable CreateTestEvents( + public IEnumerable CreateTestEvents( int count = 1, string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => Enumerable.Range(0, count) .Select(index => CreateTestEvent(index, type ?? TestEventType, metadata, contentType)); - public EventData CreateTestEvent( + public MessageData CreateTestEvent( string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => CreateTestEvent(0, type ?? TestEventType, metadata, contentType); - public IEnumerable CreateTestEventsThatThrowsException() { + public IEnumerable CreateTestEventsThatThrowsException() { // Ensure initial IEnumerator.Current does not throw yield return CreateTestEvent(1); @@ -61,17 +61,16 @@ public IEnumerable CreateTestEventsThatThrowsException() { throw new Exception(); } - protected static EventData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); + protected static MessageData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); - protected static EventData CreateTestEvent( + protected static MessageData CreateTestEvent( int index, string type, ReadOnlyMemory? metadata = null, string? contentType = null ) => new( - Uuid.NewUuid(), type, Encoding.UTF8.GetBytes($$"""{"x":{{index}}}"""), metadata, - contentType ?? "application/json" + contentType: contentType ?? "application/json" ); public async Task CreateTestUser(bool withoutGroups = true, bool useUserCredentials = false) { diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs index 122bf08f8..0d25dc79c 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using KurrentDB.Client; using KurrentDB.Client.Core.Serialization; namespace KurrentDB.Client.Tests.Core.Serialization; @@ -86,13 +85,12 @@ public void Serialize_WithMultipleMessages_ReturnsArrayOfEventData() { } class DummyMessageSerializer : IMessageSerializer { - public EventData Serialize(Message value, MessageSerializationContext context) { - return new EventData( - Uuid.NewUuid(), + public MessageData Serialize(Message value, MessageSerializationContext context) { + return new MessageData( "TestEvent", ReadOnlyMemory.Empty, ReadOnlyMemory.Empty, - "application/json" + contentType: "application/json" ); } diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs index f7165a4fe..449f822fe 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs @@ -22,7 +22,7 @@ public void Serialize_WithValidMessage_ReturnsEventData() { var eventData = serializer.Serialize(message, context); // Then - Assert.Equal(messageId, eventData.EventId); + Assert.Equal(messageId, eventData.MessageId); Assert.Equal("UserRegistered", eventData.Type); Assert.NotEmpty(eventData.Data.Span.ToArray()); Assert.NotEmpty(eventData.Metadata.Span.ToArray()); @@ -45,7 +45,7 @@ public void Serialize_WithAutoGeneratedId_GeneratesNewId() { var eventData = serializer.Serialize(message, context); // Then - Assert.NotEqual(Uuid.Empty, eventData.EventId); + Assert.NotEqual(Uuid.Empty, eventData.MessageId); } [Fact] @@ -118,9 +118,9 @@ public void TryDeserialize_WithValidEventRecord_DeserializesSuccessfully() { var testEvent = new UserRegistered("user-123", "user@random-email.com"); var testMetadata = new TestMetadata { CorrelationId = "corr-123", UserId = "user-456" }; - var eventId = Uuid.NewUuid(); + var messageId = Uuid.NewUuid(); - var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, testMetadata, eventId); + var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, testMetadata, messageId); // When var success = serializer.TryDeserialize(eventRecord, out var message); @@ -128,7 +128,7 @@ public void TryDeserialize_WithValidEventRecord_DeserializesSuccessfully() { // Then Assert.True(success); Assert.NotNull(message); - Assert.Equal(eventId, message.MessageId); + Assert.Equal(messageId, message.MessageId); var deserializedEvent = Assert.IsType(message.Data); Assert.Equal("user-123", deserializedEvent.UserId); @@ -181,9 +181,9 @@ public void TryDeserialize_WithEmptyMetadata_DeserializesWithNullMetadata() { var serializer = new MessageSerializer(schemaRegistry); var testEvent = new UserRegistered("user-123", "user@random-email.com"); - var eventId = Uuid.NewUuid(); + var messageId = Uuid.NewUuid(); - var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, null, eventId); + var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, null, messageId); // When var success = serializer.TryDeserialize(eventRecord, out var message); @@ -191,7 +191,7 @@ public void TryDeserialize_WithEmptyMetadata_DeserializesWithNullMetadata() { // Then Assert.True(success); Assert.NotNull(message); - Assert.Equal(eventId, message.MessageId); + Assert.Equal(messageId, message.MessageId); Assert.Null(message.Metadata); var deserializedEvent = Assert.IsType(message.Data); @@ -235,11 +235,11 @@ static KurrentDBClientSerializationSettings CreateTestSettings() { } static EventRecord CreateTestEventRecord( - string eventType, object? data = null, object? metadata = null, Uuid? eventId = null + string eventType, object? data = null, object? metadata = null, Uuid? messageId = null ) => new( Uuid.NewUuid().ToString(), - eventId ?? Uuid.NewUuid(), + messageId ?? Uuid.NewUuid(), StreamPosition.FromInt64(0), new Position(1, 1), new Dictionary { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs index 66d4562d5..4919009ab 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs @@ -9,23 +9,23 @@ public static class Filters { const string EventTypePrefix = nameof(EventTypePrefix); const string EventTypeRegex = nameof(EventTypeRegex); - static readonly IDictionary, Func)> + static readonly IDictionary, Func)> s_filters = - new Dictionary, Func)> { + new Dictionary, Func)> { [StreamNamePrefix] = (StreamFilter.Prefix, (_, e) => e), [StreamNameRegex] = (f => StreamFilter.RegularExpression(f), (_, e) => e), [EventTypePrefix] = (EventTypeFilter.Prefix, (term, e) => new( - e.EventId, term, e.Data, e.Metadata, + e.MessageId, e.ContentType )), [EventTypeRegex] = (f => EventTypeFilter.RegularExpression(f), (term, e) => new( - e.EventId, term, e.Data, e.Metadata, + e.MessageId, e.ContentType )) }; @@ -35,7 +35,7 @@ public static class Filters { .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(fi => (string)fi.GetRawConstantValue()!); - public static (Func getFilter, Func prepareEvent) + public static (Func getFilter, Func prepareEvent) GetFilter(string name) => s_filters[name]; } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs index 9cd08e4b9..0f92800dd 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs @@ -59,7 +59,7 @@ await subscription.Messages ) .WithTimeout(); - Assert.Equal(events.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } [RetryTheory] @@ -123,7 +123,7 @@ await Fixture.Subscriptions.CreateToAllAsync( .AsTask() .WithTimeout(); - Assert.Equal(eventsToCapture.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(eventsToCapture.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs index 2542dc113..111dc825b 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs @@ -129,7 +129,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); var resolvedEvent = await firstNonSystemEventSource.Task.WithTimeout(); - Assert.Equal(expectedEvent!.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -172,7 +172,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); var resolvedEvent = await firstNonSystemEventSource.Task.WithTimeout(); - Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -524,7 +524,7 @@ public async Task update_existing_with_check_point_filtered() { List appearedEvents = []; - EventData[] events = Fixture.CreateTestEvents(5).ToArray(); + MessageData[] events = Fixture.CreateTestEvents(5).ToArray(); Position checkPoint = default; @@ -690,7 +690,7 @@ await Fixture.Streams.AppendToStreamAsync( } Assert.True(secondCheckPoint > firstCheckPoint); - Assert.Equal(events.Select(e => e.EventId), appearedEvents.Select(e => e.Event.EventId)); + Assert.Equal(events.Select(e => e.MessageId), appearedEvents.Select(e => e.Event.EventId)); return; diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs index 6ce230a56..6a678dfb4 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs @@ -60,7 +60,7 @@ await subscription.Messages ) .WithTimeout(); - Assert.Equal(events.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } [RetryTheory] @@ -124,7 +124,7 @@ await Fixture.Subscriptions.CreateToAllAsync( .AsTask() .WithTimeout(); - Assert.Equal(eventsToCapture.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(eventsToCapture.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs index 97c9b3e3e..fbd31b235 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs @@ -112,7 +112,7 @@ await Fixture.Streams.AppendToStreamAsync( .Where(resolvedEvent => !SystemStreams.IsSystemStream(resolvedEvent.OriginalStreamId)) .FirstOrDefaultAsync().AsTask().WithTimeout(); - Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -144,7 +144,7 @@ await Fixture.Streams.AppendToStreamAsync( .AsTask() .WithTimeout(); - Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -360,10 +360,10 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { var events = Fixture.CreateTestEvents(eventWriteCount) .Select( - (e, i) => new EventData( - e.EventId, + (e, i) => new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"{i}@test"), + messageId: e.MessageId, contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) ) @@ -721,7 +721,7 @@ await Fixture.Streams.AppendToStreamAsync( } Assert.True(secondCheckPoint > firstCheckPoint); - Assert.Equal(events.Select(e => e.EventId), appearedEvents.Select(e => e.Event.EventId)); + Assert.Equal(events.Select(e => e.MessageId), appearedEvents.Select(e => e.Event.EventId)); return; diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs index af9014258..19a73cc39 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs @@ -190,7 +190,7 @@ await Subscriptions.SubscribeToStreamAsync( await Streams.AppendToStreamAsync( Stream, StreamState.Any, - [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], + [new MessageData("test-event", ReadOnlyMemory.Empty)], new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs index 3648697f8..ea46b1c5b 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -101,7 +101,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(TimeSpan.FromSeconds(10)); Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); - Assert.Equal(events[0].EventId, resolvedEvent.Event.EventId); + Assert.Equal(events[0].MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -113,7 +113,7 @@ public async Task connect_to_existing_with_start_from_beginning_and_no_stream() var events = Fixture.CreateTestEvents().ToArray(); - var eventId = events.Single().EventId; + var eventId = events.Single().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -215,7 +215,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -290,7 +290,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -327,7 +327,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var firstEvent = firstEventSource.Task; var resolvedEvent = await firstEvent.WithTimeout(TimeSpan.FromSeconds(10)); - var eventId = events.Last().EventId; + var eventId = events.Last().MessageId; Assert.Equal(new(2), resolvedEvent.Event.EventNumber); Assert.Equal(eventId, resolvedEvent.Event.EventId); @@ -370,7 +370,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(4), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Skip(4).First().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Skip(4).First().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -410,7 +410,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -450,7 +450,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -554,7 +554,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -636,23 +636,23 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { const int bufferCount = 10; const int eventWriteCount = bufferCount * 2; - EventData[] events; + MessageData[] events; TaskCompletionSource eventsReceived = new(); int eventReceivedCount = 0; events = Fixture.CreateTestEvents(eventWriteCount) .Select( - (e, i) => new EventData( - e.EventId, + (e, i) => new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"{i}@{stream}"), + messageId: e.MessageId, contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) ) .ToArray(); foreach (var e in events) - await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { e }); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -776,7 +776,7 @@ public async Task update_existing_with_check_point() { TaskCompletionSource resumedSource = new(); TaskCompletionSource appeared = new(); List appearedEvents = []; - EventData[] events = Fixture.CreateTestEvents(5).ToArray(); + MessageData[] events = Fixture.CreateTestEvents(5).ToArray(); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); await Fixture.Subscriptions.CreateToStreamAsync( @@ -911,7 +911,7 @@ public async Task when_writing_and_subscribing_to_normal_events_manual_nack() { int eventReceivedCount = 0; - EventData[] events = Fixture.CreateTestEvents(eventWriteCount) + MessageData[] events = Fixture.CreateTestEvents(eventWriteCount) .ToArray(); TaskCompletionSource eventsReceived = new(); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs index b19f8e8af..e72f77276 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs @@ -179,7 +179,7 @@ await Subscriptions.CreateToStreamAsync( await Streams.AppendToStreamAsync( Stream, StreamState.Any, - [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], + [new MessageData("test-event", ReadOnlyMemory.Empty)], new OperationOptions { UserCredentials = TestCredentials.Root } ); } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs index f611d8e46..f1e683a43 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs @@ -83,7 +83,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); - Assert.Equal(events[0].EventId, resolvedEvent.Event.EventId); + Assert.Equal(events[0].MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -91,7 +91,7 @@ public async Task connect_to_existing_with_start_from_beginning_and_no_stream() var stream = Fixture.GetStreamName(); var group = Fixture.GetGroupName(); var events = Fixture.CreateTestEvents().ToArray(); - var eventId = events.Single().EventId; + var messageId = events.Single().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -109,7 +109,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); - Assert.Equal(eventId, resolvedEvent.Event.EventId); + Assert.Equal(messageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -139,7 +139,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -192,7 +192,7 @@ public async Task connect_to_existing_with_start_from_two_and_no_stream() { var stream = Fixture.GetStreamName(); var group = Fixture.GetGroupName(); - var eventId = events.Last().EventId; + var messageId = events.Last().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -214,7 +214,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(2), resolvedEvent.Event.EventNumber); - Assert.Equal(eventId, resolvedEvent.Event.EventId); + Assert.Equal(messageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -245,7 +245,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(4), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Skip(4).First().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Skip(4).First().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -275,7 +275,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -305,7 +305,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -385,7 +385,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -520,10 +520,10 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { var events = Fixture.CreateTestEvents(eventWriteCount) .Select( - (e, i) => new EventData( - e.EventId, + (e, i) => new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"{i}@{stream}"), + messageId: e.MessageId, contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) ).ToArray(); diff --git a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs index 3040968d3..10e361dc3 100644 --- a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs @@ -17,7 +17,7 @@ public async Task appending_zero_events(StreamState expectedStreamState) { var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, expectedStreamState, - Enumerable.Empty() + Enumerable.Empty() ); writeResult.NextExpectedStreamState.ShouldBe(StreamState.NoStream); @@ -37,7 +37,7 @@ public async Task appending_zero_events_again(StreamState expectedStreamState) { var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, expectedStreamState, - Enumerable.Empty() + Enumerable.Empty() ); Assert.Equal(StreamState.NoStream, writeResult.NextExpectedStreamState); diff --git a/test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs b/test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs deleted file mode 100644 index 15edd42b8..000000000 --- a/test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs +++ /dev/null @@ -1,35 +0,0 @@ -using KurrentDB.Client; - -namespace KurrentDB.Client.Tests; - -public readonly record struct EventBinaryData(Uuid Id, byte[] Data, byte[] Metadata) { - public bool Equals(EventBinaryData other) => - Id.Equals(other.Id) - && Data.SequenceEqual(other.Data) - && Metadata.SequenceEqual(other.Metadata); - - public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); -} - -public static class EventBinaryDataConverters { - public static EventBinaryData ToBinaryData(this EventData source) => - new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); - - public static EventBinaryData ToBinaryData(this EventRecord source) => - new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); - - public static EventBinaryData ToBinaryData(this ResolvedEvent source) => - source.Event.ToBinaryData(); - - public static EventBinaryData[] ToBinaryData(this IEnumerable source) => - source.Select(x => x.ToBinaryData()).ToArray(); - - public static EventBinaryData[] ToBinaryData(this IEnumerable source) => - source.Select(x => x.ToBinaryData()).ToArray(); - - public static EventBinaryData[] ToBinaryData(this IEnumerable source) => - source.Select(x => x.ToBinaryData()).ToArray(); - - public static ValueTask ToBinaryData(this IAsyncEnumerable source) => - source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); -} diff --git a/test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs b/test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs new file mode 100644 index 000000000..918c19818 --- /dev/null +++ b/test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs @@ -0,0 +1,33 @@ +namespace KurrentDB.Client.Tests; + +public readonly record struct MessageBinaryData(Uuid Id, byte[] Data, byte[] Metadata) { + public bool Equals(MessageBinaryData other) => + Id.Equals(other.Id) + && Data.SequenceEqual(other.Data) + && Metadata.SequenceEqual(other.Metadata); + + public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); +} + +public static class EventBinaryDataConverters { + public static MessageBinaryData ToBinaryData(this MessageData source) => + new(source.MessageId, source.Data.ToArray(), source.Metadata.ToArray()); + + public static MessageBinaryData ToBinaryData(this EventRecord source) => + new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); + + public static MessageBinaryData ToBinaryData(this ResolvedEvent source) => + source.Event.ToBinaryData(); + + public static MessageBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static MessageBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static MessageBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static ValueTask ToBinaryData(this IAsyncEnumerable source) => + source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); +} diff --git a/test/KurrentDB.Client.Tests/Streams/Read/EventDataComparer.cs b/test/KurrentDB.Client.Tests/Streams/Read/MessageDataComparer.cs similarity index 65% rename from test/KurrentDB.Client.Tests/Streams/Read/EventDataComparer.cs rename to test/KurrentDB.Client.Tests/Streams/Read/MessageDataComparer.cs index fc36d3609..d7f48e991 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/EventDataComparer.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/MessageDataComparer.cs @@ -2,9 +2,9 @@ namespace KurrentDB.Client.Tests; -static class EventDataComparer { - public static bool Equal(EventData expected, EventRecord actual) { - if (expected.EventId != actual.EventId) +static class MessageDataComparer { + public static bool Equal(MessageData expected, EventRecord actual) { + if (expected.MessageId != actual.EventId) return false; if (expected.Type != actual.EventType) @@ -14,7 +14,7 @@ public static bool Equal(EventData expected, EventRecord actual) { && expected.Metadata.ToArray().SequenceEqual(actual.Metadata.ToArray()); } - public static bool Equal(EventData[] expected, EventRecord[] actual) { + public static bool Equal(MessageData[] expected, EventRecord[] actual) { if (expected.Length != actual.Length) return false; diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs index dcb2fe1d3..448375604 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs @@ -36,11 +36,11 @@ public ReadAllEventsFixture() { public string ExpectedStreamName { get; private set; } = null!; - public EventData[] Events { get; private set; } = Array.Empty(); + public MessageData[] Events { get; private set; } = []; - public EventBinaryData[] ExpectedEvents { get; private set; } = Array.Empty(); - public EventBinaryData[] ExpectedEventsReversed { get; private set; } = Array.Empty(); + public MessageBinaryData[] ExpectedEvents { get; private set; } = []; + public MessageBinaryData[] ExpectedEventsReversed { get; private set; } = []; - public EventBinaryData ExpectedFirstEvent { get; private set; } - public EventBinaryData ExpectedLastEvent { get; private set; } + public MessageBinaryData ExpectedFirstEvent { get; private set; } + public MessageBinaryData ExpectedLastEvent { get; private set; } } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs index 442292c15..fa1dd6046 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs @@ -90,15 +90,15 @@ await Fixture.Streams.SetStreamMetadataAsync( await Fixture.Streams.AppendToStreamAsync( linkedStream, StreamState.Any, - new[] { - new EventData( - Uuid.NewUuid(), + [ + new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"0@{deletedStream}"), Array.Empty(), + Uuid.NewUuid(), Constants.Metadata.ContentTypes.ApplicationOctetStream ) - } + ] ); var events = await Fixture.Streams.ReadStreamAsync( diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs index 1fd9d1d9e..772a0190b 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs @@ -77,7 +77,7 @@ public async Task returns_events_in_reversed_order(string suffix, int count, int .Select(x => x.Event).ToArrayAsync(); Assert.True( - EventDataComparer.Equal( + MessageDataComparer.Equal( Enumerable.Reverse(expected).ToArray(), actual ) @@ -97,7 +97,7 @@ public async Task be_able_to_read_single_event_from_arbitrary_position() { .Select(x => x.Event) .SingleAsync(); - Assert.True(EventDataComparer.Equal(expected, actual)); + Assert.True(MessageDataComparer.Equal(expected, actual)); } [Fact] @@ -112,7 +112,7 @@ public async Task be_able_to_read_from_arbitrary_position() { .Select(x => x.Event) .ToArrayAsync(); - Assert.True(EventDataComparer.Equal(events.Skip(2).Take(2).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(events.Skip(2).Take(2).Reverse().ToArray(), actual)); } [Fact] @@ -128,7 +128,7 @@ public async Task be_able_to_read_first_event() { .ToArrayAsync(); Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); + Assert.True(MessageDataComparer.Equal(testEvents[0], events[0])); } [Fact] @@ -143,7 +143,7 @@ public async Task be_able_to_read_last_event() { .ToArrayAsync(); Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[^1], events[0])); + Assert.True(MessageDataComparer.Equal(testEvents[^1], events[0])); } [Fact] diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs index f852121f0..644744338 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs @@ -50,15 +50,14 @@ protected ReadEventsLinkedToDeletedStreamFixture(Direction direction) { await Streams.AppendToStreamAsync( LinkedStream, StreamState.Any, - new[] { - new EventData( - Uuid.NewUuid(), + [ + new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"0@{DeletedStream}"), Array.Empty(), - Constants.Metadata.ContentTypes.ApplicationOctetStream + contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) - } + ] ); await Streams.DeleteAsync(DeletedStream, StreamState.Any); diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs index 5457f9ddb..c0a632cda 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs @@ -73,7 +73,7 @@ public async Task returns_events_in_order(string suffix, int count, int metadata .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, expected.Length) .Select(x => x.Event).ToArrayAsync(); - Assert.True(EventDataComparer.Equal(expected, actual)); + Assert.True(MessageDataComparer.Equal(expected, actual)); } [Fact] @@ -90,7 +90,7 @@ public async Task be_able_to_read_single_event_from_arbitrary_position() { .Select(x => x.Event) .SingleAsync(); - Assert.True(EventDataComparer.Equal(expected, actual)); + Assert.True(MessageDataComparer.Equal(expected, actual)); } [Fact] @@ -105,7 +105,7 @@ public async Task be_able_to_read_from_arbitrary_position() { .Select(x => x.Event) .ToArrayAsync(); - Assert.True(EventDataComparer.Equal(events.Skip(3).Take(2).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(events.Skip(3).Take(2).ToArray(), actual)); } [Fact] @@ -121,7 +121,7 @@ public async Task be_able_to_read_first_event() { .ToArrayAsync(); Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); + Assert.True(MessageDataComparer.Equal(testEvents[0], events[0])); } [Fact] diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs index de1b7eafc..0b588cb7d 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs @@ -24,7 +24,7 @@ public async Task read_stream_forwards_respects_max_count() { .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(2).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(2).ToArray(), actual)); } [Fact] @@ -42,7 +42,7 @@ public async Task read_stream_backwards_respects_max_count() { .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(2).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(2).Reverse().ToArray(), actual)); } [Fact] @@ -62,7 +62,7 @@ public async Task after_setting_less_strict_max_count_read_stream_forward_reads_ .ToArrayAsync(); Assert.Equal(4, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(1).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(1).ToArray(), actual)); } [Fact] @@ -82,7 +82,7 @@ public async Task after_setting_more_strict_max_count_read_stream_forward_reads_ .ToArrayAsync(); Assert.Equal(2, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(3).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(3).ToArray(), actual)); } [Fact] @@ -102,7 +102,7 @@ public async Task after_setting_less_strict_max_count_read_stream_backwards_read .ToArrayAsync(); Assert.Equal(4, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(1).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(1).Reverse().ToArray(), actual)); } [Fact] @@ -122,6 +122,6 @@ public async Task after_setting_more_strict_max_count_read_stream_backwards_read .ToArrayAsync(); Assert.Equal(2, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(3).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(3).Reverse().ToArray(), actual)); } } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 4bf7e6f93..1394d2b91 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -395,8 +395,7 @@ Func getTypeName var messages = GenerateMessages(); var eventData = messages.Select( message => - new EventData( - Uuid.NewUuid(), + new MessageData( getTypeName(message), Encoding.UTF8.GetBytes( JsonSerializer.Serialize( diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 564e5e600..4952cabb2 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -345,8 +345,7 @@ Func getTypeName var messages = GenerateMessages(); var eventData = messages.Select( message => - new EventData( - Uuid.NewUuid(), + new MessageData( getTypeName(message), Encoding.UTF8.GetBytes( JsonSerializer.Serialize( diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index 2167444c1..91b7c53cc 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -346,8 +346,7 @@ Func getTypeName var messages = GenerateMessages(); var eventData = messages.Select( message => - new EventData( - Uuid.NewUuid(), + new MessageData( getTypeName(message), Encoding.UTF8.GetBytes( JsonSerializer.Serialize( diff --git a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs index 40ad0f5a4..3c096cbae 100644 --- a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs @@ -66,7 +66,7 @@ public async Task recreated_with_any_expected_version(StreamState expectedState, .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.Equal(events.Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(1, 3).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -108,7 +108,7 @@ public async Task recreated_with_expected_version() { .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.Equal(events.Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(1, 3).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -159,7 +159,7 @@ public async Task recreated_preserves_metadata_except_truncate_before() { .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.Equal(events.Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(count, 3).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -308,7 +308,7 @@ await Fixture.Streams.AppendToStreamAsync( .Select(x => x.Event) .ToArrayAsync(); - Assert.Equal(firstEvents.Concat(secondEvents).Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(firstEvents.Concat(secondEvents).Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(2, 5).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) diff --git a/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs b/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs index 44b592985..7ca9a1203 100644 --- a/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs +++ b/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs @@ -4,13 +4,13 @@ namespace KurrentDB.Client.Tests.Streams; -public record SubscriptionFilter(string Name, Func Create, Func PrepareEvent) { +public record SubscriptionFilter(string Name, Func Create, Func PrepareEvent) { public override string ToString() => Name; static readonly SubscriptionFilter StreamNamePrefix = new(nameof(StreamNamePrefix), StreamFilter.Prefix, (_, evt) => evt); static readonly SubscriptionFilter StreamNameRegex = new(nameof(StreamNameRegex), f => StreamFilter.RegularExpression(f), (_, evt) => evt); - static readonly SubscriptionFilter EventTypePrefix = new(nameof(EventTypePrefix), EventTypeFilter.Prefix, (term, evt) => new(evt.EventId, term, evt.Data, evt.Metadata, evt.ContentType)); - static readonly SubscriptionFilter EventTypeRegex = new(nameof(EventTypeRegex), f => EventTypeFilter.RegularExpression(f), (term, evt) => new(evt.EventId, term, evt.Data, evt.Metadata, evt.ContentType)); + static readonly SubscriptionFilter EventTypePrefix = new(nameof(EventTypePrefix), EventTypeFilter.Prefix, (term, evt) => new(term, evt.Data, evt.Metadata, evt.MessageId, evt.ContentType)); + static readonly SubscriptionFilter EventTypeRegex = new(nameof(EventTypeRegex), f => EventTypeFilter.RegularExpression(f), (term, evt) => new(term, evt.Data, evt.Metadata, evt.MessageId, evt.ContentType)); static SubscriptionFilter() { All = new[] { diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs index 38729cad6..f25f3ab75 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs @@ -17,17 +17,17 @@ 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)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); foreach (var evt in seedEvents.Take(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.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 Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); await receivedAllEvents.Task.WithTimeout(); @@ -64,7 +64,7 @@ public async Task receives_all_events_from_end() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); using var subscription = await Fixture.Streams .SubscribeToAllAsync(FromAll.End, OnReceived, false, OnDropped) @@ -72,7 +72,7 @@ public async Task receives_all_events_from_end() { // 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 Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); await receivedAllEvents.Task.WithTimeout(); @@ -111,11 +111,11 @@ public async Task receives_all_events_from_position() { 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); 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 }); + writeResult = await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var position = FromAll.After(writeResult.LogPosition); @@ -124,7 +124,7 @@ public async Task receives_all_events_from_position() { .WithTimeout(); foreach (var evt in seedEvents.Skip(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); await receivedAllEvents.Task.WithTimeout(); @@ -162,7 +162,7 @@ public async Task receives_all_events_with_resolved_links() { var subscriptionDropped = new TaskCompletionSource(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); @@ -219,7 +219,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); @@ -233,7 +233,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil // 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 }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); @@ -243,7 +243,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil // 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 }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); // wait until all events were received and at least one checkpoint was reached? await receivedAllEvents.Task.WithTimeout(); @@ -322,7 +322,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); @@ -332,7 +332,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte // 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 }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); @@ -342,7 +342,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte // 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 }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); // wait until all events were received and at least one checkpoint was reached? await receivedAllEvents.Task.WithTimeout(); @@ -421,7 +421,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); @@ -432,7 +432,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter // 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 }); + writeResult = await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var position = FromAll.After(writeResult.LogPosition); @@ -444,7 +444,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter // 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 }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); // wait until all events were received and at least one checkpoint was reached? await receivedAllEvents.Task.WithTimeout(); @@ -513,7 +513,7 @@ public async Task receives_all_filtered_events_with_resolved_links() { var subscriptionDropped = new TaskCompletionSource(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs index acf15b47d..58ab9e411 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -18,7 +18,7 @@ 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)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); @@ -67,7 +67,7 @@ public async Task receives_all_events_from_position() { 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); var writeResult = await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); var streamPosition = StreamPosition.FromInt64(writeResult.NextExpectedStreamState.ToInt64()); @@ -116,7 +116,7 @@ public async Task receives_all_events_from_non_existing_stream() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); using var subscription = await Fixture.Streams .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived, false, OnDropped) @@ -265,7 +265,7 @@ public async Task receives_all_events_with_resolved_links() { var subscriptionDropped = new TaskCompletionSource(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs index 35ea319bd..c0dc9d664 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs @@ -13,13 +13,13 @@ 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)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); foreach (var evt in seedEvents.Take(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start); @@ -31,7 +31,7 @@ await Fixture.Streams.AppendToStreamAsync( foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -59,7 +59,7 @@ async Task Subscribe() { public async Task receives_all_events_from_end() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -71,7 +71,7 @@ public async Task receives_all_events_from_end() { // 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}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -101,12 +101,12 @@ public async Task receives_all_events_from_position() { 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); IWriteResult writeResult = new SuccessResult(); foreach (var evt in seedEvents.Take(pageSize)) writeResult = await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -122,7 +122,7 @@ public async Task receives_all_events_from_position() { foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -151,7 +151,7 @@ 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)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); @@ -194,7 +194,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync( @@ -215,7 +215,7 @@ await Fixture.Streams.AppendToStreamAsync( // 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}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -232,7 +232,7 @@ await Fixture.Streams.AppendToStreamAsync( // 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}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -281,7 +281,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync( @@ -302,7 +302,7 @@ await Fixture.Streams.AppendToStreamAsync( // 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}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -319,7 +319,7 @@ await Fixture.Streams.AppendToStreamAsync( // 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}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -368,7 +368,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync( @@ -386,7 +386,7 @@ await Fixture.Streams.AppendToStreamAsync( IWriteResult writeResult = new SuccessResult(); foreach (var evt in seedEvents.Take(pageSize)) writeResult = await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -405,7 +405,7 @@ await Fixture.Streams.AppendToStreamAsync( // 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}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt } ); @@ -445,7 +445,7 @@ 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)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs index bfeda3a60..088f40e3e 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs @@ -13,7 +13,7 @@ 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)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); @@ -53,7 +53,7 @@ public async Task receives_all_events_from_position() { 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 availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); var writeResult = await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); @@ -99,7 +99,7 @@ public async Task receives_all_events_from_non_existing_stream() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -156,7 +156,7 @@ public async Task allow_multiple_subscriptions_to_same_stream() { return; async Task Subscribe(IAsyncEnumerator subscription) { - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); while (await subscription.MoveNextAsync()) { if (subscription.Current is not StreamMessage.Event(var resolvedEvent)) { From 8d4432831d1da305aba66a46df1b07eab0da6e22 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 13 Mar 2025 13:18:26 +0100 Subject: [PATCH 06/23] [DEVEX-222] Refactored setting stream metadata to use OperationOptions --- src/KurrentDB.Client/Core/OperationOptions.cs | 22 +- .../Streams/KurrentDBClient.Append.cs | 52 ++-- .../Streams/KurrentDBClient.Metadata.cs | 108 ++++++-- .../Streams/KurrentDBClient.Read.cs | 249 ++++++++---------- .../Streams/KurrentDBClientExtensions.cs | 10 +- .../Streams/WriteResultExtensions.cs | 2 +- .../KurrentDBClientWarmupExtensions.cs | 4 +- .../SubscribeToStreamGetInfoTests.cs | 2 +- .../Streams/AppendTests.cs | 14 +- .../Streams/SoftDeleteTests.cs | 2 +- 10 files changed, 241 insertions(+), 224 deletions(-) diff --git a/src/KurrentDB.Client/Core/OperationOptions.cs b/src/KurrentDB.Client/Core/OperationOptions.cs index a8bef2553..de8aceaa4 100644 --- a/src/KurrentDB.Client/Core/OperationOptions.cs +++ b/src/KurrentDB.Client/Core/OperationOptions.cs @@ -3,16 +3,7 @@ namespace KurrentDB.Client; /// /// A class representing the options to apply to an individual operation. /// -public record OperationOptions { - /// - /// 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; } +public class OperationOptions { /// /// Maximum time that the operation will be run @@ -24,15 +15,4 @@ public record OperationOptions { /// public UserCredentials? UserCredentials { get; set; } - /// - /// Clones a copy of the current . - /// - /// - public OperationOptions With(KurrentDBClientOperationOptions clientOperationOptions) => - new() { - ThrowOnAppendFailure = ThrowOnAppendFailure ?? clientOperationOptions.ThrowOnAppendFailure, - BatchAppendSize = BatchAppendSize ?? clientOperationOptions.BatchAppendSize, - Deadline = Deadline, - UserCredentials = UserCredentials, - }; } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index cbfe8be8f..af5d0fc4e 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -31,10 +31,8 @@ public Task AppendToStreamAsync( AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { - var serializationContext = new MessageSerializationContext( - streamName, - Settings.Serialization.DefaultContentType - ); + var serializationContext + = new MessageSerializationContext(streamName, Settings.Serialization.DefaultContentType); var messageSerializer = _messageSerializer.With(Settings.Serialization, options?.SerializationSettings); @@ -58,18 +56,17 @@ public async Task AppendToStreamAsync( string streamName, StreamState expectedState, IEnumerable messageData, - OperationOptions? options = null, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { - var operationOptions = (options ?? new OperationOptions()).With(Settings.OperationOptions); - var userCredentials = options?.UserCredentials; - var deadline = options?.Deadline; + options ??= new AppendToStreamOptions(); + options.With(Settings.OperationOptions); _log.LogDebug("Append to stream - {streamName}@{expectedState}.", streamName, expectedState); var task = - userCredentials == null && await BatchAppender.IsUsable().ConfigureAwait(false) - ? BatchAppender.Append(streamName, expectedState, messageData, deadline, cancellationToken) + options.UserCredentials == null && await BatchAppender.IsUsable().ConfigureAwait(false) + ? BatchAppender.Append(streamName, expectedState, messageData, options.Deadline, cancellationToken) : AppendToStreamInternal( await GetChannelInfo(cancellationToken).ConfigureAwait(false), new AppendReq { @@ -78,18 +75,18 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), } }.WithAnyStreamRevision(expectedState), messageData, - operationOptions, + options, cancellationToken ); - return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(operationOptions); + return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(options); } ValueTask AppendToStreamInternal( ChannelInfo channelInfo, AppendReq header, IEnumerable messageData, - OperationOptions operationOptions, + AppendToStreamOptions operationOptions, CancellationToken cancellationToken ) { var userCredentials = operationOptions.UserCredentials; @@ -175,7 +172,9 @@ IWriteResult HandleSuccessAppend(AppendResp response, AppendReq header) { } IWriteResult HandleWrongExpectedRevision( - AppendResp response, AppendReq header, OperationOptions operationOptions + AppendResp response, + AppendReq header, + AppendToStreamOptions operationOptions ) { var actualStreamRevision = response.WrongExpectedVersion.CurrentRevisionOptionCase == CurrentRevisionOptionOneofCase.CurrentRevision @@ -471,12 +470,12 @@ public static Task AppendToStreamAsync( string streamName, StreamState expectedState, IEnumerable eventData, - Action? configureOperationOptions = null, + Action? configureOperationOptions = null, TimeSpan? deadline = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) { - var operationOptions = new OperationOptions { + var operationOptions = new AppendToStreamOptions { Deadline = deadline, UserCredentials = userCredentials, }; @@ -493,7 +492,26 @@ public static Task AppendToStreamAsync( } } - public record AppendToStreamOptions : OperationOptions { + public class AppendToStreamOptions : OperationOptions { + /// + /// 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; } + + /// + /// Clones a copy of the current . + /// + /// + public void With(KurrentDBClientOperationOptions clientOperationOptions) { + ThrowOnAppendFailure = clientOperationOptions.ThrowOnAppendFailure; + BatchAppendSize = clientOperationOptions.BatchAppendSize; + } + /// /// Allows to customize or disable the automatic deserialization /// diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs index c9dbb7a2f..a22646345 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs @@ -8,29 +8,33 @@ public partial class KurrentDBClient { /// 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 + string streamName, + OperationOptions? operationOptions = 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, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + ResolveLinkTos = false, + MaxCount = 1, + Deadline = operationOptions?.Deadline, + UserCredentials = operationOptions?.UserCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); - await foreach (var message in result.Messages.ConfigureAwait(false)) { + await foreach (var message in + result.Messages.ConfigureAwait(false).WithCancellation(cancellationToken)) { if (message is not StreamMessage.Event(var resolvedEvent)) { continue; } @@ -56,23 +60,18 @@ public async Task GetStreamMetadataAsync( /// 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, + string streamName, + StreamState expectedState, + StreamMetadata metadata, + SetStreamMetadata? operationOptions = null, CancellationToken cancellationToken = default ) { - var options = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(options); - - var operationOptions = - new OperationOptions { Deadline = deadline, UserCredentials = userCredentials }.With(options); + operationOptions ??= new SetStreamMetadata(); + operationOptions.With(Settings.OperationOptions); return SetStreamMetadataInternal( metadata, @@ -89,7 +88,7 @@ public Task SetStreamMetadataAsync( async Task SetStreamMetadataInternal( StreamMetadata metadata, AppendReq appendReq, - OperationOptions operationOptions, + SetStreamMetadata operationOptions, CancellationToken cancellationToken ) { var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); @@ -107,4 +106,65 @@ CancellationToken cancellationToken ).ConfigureAwait(false); } } + + public static class KurrentDBClientMetadataObsoleteExtensions { + /// + /// 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 static Task GetStreamMetadataAsync( + this KurrentDBClient dbClient, + string streamName, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.GetStreamMetadataAsync( + streamName, + new OperationOptions { Deadline = deadline, UserCredentials = 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 static Task SetStreamMetadataAsync( + this KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + StreamMetadata metadata, + Action? configureOperationOptions = null, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) { + var operationOptions = + new SetStreamMetadata { Deadline = deadline, UserCredentials = userCredentials }; + configureOperationOptions?.Invoke(operationOptions); + + return dbClient.SetStreamMetadataAsync( + streamName, + expectedState, + metadata, + operationOptions, + cancellationToken + ); + } + } + + public class SetStreamMetadata : AppendToStreamOptions; } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index de1861a25..4e146df4b 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -14,9 +14,11 @@ public partial class KurrentDBClient { /// The optional . /// public ReadAllStreamResult ReadAllAsync( - ReadAllOptions options, + ReadAllOptions? options = null, CancellationToken cancellationToken = default ) { + options ??= new ReadAllOptions(); + if (options.MaxCount <= 0) throw new ArgumentOutOfRangeException(nameof(options.MaxCount)); @@ -54,80 +56,6 @@ public ReadAllStreamResult ReadAllAsync( ); } - /// - /// 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( - new ReadAllOptions { - Direction = direction, - Position = position, - Filter = null, - MaxCount = maxCount, - ResolveLinkTos = resolveLinkTos, - Deadline = deadline, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - 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)); - - return ReadAllAsync( - new ReadAllOptions { - Direction = direction, - Position = position, - Filter = eventFilter, - MaxCount = maxCount, - ResolveLinkTos = resolveLinkTos, - Deadline = deadline, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - 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. /// @@ -266,9 +194,11 @@ public async IAsyncEnumerator GetAsyncEnumerator( /// public ReadStreamResult ReadStreamAsync( string streamName, - ReadStreamOptions options, + ReadStreamOptions? options = null, CancellationToken cancellationToken = default ) { + options ??= new ReadStreamOptions(); + if (options.MaxCount <= 0) throw new ArgumentOutOfRangeException(nameof(options.MaxCount)); @@ -303,48 +233,6 @@ public ReadStreamResult ReadStreamAsync( ); } - /// - /// 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 ReadStreamAsync( - streamName, - new ReadStreamOptions { - Direction = direction, - StreamPosition = revision, - MaxCount = maxCount, - ResolveLinkTos = resolveLinkTos, - Deadline = deadline, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - 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. /// @@ -552,7 +440,7 @@ IMessageSerializer messageSerializer /// Optional settings to customize reading all messages, for instance: max count, /// in which to read, the to start reading from, etc. /// - public class ReadAllOptions { + public class ReadAllOptions : OperationOptions { /// /// The in which to read. /// @@ -578,16 +466,6 @@ public class ReadAllOptions { /// public bool ResolveLinkTos { get; set; } - /// - /// Maximum time that the operation will be run - /// - public TimeSpan? Deadline { get; set; } - - /// - /// The optional to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } - /// /// Allows to customize or disable the automatic deserialization /// @@ -598,7 +476,7 @@ public class ReadAllOptions { /// Optional settings to customize reading stream messages, for instance: max count, /// in which to read, the to start reading from, etc. /// - public class ReadStreamOptions { + public class ReadStreamOptions : OperationOptions { /// /// The in which to read. /// @@ -619,16 +497,6 @@ public class ReadStreamOptions { /// public bool ResolveLinkTos { get; set; } - /// - /// Maximum time that the operation will be run - /// - public TimeSpan? Deadline { get; set; } - - /// - /// The optional to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } - /// /// Allows to customize or disable the automatic deserialization /// @@ -637,16 +505,82 @@ public class ReadStreamOptions { public static class KurrentDBClientReadExtensions { /// - /// Asynchronously reads all events. By default, it reads all of them from the start. The options parameter allows you to fine-tune it to your needs. + /// 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 static KurrentDBClient.ReadAllStreamResult ReadAllAsync( this KurrentDBClient dbClient, + Direction direction, + Position position, + long maxCount = long.MaxValue, + bool resolveLinkTos = false, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - dbClient.ReadAllAsync(new ReadAllOptions(), cancellationToken); + dbClient.ReadAllAsync( + new ReadAllOptions { + Direction = direction, + Position = position, + Filter = null, + MaxCount = maxCount, + ResolveLinkTos = resolveLinkTos, + Deadline = deadline, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + 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 static KurrentDBClient.ReadAllStreamResult ReadAllAsync( + this KurrentDBClient dbClient, + 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)); + + return dbClient.ReadAllAsync( + new ReadAllOptions { + Direction = direction, + Position = position, + Filter = eventFilter, + MaxCount = maxCount, + ResolveLinkTos = resolveLinkTos, + Deadline = deadline, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + } /// /// Asynchronously reads all the events from a stream. @@ -654,19 +588,42 @@ public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( /// 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. - /// Optional settings like: max count, in which to read, the to start reading from, etc. + /// 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 static KurrentDBClient.ReadStreamResult ReadStreamAsync( this KurrentDBClient dbClient, + Direction direction, string streamName, + StreamPosition revision, + long maxCount = long.MaxValue, + bool resolveLinkTos = false, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) => - dbClient.ReadStreamAsync( + ) { + if (maxCount <= 0) + throw new ArgumentOutOfRangeException(nameof(maxCount)); + + return dbClient.ReadStreamAsync( streamName, - new ReadStreamOptions(), + new ReadStreamOptions { + Direction = direction, + StreamPosition = revision, + MaxCount = maxCount, + ResolveLinkTos = resolveLinkTos, + Deadline = deadline, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); + } } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs index de438e7c4..cb4e168b8 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs @@ -23,7 +23,7 @@ public static class KurrentDBClientExtensions { public static Task SetSystemSettingsAsync( this KurrentDBClient dbClient, SystemSettings settings, - OperationOptions? options = null, + SetSystemSettingsOptions? options = null, CancellationToken cancellationToken = default ) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); @@ -61,7 +61,7 @@ public static Task SetSystemSettingsAsync( ) => dbClient.SetSystemSettingsAsync( settings, - new OperationOptions { Deadline = deadline, UserCredentials = userCredentials }, + new SetSystemSettingsOptions { Deadline = deadline, UserCredentials = userCredentials }, cancellationToken: cancellationToken ); @@ -81,7 +81,7 @@ public static async Task ConditionalAppendToStreamAsync( string streamName, StreamState expectedState, IEnumerable messageData, - OperationOptions? options = null, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { if (dbClient == null) { @@ -133,7 +133,7 @@ public static Task ConditionalAppendToStreamAsync( streamName, expectedState, eventData.Select(e => (MessageData)e), - new OperationOptions { + new AppendToStreamOptions { ThrowOnAppendFailure = false, Deadline = deadline, UserCredentials = userCredentials @@ -141,4 +141,6 @@ public static Task ConditionalAppendToStreamAsync( cancellationToken ); } + + public class SetSystemSettingsOptions : AppendToStreamOptions; } diff --git a/src/KurrentDB.Client/Streams/WriteResultExtensions.cs b/src/KurrentDB.Client/Streams/WriteResultExtensions.cs index 5d2385d21..64b583090 100644 --- a/src/KurrentDB.Client/Streams/WriteResultExtensions.cs +++ b/src/KurrentDB.Client/Streams/WriteResultExtensions.cs @@ -2,7 +2,7 @@ namespace KurrentDB.Client { static class WriteResultExtensions { public static IWriteResult OptionallyThrowWrongExpectedVersionException( this IWriteResult writeResult, - OperationOptions options + AppendToStreamOptions options ) => (options.ThrowOnAppendFailure, writeResult) switch { (true, WrongExpectedVersionResult wrongExpectedVersionResult) diff --git a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs index ab27e4339..31038525a 100644 --- a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs +++ b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs @@ -68,8 +68,8 @@ public static Task WarmUp( _ = await dbClient.AppendToStreamAsync( "warmup", StreamState.Any, - [], - new OperationOptions { UserCredentials = TestCredentials.Root }, + Enumerable.Empty(), + new AppendToStreamOptions { UserCredentials = TestCredentials.Root }, ct ); }, diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs index e72f77276..41f82f039 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs @@ -180,7 +180,7 @@ await Streams.AppendToStreamAsync( Stream, StreamState.Any, [new MessageData("test-event", ReadOnlyMemory.Empty)], - new OperationOptions { UserCredentials = TestCredentials.Root } + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } diff --git a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs index 10e361dc3..533fe6a41 100644 --- a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs @@ -306,7 +306,7 @@ public async Task stream, StreamState.StreamExists, Fixture.CreateTestEvents(), - new OperationOptions { ThrowOnAppendFailure = false } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); var wrongExpectedVersionResult = Assert.IsType(writeResult); @@ -432,7 +432,7 @@ public async Task with_timeout_any_stream_revision_fails_when_operation_expired( stream, StreamState.Any, Fixture.CreateTestEvents(100), - new OperationOptions { Deadline = TimeSpan.FromTicks(1) } + new AppendToStreamOptions { Deadline = TimeSpan.FromTicks(1) } ).ShouldThrowAsync(); ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); @@ -448,7 +448,7 @@ public async Task with_timeout_stream_revision_fails_when_operation_expired() { stream, StreamState.StreamRevision(0), Fixture.CreateTestEvents(10), - new OperationOptions { Deadline = TimeSpan.Zero } + new AppendToStreamOptions { Deadline = TimeSpan.Zero } ).ShouldThrowAsync(); ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); @@ -463,7 +463,7 @@ await Fixture.Streams streamName, StreamState.Any, Fixture.CreateTestEventsThatThrowsException(), - new OperationOptions { + new AppendToStreamOptions { UserCredentials = new UserCredentials( TestCredentials.Root.Username!, TestCredentials.Root.Password! @@ -581,7 +581,7 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_returns_wev() { stream, StreamState.StreamRevision(6), events.Take(1), - new OperationOptions { ThrowOnAppendFailure = false } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -612,7 +612,7 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_returns_wev() { stream, StreamState.StreamRevision(4), events.Take(1), - new OperationOptions { ThrowOnAppendFailure = false } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -770,7 +770,7 @@ public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_ret stream, StreamState.NoStream, events, - new OperationOptions { ThrowOnAppendFailure = false } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); diff --git a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs index 3c096cbae..4cd941665 100644 --- a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs @@ -272,7 +272,7 @@ public async Task allows_recreating_for_first_write_only_returns_wrong_expected_ stream, StreamState.NoStream, Fixture.CreateTestEvents(), - new OperationOptions { ThrowOnAppendFailure = false } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(wrongExpectedVersionResult); From 1d8fc3f1160d9836fb6aad230fb2d1f6333d9c28 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 13 Mar 2025 14:28:55 +0100 Subject: [PATCH 07/23] [DEVEX-222] Added DeserializedData and DeserializedMessages extensions for read results That should be a middle ground without forcing people to use a regular ResolvedEvent if they don't want to --- samples/appending-events/Program.cs | 4 +- samples/quick-start/Program.cs | 2 +- .../Core/Serialization/Message.cs | 26 ++++++-- .../Streams/KurrentDBClient.Append.cs | 2 +- .../Streams/KurrentDBClient.Read.cs | 23 ++++++++ .../Serialization/SerializationTests.cs | 59 +++++++++++++------ 6 files changed, 90 insertions(+), 26 deletions(-) diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index 1572169a5..4d3a2b707 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -131,7 +131,7 @@ await client.AppendToStreamAsync( } static async Task AppendOverridingUserCredentials(KurrentDBClient client, CancellationToken cancellationToken) { - var eventData =MessageData.From( + var eventData = MessageData.From( "TestEvent", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -142,7 +142,7 @@ await client.AppendToStreamAsync( "some-stream", StreamState.Any, [eventData], - new OperationOptions { UserCredentials = new UserCredentials("admin", "changeit") }, + new AppendToStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken ); diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 31332c661..11f4fec6d 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -45,7 +45,7 @@ await client.AppendToStreamAsync( "some-stream", StreamState.Any, [eventData], - new OperationOptions{ UserCredentials = new UserCredentials("admin", "changeit") }, + new AppendToStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken ); diff --git a/src/KurrentDB.Client/Core/Serialization/Message.cs b/src/KurrentDB.Client/Core/Serialization/Message.cs index c87e3d967..cda7cdeae 100644 --- a/src/KurrentDB.Client/Core/Serialization/Message.cs +++ b/src/KurrentDB.Client/Core/Serialization/Message.cs @@ -7,7 +7,23 @@ namespace KurrentDB.Client; /// The message domain data. /// Optional metadata providing additional context about the message, such as correlation IDs, timestamps, or user information. /// Unique identifier for this specific message instance. When null, the system will auto-generate an ID. -public sealed record Message(object Data, object? Metadata, Uuid? MessageId = null) { +public sealed record Message(object Data, object? Metadata, Uuid MessageId) { + /// + /// Creates a new Message with the specified domain data and random ID, but without metadata. + /// This factory method is a convenient shorthand when working with systems that don't require metadata. + /// + /// The message domain data. + /// A new immutable Message instance containing the provided data and ID with null metadata. + /// + /// + /// // Create a message with a specific ID + /// var userRegistered = new UserRegistered { Id = "123", Name = "Alice" }; + /// var message = Message.From(userRegistered); + /// + /// + public static Message From(object data) => + From(data, null); + /// /// Creates a new Message with the specified domain data and message ID, but without metadata. /// This factory method is a convenient shorthand when working with systems that don't require metadata. @@ -18,9 +34,9 @@ public sealed record Message(object Data, object? Metadata, Uuid? MessageId = nu /// /// /// // Create a message with a specific ID - /// var UserRegistered = new UserRegistered { Id = "123", Name = "Alice" }; + /// var userRegistered = new UserRegistered { Id = "123", Name = "Alice" }; /// var messageId = Uuid.NewUuid(); - /// var message = Message.From(UserRegistered, messageId); + /// var message = Message.From(userRegistered, messageId); /// /// public static Message From(object data, Uuid messageId) => @@ -51,10 +67,10 @@ public static Message From(object data, Uuid messageId) => /// var messageWithId = Message.From(orderPlaced, metadata, Uuid.NewUuid()); /// /// - public static Message From(object data, object? metadata = null, Uuid? messageId = null) { + public static Message From(object data, object? metadata, Uuid? messageId = null) { if (messageId == Uuid.Empty) throw new ArgumentOutOfRangeException(nameof(messageId), "Message ID cannot be an empty UUID."); - return new Message(data, metadata, messageId); + return new Message(data, metadata, messageId ?? Uuid.NewUuid()); } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index af5d0fc4e..268bb467a 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -444,7 +444,7 @@ public static Task AppendToStreamAsync( => dbClient.AppendToStreamAsync( streamName, expectedState, - messages.Select(m => Message.From(m)), + messages.Select(Message.From), options, cancellationToken ); diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index 4e146df4b..45f5121b0 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Threading.Channels; using EventStore.Client.Streams; using Grpc.Core; @@ -626,4 +627,26 @@ public static KurrentDBClient.ReadStreamResult ReadStreamAsync( ); } } + + public static class ReadMessagesExtensions { + public static async IAsyncEnumerable DeserializedData( + this IAsyncEnumerable resolvedEvents, + [EnumeratorCancellation] CancellationToken ct = default + ) { + await foreach (var resolvedEvent in resolvedEvents.WithCancellation(ct)) { + if (resolvedEvent.DeserializedData != null) + yield return resolvedEvent.DeserializedData; + } + } + + public static async IAsyncEnumerable DeserializedMessages( + this IAsyncEnumerable resolvedEvents, + [EnumeratorCancellation] CancellationToken ct = default + ) { + await foreach (var resolvedEvent in resolvedEvents.WithCancellation(ct)) { + if (resolvedEvent.Message != null) + yield return resolvedEvent.Message; + } + } + } } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index 91b7c53cc..d41bfa79d 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -22,7 +22,32 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s //Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); + } + + [RetryFact] + public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_serialization_and_extension() { + // Given + var stream = Fixture.GetStreamName(); + List domainMessages = GenerateMessages(); + List messages = domainMessages.Select(Message.From).ToList(); + + //When + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messages); + + //Then + List deserializedMessages = await Fixture.Streams + .ReadStreamAsync(stream) + .DeserializedMessages() + .ToListAsync(); + + List deserializedDomainMessages = await Fixture.Streams + .ReadStreamAsync(stream) + .DeserializedData() + .ToListAsync(); + + Assert.Equal(messages, deserializedMessages); + Assert.Equal(domainMessages, deserializedDomainMessages); } [RetryFact] @@ -42,7 +67,7 @@ public async Task // Then var resolvedEvents = await client.ReadStreamAsync(stream).ToListAsync(); - var messages = AssertThatMessages(AreDeserialized, expected, resolvedEvents); + var messages = AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); Assert.Equal(messagesWithMetadata, messages); } @@ -61,7 +86,7 @@ public async Task // Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); - var messages = AssertThatMessages(AreDeserialized, expected, resolvedEvents); + var messages = AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); Assert.Equal(messagesWithMetadata.Select(m => m with { Metadata = new TracingMetadata() }), messages); } @@ -77,7 +102,7 @@ public async Task read_stream_without_options_does_NOT_deserialize_resolved_mess .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } [RetryFact] @@ -91,7 +116,7 @@ public async Task read_all_without_options_does_NOT_deserialize_resolved_message .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } public static TheoryData> CustomTypeMappings() { @@ -120,7 +145,7 @@ Action customTypeMapping var resolvedEvents = await client.ReadStreamAsync(stream).ToListAsync(); Assert.All(resolvedEvents, resolvedEvent => Assert.Equal("user_registered", resolvedEvent.Event.EventType)); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryTheory] @@ -141,7 +166,7 @@ Action customTypeMapping Assert.All(resolvedEvents, resolvedEvent => Assert.Equal("user_registered", resolvedEvent.Event.EventType)); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -162,7 +187,7 @@ public async Task automatic_serialization_custom_json_settings_are_applied() { Assert.Equal(expected.Select(m => m.UserId), jsons.Select(j => j.GetProperty("user-id").GetGuid())); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } public class CustomMessageTypeNamingStrategy : IMessageTypeNamingStrategy { @@ -171,7 +196,7 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(string messageTypeName, out Type? type) { #else public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { #endif @@ -182,7 +207,7 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { #else public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { #endif @@ -208,7 +233,7 @@ public async Task append_and_read_stream_uses_custom_message_type_naming_strateg resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType) ); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -231,7 +256,7 @@ public async Task append_and_read_all_uses_custom_message_type_naming_strategy() resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType) ); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -247,7 +272,7 @@ public async Task read_stream_deserializes_resolved_message_appended_with_manual .ToListAsync(); // Then - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -263,7 +288,7 @@ public async Task read_all_deserializes_resolved_message_appended_with_manual_co .ToListAsync(); // Then - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -278,7 +303,7 @@ public async Task .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } [RetryFact] @@ -292,10 +317,10 @@ public async Task read_all_does_NOT_deserialize_resolved_message_appended_with_m .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } - static List AssertThatMessages( + static List AssertThatReadEvents( Action assertMatches, List expected, List resolvedEvents From af0ea4e5584d1dd4f9ea6181e293c7fa9514c3f6 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 14 Mar 2025 13:25:50 +0100 Subject: [PATCH 08/23] [DEVEX-222] Simplified the message serializer implementation Now serializer creates a derived serializer merging operation settings with default settings if needed. That allowed to hide internal details and there's no need to pass content type explicitly, as it's already in settings. Extended SchemaRegistry to provide wrapper methods to naming resolution context. Thanks to that MessageSerializer doesn't need to reference it internally. To make responsibilities clearer made also naming resolution context part of serializer context. --- .../KurrentDBClientSerializationSettings.cs | 13 +- .../Core/KurrentDBClientSettings.cs | 2 +- .../Core/Serialization/MessageSerializer.cs | 90 ++++++------- .../MessageTypeResolutionStrategy.cs | 14 +- .../Core/Serialization/SchemaRegistry.cs | 21 ++- ...entDBPersistentSubscriptionsClient.Read.cs | 2 +- .../Streams/KurrentDBClient.Append.cs | 13 +- .../Streams/KurrentDBClient.Read.cs | 4 +- .../Streams/KurrentDBClient.Subscriptions.cs | 4 +- .../MessageSerializerExtensionsTests.cs | 108 --------------- .../Serialization/MessageSerializerTests.cs | 126 +++++++++++++++--- ...essageTypeNamingResolutionContextTests.cs} | 8 +- .../NullMessageSerializerTests.cs | 5 +- .../Core/Serialization/SchemaRegistryTests.cs | 112 ++++++++++------ 14 files changed, 272 insertions(+), 250 deletions(-) delete mode 100644 test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs rename test/KurrentDB.Client.Tests/Core/Serialization/{MessageSerializationContextTests.cs => MessageTypeNamingResolutionContextTests.cs} (71%) diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs index d894f5df2..567a38003 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs @@ -71,7 +71,7 @@ public class KurrentDBClientSerializationSettings { /// }); /// /// - public static KurrentDBClientSerializationSettings Default( + public static KurrentDBClientSerializationSettings Get( Action? configure = null ) { var settings = new KurrentDBClientSerializationSettings(); @@ -280,6 +280,17 @@ public KurrentDBClientSerializationSettings UseMetadataType(Type type) { return this; } + /// + /// Configures which serialization format (JSON or binary) is used by default when writing messages + /// where the content type isn't explicitly specified. The default content type is "application/json" + /// + /// The serialization format content type + /// The current instance for method chaining. + public KurrentDBClientSerializationSettings UseContentType(ContentType contentType) { + DefaultContentType = contentType; + + return this; + } /// /// Creates a deep copy of the current serialization settings. diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs index 46056a5ef..27f273770 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs @@ -62,6 +62,6 @@ public partial class KurrentDBClientSettings { /// Provides configuration options for messages serialization and deserialization in the KurrentDB client. /// If null, default settings are used. /// - public KurrentDBClientSerializationSettings Serialization { get; set; } = KurrentDBClientSerializationSettings.Default(); + public KurrentDBClientSerializationSettings Serialization { get; set; } = KurrentDBClientSerializationSettings.Get(); } } diff --git a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs index 802182994..302d904a4 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs @@ -11,66 +11,41 @@ interface IMessageSerializer { #else public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized); #endif -} -record MessageSerializationContext( - string StreamName, - ContentType ContentType -) { - public string CategoryName => - StreamName.Split('-').FirstOrDefault() ?? "no_stream_category"; + public IMessageSerializer With(OperationSerializationSettings? operationSettings); } +record MessageSerializationContext(MessageTypeNamingResolutionContext NamingResolution); + static class MessageSerializerExtensions { public static MessageData[] Serialize( this IMessageSerializer serializer, IEnumerable messages, - MessageSerializationContext context - ) { - return messages.Select(m => serializer.Serialize(m, context)).ToArray(); - } - - public static IMessageSerializer With( - this IMessageSerializer defaultMessageSerializer, - KurrentDBClientSerializationSettings defaultSettings, - OperationSerializationSettings? operationSettings - ) { - if (operationSettings == null) - return defaultMessageSerializer; - - if (operationSettings.AutomaticDeserialization == AutomaticDeserialization.Disabled) - return NullMessageSerializer.Instance; - - if (operationSettings.ConfigureSettings == null) - return defaultMessageSerializer; - - var settings = defaultSettings.Clone(); - operationSettings.ConfigureSettings.Invoke(settings); - - return new MessageSerializer(SchemaRegistry.From(settings)); - } + MessageSerializationContext serializationContext + ) => + messages.Select(m => serializer.Serialize(m, serializationContext)).ToArray(); } -class MessageSerializer(SchemaRegistry schemaRegistry) : IMessageSerializer { +class MessageSerializer(SchemaRegistry schemaRegistry, KurrentDBClientSerializationSettings serializationSettings) + : IMessageSerializer { readonly SystemTextJsonSerializer _metadataSerializer = new SystemTextJsonSerializer( new SystemTextJsonSerializationSettings { Options = KurrentDBClient.StreamMetadataJsonSerializerOptions } ); - readonly IMessageTypeNamingStrategy _messageTypeNamingStrategy = - schemaRegistry.MessageTypeNamingStrategy; + readonly string _contentType = serializationSettings.DefaultContentType.ToMessageContentType(); public MessageData Serialize(Message message, MessageSerializationContext serializationContext) { var (data, metadata, messageId) = message; - var eventType = _messageTypeNamingStrategy + var messageType = schemaRegistry .ResolveTypeName( message.Data.GetType(), - new MessageTypeNamingResolutionContext(serializationContext.CategoryName) + serializationContext.NamingResolution ); var serializedData = schemaRegistry - .GetSerializer(serializationContext.ContentType) + .GetSerializer(serializationSettings.DefaultContentType) .Serialize(data); var serializedMetadata = metadata != null @@ -78,11 +53,11 @@ public MessageData Serialize(Message message, MessageSerializationContext serial : ReadOnlyMemory.Empty; return new MessageData( - eventType, + messageType, serializedData, serializedMetadata, messageId, - serializationContext.ContentType.ToMessageContentType() + _contentType ); } @@ -91,7 +66,7 @@ public bool TryDeserialize(EventRecord record, out Message? deserialized) { #else public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized) { #endif - if (!TryResolveClrType(record, out var clrType)) { + if (!schemaRegistry.TryResolveClrType(record.EventType, out var clrType)) { deserialized = null; return false; } @@ -105,7 +80,8 @@ public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? return false; } - object? metadata = record.Metadata.Length > 0 && TryResolveClrMetadataType(record, out var clrMetadataType) + object? metadata = record.Metadata.Length > 0 + && schemaRegistry.TryResolveClrMetadataType(record.EventType, out var clrMetadataType) ? _metadataSerializer.Deserialize(record.Metadata, clrMetadataType!) : null; @@ -113,21 +89,27 @@ public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? return true; } - public static MessageSerializer From(KurrentDBClientSerializationSettings? settings = null) { - settings ??= KurrentDBClientSerializationSettings.Default(); + public IMessageSerializer With(OperationSerializationSettings? operationSettings) { + if (operationSettings == null) + return this; + + if (operationSettings.AutomaticDeserialization == AutomaticDeserialization.Disabled) + return NullMessageSerializer.Instance; + + if (operationSettings.ConfigureSettings == null) + return this; - return new MessageSerializer(SchemaRegistry.From(settings)); + var settings = serializationSettings.Clone(); + operationSettings.ConfigureSettings.Invoke(settings); + + return new MessageSerializer(SchemaRegistry.From(settings), settings); } - bool TryResolveClrType(EventRecord record, out Type? clrType) => - schemaRegistry - .MessageTypeNamingStrategy - .TryResolveClrType(record.EventType, out clrType); + public static MessageSerializer From(KurrentDBClientSerializationSettings? settings = null) { + settings ??= KurrentDBClientSerializationSettings.Get(); - bool TryResolveClrMetadataType(EventRecord record, out Type? clrMetadataType) => - schemaRegistry - .MessageTypeNamingStrategy - .TryResolveClrMetadataType(record.EventType, out clrMetadataType); + return new MessageSerializer(SchemaRegistry.From(settings), settings); + } } class NullMessageSerializer : IMessageSerializer { @@ -145,4 +127,8 @@ public bool TryDeserialize(EventRecord eventRecord, [NotNullWhen(true)] out Mess deserialized = null; return false; } + + public IMessageSerializer With(OperationSerializationSettings? operationSettings) { + return this; + } } diff --git a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs index 28a62b796..d60749fb1 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs @@ -11,8 +11,7 @@ public interface IMessageTypeNamingStrategy { #else bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type); #endif - - + #if NET48 bool TryResolveClrMetadataType(string messageTypeName, out Type? type); #else @@ -20,7 +19,10 @@ public interface IMessageTypeNamingStrategy { #endif } -public record MessageTypeNamingResolutionContext(string CategoryName); +public record MessageTypeNamingResolutionContext(string CategoryName) { + public static MessageTypeNamingResolutionContext FromStreamName(string streamName) => + new(streamName.Split('-').FirstOrDefault() ?? "no_stream_category"); +} class MessageTypeNamingStrategyWrapper( IMessageTypeRegistry messageTypeRegistry, @@ -66,9 +68,9 @@ public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true) public class DefaultMessageTypeNamingStrategy(Type? defaultMetadataType) : IMessageTypeNamingStrategy { readonly Type _defaultMetadataType = defaultMetadataType ?? typeof(TracingMetadata); - + public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) => - $"{resolutionContext.CategoryName}-{messageType.FullName}"; + $"{resolutionContext.CategoryName}-{messageType.FullName}"; #if NET48 public bool TryResolveClrType(string messageTypeName, out Type? type) { @@ -83,7 +85,7 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } var clrTypeName = messageTypeName[(categorySeparatorIndex + 1)..]; - + type = TypeProvider.GetTypeByFullName(clrTypeName); return type != null; diff --git a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs index 4b8020d2f..dc27c4530 100644 --- a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs @@ -1,4 +1,4 @@ -using KurrentDB.Client; +using System.Diagnostics.CodeAnalysis; namespace KurrentDB.Client.Core.Serialization; @@ -30,11 +30,26 @@ class SchemaRegistry( IDictionary serializers, IMessageTypeNamingStrategy messageTypeNamingStrategy ) { - public IMessageTypeNamingStrategy MessageTypeNamingStrategy { get; } = messageTypeNamingStrategy; - public ISerializer GetSerializer(ContentType schemaType) => serializers[schemaType]; + public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) => + messageTypeNamingStrategy.ResolveTypeName(messageType, resolutionContext); + +#if NET48 + public bool TryResolveClrType(string messageTypeName, out Type? type) => +#else + public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) => +#endif + messageTypeNamingStrategy.TryResolveClrType(messageTypeName, out type); + +#if NET48 + public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) => +#else + public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) => +#endif + messageTypeNamingStrategy.TryResolveClrMetadataType(messageTypeName, out type); + public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) { var messageTypeNamingStrategy = settings.MessageTypeNamingStrategy ?? new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType); diff --git a/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs b/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs index 790a66caf..7c6757469 100644 --- a/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs +++ b/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs @@ -269,7 +269,7 @@ public PersistentSubscriptionResult SubscribeToStream( new() { Options = readOptions }, Settings, options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), + _messageSerializer.With(options.SerializationSettings), cancellationToken ); } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index 268bb467a..b2d5e4309 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -12,6 +12,7 @@ using Kurrent.Diagnostics.Tracing; using static EventStore.Client.Streams.AppendResp.Types.WrongExpectedVersion; using static EventStore.Client.Streams.Streams; +using static KurrentDB.Client.Core.Serialization.MessageTypeNamingResolutionContext; namespace KurrentDB.Client { public partial class KurrentDBClient { @@ -31,19 +32,17 @@ public Task AppendToStreamAsync( AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { - var serializationContext - = new MessageSerializationContext(streamName, Settings.Serialization.DefaultContentType); - - var messageSerializer = _messageSerializer.With(Settings.Serialization, options?.SerializationSettings); - - var messageData = messageSerializer.Serialize(messages, serializationContext); + var messageSerializationContext = new MessageSerializationContext(FromStreamName(streamName)); + + var messageData = _messageSerializer.With(options?.SerializationSettings) + .Serialize(messages, messageSerializationContext); return AppendToStreamAsync(streamName, expectedState, messageData, options, cancellationToken); } /// /// Appends events asynchronously to a stream using raw message data. - /// If you want to use auto-serialization, use overload with .. + /// If you want to use auto-serialization, use overload with .. /// This method intends to cover low-level scenarios in which you want to have full control of the serialization mechanism. /// /// The name of the stream to append events to. diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index 45f5121b0..9670f7041 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -52,7 +52,7 @@ public ReadAllStreamResult ReadAllAsync( readReq, Settings, options, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), + _messageSerializer.With(options.SerializationSettings), cancellationToken ); } @@ -229,7 +229,7 @@ public ReadStreamResult ReadStreamAsync( Settings, options.Deadline, options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), + _messageSerializer.With(options.SerializationSettings), cancellationToken ); } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs index c4aef8879..4d1da858f 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs @@ -190,7 +190,7 @@ public StreamSubscriptionResult SubscribeToAll( }, Settings, options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), + _messageSerializer.With(options.SerializationSettings), cancellationToken ); @@ -329,7 +329,7 @@ public StreamSubscriptionResult SubscribeToStream( }, Settings, options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), + _messageSerializer.With(options.SerializationSettings), cancellationToken ); diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs deleted file mode 100644 index 0d25dc79c..000000000 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using KurrentDB.Client.Core.Serialization; - -namespace KurrentDB.Client.Tests.Core.Serialization; - -public class MessageSerializerExtensionsTests { - [Fact] - public void With_NullOperationSettings_ReturnsDefaultSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = new KurrentDBClientSerializationSettings(); - - // When - var result = defaultSerializer.With(defaultSettings, null); - - // Then - Assert.Same(defaultSerializer, result); - } - - [Fact] - public void With_DisabledAutomaticDeserialization_ReturnsNullSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = new KurrentDBClientSerializationSettings(); - var operationSettings = OperationSerializationSettings.Disabled; - - // When - var result = defaultSerializer.With(defaultSettings, operationSettings); - - // Then - Assert.Same(NullMessageSerializer.Instance, result); - } - - [Fact] - public void With_NoConfigureSettings_ReturnsDefaultSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = new KurrentDBClientSerializationSettings(); - var operationSettings = new OperationSerializationSettings(); // Default-enabled with no config - - // When - var result = defaultSerializer.With(defaultSettings, operationSettings); - - // Then - Assert.Same(defaultSerializer, result); - } - - [Fact] - public void With_ConfigureSettings_CreatesNewMessageSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = KurrentDBClientSerializationSettings.Default(); - - var operationSettings = OperationSerializationSettings.Configure( - s => - s.RegisterMessageType("CustomMessageName") - ); - - // When - var result = defaultSerializer.With(defaultSettings, operationSettings); - - // Then - Assert.NotSame(defaultSerializer, result); - Assert.IsType(result); - } - - [Fact] - public void Serialize_WithMultipleMessages_ReturnsArrayOfEventData() { - // Given - var serializer = new DummyMessageSerializer(); - var messages = new List { - Message.From(new object()), - Message.From(new object()), - Message.From(new object()) - }; - - var context = new MessageSerializationContext("test-stream", ContentType.Json); - - // When - var result = serializer.Serialize(messages, context); - - // Then - Assert.Equal(3, result.Length); - Assert.All(result, eventData => Assert.Equal("TestEvent", eventData.Type)); - } - - class DummyMessageSerializer : IMessageSerializer { - public MessageData Serialize(Message value, MessageSerializationContext context) { - return new MessageData( - "TestEvent", - ReadOnlyMemory.Empty, - ReadOnlyMemory.Empty, - contentType: "application/json" - ); - } - -#if NET48 - public bool TryDeserialize(EventRecord record, out Message? deserialized) { -#else - public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized) { -#endif - deserialized = null; - return false; - } - } - - public record UserRegistered(string UserId, string Email); -} diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs index 449f822fe..2c1b67daf 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs @@ -1,22 +1,23 @@ -using KurrentDB.Client; using KurrentDB.Client.Core.Serialization; namespace KurrentDB.Client.Tests.Core.Serialization; +using static MessageTypeNamingResolutionContext; + public class MessageSerializerTests { [Fact] public void Serialize_WithValidMessage_ReturnsEventData() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var metadata = new TestMetadata(); var messageId = Uuid.NewUuid(); var message = Message.From(data, metadata, messageId); - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); @@ -34,12 +35,12 @@ public void Serialize_WithAutoGeneratedId_GeneratesNewId() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); // No ID provided - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); @@ -53,12 +54,12 @@ public void Serialize_WithoutMetadata_GeneratesEmptyMetadata() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); @@ -70,14 +71,14 @@ public void Serialize_WithoutMetadata_GeneratesEmptyMetadata() { [Fact] public void Serialize_WithBinaryContentType_UsesBinarySerializer() { // Given - var settings = CreateTestSettings(); + var settings = CreateTestSettings().UseContentType(ContentType.Bytes); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); - var context = new MessageSerializationContext("user-123", ContentType.Bytes); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); @@ -91,7 +92,7 @@ public void SerializeMultiple_WithValidMessages_ReturnsEventDataArray() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var messages = new[] { Message.From(new UserRegistered("1", "u1@random-email.com")), @@ -99,7 +100,7 @@ public void SerializeMultiple_WithValidMessages_ReturnsEventDataArray() { Message.From(new UserRegistered("3", "u3@random-email.com")) }; - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventDataArray = serializer.Serialize(messages, context); @@ -114,11 +115,11 @@ public void TryDeserialize_WithValidEventRecord_DeserializesSuccessfully() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var testEvent = new UserRegistered("user-123", "user@random-email.com"); var testMetadata = new TestMetadata { CorrelationId = "corr-123", UserId = "user-456" }; - var messageId = Uuid.NewUuid(); + var messageId = Uuid.NewUuid(); var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, testMetadata, messageId); @@ -144,7 +145,7 @@ public void TryDeserialize_WithUnknownEventType_ReturnsFalse() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var testEvent = new UserRegistered("user-123", "user@random-email.com"); var eventRecord = CreateTestEventRecord("UnknownEventType", testEvent); @@ -162,7 +163,7 @@ public void TryDeserialize_WithNullData_ReturnsFalse() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var eventRecord = CreateTestEventRecord("UserRegistered", null); // When @@ -178,10 +179,10 @@ public void TryDeserialize_WithEmptyMetadata_DeserializesWithNullMetadata() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var testEvent = new UserRegistered("user-123", "user@random-email.com"); - var messageId = Uuid.NewUuid(); + var messageId = Uuid.NewUuid(); var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, null, messageId); @@ -219,11 +220,92 @@ public void MessageSerializer_From_CreatesInstanceWithProvidedSettings() { // Then - test it works with our settings var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); - var context = new MessageSerializationContext("user-123", ContentType.Json); + + var context = new MessageSerializationContext(FromStreamName("user-123")); var eventData = serializer.Serialize(message, context); Assert.Equal("UserRegistered", eventData.Type); } + + [Fact] + public void With_NullOperationSettings_ReturnsDefaultMessageSerializer() { + // Given + var defaultSettings = new KurrentDBClientSerializationSettings(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + + // When + var result = messageSerializer.With(null); + + // Then + Assert.Same(messageSerializer, result); + } + + [Fact] + public void With_DisabledAutomaticDeserialization_ReturnsNullSerializer() { + // Given + var defaultSettings = new KurrentDBClientSerializationSettings(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + var operationSettings = OperationSerializationSettings.Disabled; + + // When + var result = messageSerializer.With(operationSettings); + + // Then + Assert.Same(NullMessageSerializer.Instance, result); + } + + [Fact] + public void With_NoConfigureSettings_ReturnsDefaultMessageSerializer() { + // Given + var defaultSettings = new KurrentDBClientSerializationSettings(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + var operationSettings = new OperationSerializationSettings(); // Default-enabled with no config + + // When + var result = messageSerializer.With(operationSettings); + + // Then + Assert.Same(messageSerializer, result); + } + + [Fact] + public void With_ConfigureSettings_CreatesNewMessageSerializer() { + // Given + var defaultSettings = KurrentDBClientSerializationSettings.Get(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + + var operationSettings = OperationSerializationSettings.Configure( + s => + s.RegisterMessageType("CustomMessageName") + ); + + // When + var result = messageSerializer.With(operationSettings); + + // Then + Assert.NotSame(messageSerializer, result); + Assert.IsType(result); + } + + [Fact] + public void Serialize_WithMultipleMessages_ReturnsArrayOfMessageData() { + // Given + var defaultSettings = KurrentDBClientSerializationSettings.Get(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + var messages = new List { + Message.From(new UserRegistered("Joe Doe", "joe.doe@unknown.com")), + Message.From(new UserRegistered("Anne Smith", "anne.smith@unknown.com")), + Message.From(new UserRegistered("Ouro the Dragon", "ouro.dragon@unknown.com")) + }; + + var context = new MessageSerializationContext(FromStreamName("test-stream")); + + // When + var result = messageSerializer.Serialize(messages, context); + + // Then + Assert.Equal(3, result.Length); + } static KurrentDBClientSerializationSettings CreateTestSettings() { var settings = new KurrentDBClientSerializationSettings(); @@ -248,11 +330,15 @@ static EventRecord CreateTestEventRecord( { Constants.Metadata.ContentType, Constants.Metadata.ContentTypes.ApplicationJson } }, data != null ? _serializer.Serialize(data) : ReadOnlyMemory.Empty, - metadata != null ? _serializer.Serialize(metadata) : ReadOnlyMemory.Empty + metadata != null ? _metadataSerializer.Serialize(metadata) : ReadOnlyMemory.Empty ); static readonly SystemTextJsonSerializer _serializer = new SystemTextJsonSerializer(); + static readonly SystemTextJsonSerializer _metadataSerializer = new SystemTextJsonSerializer( + new SystemTextJsonSerializationSettings { Options = KurrentDBClient.StreamMetadataJsonSerializerOptions } + ); + public record UserRegistered(string UserId, string Email); public record UserAssignedToRole(string UserId, string Role); diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializationContextTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeNamingResolutionContextTests.cs similarity index 71% rename from test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializationContextTests.cs rename to test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeNamingResolutionContextTests.cs index 46cf18ffc..4e8529d37 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializationContextTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeNamingResolutionContextTests.cs @@ -2,13 +2,13 @@ namespace KurrentDB.Client.Tests.Core.Serialization; -public class MessageSerializationContextTests +public class MessageTypeNamingResolutionContextTests { [Fact] public void CategoryName_ExtractsFromStreamName() { // Arrange - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = MessageTypeNamingResolutionContext.FromStreamName("user-123"); // Act var categoryName = context.CategoryName; @@ -21,7 +21,7 @@ public void CategoryName_ExtractsFromStreamName() public void CategoryName_ExtractsFromStreamNameWithMoreThanOneDash() { // Arrange - var context = new MessageSerializationContext("user-some-123", ContentType.Json); + var context = MessageTypeNamingResolutionContext.FromStreamName("user-some-123"); // Act var categoryName = context.CategoryName; @@ -34,7 +34,7 @@ public void CategoryName_ExtractsFromStreamNameWithMoreThanOneDash() public void CategoryName_ReturnsTheWholeStreamName() { // Arrange - var context = new MessageSerializationContext("user123", ContentType.Json); + var context = MessageTypeNamingResolutionContext.FromStreamName("user123"); // Act var categoryName = context.CategoryName; diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs index f1929e7ef..270c65d9c 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs @@ -1,15 +1,16 @@ using KurrentDB.Client.Core.Serialization; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.Core.Serialization; +using static MessageTypeNamingResolutionContext; + public class NullMessageSerializerTests { [Fact] public void Serialize_ThrowsException() { // Given var serializer = NullMessageSerializer.Instance; var message = Message.From(new object()); - var context = new MessageSerializationContext("test-stream", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("test-stream")); // When & Assert Assert.Throws(() => serializer.Serialize(message, context)); diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs index 4f78d7793..e23b699cd 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using KurrentDB.Client.Core.Serialization; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.Core.Serialization; @@ -15,23 +14,6 @@ record TestEvent3; record TestMetadata; - [Fact] - public void Constructor_InitializesProperties() { - // Given - var serializers = new Dictionary { - { ContentType.Json, new SystemTextJsonSerializer() }, - { ContentType.Bytes, new SystemTextJsonSerializer() } - }; - - var namingStrategy = new DefaultMessageTypeNamingStrategy(typeof(TestMetadata)); - - // When - var registry = new SchemaRegistry(serializers, namingStrategy); - - // Then - Assert.Same(namingStrategy, registry.MessageTypeNamingStrategy); - } - [Fact] public void GetSerializer_ReturnsCorrectSerializer() { // Given @@ -68,11 +50,9 @@ public void From_WithDefaultSettings_CreatesRegistryWithDefaults() { // Then Assert.NotNull(registry); - Assert.NotNull(registry.MessageTypeNamingStrategy); Assert.NotNull(registry.GetSerializer(ContentType.Json)); Assert.NotNull(registry.GetSerializer(ContentType.Bytes)); - Assert.IsType(registry.MessageTypeNamingStrategy); Assert.IsType(registry.GetSerializer(ContentType.Json)); Assert.IsType(registry.GetSerializer(ContentType.Bytes)); } @@ -125,18 +105,76 @@ public void From_WithMessageTypeMap_RegistersTypes() { settings.RegisterMessageType("test-event-2"); // When - var registry = SchemaRegistry.From(settings); - var namingStrategy = registry.MessageTypeNamingStrategy; + var registry = SchemaRegistry.From(settings); // Then // Verify types can be resolved - Assert.True(namingStrategy.TryResolveClrType("test-event-1", out var type1)); + Assert.True(registry.TryResolveClrType("test-event-1", out var type1)); Assert.Equal(typeof(TestEvent1), type1); - Assert.True(namingStrategy.TryResolveClrType("test-event-2", out var type2)); + Assert.True(registry.TryResolveClrType("test-event-2", out var type2)); Assert.Equal(typeof(TestEvent2), type2); } + [Fact] + public void From_WithCategoryMessageTypesMap_WithDefaultMessageAutoRegistration() { + // Given + var settings = new KurrentDBClientSerializationSettings(); + var defaultMessageTypeNamingStrategy = new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType); + + // When + var registry = SchemaRegistry.From(settings); + + // Then + // For categories, the naming strategy should have resolved the type names + // using the ResolveTypeName method, which by default uses the type's name + string typeName1 = registry.ResolveTypeName( + typeof(TestEvent1), + new MessageTypeNamingResolutionContext("category1") + ); + + string expectedTypeName1 = defaultMessageTypeNamingStrategy.ResolveTypeName( + typeof(TestEvent1), + new MessageTypeNamingResolutionContext("category1") + ); + + Assert.Equal(expectedTypeName1, typeName1); + + string typeName2 = registry.ResolveTypeName( + typeof(TestEvent2), + new MessageTypeNamingResolutionContext("category1") + ); + + string expectedTypeName2 = defaultMessageTypeNamingStrategy.ResolveTypeName( + typeof(TestEvent2), + new MessageTypeNamingResolutionContext("category1") + ); + + Assert.Equal(expectedTypeName2, typeName2); + + string typeName3 = registry.ResolveTypeName( + typeof(TestEvent3), + new MessageTypeNamingResolutionContext("category2") + ); + + string expectedTypeName3 = defaultMessageTypeNamingStrategy.ResolveTypeName( + typeof(TestEvent3), + new MessageTypeNamingResolutionContext("category2") + ); + + Assert.Equal(expectedTypeName3, typeName3); + + // Verify types can be resolved by the type names + Assert.True(registry.TryResolveClrType(typeName1, out var resolvedType1)); + Assert.Equal(typeof(TestEvent1), resolvedType1); + + Assert.True(registry.TryResolveClrType(typeName2, out var resolvedType2)); + Assert.Equal(typeof(TestEvent2), resolvedType2); + + Assert.True(registry.TryResolveClrType(typeName3, out var resolvedType3)); + Assert.Equal(typeof(TestEvent3), resolvedType3); + } + [Fact] public void From_WithCategoryMessageTypesMap_RegistersTypesWithCategories() { // Given @@ -146,35 +184,34 @@ public void From_WithCategoryMessageTypesMap_RegistersTypesWithCategories() { settings.RegisterMessageTypeForCategory("category2"); // When - var registry = SchemaRegistry.From(settings); - var namingStrategy = registry.MessageTypeNamingStrategy; + var registry = SchemaRegistry.From(settings); // Then // For categories, the naming strategy should have resolved the type names // using the ResolveTypeName method, which by default uses the type's name - string typeName1 = namingStrategy.ResolveTypeName( + string typeName1 = registry.ResolveTypeName( typeof(TestEvent1), new MessageTypeNamingResolutionContext("category1") ); - string typeName2 = namingStrategy.ResolveTypeName( + string typeName2 = registry.ResolveTypeName( typeof(TestEvent2), new MessageTypeNamingResolutionContext("category1") ); - string typeName3 = namingStrategy.ResolveTypeName( + string typeName3 = registry.ResolveTypeName( typeof(TestEvent3), new MessageTypeNamingResolutionContext("category2") ); // Verify types can be resolved by the type names - Assert.True(namingStrategy.TryResolveClrType(typeName1, out var resolvedType1)); + Assert.True(registry.TryResolveClrType(typeName1, out var resolvedType1)); Assert.Equal(typeof(TestEvent1), resolvedType1); - Assert.True(namingStrategy.TryResolveClrType(typeName2, out var resolvedType2)); + Assert.True(registry.TryResolveClrType(typeName2, out var resolvedType2)); Assert.Equal(typeof(TestEvent2), resolvedType2); - Assert.True(namingStrategy.TryResolveClrType(typeName3, out var resolvedType3)); + Assert.True(registry.TryResolveClrType(typeName3, out var resolvedType3)); Assert.Equal(typeof(TestEvent3), resolvedType3); } @@ -190,11 +227,8 @@ public void From_WithCustomNamingStrategy_UsesProvidedStrategy() { // Then // The registry wraps the naming strategy, but should still use it - var wrappedStrategy = registry.MessageTypeNamingStrategy; - Assert.IsType(wrappedStrategy); - // Test to make sure it behaves like our custom strategy - string typeName = wrappedStrategy.ResolveTypeName( + string typeName = registry.ResolveTypeName( typeof(TestEvent1), new MessageTypeNamingResolutionContext("test") ); @@ -215,12 +249,8 @@ public void From_WithNoMessageTypeNamingStrategy_UsesDefaultStrategy() { var registry = SchemaRegistry.From(settings); // Then - Assert.NotNull(registry.MessageTypeNamingStrategy); - // The wrapped default strategy should use our metadata type - Assert.True( - registry.MessageTypeNamingStrategy.TryResolveClrMetadataType("some-type", out var defaultMetadataType) - ); + Assert.True(registry.TryResolveClrMetadataType("some-type", out var defaultMetadataType)); Assert.Equal(typeof(TestMetadata), defaultMetadataType); } From 6d0545033905944750189a9024aa670448d569f9 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 20 Mar 2025 12:32:17 +0100 Subject: [PATCH 09/23] [DEVEX-222] Marked old Stream Metadata options as obsolete --- .../Streams/KurrentDBClient.Metadata.cs | 14 ++++--- .../SubscribeToAllFilterObsoleteTests.cs | 9 ++-- .../SubscribeToAllFilterTests.cs | 4 +- .../AllStreamWithNoAclSecurityTests.cs | 41 ++++++++++++++----- .../Security/SecurityFixture.cs | 26 ++++++------ .../StreamSecurityInheritanceTests.cs | 22 +++++----- .../Streams/Read/ReadAllEventsFixture.cs | 2 +- .../Streams/StreamMetadataTests.cs | 17 ++++---- .../Subscriptions/SubscribeToAllTests.cs | 15 +++++-- .../Subscriptions/SubscribeToStreamTests.cs | 2 +- 10 files changed, 92 insertions(+), 60 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs index a22646345..4b3b1b2cf 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs @@ -67,10 +67,10 @@ public Task SetStreamMetadataAsync( string streamName, StreamState expectedState, StreamMetadata metadata, - SetStreamMetadata? operationOptions = null, + SetStreamMetadataOptions? operationOptions = null, CancellationToken cancellationToken = default ) { - operationOptions ??= new SetStreamMetadata(); + operationOptions ??= new SetStreamMetadataOptions(); operationOptions.With(Settings.OperationOptions); return SetStreamMetadataInternal( @@ -88,7 +88,7 @@ public Task SetStreamMetadataAsync( async Task SetStreamMetadataInternal( StreamMetadata metadata, AppendReq appendReq, - SetStreamMetadata operationOptions, + SetStreamMetadataOptions operationOptions, CancellationToken cancellationToken ) { var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); @@ -117,6 +117,7 @@ public static class KurrentDBClientMetadataObsoleteExtensions { /// The optional to perform operation with. /// The optional . /// + [Obsolete("Use method with OperationOptions parameter")] public static Task GetStreamMetadataAsync( this KurrentDBClient dbClient, string streamName, @@ -142,18 +143,19 @@ public static Task GetStreamMetadataAsync( /// The optional to perform operation with. /// The optional . /// + [Obsolete("Use method with SetStreamMetadataOptions parameter")] public static Task SetStreamMetadataAsync( this KurrentDBClient dbClient, string streamName, StreamState expectedState, StreamMetadata metadata, - Action? configureOperationOptions = null, + Action? configureOperationOptions = null, TimeSpan? deadline = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) { var operationOptions = - new SetStreamMetadata { Deadline = deadline, UserCredentials = userCredentials }; + new SetStreamMetadataOptions { Deadline = deadline, UserCredentials = userCredentials }; configureOperationOptions?.Invoke(operationOptions); return dbClient.SetStreamMetadataAsync( @@ -166,5 +168,5 @@ public static Task SetStreamMetadataAsync( } } - public class SetStreamMetadata : AppendToStreamOptions; + public class SetStreamMetadataOptions : AppendToStreamOptions; } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs index 0f92800dd..853ecfd14 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs @@ -1,5 +1,4 @@ using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.PersistentSubscriptions; @@ -24,7 +23,7 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); var appearedEvents = new List(); @@ -86,14 +85,14 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in eventsToSkip) { await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, - new[] { e } + [e] ); } @@ -101,7 +100,7 @@ await Fixture.Streams.AppendToStreamAsync( var result = await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, - new[] { e } + [e] ); eventToCaptureResult ??= result; diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs index 6a678dfb4..028ed270f 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs @@ -25,7 +25,7 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); var appearedEvents = new List(); @@ -87,7 +87,7 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in eventsToSkip) { diff --git a/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs index 2abc3645e..3d0d1c52a 100644 --- a/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs @@ -1,24 +1,35 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client.Tests; namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class AllStreamWithNoAclSecurityTests(ITestOutputHelper output, AllStreamWithNoAclSecurityTests.CustomFixture fixture) +public class AllStreamWithNoAclSecurityTests( + ITestOutputHelper output, + AllStreamWithNoAclSecurityTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task write_to_all_is_never_allowed() { await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin) + ); } [Fact] public async Task delete_of_all_is_never_allowed() { await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync( + () => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin) + ); } [Fact] @@ -33,7 +44,10 @@ public async Task reading_and_subscribing_is_not_allowed_when_no_credentials_are public async Task reading_and_subscribing_is_not_allowed_for_usual_user() { await Assert.ThrowsAsync(() => Fixture.ReadAllForward(TestCredentials.TestUser1)); await Assert.ThrowsAsync(() => Fixture.ReadAllBackward(TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToAll(TestCredentials.TestUser1)); } @@ -51,7 +65,9 @@ public async Task meta_write_is_not_allowed_when_no_credentials_are_passed() => [Fact] public async Task meta_write_is_not_allowed_for_usual_user() => - await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); [Fact] public Task meta_write_is_allowed_for_admin_user() => @@ -61,7 +77,12 @@ public class CustomFixture : SecurityFixture { protected override async Task Given() { await base.Given(); - await Streams.SetStreamMetadataAsync(AllStream, StreamState.Any, new(), userCredentials: TestCredentials.Root); + await Streams.SetStreamMetadataAsync( + AllStream, + StreamState.Any, + new(), + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs index 15b124560..a2aad620f 100644 --- a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs +++ b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs @@ -52,42 +52,42 @@ await Streams.SetStreamMetadataAsync( NoAclStream, StreamState.NoStream, new(), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( ReadStream, StreamState.NoStream, new(acl: new(TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( WriteStream, StreamState.NoStream, new(acl: new(writeRole: TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( MetaReadStream, StreamState.NoStream, new(acl: new(metaReadRole: TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( MetaWriteStream, StreamState.NoStream, new(acl: new(metaWriteRole: TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( AllStream, StreamState.Any, new(acl: new(TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -101,7 +101,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: TestCredentials.TestUser1.Username ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -115,7 +115,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: SystemRoles.Admins ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -129,7 +129,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: SystemRoles.All ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -143,7 +143,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: SystemRoles.All ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } @@ -223,7 +223,7 @@ public Task ReadAllBackward(UserCredentials? userCredentials = null) => .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadMeta(string streamId, UserCredentials? userCredentials = null) => - Streams.GetStreamMetadataAsync(streamId, userCredentials: userCredentials) + Streams.GetStreamMetadataAsync(streamId, new OperationOptions { UserCredentials = userCredentials }) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task WriteMeta( @@ -240,7 +240,7 @@ public Task WriteMeta( metaReadRole: role ) ), - userCredentials: userCredentials + new SetStreamMetadataOptions { UserCredentials = userCredentials } ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); @@ -269,7 +269,7 @@ await Streams.SetStreamMetadataAsync( streamId, StreamState.NoStream, metadataPermanent, - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); diff --git a/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs b/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs index c15798794..9de765b96 100644 --- a/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs +++ b/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs @@ -112,77 +112,77 @@ await Streams.SetStreamMetadataAsync( "user-no-acl", StreamState.NoStream, new(), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-diff", StreamState.NoStream, new(acl: new(writeRole: "user2")), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-multiple", StreamState.NoStream, new(acl: new(writeRoles: new[] { "user1", "user2" })), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-restricted", StreamState.NoStream, new(acl: new(writeRoles: Array.Empty())), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-all", StreamState.NoStream, new(acl: new(writeRole: SystemRoles.All)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-r-restricted", StreamState.NoStream, new(acl: new("user1")), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-no-acl", StreamState.NoStream, new(), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-diff", StreamState.NoStream, new(acl: new(writeRole: "user2")), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-multiple", StreamState.NoStream, new(acl: new(writeRoles: new[] { "user1", "user2" })), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-restricted", StreamState.NoStream, new(acl: new(writeRoles: Array.Empty())), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-all", StreamState.NoStream, new(acl: new(writeRole: SystemRoles.All)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); } } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs index 448375604..3d78be13b 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs @@ -14,7 +14,7 @@ public ReadAllEventsFixture() { SystemStreams.AllStream, StreamState.NoStream, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); Events = CreateTestEvents(20) diff --git a/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs b/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs index dc8b9863a..1ffb4a7ec 100644 --- a/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs @@ -1,12 +1,12 @@ using System.Text.Json; -using KurrentDB.Client; using Grpc.Core; namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Metadata")] -public class StreamMetadataTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class StreamMetadataTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Fact] public async Task getting_for_an_existing_stream_and_no_metadata_exists() { var stream = Fixture.GetStreamName(); @@ -102,7 +102,7 @@ await Fixture.Streams.SetStreamMetadataAsync( stream, StreamState.StreamRevision(2), new(), - options => { options.ThrowOnAppendFailure = false; } + new SetStreamMetadataOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -162,7 +162,7 @@ public async Task with_timeout_set_with_any_stream_revision_fails_when_operation stream, StreamState.Any, new(), - deadline: TimeSpan.Zero + new SetStreamMetadataOptions { Deadline = TimeSpan.Zero } ) ); @@ -179,7 +179,7 @@ public async Task with_timeout_set_with_stream_revision_fails_when_operation_exp stream, StreamState.StreamRevision(0), new(), - deadline: TimeSpan.Zero + new SetStreamMetadataOptions { Deadline = TimeSpan.Zero } ) ); @@ -188,7 +188,10 @@ public async Task with_timeout_set_with_stream_revision_fails_when_operation_exp [Fact] public async Task with_timeout_get_fails_when_operation_expired() { - var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.GetStreamMetadataAsync(stream, TimeSpan.Zero)); + var stream = Fixture.GetStreamName(); + var rpcException = + await Assert.ThrowsAsync( + () => Fixture.Streams.GetStreamMetadataAsync(stream, new OperationOptions { Deadline = TimeSpan.Zero }) + ); } } diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs index c0dc9d664..c4b48cc28 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs @@ -449,7 +449,8 @@ public async Task receives_all_filtered_events_with_resolved_links() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); - var filterOptions = new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentDBPermanentFixture.TestEventType}")); + var filterOptions = + new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentDBPermanentFixture.TestEventType}")); await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, true, filterOptions: filterOptions); @@ -467,7 +468,9 @@ public async Task receives_all_filtered_events_with_resolved_links() { async Task Subscribe() { while (await enumerator.MoveNextAsync()) { if (enumerator.Current is not StreamMessage.Event(var resolvedEvent) || - !resolvedEvent.OriginalEvent.EventStreamId.StartsWith($"$et-{KurrentDBPermanentFixture.TestEventType}")) { + !resolvedEvent.OriginalEvent.EventStreamId.StartsWith( + $"$et-{KurrentDBPermanentFixture.TestEventType}" + )) { continue; } @@ -487,10 +490,14 @@ await Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.NoStream, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); - await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + await Streams.AppendToStreamAsync( + $"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", + StreamState.NoStream, + CreateTestEvents(10) + ); }; } } diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs index 088f40e3e..5240ccf57 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs @@ -202,7 +202,7 @@ await Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); From 994f3b048c39f809745ae49da086f6b5d2af1427 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 20 Mar 2025 14:02:27 +0100 Subject: [PATCH 10/23] [DEVEX-222] Made old Read methods obsolete and used the new ones in tests --- samples/appending-events/Program.cs | 8 +- samples/quick-start/Program.cs | 2 - samples/reading-events/Program.cs | 67 +++------- .../Controllers/EventStoreController.cs | 2 +- .../Streams/KurrentDBClient.Append.cs | 9 +- .../Streams/KurrentDBClient.Read.cs | 125 +++++++++++++++++- .../KurrentDBClientWarmupExtensions.cs | 11 +- .../Obsolete/SubscribeToAllObsoleteTests.cs | 35 ++++- .../SubscribeToAll/SubscribeToAllTests.cs | 116 +++++++++++----- .../Security/SecurityFixture.cs | 51 +++---- .../Streams/AppendTests.cs | 59 +++++---- .../Read/ReadAllEventsBackwardTests.cs | 35 +++-- .../Streams/Read/ReadAllEventsForwardTests.cs | 45 ++++--- .../Streams/Read/ReadStreamBackwardTests.cs | 66 +++++---- ...dStreamEventsLinkedToDeletedStreamTests.cs | 15 ++- .../Streams/Read/ReadStreamForwardTests.cs | 47 +++---- ...reamWhenHavingMaxCountSetForStreamTests.cs | 33 ++++- .../Serialization/SerializationTests.cs | 34 ++++- .../Streams/SoftDeleteTests.cs | 16 +-- .../Subscriptions/SubscribeToAllTests.cs | 6 +- 20 files changed, 496 insertions(+), 286 deletions(-) diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index 4d3a2b707..b934119ad 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -94,15 +94,11 @@ await client.AppendToStreamAsync( #region append-with-concurrency-check - var clientOneRead = client.ReadStreamAsync( - Direction.Forwards, - "concurrency-stream", - StreamPosition.Start - ); + var clientOneRead = client.ReadStreamAsync("concurrency-stream"); var clientOneRevision = (await clientOneRead.LastAsync()).Event.EventNumber.ToUInt64(); - var clientTwoRead = client.ReadStreamAsync(Direction.Forwards, "concurrency-stream", StreamPosition.Start); + var clientTwoRead = client.ReadStreamAsync("concurrency-stream"); var clientTwoRevision = (await clientTwoRead.LastAsync()).Event.EventNumber.ToUInt64(); var clientOneData = MessageData.From( diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 11f4fec6d..6170072f3 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -54,9 +54,7 @@ await client.AppendToStreamAsync( #region readStream var result = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - StreamPosition.Start, cancellationToken: cancellationToken ); diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index f5dea1890..513c48ecb 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -25,17 +25,13 @@ await client.AppendToStreamAsync( static async Task ReadFromStream(KurrentDBClient client) { #region read-from-stream - var events = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.Start - ); + var events = client.ReadStreamAsync("some-stream"); #endregion read-from-stream #region iterate-stream - await foreach (var @event in events) Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); + await foreach (var @event in events) Console.WriteLine(@event.DeserializedData); #endregion iterate-stream @@ -50,17 +46,14 @@ static async Task ReadFromStream(KurrentDBClient client) { static async Task ReadFromStreamMessages(KurrentDBClient client) { #region read-from-stream-messages - var streamPosition = StreamPosition.Start; - var results = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - streamPosition - ); + var results = client.ReadStreamAsync("some-stream"); #endregion read-from-stream-messages #region iterate-stream-messages + var streamPosition = StreamPosition.Start; + await foreach (var message in results.Messages) switch (message) { case StreamMessage.Ok ok: @@ -95,17 +88,15 @@ static async Task ReadFromStreamPosition(KurrentDBClient client) { #region read-from-stream-position var events = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - 10, - 20 + new ReadStreamOptions { StreamPosition = 10, MaxCount = 20 } ); #endregion read-from-stream-position #region iterate-stream - await foreach (var @event in events) Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); + await foreach (var @event in events) Console.WriteLine(@event.DeserializedData); #endregion iterate-stream } @@ -114,10 +105,8 @@ static async Task ReadFromStreamPositionCheck(KurrentDBClient client) { #region checking-for-stream-presence var result = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - 10, - 20 + new ReadStreamOptions { StreamPosition = 10, MaxCount = 20 } ); if (await result.ReadState == ReadState.StreamNotFound) return; @@ -131,9 +120,8 @@ static async Task ReadFromStreamBackwards(KurrentDBClient client) { #region reading-backwards var events = client.ReadStreamAsync( - Direction.Backwards, "some-stream", - StreamPosition.End + new ReadStreamOptions { Direction = Direction.Backwards, StreamPosition = StreamPosition.End } ); await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); @@ -145,9 +133,8 @@ static async Task ReadFromStreamMessagesBackwards(KurrentDBClient client) { #region read-from-stream-messages-backwards var results = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - StreamPosition.End + new ReadStreamOptions { Direction = Direction.Backwards, StreamPosition = StreamPosition.End } ); #endregion read-from-stream-messages-backwards @@ -179,13 +166,13 @@ static async Task ReadFromStreamMessagesBackwards(KurrentDBClient client) { static async Task ReadFromAllStream(KurrentDBClient client) { #region read-from-all-stream - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + var events = client.ReadAllAsync(); #endregion read-from-all-stream #region read-from-all-stream-iterate - await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + await foreach (var e in events) Console.WriteLine(e.DeserializedData); #endregion read-from-all-stream-iterate } @@ -194,10 +181,7 @@ static async Task ReadFromAllStreamMessages(KurrentDBClient client) { #region read-from-all-stream-messages var position = Position.Start; - var results = client.ReadAllAsync( - Direction.Forwards, - position - ); + var results = client.ReadAllAsync(new ReadAllOptions { Position = position }); #endregion read-from-all-stream-messages @@ -220,12 +204,12 @@ static async Task ReadFromAllStreamMessages(KurrentDBClient client) { static async Task IgnoreSystemEvents(KurrentDBClient client) { #region ignore-system-events - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + var events = client.ReadAllAsync(); await foreach (var e in events) { if (e.Event.EventType.StartsWith("$")) continue; - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + Console.WriteLine(e.DeserializedData); } #endregion ignore-system-events @@ -234,7 +218,7 @@ static async Task IgnoreSystemEvents(KurrentDBClient client) { static async Task ReadFromAllStreamBackwards(KurrentDBClient client) { #region read-from-all-stream-backwards - var events = client.ReadAllAsync(Direction.Backwards, Position.End); + var events = client.ReadAllAsync(new ReadAllOptions { Direction = Direction.Backwards, Position = Position.End }); #endregion read-from-all-stream-backwards @@ -249,10 +233,7 @@ static async Task ReadFromAllStreamBackwardsMessages(KurrentDBClient client) { #region read-from-all-stream-messages-backwards var position = Position.End; - var results = client.ReadAllAsync( - Direction.Backwards, - position - ); + var results = client.ReadAllAsync(new ReadAllOptions { Direction = Direction.Backwards, Position = position }); #endregion read-from-all-stream-messages-backwards @@ -274,7 +255,7 @@ static async Task ReadFromAllStreamBackwardsMessages(KurrentDBClient client) { } static async Task FilteringOutSystemEvents(KurrentDBClient client) { - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + var events = client.ReadAllAsync(); await foreach (var e in events) if (!e.Event.EventType.StartsWith("$")) @@ -285,10 +266,8 @@ static void ReadStreamOverridingUserCredentials(KurrentDBClient client, Cancella #region overriding-user-credentials var result = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - StreamPosition.Start, - userCredentials: new UserCredentials("admin", "changeit"), + new ReadStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken: cancellationToken ); @@ -299,9 +278,7 @@ static void ReadAllOverridingUserCredentials(KurrentDBClient client, Cancellatio #region read-all-overriding-user-credentials var result = client.ReadAllAsync( - Direction.Forwards, - Position.Start, - userCredentials: new UserCredentials("admin", "changeit"), + new ReadAllOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken: cancellationToken ); @@ -312,9 +289,7 @@ static void ReadAllResolvingLinkTos(KurrentDBClient client, CancellationToken ca #region read-from-all-stream-resolving-link-Tos var result = client.ReadAllAsync( - Direction.Forwards, - Position.Start, - resolveLinkTos: true, + new ReadAllOptions { ResolveLinkTos = true }, cancellationToken: cancellationToken ); diff --git a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs index 1773873ff..a3cd6330d 100644 --- a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs +++ b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs @@ -14,7 +14,7 @@ public EventStoreController(KurrentDBClient KurrentDBClient) { [HttpGet] public IAsyncEnumerable Get() { - return _KurrentClient.ReadAllAsync(Direction.Forwards, Position.Start); + return _KurrentClient.ReadAllAsync(); } #endregion using-dependency } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index b2d5e4309..2fa948763 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -33,7 +33,7 @@ public Task AppendToStreamAsync( CancellationToken cancellationToken = default ) { var messageSerializationContext = new MessageSerializationContext(FromStreamName(streamName)); - + var messageData = _messageSerializer.With(options?.SerializationSettings) .Serialize(messages, messageSerializationContext); @@ -507,10 +507,11 @@ public class AppendToStreamOptions : OperationOptions { /// /// public void With(KurrentDBClientOperationOptions clientOperationOptions) { - ThrowOnAppendFailure = clientOperationOptions.ThrowOnAppendFailure; - BatchAppendSize = clientOperationOptions.BatchAppendSize; + ThrowOnAppendFailure ??= clientOperationOptions.ThrowOnAppendFailure; + + BatchAppendSize ??= clientOperationOptions.BatchAppendSize; } - + /// /// Allows to customize or disable the automatic deserialization /// diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index 9670f7041..74119251e 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -471,6 +471,64 @@ public class ReadAllOptions : OperationOptions { /// Allows to customize or disable the automatic deserialization /// public OperationSerializationSettings? SerializationSettings { get; set; } + + public static ReadAllOptions Get() => + new ReadAllOptions(); + + public ReadAllOptions Forwards() { + Direction = Direction.Forwards; + + return this; + } + + public ReadAllOptions WithFilter(IEventFilter filter) { + Filter = filter; + + return this; + } + + public ReadAllOptions Backwards() { + Direction = Direction.Backwards; + + return this; + } + + public ReadAllOptions From(Position streamPosition) { + Position = streamPosition; + + return this; + } + + public ReadAllOptions FromStart() => + From(Position.Start); + + public ReadAllOptions FromEnd() => + From(Position.End); + + public ReadAllOptions WithMaxCount(long maxCount) { + MaxCount = maxCount; + + return this; + } + + public ReadAllOptions MaxOne() => + WithMaxCount(1); + + public ReadAllOptions First() => + FromStart() + .Forwards() + .MaxOne(); + + public ReadAllOptions Last() => + FromEnd() + .Backwards() + .MaxOne(); + + public ReadAllOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } } /// @@ -502,9 +560,62 @@ public class ReadStreamOptions : OperationOptions { /// Allows to customize or disable the automatic deserialization /// public OperationSerializationSettings? SerializationSettings { get; set; } + + public static ReadStreamOptions Get() => + new ReadStreamOptions(); + + public ReadStreamOptions Forwards() { + Direction = Direction.Forwards; + + return this; + } + + public ReadStreamOptions Backwards() { + Direction = Direction.Backwards; + + return this; + } + + public ReadStreamOptions From(StreamPosition streamPosition) { + StreamPosition = streamPosition; + + return this; + } + + public ReadStreamOptions FromStart() => + From(StreamPosition.Start); + + public ReadStreamOptions FromEnd() => + From(StreamPosition.End); + + public ReadStreamOptions WithMaxCount(long maxCount) { + MaxCount = maxCount; + + return this; + } + + public ReadStreamOptions MaxOne() => + WithMaxCount(1); + + public ReadStreamOptions First() => + FromStart() + .Forwards() + .MaxOne(); + + public ReadStreamOptions Last() => + FromEnd() + .Backwards() + .MaxOne(); + + public ReadStreamOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } } - public static class KurrentDBClientReadExtensions { + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class ObsoleteKurrentDBClientReadExtensions { /// /// Asynchronously reads all events. /// @@ -517,6 +628,10 @@ public static class KurrentDBClientReadExtensions { /// The optional to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with ReadAllOptions and get auto-serialization capabilities", + false + )] public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( this KurrentDBClient dbClient, Direction direction, @@ -554,6 +669,10 @@ public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( /// The optional to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with ReadAllOptions and get auto-serialization capabilities", + false + )] public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( this KurrentDBClient dbClient, Direction direction, @@ -598,6 +717,10 @@ public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( /// The optional to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with ReadStreamOptions and get auto-serialization capabilities", + false + )] public static KurrentDBClient.ReadStreamResult ReadStreamAsync( this KurrentDBClient dbClient, Direction direction, diff --git a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs index 31038525a..a327d2830 100644 --- a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs +++ b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs @@ -1,5 +1,4 @@ using Grpc.Core; -using KurrentDB.Client; using Polly; using Polly.Contrib.WaitAndRetry; using static System.TimeSpan; @@ -52,11 +51,11 @@ public static Task WarmUp( // 2. we are connected to leader if we require it var users = await dbClient .ReadStreamAsync( - direction: Direction.Forwards, - streamName: "$dbUsers", - revision: StreamPosition.Start, - maxCount: 1, - userCredentials: TestCredentials.Root, + "$dbUsers", + new ReadStreamOptions { + MaxCount = 1, + UserCredentials = TestCredentials.Root + }, cancellationToken: ct ) .ToArrayAsync(ct); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs index 111dc825b..e9efd4b78 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs @@ -10,7 +10,11 @@ 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); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(maxSubscriberCount: 1), + userCredentials: TestCredentials.Root + ); using var first = await Fixture.Subscriptions.SubscribeToAllAsync( group, @@ -67,10 +71,14 @@ await Fixture.Streams.AppendToStreamAsync( } var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, @@ -150,7 +158,12 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + 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) => { @@ -208,7 +221,7 @@ public async Task connect_to_existing_with_start_from_set_to_valid_middle_positi TaskCompletionSource firstEventSource = new(); var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); var expectedEvent = events[events.Length / 2]; //just a random event in the middle of the results @@ -245,7 +258,11 @@ public async Task connect_with_retries() { TaskCompletionSource retryCountSource = new(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); // Act using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( @@ -613,7 +630,11 @@ public async Task update_existing_with_subscribers() { TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); using var subscription = Fixture.Subscriptions.SubscribeToAllAsync( group, diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs index fbd31b235..573c24ea4 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs @@ -28,16 +28,24 @@ 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); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(maxSubscriberCount: 1), + userCredentials: TestCredentials.Root + ); - var ex = await Assert.ThrowsAsync(() => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout())); + var ex = await Assert.ThrowsAsync( + () => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout()) + ); Assert.Equal(SystemStreams.AllStream, ex.StreamName); Assert.Equal(group, ex.GroupName); return; async Task Subscribe() { - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await subscription.Messages.AnyAsync(); } } @@ -49,7 +57,8 @@ public async Task connect_to_existing_with_permissions() { await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); // Act - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); // Assert subscription.Messages @@ -72,10 +81,14 @@ await Fixture.Streams.AppendToStreamAsync( } var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); @@ -131,7 +144,12 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End), + userCredentials: TestCredentials.Root + ); + var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -177,7 +195,7 @@ public async Task connect_to_existing_with_start_from_set_to_valid_middle_positi var group = Fixture.GetGroupName(); var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); var expectedEvent = events[events.Length / 2]; //just a random event in the middle of the results @@ -205,7 +223,11 @@ await Fixture.Subscriptions.CreateToAllAsync( public async Task connect_with_retries() { // Arrange var group = Fixture.GetGroupName(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); // Act var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); @@ -270,12 +292,15 @@ public async Task create_with_commit_position_equal_to_last_indexed_position() { var group = Fixture.GetGroupName(); // Act - var lastEvent = await Fixture.Streams.ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - userCredentials: TestCredentials.Root - ).FirstAsync(); + var lastEvent = await Fixture.Streams + .ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = TestCredentials.Root + } + ).FirstAsync(); var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); await Fixture.Subscriptions.CreateToAllAsync( @@ -306,7 +331,8 @@ public async Task deleting_existing_with_subscriber() { await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); await Fixture.Subscriptions.DeleteToAllAsync(group, userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -348,7 +374,10 @@ await Fixture.Subscriptions.CreateToAllAsync( [RetryFact] public async Task deleting_nonexistent() { await Assert.ThrowsAsync( - () => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString(), userCredentials: TestCredentials.Root) + () => Fixture.Subscriptions.DeleteToAllAsync( + Guid.NewGuid().ToString(), + userCredentials: TestCredentials.Root + ) ); } @@ -379,7 +408,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + await subscription.Messages.OfType() .Take(events.Length) .ForEachAwaitAsync(e => subscription.Ack(e.ResolvedEvent)) @@ -404,7 +438,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + await subscription!.Messages.OfType() .Take(events.Length) .ForEachAwaitAsync(e => subscription.Ack(e.ResolvedEvent)) @@ -425,7 +464,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + bufferSize: bufferCount, + userCredentials: TestCredentials.Root + ); + foreach (var e in events) { await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] { e }); } @@ -646,10 +690,12 @@ await Fixture.Subscriptions.CreateToAllAsync( ); var lastEvent = await Fixture.Streams.ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - userCredentials: TestCredentials.Root + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = TestCredentials.Root + } ).FirstAsync(); var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); @@ -664,7 +710,11 @@ await Fixture.Subscriptions.UpdateToAllAsync( public async Task update_existing_with_subscribers() { var group = Fixture.GetGroupName(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); @@ -707,8 +757,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoints().WithTimeout()); @@ -832,10 +884,12 @@ public async Task create_with_commit_position_larger_than_last_indexed_position( var group = Fixture.GetGroupName(); var lastEvent = await Fixture.Streams.ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - userCredentials: TestCredentials.Root + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = TestCredentials.Root + } ).FirstAsync(); var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); diff --git a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs index a2aad620f..d845c4211 100644 --- a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs +++ b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs @@ -150,39 +150,26 @@ await Streams.SetStreamMetadataAsync( protected virtual Task When() => Task.CompletedTask; public Task ReadEvent(string streamId, UserCredentials? userCredentials = null) => - Streams.ReadStreamAsync( - Direction.Forwards, - streamId, - StreamPosition.Start, - 1, - false, - userCredentials: userCredentials - ) + Streams.ReadStreamAsync(streamId, new ReadStreamOptions { MaxCount = 1, UserCredentials = userCredentials }) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadStreamForward(string streamId, UserCredentials? userCredentials = null) => - Streams.ReadStreamAsync( - Direction.Forwards, - streamId, - StreamPosition.Start, - 1, - false, - userCredentials: userCredentials - ) + Streams.ReadStreamAsync(streamId, new ReadStreamOptions { MaxCount = 1, UserCredentials = userCredentials }) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials = null) => Streams.ReadStreamAsync( - Direction.Backwards, streamId, - StreamPosition.Start, - 1, - false, - userCredentials: userCredentials + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 1, + UserCredentials = userCredentials + } ) .ToArrayAsync() .AsTask() @@ -198,25 +185,19 @@ public Task AppendStream(string streamId, UserCredentials? userCre .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadAllForward(UserCredentials? userCredentials = null) => - Streams.ReadAllAsync( - Direction.Forwards, - Position.Start, - 1, - false, - userCredentials: userCredentials - ) + Streams.ReadAllAsync(new ReadAllOptions { MaxCount = 1, UserCredentials = userCredentials }) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadAllBackward(UserCredentials? userCredentials = null) => - Streams - .ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - false, - userCredentials: userCredentials + Streams.ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = userCredentials + } ) .ToArrayAsync() .AsTask() diff --git a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs index 533fe6a41..dbfef0046 100644 --- a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs @@ -1,4 +1,3 @@ -using KurrentDB.Client; using Grpc.Core; using Humanizer; @@ -24,7 +23,7 @@ public async Task appending_zero_events(StreamState expectedStreamState) { } await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = iterations }) .ShouldThrowAsync(ex => ex.Stream.ShouldBe(stream)); } @@ -44,7 +43,7 @@ public async Task appending_zero_events_again(StreamState expectedStreamState) { } await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = iterations }) .ShouldThrowAsync(ex => ex.Stream.ShouldBe(stream)); } @@ -60,7 +59,7 @@ public async Task create_stream_expected_version_on_first_write_if_does_not_exis Assert.Equal(new(0), writeResult.NextExpectedStreamState); - var count = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 2) + var count = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 2 }) .CountAsync(); Assert.Equal(1, count); @@ -472,7 +471,7 @@ await Fixture.Streams ) .ShouldThrowAsync(); - var state = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start) + var state = await Fixture.Streams.ReadStreamAsync(streamName) .ReadState; state.ShouldBe(ReadState.StreamNotFound); @@ -521,7 +520,8 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0em1_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -536,8 +536,8 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_4e4_0any_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -551,8 +551,9 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e5_non_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(5), events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 2 }) + .CountAsync(); + Assert.Equal(events.Length + 1, count); } @@ -628,8 +629,9 @@ public async Task sequence_0em1_0e0_non_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(0), events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 2 }) + .CountAsync(); + Assert.Equal(events.Length + 1, count); } @@ -644,8 +646,8 @@ public async Task sequence_0em1_0any_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -659,8 +661,8 @@ public async Task sequence_0em1_0em1_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -675,8 +677,9 @@ public async Task sequence_0em1_1e0_2e1_1any_1any_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -690,8 +693,9 @@ public async Task sequence_S_0em1_1em1_E_S_0em1_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -705,8 +709,9 @@ public async Task sequence_S_0em1_1em1_E_S_0any_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -721,8 +726,9 @@ public async Task sequence_S_0em1_1em1_E_S_1e0_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(0), events.Skip(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -736,8 +742,9 @@ public async Task sequence_S_0em1_1em1_E_S_1any_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs index c27957e85..51998295a 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs @@ -13,15 +13,21 @@ public class ReadAllEventsBackwardTests(ITestOutputHelper output, ReadAllEventsF : KurrentTemporaryTests(output, fixture) { [Fact] public async Task return_empty_if_reading_from_start() { - var result = await Fixture.Streams.ReadAllAsync(Direction.Backwards, Position.Start, 1).CountAsync(); + var result = await Fixture.Streams.ReadAllAsync( + new ReadAllOptions { Direction = Direction.Backwards, Position = Position.Start, MaxCount = 1 } + ).CountAsync(); + result.ShouldBe(0); } [Fact] public async Task return_partial_slice_if_not_enough_events() { - var count = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + var count = await Fixture.Streams.ReadAllAsync().CountAsync(); var sliceSize = count * 2; - var result = await Fixture.Streams.ReadAllAsync(Direction.Backwards, Position.End, sliceSize).ToArrayAsync(); + var result = await Fixture.Streams.ReadAllAsync( + new ReadAllOptions { Direction = Direction.Backwards, Position = Position.End, MaxCount = sliceSize } + ).ToArrayAsync(); + result.Length.ShouldBeLessThan(sliceSize); } @@ -29,7 +35,7 @@ public async Task return_partial_slice_if_not_enough_events() { public async Task return_events_in_reversed_order_compared_to_written() { // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End) + .ReadAllAsync(new ReadAllOptions().Backwards().FromEnd()) .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .ToBinaryData(); @@ -39,7 +45,7 @@ public async Task return_events_in_reversed_order_compared_to_written() { [Fact] public async Task return_single_event() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End, 1) + .ReadAllAsync(new ReadAllOptions().Last()) .ToArrayAsync(); result.ShouldHaveSingleItem(); @@ -49,7 +55,7 @@ public async Task return_single_event() { public async Task max_count_is_respected() { var maxCount = Fixture.ExpectedEvents.Length / 2; var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End, maxCount) + .ReadAllAsync(new ReadAllOptions().Backwards().FromEnd().WithMaxCount(maxCount)) .Take(Fixture.ExpectedEvents.Length) .ToArrayAsync(); @@ -59,7 +65,7 @@ public async Task max_count_is_respected() { [Fact] public async Task stream_found() { var count = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End) + .ReadAllAsync(new ReadAllOptions().Backwards().FromEnd()) .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .CountAsync(); @@ -69,7 +75,14 @@ public async Task stream_found() { [Fact] public async Task with_timeout_fails_when_operation_expired() { var ex = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.Start, 1, false, TimeSpan.Zero) + .ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.Start, + MaxCount = 1, + Deadline = TimeSpan.Zero + } + ) .ToArrayAsync() .AsTask().ShouldThrowAsync(); @@ -79,7 +92,11 @@ public async Task with_timeout_fails_when_operation_expired() { [Fact] public async Task filter_events_by_type() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End, EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)) + .ReadAllAsync( + new ReadAllOptions().Backwards().FromEnd().WithFilter( + EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix) + ) + ) .ToListAsync(); result.ForEach(x => x.Event.EventType.ShouldStartWith(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)); diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs index fa1dd6046..2fe0fbc18 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs @@ -9,17 +9,25 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Forwards")] [Trait("Category", "Database:Dedicated")] -public class ReadAllEventsForwardTests(ITestOutputHelper output, ReadAllEventsFixture fixture) : KurrentTemporaryTests(output, fixture) { +public class ReadAllEventsForwardTests(ITestOutputHelper output, ReadAllEventsFixture fixture) + : KurrentTemporaryTests(output, fixture) { [Fact] public async Task return_empty_if_reading_from_end() { - var result = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.End, 1).CountAsync(); + var result = await Fixture.Streams.ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Forwards, + Position = Position.End, + MaxCount = 1 + } + ).CountAsync(); + result.ShouldBe(0); } [Fact] public async Task return_partial_slice_if_not_enough_events() { var sliceSize = Fixture.ExpectedEvents.Length * 2; - var result = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start, sliceSize).ToArrayAsync(); + var result = await Fixture.Streams.ReadAllAsync(new ReadAllOptions { MaxCount = sliceSize }).ToArrayAsync(); result.Length.ShouldBeLessThan(sliceSize); } @@ -27,7 +35,7 @@ public async Task return_partial_slice_if_not_enough_events() { public async Task return_events_in_correct_order_compared_to_written() { // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start) + .ReadAllAsync() .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .ToBinaryData(); @@ -37,7 +45,7 @@ public async Task return_events_in_correct_order_compared_to_written() { [Fact] public async Task return_single_event() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 1) + .ReadAllAsync(new ReadAllOptions { MaxCount = 1 }) .ToArrayAsync(); result.ShouldHaveSingleItem(); @@ -47,7 +55,7 @@ public async Task return_single_event() { public async Task max_count_is_respected() { var maxCount = Fixture.ExpectedEvents.Length / 2; var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, maxCount) + .ReadAllAsync(new ReadAllOptions { MaxCount = maxCount }) .Take(Fixture.ExpectedEvents.Length) .ToArrayAsync(); @@ -57,7 +65,7 @@ public async Task max_count_is_respected() { [Fact] public async Task reads_all_events_by_default() { var count = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start) + .ReadAllAsync() .CountAsync(); Assert.True(count >= Fixture.ExpectedEvents.Length); @@ -66,7 +74,7 @@ public async Task reads_all_events_by_default() { [Fact] public async Task stream_found() { var count = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start) + .ReadAllAsync() .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .CountAsync(); @@ -102,10 +110,8 @@ await Fixture.Streams.AppendToStreamAsync( ); var events = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, linkedStream, - StreamPosition.Start, - resolveLinkTos: true + new ReadStreamOptions { ResolveLinkTos = true } ) .ToArrayAsync(); @@ -115,10 +121,7 @@ await Fixture.Streams.AppendToStreamAsync( [Fact] public async Task enumeration_all_referencing_messages_twice_does_not_throw() { var result = Fixture.Streams.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root + new ReadAllOptions { MaxCount = 32, UserCredentials = TestCredentials.Root } ); _ = result.Messages; @@ -128,10 +131,7 @@ public async Task enumeration_all_referencing_messages_twice_does_not_throw() { [Fact] public async Task enumeration_all_enumerating_messages_twice_throws() { var result = Fixture.Streams.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root + new ReadAllOptions { MaxCount = 32, UserCredentials = TestCredentials.Root } ); await result.Messages.ToArrayAsync(); @@ -145,7 +145,12 @@ await result.Messages.ToArrayAsync() [Fact] public async Task filter_events_by_type() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)) + .ReadAllAsync( + new ReadAllOptions + { + Filter = EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix) + } + ) .ToListAsync(); result.ForEach(x => x.Event.EventType.ShouldStartWith(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)); diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs index 772a0190b..b9ba0fb06 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs @@ -17,11 +17,18 @@ public async Task count_le_equal_zero_throws(long maxCount) { var ex = await Assert.ThrowsAsync( () => - Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, maxCount) + Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.Start, + MaxCount = maxCount + } + ) .ToArrayAsync().AsTask() ); - Assert.Equal(nameof(maxCount), ex.ParamName); + Assert.Equal(nameof(ReadStreamOptions.MaxCount), ex.ParamName); } [Fact] @@ -30,7 +37,7 @@ public async Task stream_does_not_exist_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .ToArrayAsync().AsTask() ); @@ -41,7 +48,7 @@ public async Task stream_does_not_exist_throws() { public async Task stream_does_not_exist_can_be_checked() { var stream = Fixture.GetStreamName(); - var result = Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1); + var result = Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().Last()); var state = await result.ReadState; Assert.Equal(ReadState.StreamNotFound, state); @@ -55,7 +62,7 @@ public async Task stream_deleted_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .ToArrayAsync().AsTask() ); @@ -73,7 +80,7 @@ public async Task returns_events_in_reversed_order(string suffix, int count, int await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var actual = await Fixture.Streams - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, expected.Length) + .ReadStreamAsync(stream, new ReadStreamOptions().FromEnd().Backwards().WithMaxCount(expected.Length)) .Select(x => x.Event).ToArrayAsync(); Assert.True( @@ -93,7 +100,8 @@ public async Task be_able_to_read_single_event_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, new(7), 1) + var actual = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().From(new(7)).Backwards().MaxOne()) .Select(x => x.Event) .SingleAsync(); @@ -108,7 +116,8 @@ public async Task be_able_to_read_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, new(3), 2) + var actual = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).Backwards().WithMaxCount(2)) .Select(x => x.Event) .ToArrayAsync(); @@ -123,7 +132,8 @@ public async Task be_able_to_read_first_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, 1) + var events = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().Backwards().FromStart().WithMaxCount(1)) .Select(x => x.Event) .ToArrayAsync(); @@ -138,7 +148,8 @@ public async Task be_able_to_read_last_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + var events = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .Select(x => x.Event) .ToArrayAsync(); @@ -160,7 +171,7 @@ await Fixture.Streams.AppendToStreamAsync( ); var events = await Fixture.Streams - .ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, maxCount) + .ReadStreamAsync(streamName, new ReadStreamOptions().Backwards().FromEnd().WithMaxCount(maxCount)) .Take(count) .ToArrayAsync(); @@ -177,7 +188,8 @@ public async Task populates_log_position_of_event() { var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + var actual = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .Select(x => x.Event) .ToArrayAsync(); @@ -194,14 +206,7 @@ public async Task with_timeout_fails_when_operation_expired() { var rpcException = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync( - Direction.Backwards, - stream, - StreamPosition.End, - 1, - false, - TimeSpan.Zero - ) + .ReadStreamAsync(stream, new ReadStreamOptions { Deadline = TimeSpan.Zero }.Last()) .ToArrayAsync().AsTask() ); @@ -211,11 +216,8 @@ public async Task with_timeout_fails_when_operation_expired() { [Fact] public async Task enumeration_referencing_messages_twice_does_not_throw() { var result = Fixture.Streams.ReadStreamAsync( - Direction.Forwards, "$dbUsers", - StreamPosition.Start, - 32, - userCredentials: TestCredentials.Root + new ReadStreamOptions { UserCredentials = TestCredentials.Root, MaxCount = 32 } ); _ = result.Messages; @@ -225,11 +227,8 @@ public async Task enumeration_referencing_messages_twice_does_not_throw() { [Fact] public async Task enumeration_enumerating_messages_twice_throws() { var result = Fixture.Streams.ReadStreamAsync( - Direction.Forwards, "$dbUsers", - StreamPosition.Start, - 32, - userCredentials: TestCredentials.Root + new ReadStreamOptions { UserCredentials = TestCredentials.Root, MaxCount = 32 } ); await result.Messages.ToArrayAsync(); @@ -242,11 +241,9 @@ await result.Messages.ToArrayAsync() [Fact] public async Task stream_not_found() { - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Backwards, - Fixture.GetStreamName(), - StreamPosition.End - ).Messages.SingleAsync(); + var result = await Fixture.Streams + .ReadStreamAsync(Fixture.GetStreamName(), new ReadStreamOptions().Backwards().FromEnd()).Messages + .SingleAsync(); Assert.Equal(StreamMessage.NotFound.Instance, result); } @@ -262,9 +259,8 @@ public async Task stream_found() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); var result = await Fixture.Streams.ReadStreamAsync( - Direction.Backwards, streamName, - StreamPosition.End + new ReadStreamOptions().Backwards().FromEnd() ).Messages.ToArrayAsync(); Assert.Equal( diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs index 644744338..9d853880a 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs @@ -27,14 +27,16 @@ public abstract class ReadStreamEventsLinkedToDeletedStreamTests(ReadEventsLinke [UsedImplicitly] [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Forwards")] - public class Forwards(Forwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), IClassFixture { + public class Forwards(Forwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), + IClassFixture { public class CustomFixture() : ReadEventsLinkedToDeletedStreamFixture(Direction.Forwards); } [UsedImplicitly] [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Backwards")] - public class Backwards(Backwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), IClassFixture { + public class Backwards(Backwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), + IClassFixture { public class CustomFixture() : ReadEventsLinkedToDeletedStreamFixture(Direction.Backwards); } } @@ -63,11 +65,12 @@ await Streams.AppendToStreamAsync( await Streams.DeleteAsync(DeletedStream, StreamState.Any); Events = await Streams.ReadStreamAsync( - direction, LinkedStream, - StreamPosition.Start, - 1, - true + new ReadStreamOptions { + Direction = direction, + MaxCount = 1, + ResolveLinkTos = true + } ).ToArrayAsync(); }; } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs index c0a632cda..07abfbbbc 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs @@ -1,12 +1,11 @@ -using KurrentDB.Client; - namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Streams")] [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Forwards")] -public class ReadStreamForwardTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class ReadStreamForwardTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Theory] [InlineData(0)] public async Task count_le_equal_zero_throws(long maxCount) { @@ -14,11 +13,11 @@ public async Task count_le_equal_zero_throws(long maxCount) { var ex = await Assert.ThrowsAsync( () => - Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, maxCount) + Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().WithMaxCount(maxCount)) .ToArrayAsync().AsTask() ); - Assert.Equal(nameof(maxCount), ex.ParamName); + Assert.Equal(nameof(ReadStreamOptions.MaxCount), ex.ParamName); } [Fact] @@ -27,7 +26,7 @@ public async Task stream_does_not_exist_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .ToArrayAsync().AsTask() ); @@ -38,7 +37,7 @@ public async Task stream_does_not_exist_throws() { public async Task stream_does_not_exist_can_be_checked() { var stream = Fixture.GetStreamName(); - var result = Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1); + var result = Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()); var state = await result.ReadState; Assert.Equal(ReadState.StreamNotFound, state); @@ -52,7 +51,7 @@ public async Task stream_deleted_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .ToArrayAsync().AsTask() ); @@ -70,7 +69,7 @@ public async Task returns_events_in_order(string suffix, int count, int metadata await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var actual = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, expected.Length) + .ReadStreamAsync(stream, new ReadStreamOptions().WithMaxCount(expected.Length)) .Select(x => x.Event).ToArrayAsync(); Assert.True(MessageDataComparer.Equal(expected, actual)); @@ -86,7 +85,7 @@ public async Task be_able_to_read_single_event_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, new(7), 1) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().From(new(7)).MaxOne()) .Select(x => x.Event) .SingleAsync(); @@ -101,7 +100,7 @@ public async Task be_able_to_read_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, new(3), 2) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).WithMaxCount(2)) .Select(x => x.Event) .ToArrayAsync(); @@ -116,7 +115,7 @@ public async Task be_able_to_read_first_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + var events = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .Select(x => x.Event) .ToArrayAsync(); @@ -136,7 +135,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(count) ); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start, maxCount) + var events = await Fixture.Streams.ReadStreamAsync(streamName, new ReadStreamOptions().WithMaxCount(maxCount)) .Take(count) .ToArrayAsync(); @@ -154,7 +153,7 @@ await Fixture.Streams.AppendToStreamAsync( ); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start) + .ReadStreamAsync(streamName) .CountAsync(); Assert.True(count == maxCount); @@ -171,7 +170,7 @@ public async Task populates_log_position_of_event() { var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .Select(x => x.Event) .ToArrayAsync(); @@ -182,11 +181,7 @@ public async Task populates_log_position_of_event() { [Fact] public async Task stream_not_found() { - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, - Fixture.GetStreamName(), - StreamPosition.Start - ).Messages.SingleAsync(); + var result = await Fixture.Streams.ReadStreamAsync(Fixture.GetStreamName()).Messages.SingleAsync(); Assert.Equal(StreamMessage.NotFound.Instance, result); } @@ -201,11 +196,7 @@ public async Task stream_found() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, - streamName, - StreamPosition.Start - ).Messages.ToArrayAsync(); + var result = await Fixture.Streams.ReadStreamAsync(streamName).Messages.ToArrayAsync(); Assert.Equal( eventCount + (Fixture.EventStoreHasLastStreamPosition ? 2 : 1), @@ -242,11 +233,7 @@ await Fixture.Streams.SetStreamMetadataAsync( new(truncateBefore: new StreamPosition(32)) ); - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, - streamName, - StreamPosition.Start - ).Messages.ToArrayAsync(); + var result = await Fixture.Streams.ReadStreamAsync(streamName).Messages.ToArrayAsync(); Assert.Equal( eventCount - 32 + (Fixture.EventStoreHasLastStreamPosition ? 3 : 1), diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs index 0b588cb7d..b5b006b56 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs @@ -19,7 +19,7 @@ public async Task read_stream_forwards_respects_max_count() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 100 }) .Select(x => x.Event) .ToArrayAsync(); @@ -37,7 +37,14 @@ public async Task read_stream_backwards_respects_max_count() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + var actual = await Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 100 + } + ) .Select(x => x.Event) .ToArrayAsync(); @@ -57,7 +64,7 @@ public async Task after_setting_less_strict_max_count_read_stream_forward_reads_ await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(4)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 100 }) .Select(x => x.Event) .ToArrayAsync(); @@ -77,7 +84,7 @@ public async Task after_setting_more_strict_max_count_read_stream_forward_reads_ await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(2)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 100 }) .Select(x => x.Event) .ToArrayAsync(); @@ -97,7 +104,14 @@ public async Task after_setting_less_strict_max_count_read_stream_backwards_read await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(4)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + var actual = await Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 100 + } + ) .Select(x => x.Event) .ToArrayAsync(); @@ -117,7 +131,14 @@ public async Task after_setting_more_strict_max_count_read_stream_backwards_read await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(2)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + var actual = await Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 100 + } + ) .Select(x => x.Event) .ToArrayAsync(); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index d41bfa79d..af87e6fd2 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -40,12 +40,12 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s .ReadStreamAsync(stream) .DeserializedMessages() .ToListAsync(); - + List deserializedDomainMessages = await Fixture.Streams .ReadStreamAsync(stream) .DeserializedData() .ToListAsync(); - + Assert.Equal(messages, deserializedMessages); Assert.Equal(domainMessages, deserializedDomainMessages); } @@ -97,8 +97,29 @@ public async Task read_stream_without_options_does_NOT_deserialize_resolved_mess var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Streams .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) +#pragma warning restore CS0618 // Type or member is obsolete + .ToListAsync(); + + // Then + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); + } + + [RetryFact] + public async Task read_stream_with_disabled_autoserialization_does_NOT_deserialize_resolved_message() { + // Given + var (stream, expected) = await AppendEventsUsingAutoSerialization(); + + // When + var resolvedEvents = await Fixture.Streams + .ReadStreamAsync( + stream, + new ReadStreamOptions { + SerializationSettings = OperationSerializationSettings.Disabled + } + ) .ToListAsync(); // Then @@ -106,13 +127,18 @@ public async Task read_stream_without_options_does_NOT_deserialize_resolved_mess } [RetryFact] - public async Task read_all_without_options_does_NOT_deserialize_resolved_message() { + public async Task read_all_with_disabled_autoserialization_does_NOT_deserialize_resolved_message() { // Given var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When var resolvedEvents = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, StreamFilter.Prefix(stream)) + .ReadAllAsync( + new ReadAllOptions { + Filter = StreamFilter.Prefix(stream), + SerializationSettings = OperationSerializationSettings.Disabled + } + ) .ToListAsync(); // Then diff --git a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs index 4cd941665..8c80afe61 100644 --- a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs @@ -34,7 +34,7 @@ public async Task reading_throws() { await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamState); await Assert.ThrowsAsync( - () => Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + () => Fixture.Streams.ReadStreamAsync(stream) .ToArrayAsync().AsTask() ); } @@ -61,7 +61,7 @@ public async Task recreated_with_any_expected_version(StreamState expectedState, await Task.Delay(50); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); @@ -103,7 +103,7 @@ public async Task recreated_with_expected_version() { await Task.Delay(50); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); @@ -154,7 +154,7 @@ public async Task recreated_preserves_metadata_except_truncate_before() { await Task.Delay(500); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); @@ -197,7 +197,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.TombstoneAsync(stream, StreamState.Any); var ex = await Assert.ThrowsAsync( - () => Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + () => Fixture.Streams.ReadStreamAsync(stream) .ToArrayAsync().AsTask() ); @@ -304,7 +304,7 @@ await Fixture.Streams.AppendToStreamAsync( Assert.Equal(new(6), writeResult.NextExpectedStreamState); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); @@ -347,7 +347,7 @@ public async Task recreated_on_empty_when_metadata_set() { await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + .ReadStreamAsync(stream) .ToArrayAsync().AsTask() ); @@ -404,7 +404,7 @@ public async Task recreated_on_non_empty_when_metadata_set() { await Task.Delay(200); var actual = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + .ReadStreamAsync(stream) .ToArrayAsync(); Assert.Empty(actual); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs index c4b48cc28..58223aac9 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs @@ -203,7 +203,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(3) ); - var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + var existingEventsCount = await Fixture.Streams.ReadAllAsync() .Messages.CountAsync(); Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); @@ -290,7 +290,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(3) ); - var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + var existingEventsCount = await Fixture.Streams.ReadAllAsync() .Messages.CountAsync(); Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); @@ -377,7 +377,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(3) ); - var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + var existingEventsCount = await Fixture.Streams.ReadAllAsync() .Messages.CountAsync(); Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); From 8c6e39fa2b71146cfdcdb20f27c7d8ca3b71551c Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 20 Mar 2025 15:11:56 +0100 Subject: [PATCH 11/23] [DEVEX-222] Made Read options nullable, to precisely know if user provided options or not --- .../Streams/KurrentDBClient.Read.cs | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index 74119251e..965fca510 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -28,16 +28,17 @@ public ReadAllStreamResult ReadAllAsync( ReadDirection = options.Direction switch { Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - _ => throw InvalidOption(options.Direction) + null => ReadReq.Types.Options.Types.ReadDirection.Forwards, + _ => throw InvalidOption(options.Direction.Value) }, - ResolveLinks = options.ResolveLinkTos, + ResolveLinks = options.ResolveLinkTos ?? false, All = new() { Position = new() { - CommitPosition = options.Position.CommitPosition, - PreparePosition = options.Position.PreparePosition + CommitPosition = (options.Position ?? Position.Start).CommitPosition, + PreparePosition = (options.Position ?? Position.Start).PreparePosition } }, - Count = (ulong)options.MaxCount, + Count = (ulong)(options.MaxCount ?? long.MaxValue), UuidOption = new() { Structured = new() }, ControlOption = new() { Compatibility = 1 }, Filter = GetFilterOptions(options.Filter) @@ -213,14 +214,15 @@ public ReadStreamResult ReadStreamAsync( ReadDirection = options.Direction switch { Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - _ => throw InvalidOption(options.Direction) + null => ReadReq.Types.Options.Types.ReadDirection.Forwards, + _ => throw InvalidOption(options.Direction.Value) }, - ResolveLinks = options.ResolveLinkTos, + ResolveLinks = options.ResolveLinkTos ?? false, Stream = ReadReq.Types.Options.Types.StreamOptions.FromStreamNameAndRevision( streamName, - options.StreamPosition + options.StreamPosition ?? StreamPosition.Start ), - Count = (ulong)options.MaxCount, + Count = (ulong)(options.MaxCount ?? long.MaxValue), UuidOption = new() { Structured = new() }, NoFilter = new(), ControlOption = new() { Compatibility = 1 } @@ -443,14 +445,14 @@ IMessageSerializer messageSerializer /// public class ReadAllOptions : OperationOptions { /// - /// The in which to read. + /// The in which to read. When not provided Forwards is used. /// - public Direction Direction { get; set; } = Direction.Forwards; + public Direction? Direction { get; set; } /// - /// The to start reading from. + /// The to start reading from. When not provided Start is used. /// - public Position Position { get; set; } = Position.Start; + public Position? Position { get; set; } /// /// The to apply. @@ -458,14 +460,14 @@ public class ReadAllOptions : OperationOptions { public IEventFilter? Filter { get; set; } /// - /// The number of events to read from the stream. + /// The number of events to read from the stream. When not provided, no limit is set. /// - public long MaxCount { get; set; } = long.MaxValue; + public long? MaxCount { get; set; } /// - /// Whether to resolve LinkTo events automatically. + /// Whether to resolve LinkTo events automatically. When not provided, false is used. /// - public bool ResolveLinkTos { get; set; } + public bool? ResolveLinkTos { get; set; } /// /// Allows to customize or disable the automatic deserialization @@ -476,7 +478,7 @@ public static ReadAllOptions Get() => new ReadAllOptions(); public ReadAllOptions Forwards() { - Direction = Direction.Forwards; + Direction = KurrentDB.Client.Direction.Forwards; return this; } @@ -488,7 +490,7 @@ public ReadAllOptions WithFilter(IEventFilter filter) { } public ReadAllOptions Backwards() { - Direction = Direction.Backwards; + Direction = KurrentDB.Client.Direction.Backwards; return this; } @@ -500,10 +502,10 @@ public ReadAllOptions From(Position streamPosition) { } public ReadAllOptions FromStart() => - From(Position.Start); + From(KurrentDB.Client.Position.Start); public ReadAllOptions FromEnd() => - From(Position.End); + From(KurrentDB.Client.Position.End); public ReadAllOptions WithMaxCount(long maxCount) { MaxCount = maxCount; @@ -539,22 +541,22 @@ public class ReadStreamOptions : OperationOptions { /// /// The in which to read. /// - public Direction Direction { get; set; } = Direction.Forwards; + public Direction? Direction { get; set; } /// /// The to start reading from. /// - public StreamPosition StreamPosition { get; set; } = StreamPosition.Start; + public StreamPosition? StreamPosition { get; set; } /// /// The number of events to read from the stream. /// - public long MaxCount { get; set; } = long.MaxValue; + public long? MaxCount { get; set; } /// /// Whether to resolve LinkTo events automatically. /// - public bool ResolveLinkTos { get; set; } + public bool? ResolveLinkTos { get; set; } /// /// Allows to customize or disable the automatic deserialization @@ -565,13 +567,13 @@ public static ReadStreamOptions Get() => new ReadStreamOptions(); public ReadStreamOptions Forwards() { - Direction = Direction.Forwards; + Direction = KurrentDB.Client.Direction.Forwards; return this; } public ReadStreamOptions Backwards() { - Direction = Direction.Backwards; + Direction = KurrentDB.Client.Direction.Backwards; return this; } @@ -583,10 +585,10 @@ public ReadStreamOptions From(StreamPosition streamPosition) { } public ReadStreamOptions FromStart() => - From(StreamPosition.Start); + From(KurrentDB.Client.StreamPosition.Start); public ReadStreamOptions FromEnd() => - From(StreamPosition.End); + From(KurrentDB.Client.StreamPosition.End); public ReadStreamOptions WithMaxCount(long maxCount) { MaxCount = maxCount; From 3bf1ced76572512065f820bbc2a88cf4ccaeb480 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 21 Mar 2025 13:06:08 +0100 Subject: [PATCH 12/23] [DEVEX-222] Made the Read Options nullable and thanks to that smarter, so e.g. FromEnd assigns also backwards if it wasn't set --- .../Streams/KurrentDBClient.Read.cs | 78 +++++++++++++------ .../Read/ReadAllEventsBackwardTests.cs | 8 +- .../Streams/Read/ReadStreamBackwardTests.cs | 12 +-- .../Streams/Read/ReadStreamForwardTests.cs | 8 +- 4 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index 965fca510..400de7ade 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -28,7 +28,7 @@ public ReadAllStreamResult ReadAllAsync( ReadDirection = options.Direction switch { Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - null => ReadReq.Types.Options.Types.ReadDirection.Forwards, + null => ReadReq.Types.Options.Types.ReadDirection.Forwards, _ => throw InvalidOption(options.Direction.Value) }, ResolveLinks = options.ResolveLinkTos ?? false, @@ -477,44 +477,60 @@ public class ReadAllOptions : OperationOptions { public static ReadAllOptions Get() => new ReadAllOptions(); - public ReadAllOptions Forwards() { - Direction = KurrentDB.Client.Direction.Forwards; + public ReadAllOptions WithFilter(IEventFilter filter) { + Filter = filter; return this; } - public ReadAllOptions WithFilter(IEventFilter filter) { - Filter = filter; + public ReadAllOptions Forwards() { + Direction = KurrentDB.Client.Direction.Forwards; + Position ??= KurrentDB.Client.Position.Start; return this; } public ReadAllOptions Backwards() { - Direction = KurrentDB.Client.Direction.Backwards; + Direction = KurrentDB.Client.Direction.Backwards; + Position ??= KurrentDB.Client.Position.End; + + return this; + } + + public ReadAllOptions From(Position position) { + this.Position = position; return this; } - public ReadAllOptions From(Position streamPosition) { - Position = streamPosition; + public ReadAllOptions FromStart() { + Position = KurrentDB.Client.Position.Start; + Direction ??= Client.Direction.Forwards; return this; } - public ReadAllOptions FromStart() => - From(KurrentDB.Client.Position.Start); + public ReadAllOptions FromEnd() { + Position = KurrentDB.Client.Position.End; + Direction ??= Client.Direction.Backwards; - public ReadAllOptions FromEnd() => - From(KurrentDB.Client.Position.End); + return this; + } + + public ReadAllOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; - public ReadAllOptions WithMaxCount(long maxCount) { + return this; + } + + public ReadAllOptions Max(long maxCount) { MaxCount = maxCount; return this; } public ReadAllOptions MaxOne() => - WithMaxCount(1); + Max(1); public ReadAllOptions First() => FromStart() @@ -525,7 +541,7 @@ public ReadAllOptions Last() => FromEnd() .Backwards() .MaxOne(); - + public ReadAllOptions DisableAutoSerialization() { SerializationSettings = OperationSerializationSettings.Disabled; @@ -567,13 +583,15 @@ public static ReadStreamOptions Get() => new ReadStreamOptions(); public ReadStreamOptions Forwards() { - Direction = KurrentDB.Client.Direction.Forwards; + Direction = KurrentDB.Client.Direction.Forwards; + StreamPosition ??= KurrentDB.Client.StreamPosition.Start; return this; } public ReadStreamOptions Backwards() { - Direction = KurrentDB.Client.Direction.Backwards; + Direction = KurrentDB.Client.Direction.Backwards; + StreamPosition ??= KurrentDB.Client.StreamPosition.End; return this; } @@ -584,20 +602,34 @@ public ReadStreamOptions From(StreamPosition streamPosition) { return this; } - public ReadStreamOptions FromStart() => - From(KurrentDB.Client.StreamPosition.Start); + public ReadStreamOptions FromStart() { + StreamPosition = KurrentDB.Client.StreamPosition.Start; + Direction ??= Client.Direction.Forwards; + + return this; + } + + public ReadStreamOptions FromEnd() { + StreamPosition = KurrentDB.Client.StreamPosition.End; + Direction ??= Client.Direction.Backwards; - public ReadStreamOptions FromEnd() => - From(KurrentDB.Client.StreamPosition.End); + return this; + } + + public ReadStreamOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } - public ReadStreamOptions WithMaxCount(long maxCount) { + public ReadStreamOptions Max(long maxCount) { MaxCount = maxCount; return this; } public ReadStreamOptions MaxOne() => - WithMaxCount(1); + Max(1); public ReadStreamOptions First() => FromStart() diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs index 51998295a..0c686ed8e 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs @@ -35,7 +35,7 @@ public async Task return_partial_slice_if_not_enough_events() { public async Task return_events_in_reversed_order_compared_to_written() { // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... var result = await Fixture.Streams - .ReadAllAsync(new ReadAllOptions().Backwards().FromEnd()) + .ReadAllAsync(new ReadAllOptions().FromEnd()) .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .ToBinaryData(); @@ -55,7 +55,7 @@ public async Task return_single_event() { public async Task max_count_is_respected() { var maxCount = Fixture.ExpectedEvents.Length / 2; var result = await Fixture.Streams - .ReadAllAsync(new ReadAllOptions().Backwards().FromEnd().WithMaxCount(maxCount)) + .ReadAllAsync(new ReadAllOptions().FromEnd().Max(maxCount)) .Take(Fixture.ExpectedEvents.Length) .ToArrayAsync(); @@ -65,7 +65,7 @@ public async Task max_count_is_respected() { [Fact] public async Task stream_found() { var count = await Fixture.Streams - .ReadAllAsync(new ReadAllOptions().Backwards().FromEnd()) + .ReadAllAsync(new ReadAllOptions().FromEnd()) .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .CountAsync(); @@ -93,7 +93,7 @@ public async Task with_timeout_fails_when_operation_expired() { public async Task filter_events_by_type() { var result = await Fixture.Streams .ReadAllAsync( - new ReadAllOptions().Backwards().FromEnd().WithFilter( + new ReadAllOptions().FromEnd().WithFilter( EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix) ) ) diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs index b9ba0fb06..8f803ae04 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs @@ -80,7 +80,7 @@ public async Task returns_events_in_reversed_order(string suffix, int count, int await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var actual = await Fixture.Streams - .ReadStreamAsync(stream, new ReadStreamOptions().FromEnd().Backwards().WithMaxCount(expected.Length)) + .ReadStreamAsync(stream, new ReadStreamOptions().FromEnd().Max(expected.Length)) .Select(x => x.Event).ToArrayAsync(); Assert.True( @@ -117,7 +117,7 @@ public async Task be_able_to_read_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); var actual = await Fixture.Streams - .ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).Backwards().WithMaxCount(2)) + .ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).Backwards().Max(2)) .Select(x => x.Event) .ToArrayAsync(); @@ -133,7 +133,7 @@ public async Task be_able_to_read_first_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); var events = await Fixture.Streams - .ReadStreamAsync(stream, new ReadStreamOptions().Backwards().FromStart().WithMaxCount(1)) + .ReadStreamAsync(stream, new ReadStreamOptions().Backwards().FromStart().Max(1)) .Select(x => x.Event) .ToArrayAsync(); @@ -171,7 +171,7 @@ await Fixture.Streams.AppendToStreamAsync( ); var events = await Fixture.Streams - .ReadStreamAsync(streamName, new ReadStreamOptions().Backwards().FromEnd().WithMaxCount(maxCount)) + .ReadStreamAsync(streamName, new ReadStreamOptions().FromEnd().Max(maxCount)) .Take(count) .ToArrayAsync(); @@ -242,7 +242,7 @@ await result.Messages.ToArrayAsync() [Fact] public async Task stream_not_found() { var result = await Fixture.Streams - .ReadStreamAsync(Fixture.GetStreamName(), new ReadStreamOptions().Backwards().FromEnd()).Messages + .ReadStreamAsync(Fixture.GetStreamName(), new ReadStreamOptions().FromEnd()).Messages .SingleAsync(); Assert.Equal(StreamMessage.NotFound.Instance, result); @@ -260,7 +260,7 @@ public async Task stream_found() { var result = await Fixture.Streams.ReadStreamAsync( streamName, - new ReadStreamOptions().Backwards().FromEnd() + new ReadStreamOptions().FromEnd() ).Messages.ToArrayAsync(); Assert.Equal( diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs index 07abfbbbc..08bfc2753 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs @@ -13,7 +13,7 @@ public async Task count_le_equal_zero_throws(long maxCount) { var ex = await Assert.ThrowsAsync( () => - Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().WithMaxCount(maxCount)) + Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().Max(maxCount)) .ToArrayAsync().AsTask() ); @@ -69,7 +69,7 @@ public async Task returns_events_in_order(string suffix, int count, int metadata await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var actual = await Fixture.Streams - .ReadStreamAsync(stream, new ReadStreamOptions().WithMaxCount(expected.Length)) + .ReadStreamAsync(stream, new ReadStreamOptions().Max(expected.Length)) .Select(x => x.Event).ToArrayAsync(); Assert.True(MessageDataComparer.Equal(expected, actual)); @@ -100,7 +100,7 @@ public async Task be_able_to_read_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).WithMaxCount(2)) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).Max(2)) .Select(x => x.Event) .ToArrayAsync(); @@ -135,7 +135,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(count) ); - var events = await Fixture.Streams.ReadStreamAsync(streamName, new ReadStreamOptions().WithMaxCount(maxCount)) + var events = await Fixture.Streams.ReadStreamAsync(streamName, new ReadStreamOptions().Max(maxCount)) .Take(count) .ToArrayAsync(); From 842065c335589f9a126159ed8a2963b1d7943b83 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 21 Mar 2025 13:38:28 +0100 Subject: [PATCH 13/23] [DEVEX-222] Made old SetStreamMetadata and ConditionalAppend methods obsolete --- .../Streams/KurrentDBClientExtensions.cs | 11 +++- .../Security/MultipleRoleSecurityTests.cs | 30 ++++++--- ...verridenSystemStreamSecurityForAllTests.cs | 10 ++- .../OverridenSystemStreamSecurityTests.cs | 19 ++++-- .../OverridenUserStreamSecurityTests.cs | 23 +++++-- .../StreamSecurityInheritanceTests.cs | 66 ++++++++++++++----- 6 files changed, 120 insertions(+), 39 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs index cb4e168b8..5c890b3eb 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs @@ -52,6 +52,10 @@ public static Task SetSystemSettingsAsync( /// /// /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SetSystemSettingsOptions options", + false + )] public static Task SetSystemSettingsAsync( this KurrentDBClient dbClient, SystemSettings settings, @@ -118,13 +122,16 @@ public static async Task ConditionalAppendToStreamAsync( /// /// /// + + [Obsolete( + "This method may be removed in future releases. Use the overload with AppendToStreamOptions options", + false + )] public static Task ConditionalAppendToStreamAsync( this KurrentDBClient dbClient, string streamName, StreamState expectedState, -#pragma warning disable CS0618 // Type or member is obsolete IEnumerable eventData, -#pragma warning restore CS0618 // Type or member is obsolete TimeSpan? deadline = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default diff --git a/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs index 8d30f2f8a..103e40dc9 100644 --- a/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs @@ -10,13 +10,24 @@ public class MultipleRoleSecurityTests(ITestOutputHelper output, MultipleRoleSec [Fact] public async Task multiple_roles_are_handled_correctly() { await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream")); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestAdmin)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser2) + ); + + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("usr-stream", TestCredentials.TestAdmin) + ); await Assert.ThrowsAsync(() => Fixture.AppendStream("usr-stream")); await Fixture.AppendStream("usr-stream", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.AppendStream("usr-stream", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("usr-stream", TestCredentials.TestUser2) + ); + await Fixture.AppendStream("usr-stream", TestCredentials.TestAdmin); await Fixture.DeleteStream("usr-stream2", TestCredentials.TestUser1); @@ -32,13 +43,16 @@ public class CustomFixture : SecurityFixture { protected override async Task When() { var settings = new SystemSettings( new( - new[] { "user1", "user2" }, - new[] { "$admins", "user1" }, - new[] { "user1", SystemRoles.All } + ["user1", "user2"], + ["$admins", "user1"], + ["user1", SystemRoles.All] ) ); - await Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + await Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs index a3d5f6900..8b11c9854 100644 --- a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs +++ b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs @@ -5,7 +5,10 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class OverridenSystemStreamSecurityForAllTests(ITestOutputHelper output, OverridenSystemStreamSecurityForAllTests.CustomFixture fixture) +public class OverridenSystemStreamSecurityForAllTests( + ITestOutputHelper output, + OverridenSystemStreamSecurityForAllTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task operations_on_system_stream_succeeds_for_user() { @@ -68,7 +71,10 @@ protected override Task When() { ) ); - return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs index f4875fccd..098db5cdf 100644 --- a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class OverridenSystemStreamSecurityTests(ITestOutputHelper output, OverridenSystemStreamSecurityTests.CustomFixture fixture) +public class OverridenSystemStreamSecurityTests( + ITestOutputHelper output, + OverridenSystemStreamSecurityTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task operations_on_system_stream_succeed_for_authorized_user() { @@ -27,7 +30,10 @@ public async Task operations_on_system_stream_succeed_for_authorized_user() { public async Task operations_on_system_stream_fail_for_not_authorized_user() { var stream = $"${Fixture.GetStreamName()}"; await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2) + ); + await Assert.ThrowsAsync( () => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2) @@ -38,7 +44,9 @@ await Assert.ThrowsAsync( await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2) + ); await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream, TestCredentials.TestUser2)); } @@ -84,7 +92,10 @@ protected override Task When() { userStreamAcl: default ); - return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs index c89cdad53..2ca80b559 100644 --- a/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class OverridenUserStreamSecurityTests(ITestOutputHelper output, OverridenUserStreamSecurityTests.CustomFixture fixture) +public class OverridenUserStreamSecurityTests( + ITestOutputHelper output, + OverridenUserStreamSecurityTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task operations_on_user_stream_succeeds_for_authorized_user() { @@ -27,14 +30,21 @@ public async Task operations_on_user_stream_succeeds_for_authorized_user() { public async Task operations_on_user_stream_fail_for_not_authorized_user() { var stream = Fixture.GetStreamName(); await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2) + ); + + await Assert.ThrowsAsync( + () => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2) + ); await Assert.ThrowsAsync(() => Fixture.AppendStream(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2) + ); await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream, TestCredentials.TestUser2)); } @@ -75,7 +85,10 @@ public async Task operations_on_user_stream_succeed_for_admin() { public class CustomFixture : SecurityFixture { protected override Task When() { var settings = new SystemSettings(new("user1", "user1", "user1", "user1", "user1")); - return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs b/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs index 9de765b96..460586c31 100644 --- a/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs +++ b/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs @@ -5,18 +5,25 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class StreamSecurityInheritanceTests(ITestOutputHelper output, StreamSecurityInheritanceTests.CustomFixture fixture) +public class StreamSecurityInheritanceTests( + ITestOutputHelper output, + StreamSecurityInheritanceTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task acl_inheritance_is_working_properly_on_user_streams() { await Assert.ThrowsAsync(() => Fixture.AppendStream("user-no-acl")); await Fixture.AppendStream("user-no-acl", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-no-acl", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-no-acl", TestCredentials.TestUser2) + ); await Fixture.AppendStream("user-no-acl", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-diff")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-diff", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-w-diff", TestCredentials.TestUser1) + ); await Fixture.AppendStream("user-w-diff", TestCredentials.TestUser2); await Fixture.AppendStream("user-w-diff", TestCredentials.TestAdmin); @@ -28,9 +35,13 @@ public async Task acl_inheritance_is_working_properly_on_user_streams() { await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser1) + ); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser2) + ); await Fixture.AppendStream("user-w-restricted", TestCredentials.TestAdmin); @@ -45,7 +56,9 @@ public async Task acl_inheritance_is_working_properly_on_user_streams() { await Assert.ThrowsAsync(() => Fixture.ReadEvent("user-r-restricted")); await Fixture.AppendStream("user-r-restricted", TestCredentials.TestUser1); await Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser2) + ); await Fixture.ReadEvent("user-r-restricted", TestCredentials.TestAdmin); } @@ -63,12 +76,16 @@ public async Task acl_inheritance_is_working_properly_on_user_streams_when_not_a public async Task acl_inheritance_is_working_properly_on_system_streams() { await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-no-acl")); await Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser2) + ); await Fixture.AppendStream("$sys-no-acl", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-diff")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser1) + ); await Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser2); await Fixture.AppendStream("$sys-w-diff", TestCredentials.TestAdmin); @@ -79,8 +96,13 @@ public async Task acl_inheritance_is_working_properly_on_system_streams() { await Fixture.AppendStream("$sys-w-multiple", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser2) + ); await Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestAdmin); @@ -89,15 +111,20 @@ public async Task acl_inheritance_is_working_properly_on_system_streams() { await Fixture.AppendStream("$sys-w-all", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl")); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser1) + ); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser2) + ); await Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestAdmin); } [AnonymousAccess.Fact] - public async Task acl_inheritance_is_working_properly_on_system_streams_when_not_authenticated() => await Fixture.AppendStream("$sys-w-all"); + public async Task acl_inheritance_is_working_properly_on_system_streams_when_not_authenticated() => + await Fixture.AppendStream("$sys-w-all"); public class CustomFixture : SecurityFixture { protected override async Task When() { @@ -106,7 +133,10 @@ protected override async Task When() { new(writeRole: "user1") ); - await Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + await Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); await Streams.SetStreamMetadataAsync( "user-no-acl", @@ -125,14 +155,14 @@ await Streams.SetStreamMetadataAsync( await Streams.SetStreamMetadataAsync( "user-w-multiple", StreamState.NoStream, - new(acl: new(writeRoles: new[] { "user1", "user2" })), + new(acl: new(writeRoles: ["user1", "user2"])), new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-restricted", StreamState.NoStream, - new(acl: new(writeRoles: Array.Empty())), + new(acl: new(writeRoles: [])), new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); @@ -167,14 +197,14 @@ await Streams.SetStreamMetadataAsync( await Streams.SetStreamMetadataAsync( "$sys-w-multiple", StreamState.NoStream, - new(acl: new(writeRoles: new[] { "user1", "user2" })), + new(acl: new(writeRoles: ["user1", "user2"])), new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-restricted", StreamState.NoStream, - new(acl: new(writeRoles: Array.Empty())), + new(acl: new(writeRoles: [])), new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); From 35caf9e8b2a786647a065bd17cbc6f19fc6e3c35 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 21 Mar 2025 13:56:47 +0100 Subject: [PATCH 14/23] [DEVEX-222] Added delete method with options --- src/KurrentDB.Client/Core/OperationOptions.cs | 10 +++ .../Streams/KurrentDBClient.Append.cs | 1 - .../Streams/KurrentDBClient.Delete.cs | 77 +++++++++++++++---- .../Streams/DeleteTests.cs | 43 ++++++++--- 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/src/KurrentDB.Client/Core/OperationOptions.cs b/src/KurrentDB.Client/Core/OperationOptions.cs index de8aceaa4..f3dca9b88 100644 --- a/src/KurrentDB.Client/Core/OperationOptions.cs +++ b/src/KurrentDB.Client/Core/OperationOptions.cs @@ -15,4 +15,14 @@ public class OperationOptions { /// public UserCredentials? UserCredentials { get; set; } + /// + /// Clones a copy of the current . + /// + /// + public OperationOptions With(KurrentDBClientSettings clientSettings) { + Deadline ??= clientSettings.DefaultDeadline; + UserCredentials ??= clientSettings.DefaultCredentials; + + return this; + } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index 2fa948763..343b859b6 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -508,7 +508,6 @@ public class AppendToStreamOptions : OperationOptions { /// public void With(KurrentDBClientOperationOptions clientOperationOptions) { ThrowOnAppendFailure ??= clientOperationOptions.ThrowOnAppendFailure; - BatchAppendSize ??= clientOperationOptions.BatchAppendSize; } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs index 17a03f721..379657221 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs @@ -8,33 +8,78 @@ public partial class KurrentDBClient { /// /// 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. + /// Optional settings for the delete operation, e.g. deadline, user credentials etc. /// 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); + DeleteOptions? options = null, + CancellationToken cancellationToken = default + ) => + DeleteInternal( + new DeleteReq { + Options = new DeleteReq.Types.Options { + StreamIdentifier = streamName + } + }.WithAnyStreamRevision(expectedState), + options, + cancellationToken + ); - private async Task DeleteInternal(DeleteReq request, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken) { + async Task DeleteInternal( + DeleteReq request, + DeleteOptions? options, + CancellationToken cancellationToken + ) { _log.LogDebug("Deleting stream {streamName}.", request.Options.StreamIdentifier); var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new EventStore.Client.Streams.Streams.StreamsClient( - channelInfo.CallInvoker).DeleteAsync(request, - KurrentDBCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); + using var call = new Streams.StreamsClient(channelInfo.CallInvoker).DeleteAsync( + request, + KurrentDBCallOptions.CreateNonStreaming( + Settings, + options?.Deadline, + options?.UserCredentials, + cancellationToken + ) + ); + var result = await call.ResponseAsync.ConfigureAwait(false); return new DeleteResult(new Position(result.Position.CommitPosition, result.Position.PreparePosition)); } } + + public class DeleteOptions : OperationOptions; + + public static class KurrentDBClientObsoleteDeleteExtensions { + /// + /// 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 . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with DeleteOptions parameter", + false + )] + public static Task DeleteAsync( + this KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.DeleteAsync( + streamName, + expectedState, + new DeleteOptions{ Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken + ); + } } diff --git a/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs index 83903311f..c6b74a1c4 100644 --- a/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs @@ -5,9 +5,12 @@ namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Delete")] -public class DeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class DeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Theory, ExpectedStreamStateCases] - public async Task hard_deleting_a_stream_that_does_not_exist_with_expected_version_does_not_throw(StreamState expectedVersion, string name) { + public async Task hard_deleting_a_stream_that_does_not_exist_with_expected_version_does_not_throw( + StreamState expectedVersion, string name + ) { var stream = $"{Fixture.GetStreamName()}_{name}"; await Fixture.Streams.TombstoneAsync(stream, expectedVersion); @@ -26,14 +29,18 @@ public async Task soft_deleting_a_stream_that_exists() { public async Task hard_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { var stream = Fixture.GetStreamName(); - await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0))); + await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0)) + ); } [Fact] public async Task soft_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { var stream = Fixture.GetStreamName(); - await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, StreamState.StreamRevision(0))); + await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync(stream, StreamState.StreamRevision(0)) + ); } [Fact] @@ -72,13 +79,17 @@ public async Task hard_deleting_a_deleted_stream_should_throw() { await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); - await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream)); + await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream) + ); } [Fact] public async Task with_timeout_any_stream_revision_delete_fails_when_operation_expired() { - var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, StreamState.Any, TimeSpan.Zero)); + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync(stream, StreamState.Any, new DeleteOptions { Deadline = TimeSpan.Zero }) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } @@ -87,15 +98,23 @@ public async Task with_timeout_any_stream_revision_delete_fails_when_operation_e public async Task with_timeout_stream_revision_delete_fails_when_operation_expired() { var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, StreamState.StreamRevision(0), TimeSpan.Zero)); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync( + stream, + StreamState.StreamRevision(0), + new DeleteOptions { Deadline = TimeSpan.Zero } + ) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } [Fact] public async Task with_timeout_any_stream_revision_tombstoning_fails_when_operation_expired() { - var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.Any, TimeSpan.Zero)); + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.Any, TimeSpan.Zero) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } @@ -104,7 +123,9 @@ public async Task with_timeout_any_stream_revision_tombstoning_fails_when_operat public async Task with_timeout_stream_revision_tombstoning_fails_when_operation_expired() { var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0), TimeSpan.Zero)); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0), TimeSpan.Zero) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } From dca00fbb0ffe70d5271698b1c42e30239836da0e Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 21 Mar 2025 14:32:27 +0100 Subject: [PATCH 15/23] [DEVEX-222] Made old subscription methods obsolete and adjusted tests to use them --- .../Streams/KurrentDBClient.Subscriptions.cs | 685 +++++++++--------- .../Obsolete/SubscribeToAllObsoleteTests.cs | 9 +- ...dateExistingWithCheckpointObsoleteTests.cs | 20 +- .../SubscribeToAll/SubscribeToAllTests.cs | 9 +- ...beToAllUpdateExistingWithCheckpointTest.cs | 5 +- .../SubscribeToStreamObsoleteTests.cs | 3 +- .../Security/SecurityFixture.cs | 10 +- .../SerializationTests.Subscriptions.cs | 6 +- .../Subscriptions/SubscribeToAllTests.cs | 36 +- .../Subscriptions/SubscribeToStreamTests.cs | 26 +- 10 files changed, 414 insertions(+), 395 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs index 4d1da858f..0509bc9cb 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs @@ -6,145 +6,7 @@ using static EventStore.Client.Streams.ReadResp.ContentOneofCase; namespace KurrentDB.Client { - /// - /// Subscribes to all events options. - /// - public class SubscribeToAllOptions { - /// - /// A (exclusive of) to start the subscription from. - /// - public FromAll Start { get; set; } = FromAll.Start; - - /// - /// Whether to resolve LinkTo events automatically. - /// - public bool ResolveLinkTos { get; set; } - - /// - /// The optional to apply. - /// - public SubscriptionFilterOptions? FilterOptions { get; set; } - - /// - /// The optional to apply. - /// - public IEventFilter Filter { set => FilterOptions = new SubscriptionFilterOptions(value); } - - /// - /// The optional user credentials to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } - - /// - /// Allows to customize or disable the automatic deserialization - /// - public OperationSerializationSettings? SerializationSettings { get; set; } - } - - /// - /// Subscribes to all events options. - /// - public class SubscribeToStreamOptions { - /// - /// A (exclusive of) to start the subscription from. - /// - public FromStream Start { get; set; } = FromStream.Start; - - /// - /// Whether to resolve LinkTo events automatically. - /// - public bool ResolveLinkTos { get; set; } - - /// - /// The optional user credentials to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } - - /// - /// Allows to customize or disable the automatic deserialization - /// - public OperationSerializationSettings? SerializationSettings { get; set; } - } - - public class SubscriptionListener { -#if NET48 - /// - /// A handler called when a new event is received over the subscription. - /// - public Func EventAppeared { get; set; } = null!; -#else - public required Func EventAppeared { get; set; } -#endif - /// - /// A handler called if the subscription is dropped. - /// - public Action? SubscriptionDropped { get; set; } - - /// - /// A handler called when a checkpoint is reached. - /// Set the checkpointInterval in subscription filter options to define how often this method is called. - /// - public Func? CheckpointReached { get; set; } - - /// - /// Returns the subscription listener with configured handlers - /// - /// Handler invoked when a new event is received over the subscription. - /// A handler invoked if the subscription is dropped. - /// A handler called when a checkpoint is reached. - /// Set the checkpointInterval in subscription filter options to define how often this method is called. - /// - /// - public static SubscriptionListener Handle( - Func eventAppeared, - Action? subscriptionDropped = null, - Func? checkpointReached = null - ) => - new SubscriptionListener { - EventAppeared = eventAppeared, - SubscriptionDropped = subscriptionDropped, - CheckpointReached = checkpointReached - }; - } - public partial class KurrentDBClient { - /// - /// 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 - ) { - var listener = SubscriptionListener.Handle( - eventAppeared, - subscriptionDropped, - filterOptions?.CheckpointReached - ); - - var options = new SubscribeToAllOptions { - Start = start, - FilterOptions = filterOptions, - ResolveLinkTos = resolveLinkTos, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled, - }; - - return SubscribeToAllAsync(listener, options, cancellationToken); - } - /// /// Subscribes to all events. /// @@ -154,9 +16,10 @@ public Task SubscribeToAllAsync( /// public Task SubscribeToAllAsync( SubscriptionListener listener, - SubscribeToAllOptions options, + SubscribeToAllOptions? options = null, CancellationToken cancellationToken = default ) { + options ??= new SubscribeToAllOptions(); listener.CheckpointReached ??= options.FilterOptions?.CheckpointReached; return StreamSubscription.Confirm( @@ -174,84 +37,29 @@ public Task SubscribeToAllAsync( /// The optional . /// public StreamSubscriptionResult SubscribeToAll( - SubscribeToAllOptions options, - 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 = options.ResolveLinkTos, - All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(options.Start), - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - Filter = GetFilterOptions(options.FilterOptions)!, - UuidOption = new() { Structured = new() } - } - }, - Settings, - options.UserCredentials, - _messageSerializer.With(options.SerializationSettings), - 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, + SubscribeToAllOptions? options = null, CancellationToken cancellationToken = default - ) => - SubscribeToAll( - new SubscribeToAllOptions { - Start = start, - ResolveLinkTos = resolveLinkTos, - FilterOptions = filterOptions, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// Subscribes to a stream from a checkpoint. - /// - /// A (exclusive of) to start the subscription from. - /// The name of the stream to subscribe for notifications about new events. - /// 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 - ) => - SubscribeToStreamAsync( - streamName, - SubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToStreamOptions { - Start = start, - ResolveLinkTos = resolveLinkTos, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled + ) { + options ??= new SubscribeToAllOptions(); + + return new StreamSubscriptionResult( + async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), + new ReadReq { + Options = new ReadReq.Types.Options { + ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, + ResolveLinks = options.ResolveLinkTos, + All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(options.Start), + Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), + Filter = GetFilterOptions(options.FilterOptions)!, + UuidOption = new() { Structured = new() } + } }, + Settings, + options.UserCredentials, + _messageSerializer.With(options.SerializationSettings), cancellationToken ); + } /// /// Subscribes to a stream from a checkpoint. @@ -264,87 +72,63 @@ public Task SubscribeToStreamAsync( public Task SubscribeToStreamAsync( string streamName, SubscriptionListener listener, - SubscribeToStreamOptions options, + SubscribeToStreamOptions? options = null, CancellationToken cancellationToken = default - ) { - return StreamSubscription.Confirm( + ) => + StreamSubscription.Confirm( SubscribeToStream(streamName, options, cancellationToken), listener, _log, cancellationToken ); - } /// /// Subscribes to a stream from a checkpoint. /// - /// A (exclusive of) to start the subscription from. /// The name of the stream to subscribe for notifications about new events. - /// Whether to resolve LinkTo events automatically. - /// The optional user credentials to perform operation with. + /// Optional settings like: Position from which to read, etc. /// The optional . /// public StreamSubscriptionResult SubscribeToStream( string streamName, - FromStream start, - bool resolveLinkTos = false, - UserCredentials? userCredentials = null, + SubscribeToStreamOptions? options = null, CancellationToken cancellationToken = default - ) => - SubscribeToStream( - streamName, - new SubscribeToStreamOptions { - Start = start, - ResolveLinkTos = resolveLinkTos, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled + ) { + options ??= new SubscribeToStreamOptions(); + + return new StreamSubscriptionResult( + async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), + new ReadReq { + Options = new ReadReq.Types.Options { + ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, + ResolveLinks = options.ResolveLinkTos, + Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition( + streamName, + options.Start + ), + Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), + UuidOption = new() { Structured = new() } + } }, + Settings, + options.UserCredentials, + _messageSerializer.With(options.SerializationSettings), cancellationToken ); - - /// - /// Subscribes to a stream from a checkpoint. - /// - /// The name of the stream to subscribe for notifications about new events. - /// Optional settings like: Position from which to read, etc. - /// The optional . - /// - public StreamSubscriptionResult SubscribeToStream( - string streamName, - SubscribeToStreamOptions options, - 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 = options.ResolveLinkTos, - Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition( - streamName, - options.Start - ), - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - UuidOption = new() { Structured = new() } - } - }, - Settings, - options.UserCredentials, - _messageSerializer.With(options.SerializationSettings), - 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 KurrentDBClientSettings _settings; - private AsyncServerStreamingCall? _call; + readonly ReadReq _request; + readonly Channel _channel; + readonly CancellationTokenSource _cts; + readonly CallOptions _callOptions; + readonly KurrentDBClientSettings _settings; + AsyncServerStreamingCall? _call; - private int _messagesEnumerated; + int _messagesEnumerated; /// /// The server-generated unique identifier for the subscription. @@ -521,150 +305,377 @@ public async IAsyncEnumerator GetAsyncEnumerator( } } - public static class KurrentDBClientSubscribeToAllExtensions { + /// + /// Subscribes to all events options. + /// + public class SubscribeToAllOptions { /// - /// Subscribes to all events. + /// A (exclusive of) to start the subscription from. /// - /// - /// Listener configured to receive notifications about new events and subscription state change. - /// The optional . + public FromAll Start { get; set; } = FromAll.Start; + + /// + /// Whether to resolve LinkTo events automatically. + /// + public bool ResolveLinkTos { get; set; } + + /// + /// The optional to apply. + /// + public SubscriptionFilterOptions? FilterOptions { get; set; } + + /// + /// The optional to apply. + /// + public IEventFilter Filter { set => FilterOptions = new SubscriptionFilterOptions(value); } + + /// + /// The optional user credentials to perform operation with. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Allows to customize or disable the automatic deserialization + /// + public OperationSerializationSettings? SerializationSettings { get; set; } + + public static SubscribeToAllOptions Get() => + new SubscribeToAllOptions(); + + public SubscribeToAllOptions WithFilter(SubscriptionFilterOptions filter) { + FilterOptions = filter; + + return this; + } + + public SubscribeToAllOptions WithFilter(IEventFilter filter) { + Filter = filter; + + return this; + } + + public SubscribeToAllOptions From(FromAll position) { + Start = position; + + return this; + } + + public SubscribeToAllOptions FromStart() { + Start = FromAll.Start; + + return this; + } + + public SubscribeToAllOptions FromEnd() { + Start = FromAll.End; + + return this; + } + + public SubscribeToAllOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } + + public SubscribeToAllOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } + } + + /// + /// Subscribes to all events options. + /// + public class SubscribeToStreamOptions { + /// + /// A (exclusive of) to start the subscription from. + /// + public FromStream Start { get; set; } = FromStream.Start; + + /// + /// Whether to resolve LinkTo events automatically. + /// + public bool ResolveLinkTos { get; set; } + + /// + /// The optional user credentials to perform operation with. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Allows to customize or disable the automatic deserialization + /// + public OperationSerializationSettings? SerializationSettings { get; set; } + + public static SubscribeToStreamOptions Get() => + new SubscribeToStreamOptions(); + + public SubscribeToStreamOptions From(FromStream position) { + Start = position; + + return this; + } + + public SubscribeToStreamOptions FromStart() { + Start = FromStream.Start; + + return this; + } + + public SubscribeToStreamOptions FromEnd() { + Start = FromStream.End; + + return this; + } + + public SubscribeToStreamOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } + + public SubscribeToStreamOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } + } + + public class SubscriptionListener { +#if NET48 + /// + /// A handler called when a new event is received over the subscription. + /// + public Func EventAppeared { get; set; } = null!; +#else + public required Func EventAppeared { get; set; } +#endif + /// + /// A handler called if the subscription is dropped. + /// + public Action? SubscriptionDropped { get; set; } + + /// + /// A handler called when a checkpoint is reached. + /// Set the checkpointInterval in subscription filter options to define how often this method is called. + /// + public Func? CheckpointReached { get; set; } + + /// + /// Returns the subscription listener with configured handlers + /// + /// Handler invoked when a new event is received over the subscription. + /// A handler invoked if the subscription is dropped. + /// A handler called when a checkpoint is reached. + /// Set the checkpointInterval in subscription filter options to define how often this method is called. + /// /// - public static Task SubscribeToAllAsync( - this KurrentDBClient kurrentDbClient, - SubscriptionListener listener, - CancellationToken cancellationToken = default + public static SubscriptionListener Handle( + Func eventAppeared, + Action? subscriptionDropped = null, + Func? checkpointReached = null ) => - kurrentDbClient.SubscribeToAllAsync(listener, new SubscribeToAllOptions(), cancellationToken); + new SubscriptionListener { + EventAppeared = eventAppeared, + SubscriptionDropped = subscriptionDropped, + CheckpointReached = checkpointReached + }; + } + public static class KurrentDBClientSubscribeExtensions { /// /// Subscribes to all events. /// /// - /// + /// Handler invoked when a new event is received over the subscription. + /// Optional settings like: Position from which to read, to apply, etc. /// The optional . /// public static Task SubscribeToAllAsync( this KurrentDBClient kurrentDbClient, Func eventAppeared, + SubscribeToAllOptions? options = null, CancellationToken cancellationToken = default ) => kurrentDbClient.SubscribeToAllAsync( - eventAppeared, - new SubscribeToAllOptions(), + SubscriptionListener.Handle(eventAppeared), + options, cancellationToken ); /// - /// Subscribes to all events. + /// Subscribes to messages from a specific stream /// /// + /// The name of the stream to subscribe for notifications about new events. /// Handler invoked when a new event is received over the subscription. /// Optional settings like: Position from which to read, to apply, etc. /// The optional . /// - public static Task SubscribeToAllAsync( + public static Task SubscribeToStreamAsync( this KurrentDBClient kurrentDbClient, + string streamName, Func eventAppeared, - SubscribeToAllOptions options, + SubscribeToStreamOptions? options = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToAllAsync( + kurrentDbClient.SubscribeToStreamAsync( + streamName, SubscriptionListener.Handle(eventAppeared), options, cancellationToken ); + } + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class KurrentDBClientObsoleteSubscribeExtensions { /// /// 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 static KurrentDBClient.StreamSubscriptionResult SubscribeToAll( - this KurrentDBClient kurrentDbClient, + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToAllOptions and get auto-serialization capabilities", + false + )] + public static Task SubscribeToAllAsync( + this KurrentDBClient dbClient, + FromAll start, + Func eventAppeared, + bool resolveLinkTos = false, + Action? subscriptionDropped = null, + SubscriptionFilterOptions? filterOptions = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) => - kurrentDbClient.SubscribeToAll(new SubscribeToAllOptions(), cancellationToken); - } + ) { + var listener = SubscriptionListener.Handle( + eventAppeared, + subscriptionDropped, + filterOptions?.CheckpointReached + ); + + var options = new SubscribeToAllOptions { + Start = start, + FilterOptions = filterOptions, + ResolveLinkTos = resolveLinkTos, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled, + }; + + return dbClient.SubscribeToAllAsync(listener, options, cancellationToken); + } - public static class KurrentDBClientSubscribeToStreamExtensions { /// - /// Subscribes to messages from a specific stream + /// Subscribes to all events. /// - /// - /// The name of the stream to subscribe for notifications about new events. - /// Listener configured to receive notifications about new events and subscription state change. + /// + /// 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 static Task SubscribeToStreamAsync( - this KurrentDBClient kurrentDbClient, - string streamName, - SubscriptionListener listener, + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToAllOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBClient.StreamSubscriptionResult SubscribeToAll( + this KurrentDBClient dbClient, + FromAll start, + bool resolveLinkTos = false, + SubscriptionFilterOptions? filterOptions = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( - streamName, - listener, - new SubscribeToStreamOptions(), + dbClient.SubscribeToAll( + new SubscribeToAllOptions { + Start = start, + ResolveLinkTos = resolveLinkTos, + FilterOptions = filterOptions, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// - /// Subscribes to messages from a specific stream + /// Subscribes to a stream from a checkpoint. /// - /// + /// A (exclusive of) to start the subscription from. + /// /// The name of the stream to subscribe for notifications about new events. - /// + /// Whether to resolve LinkTo events automatically. + /// The optional user credentials to perform operation with. /// The optional . /// - public static Task SubscribeToStreamAsync( - this KurrentDBClient kurrentDbClient, + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToStreamOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBClient.StreamSubscriptionResult SubscribeToStream( + this KurrentDBClient dbClient, string streamName, - Func eventAppeared, + FromStream start, + bool resolveLinkTos = false, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( + dbClient.SubscribeToStream( streamName, - eventAppeared, - new SubscribeToStreamOptions(), + new SubscribeToStreamOptions { + Start = start, + ResolveLinkTos = resolveLinkTos, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// - /// Subscribes to messages from a specific stream + /// Subscribes to a stream from a checkpoint. /// - /// + /// A (exclusive of) to start the subscription from. + /// /// The name of the stream to subscribe for notifications about new events. - /// Handler invoked when a new event is received over the subscription. - /// Optional settings like: Position from which to read, to apply, etc. + /// 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 . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToStreamOptions and get auto-serialization capabilities", + false + )] public static Task SubscribeToStreamAsync( - this KurrentDBClient kurrentDbClient, + this KurrentDBClient dbClient, string streamName, + FromStream start, Func eventAppeared, - SubscribeToStreamOptions options, + bool resolveLinkTos = false, + Action? subscriptionDropped = default, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( + dbClient.SubscribeToStreamAsync( streamName, - SubscriptionListener.Handle(eventAppeared), - options, + SubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToStreamOptions { + Start = start, + ResolveLinkTos = resolveLinkTos, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); - - /// - /// Subscribes to messages from a specific stream - /// - /// - /// The name of the stream to subscribe for notifications about new events. - /// The optional . - /// - public static KurrentDBClient.StreamSubscriptionResult SubscribeToStream( - this KurrentDBClient kurrentDbClient, - string streamName, - CancellationToken cancellationToken = default - ) => - kurrentDbClient.SubscribeToStream(streamName, new SubscribeToStreamOptions(), cancellationToken); } } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs index e9efd4b78..4b9763e69 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs @@ -516,8 +516,7 @@ await Fixture.Subscriptions.CreateToAllAsync( async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -609,8 +608,7 @@ await Fixture.Subscriptions.SubscribeToAllAsync( async Task Checkpointed() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -719,8 +717,7 @@ async Task WaitForCheckpoints() { bool firstCheckpointSet = false; await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs index 24ad7a96a..cb19a7807 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs @@ -5,7 +5,10 @@ namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllUpdateExistingWithCheckpointObsoleteTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllUpdateExistingWithCheckpointObsoleteTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task update_existing_with_check_point_should_resumes_from_check_point() { @@ -25,7 +28,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + userCredentials: TestCredentials.Root + ); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -38,7 +45,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); - await using var sub = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + userCredentials: TestCredentials.Root + ); var resolvedEvent = await sub.Messages .OfType() @@ -71,8 +82,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-{stream}::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs index 573c24ea4..ede5c91ef 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs @@ -585,8 +585,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -664,8 +663,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -795,8 +793,7 @@ async Task WaitForCheckpoints() { bool firstCheckpointSet = false; await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs index 17592f16c..051a151c6 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs @@ -1,6 +1,4 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client.Tests; namespace KurrentDB.Client.Tests.PersistentSubscriptions; @@ -71,8 +69,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-{stream}::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs index ea46b1c5b..89f775e2e 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -844,8 +844,7 @@ await Fixture.Subscriptions.SubscribeToStreamAsync( async Task Checkpointed() { await using var subscription = Fixture.Streams.SubscribeToStream( checkPointStream, - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs index d845c4211..472e0303d 100644 --- a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs +++ b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs @@ -193,9 +193,9 @@ public Task ReadAllForward(UserCredentials? userCredentials = null) => public Task ReadAllBackward(UserCredentials? userCredentials = null) => Streams.ReadAllAsync( new ReadAllOptions { - Direction = Direction.Backwards, - Position = Position.End, - MaxCount = 1, + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, UserCredentials = userCredentials } ) @@ -227,7 +227,7 @@ public Task WriteMeta( public async Task SubscribeToStream(string streamId, UserCredentials? userCredentials = null) { await using var subscription = - Streams.SubscribeToStream(streamId, FromStream.Start, userCredentials: userCredentials); + Streams.SubscribeToStream(streamId, new SubscribeToStreamOptions { UserCredentials = userCredentials }); await subscription .Messages.OfType().AnyAsync().AsTask() @@ -236,7 +236,7 @@ await subscription public async Task SubscribeToAll(UserCredentials? userCredentials = null) { await using var subscription = - Streams.SubscribeToAll(FromAll.Start, userCredentials: userCredentials); + Streams.SubscribeToAll(new SubscribeToAllOptions { UserCredentials = userCredentials }); await subscription .Messages.OfType().AnyAsync().AsTask() diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 4952cabb2..7a18109a7 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -73,7 +73,7 @@ public async Task subscribe_to_stream_without_options_does_NOT_deserialize_resol // When var resolvedEvents = await Fixture.Streams - .SubscribeToStream(stream, FromStream.Start).Take(2) + .SubscribeToStream(stream).Take(2) .ToListAsync(); // Then @@ -87,7 +87,9 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved // When var resolvedEvents = await Fixture.Streams - .SubscribeToAll(FromAll.Start, filterOptions: new SubscriptionFilterOptions(StreamFilter.Prefix(stream))) + .SubscribeToAll( + new SubscribeToAllOptions().WithFilter(new SubscriptionFilterOptions(StreamFilter.Prefix(stream))) + ) .Take(2) .ToListAsync(); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs index 58223aac9..9662df450 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs @@ -22,7 +22,7 @@ await Fixture.Streams.AppendToStreamAsync( [evt] ); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start); + await using var subscription = Fixture.Streams.SubscribeToAll(); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -33,7 +33,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync( $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await Subscribe().WithTimeout(); @@ -61,7 +61,7 @@ public async Task receives_all_events_from_end() { var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().FromEnd()); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -73,7 +73,7 @@ public async Task receives_all_events_from_end() { await Fixture.Streams.AppendToStreamAsync( $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await Subscribe().WithTimeout(); @@ -108,12 +108,12 @@ public async Task receives_all_events_from_position() { writeResult = await Fixture.Streams.AppendToStreamAsync( $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var position = FromAll.After(writeResult.LogPosition); - await using var subscription = Fixture.Streams.SubscribeToAll(position); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().From(position)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -124,7 +124,7 @@ public async Task receives_all_events_from_position() { await Fixture.Streams.AppendToStreamAsync( $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await Subscribe().WithTimeout(); @@ -155,7 +155,7 @@ public async Task receives_all_events_with_resolved_links() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, true); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().WithResolveLinkTos()); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -217,12 +217,12 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -234,7 +234,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); bool checkpointReached = false; @@ -304,12 +304,12 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End, filterOptions: filterOptions); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().FromEnd().WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -321,7 +321,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); bool checkpointReached = false; @@ -388,14 +388,14 @@ await Fixture.Streams.AppendToStreamAsync( writeResult = await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [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 subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().From(position).WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -407,7 +407,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); bool checkpointReached = false; @@ -453,7 +453,7 @@ public async Task receives_all_filtered_events_with_resolved_links() { new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentDBPermanentFixture.TestEventType}")); await using var subscription = - Fixture.Streams.SubscribeToAll(FromAll.Start, true, filterOptions: filterOptions); + Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().WithResolveLinkTos().WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs index 5240ccf57..7bcd8b733 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs @@ -1,5 +1,3 @@ -using KurrentDB.Client; - namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Subscriptions")] @@ -17,7 +15,7 @@ public async Task receives_all_events_from_start() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -61,8 +59,12 @@ public async Task receives_all_events_from_position() { var streamPosition = StreamPosition.FromInt64(writeResult.NextExpectedStreamState.ToInt64()); var checkpoint = FromStream.After(streamPosition); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, checkpoint); - await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + await using var subscription = Fixture.Streams.SubscribeToStream( + streamName, + new SubscribeToStreamOptions().From(checkpoint) + ); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -101,7 +103,7 @@ public async Task receives_all_events_from_non_existing_stream() { var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -137,14 +139,14 @@ public async Task allow_multiple_subscriptions_to_same_stream() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); - await using var subscription1 = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription1 = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator1 = subscription1.Messages.GetAsyncEnumerator(); Assert.True(await enumerator1.MoveNextAsync()); Assert.IsType(enumerator1.Current); - await using var subscription2 = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription2 = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator2 = subscription2.Messages.GetAsyncEnumerator(); Assert.True(await enumerator2.MoveNextAsync()); @@ -176,7 +178,7 @@ async Task Subscribe(IAsyncEnumerator subscription) { public async Task drops_when_stream_tombstoned() { var streamName = Fixture.GetStreamName(); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -205,7 +207,11 @@ await Streams.SetStreamMetadataAsync( new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); - await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + await Streams.AppendToStreamAsync( + $"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", + StreamState.NoStream, + CreateTestEvents(10) + ); }; } } From 255ba7dc240a0b85adcbdfdeaa3d9c543bdef4a8 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 21 Mar 2025 14:43:11 +0100 Subject: [PATCH 16/23] [DEVEX-222] Added Tombstone method with options and made the old obsolete --- .../Streams/KurrentDBClient.Delete.cs | 3 +- .../Streams/KurrentDBClient.Tombstone.cs | 76 +++++++++++++++---- .../Security/SecurityFixture.cs | 2 +- .../Streams/DeleteTests.cs | 12 ++- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs index 379657221..fbf3a0afd 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs @@ -52,7 +52,8 @@ CancellationToken cancellationToken public class DeleteOptions : OperationOptions; - public static class KurrentDBClientObsoleteDeleteExtensions { + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class ObsoleteKurrentDBClientDeleteExtensions { /// /// Deletes a stream asynchronously. /// diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs index 5957066f6..b97a92426 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs @@ -8,32 +8,80 @@ public partial class KurrentDBClient { /// /// The name of the stream to tombstone. /// The expected of the stream being deleted. - /// - /// The optional to perform operation with. + /// Optional settings for the tombstone operation, e.g. deadline, user credentials etc. /// 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); + TombstoneOptions? options = null, + CancellationToken cancellationToken = default + ) => + TombstoneInternal( + new TombstoneReq { + Options = new TombstoneReq.Types.Options { + StreamIdentifier = streamName + } + }.WithAnyStreamRevision(expectedState), + options, + cancellationToken + ); - private async Task TombstoneInternal(TombstoneReq request, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken) { + async Task TombstoneInternal( + TombstoneReq request, + TombstoneOptions? options, + CancellationToken cancellationToken + ) { _log.LogDebug("Tombstoning stream {streamName}.", request.Options.StreamIdentifier); var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new EventStore.Client.Streams.Streams.StreamsClient( - channelInfo.CallInvoker).TombstoneAsync(request, - KurrentDBCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); + using var call = new Streams.StreamsClient(channelInfo.CallInvoker).TombstoneAsync( + request, + KurrentDBCallOptions.CreateNonStreaming( + Settings, + options?.Deadline, + options?.UserCredentials, + cancellationToken + ) + ); + var result = await call.ResponseAsync.ConfigureAwait(false); return new DeleteResult(new Position(result.Position.CommitPosition, result.Position.PreparePosition)); } } + + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class ObsoleteKurrentDBClientTombstoneExtensions { + /// + /// 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 . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with TombstoneOptions parameter", + false + )] + public static Task TombstoneAsync( + KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.TombstoneAsync( + streamName, + expectedState, + new TombstoneOptions { Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken + ); + } + + public class TombstoneOptions : OperationOptions; } diff --git a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs index 472e0303d..991b7be77 100644 --- a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs +++ b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs @@ -258,6 +258,6 @@ await Streams.SetStreamMetadataAsync( } public Task DeleteStream(string streamId, UserCredentials? userCredentials = null) => - Streams.TombstoneAsync(streamId, StreamState.Any, userCredentials: userCredentials) + Streams.TombstoneAsync(streamId, StreamState.Any, new TombstoneOptions { UserCredentials = userCredentials }) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } diff --git a/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs index c6b74a1c4..b9f454692 100644 --- a/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs @@ -113,7 +113,11 @@ public async Task with_timeout_stream_revision_delete_fails_when_operation_expir public async Task with_timeout_any_stream_revision_tombstoning_fails_when_operation_expired() { var stream = Fixture.GetStreamName(); var rpcException = await Assert.ThrowsAsync( - () => Fixture.Streams.TombstoneAsync(stream, StreamState.Any, TimeSpan.Zero) + () => Fixture.Streams.TombstoneAsync( + stream, + StreamState.Any, + new TombstoneOptions { Deadline = TimeSpan.Zero } + ) ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); @@ -124,7 +128,11 @@ public async Task with_timeout_stream_revision_tombstoning_fails_when_operation_ var stream = Fixture.GetStreamName(); var rpcException = await Assert.ThrowsAsync( - () => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0), TimeSpan.Zero) + () => Fixture.Streams.TombstoneAsync( + stream, + StreamState.StreamRevision(0), + new TombstoneOptions { Deadline = TimeSpan.Zero } + ) ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); From 6d48b9cd01b726ecc93bfdc55e14a56a648f9c75 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 21 Mar 2025 14:54:25 +0100 Subject: [PATCH 17/23] [DEVEX-222] Made all persistent subscription methods obsolete --- samples/server-side-filtering/Program.cs | 57 +- samples/subscribing-to-streams/Program.cs | 98 ++-- ...entDBPersistentSubscriptionsClient.Read.cs | 502 +++++++++--------- .../Streams/KurrentDBClient.Subscriptions.cs | 16 +- ...xistingWithStartFromNotSetObsoleteTests.cs | 10 +- ...hStartFromSetToEndPositionObsoleteTests.cs | 35 +- ...nectWithoutReadPermissionsObsoleteTests.cs | 2 +- .../SubscribeToAllFilterObsoleteTests.cs | 11 +- .../SubscribeToAllGetInfoObsoleteTests.cs | 2 +- ...eToAllNoDefaultCredentialsObsoleteTests.cs | 2 +- .../Obsolete/SubscribeToAllObsoleteTests.cs | 260 +++++---- ...dateExistingWithCheckpointObsoleteTests.cs | 4 +- ...nnectToExistingWithStartFromNotSetTests.cs | 10 +- ...stingWithStartFromSetToEndPositionTests.cs | 17 +- ...ToAllConnectWithoutReadPermissionsTests.cs | 12 +- .../SubscribeToAllFilterTests.cs | 8 +- .../SubscribeToAllGetInfoTests.cs | 10 +- ...SubscribeToAllNoDefaultCredentialsTests.cs | 16 +- .../SubscribeToAll/SubscribeToAllTests.cs | 107 +++- ...beToAllUpdateExistingWithCheckpointTest.cs | 12 +- .../SubscribeToStreamGetInfoObsoleteTests.cs | 2 +- .../SubscribeToStreamObsoleteTests.cs | 414 ++++++++------- ...ctToExistingWithStartFromBeginningTests.cs | 2 +- .../SubscribeToStreamGetInfoTests.cs | 9 +- .../SubscribeToStreamTests.cs | 126 +++-- ...ializationTests.PersistentSubscriptions.cs | 19 +- .../SerializationTests.Subscriptions.cs | 10 +- 27 files changed, 1035 insertions(+), 738 deletions(-) diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index b50ca0339..1accf0621 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -1,6 +1,5 @@ #pragma warning disable CS8321 // Local function is declared but never used -using KurrentDB.Client; using EventTypeFilter = KurrentDB.Client.EventTypeFilter; const int eventCount = 100; @@ -9,23 +8,26 @@ await using var client = new KurrentDBClient(KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false")); -_ = Task.Run(async () => { - await using var subscription = client.SubscribeToAll( - FromAll.Start, - filterOptions: new SubscriptionFilterOptions(EventTypeFilter.Prefix("some-"))); - await foreach (var message in subscription.Messages) { - switch (message) { - case StreamMessage.Event(var e): - Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - semaphore.Release(); - break; - case StreamMessage.AllStreamCheckpointReached(var p): - Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); - break; +_ = Task.Run( + async () => { + await using var subscription = client.SubscribeToAll( + new SubscribeToAllOptions { FilterOptions = new SubscriptionFilterOptions(EventTypeFilter.Prefix("some-")) } + ); + + await foreach (var message in subscription.Messages) { + switch (message) { + case StreamMessage.Event(var e): + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + semaphore.Release(); + break; + + case StreamMessage.AllStreamCheckpointReached(var p): + Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); + break; + } } } -}); - +); await Task.Delay(2000); @@ -50,8 +52,11 @@ static async Task ExcludeSystemEvents(KurrentDBClient client) { #region exclude-system await using var subscription = client.SubscribeToAll( - FromAll.Start, - filterOptions: new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents())); + new SubscribeToAllOptions { + FilterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents()) + } + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -70,7 +75,7 @@ static async Task EventTypePrefix(KurrentDBClient client) { #endregion event-type-prefix - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -87,7 +92,7 @@ static async Task EventTypeRegex(KurrentDBClient client) { #endregion event-type-regex - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -104,7 +109,7 @@ static async Task StreamPrefix(KurrentDBClient client) { #endregion stream-prefix - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -121,7 +126,7 @@ static async Task StreamRegex(KurrentDBClient client) { #endregion stream-regex - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -136,18 +141,19 @@ static async Task CheckpointCallback(KurrentDBClient client) { var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents()); - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); break; + case StreamMessage.AllStreamCheckpointReached(var p): Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); break; } } - + #endregion checkpoint } @@ -158,12 +164,13 @@ static async Task CheckpointCallbackWithInterval(KurrentDBClient client) { #endregion checkpoint-with-interval - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); break; + case StreamMessage.AllStreamCheckpointReached(var p): Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); break; diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index 09ac19559..7b14e70a4 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -1,18 +1,19 @@ -using KurrentDB.Client; - -#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS8321 // Local function is declared but never used // ReSharper disable UnusedParameter.Local // ReSharper disable UnusedVariable await using var client = new KurrentDBClient(KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false")); -await Task.WhenAll(YieldSamples().Select(async sample => { - try { - await sample; - } catch (OperationCanceledException) { } -})); - +await Task.WhenAll( + YieldSamples().Select( + async sample => { + try { + await sample; + } catch (OperationCanceledException) { } + } + ) +); return; @@ -34,8 +35,12 @@ static async Task SubscribeToStreamFromPosition(KurrentDBClient client, Cancella await using var subscription = client.SubscribeToStream( "some-stream", - FromStream.After(StreamPosition.FromInt64(20)), - cancellationToken: ct); + new SubscribeToStreamOptions { + Start = FromStream.After(StreamPosition.FromInt64(20)) + }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -53,8 +58,10 @@ static async Task SubscribeToStreamLive(KurrentDBClient client, CancellationToke await using var subscription = client.SubscribeToStream( "some-stream", - FromStream.End, - cancellationToken: ct); + new SubscribeToStreamOptions { Start = FromStream.End }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -72,9 +79,10 @@ static async Task SubscribeToStreamResolvingLinkTos(KurrentDBClient client, Canc await using var subscription = client.SubscribeToStream( "$et-myEventType", - FromStream.Start, - true, - cancellationToken: ct); + new SubscribeToStreamOptions { ResolveLinkTos = true }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -91,7 +99,7 @@ static async Task SubscribeToStreamSubscriptionDropped(KurrentDBClient client, C #region subscribe-to-stream-subscription-dropped var checkpoint = await ReadStreamCheckpointAsync() switch { - null => FromStream.Start, + null => FromStream.Start, var position => FromStream.After(position.Value) }; @@ -99,8 +107,10 @@ static async Task SubscribeToStreamSubscriptionDropped(KurrentDBClient client, C try { await using var subscription = client.SubscribeToStream( "some-stream", - checkpoint, - cancellationToken: ct); + new SubscribeToStreamOptions { Start = checkpoint }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -125,10 +135,8 @@ static async Task SubscribeToStreamSubscriptionDropped(KurrentDBClient client, C static async Task SubscribeToStream(KurrentDBClient client, CancellationToken ct) { #region subscribe-to-stream - await using var subscription = client.SubscribeToStream( - "some-stream", - FromStream.Start, - cancellationToken: ct); + await using var subscription = client.SubscribeToStream("some-stream", cancellationToken: ct); + await foreach (var message in subscription.Messages.WithCancellation(ct)) { switch (message) { case StreamMessage.Event(var evnt): @@ -144,9 +152,8 @@ static async Task SubscribeToStream(KurrentDBClient client, CancellationToken ct static async Task SubscribeToAll(KurrentDBClient client, CancellationToken ct) { #region subscribe-to-all - await using var subscription = client.SubscribeToAll( - FromAll.Start, - cancellationToken: ct); + await using var subscription = client.SubscribeToAll(cancellationToken: ct); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -169,8 +176,12 @@ static async Task SubscribeToAllFromPosition(KurrentDBClient client, Cancellatio ); await using var subscription = client.SubscribeToAll( - FromAll.After(result.LogPosition), - cancellationToken: ct); + new SubscribeToAllOptions { + Start = FromAll.After(result.LogPosition) + }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -187,8 +198,12 @@ static async Task SubscribeToAllLive(KurrentDBClient client, CancellationToken c #region subscribe-to-all-live var subscription = client.SubscribeToAll( - FromAll.End, - cancellationToken: ct); + new SubscribeToAllOptions { + Start = FromAll.End + }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -205,15 +220,17 @@ static async Task SubscribeToAllSubscriptionDropped(KurrentDBClient client, Canc #region subscribe-to-all-subscription-dropped var checkpoint = await ReadCheckpointAsync() switch { - null => FromAll.Start, + null => FromAll.Start, var position => FromAll.After(position.Value) }; Subscribe: try { await using var subscription = client.SubscribeToAll( - checkpoint, - cancellationToken: ct); + new SubscribeToAllOptions { Start = checkpoint }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -243,9 +260,10 @@ static async Task SubscribeToFiltered(KurrentDBClient client, CancellationToken var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); await using var subscription = client.SubscribeToAll( - FromAll.Start, - filterOptions: prefixStreamFilter, - cancellationToken: ct); + new SubscribeToAllOptions { FilterOptions = prefixStreamFilter }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -253,6 +271,7 @@ static async Task SubscribeToFiltered(KurrentDBClient client, CancellationToken await HandleEvent(evnt); break; + case StreamMessage.AllStreamCheckpointReached(var position): Console.WriteLine($"Checkpoint reached: {position}"); break; @@ -272,9 +291,10 @@ static async Task OverridingUserCredentials(KurrentDBClient client, Cancellation #region overriding-user-credentials await using var subscription = client.SubscribeToAll( - FromAll.Start, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: ct); + new SubscribeToAllOptions { UserCredentials = new UserCredentials("admin", "changeit") }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): diff --git a/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs b/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs index 7c6757469..45d1b04dd 100644 --- a/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs +++ b/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs @@ -6,99 +6,11 @@ using KurrentDB.Client.Core.Serialization; using static EventStore.Client.PersistentSubscriptions.PersistentSubscriptions; using static EventStore.Client.PersistentSubscriptions.ReadResp.ContentOneofCase; +using static KurrentDB.Client.KurrentDBPersistentSubscriptionsClient; namespace KurrentDB.Client { - public class SubscribeToPersistentSubscriptionOptions { - /// - /// The size of the buffer. - /// - public int BufferSize { get; set; } = 10; - - /// - /// The optional user credentials to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } = null; - - /// - /// Allows to customize or disable the automatic deserialization - /// - public OperationSerializationSettings? SerializationSettings { get; set; } - } - - public class PersistentSubscriptionListener { - /// - /// A handler called when a new event is received over the subscription. - /// -#if NET48 - public Func EventAppeared { get; set; } = - null!; -#else - public required Func EventAppeared { - get; - set; - } -#endif - /// - /// A handler called if the subscription is dropped. - /// - public Action? SubscriptionDropped { get; set; } - - /// - /// Returns the subscription listener with configured handlers - /// - /// Handler invoked when a new event is received over the subscription. - /// A handler invoked if the subscription is dropped. - /// A handler called when a checkpoint is reached. - /// Set the checkpointInterval in subscription filter options to define how often this method is called. - /// - /// - public static PersistentSubscriptionListener Handle( - Func eventAppeared, - Action? subscriptionDropped = null - ) => - new PersistentSubscriptionListener { - EventAppeared = eventAppeared, - SubscriptionDropped = subscriptionDropped - }; - } - partial class KurrentDBPersistentSubscriptionsClient { - /// - /// 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 SubscribeToStreamAsync( - streamName, - groupName, - PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - BufferSize = bufferSize, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - } - + internal const int DefaultBufferSize = 10; /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged /// @@ -106,104 +18,40 @@ public async Task SubscribeAsync( /// /// public Task SubscribeToStreamAsync( - string streamName, - string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, - int bufferSize = 10, - CancellationToken cancellationToken = default - ) => - SubscribeToStreamAsync( - streamName, - groupName, - PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - BufferSize = bufferSize, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged - /// - /// - /// - /// - public async Task SubscribeToStreamAsync( string streamName, string groupName, PersistentSubscriptionListener listener, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default - ) { - return await PersistentSubscription + ) => + PersistentSubscription .Confirm( SubscribeToStream(streamName, groupName, options, cancellationToken), listener, _log, 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, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - return SubscribeToStream( - streamName, - groupName, - new SubscribeToPersistentSubscriptionOptions { - BufferSize = bufferSize, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - } - + ); - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged. + /// 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( + /// + /// + /// + public Task SubscribeToStreamAsync( string streamName, string groupName, - UserCredentials? userCredentials, + Func eventAppeared, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default - ) { - return SubscribeToStream( + ) => + SubscribeToStreamAsync( streamName, groupName, - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, + PersistentSubscriptionListener.Handle(eventAppeared), + options, cancellationToken ); - } /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged. @@ -216,7 +64,7 @@ public PersistentSubscriptionResult SubscribeToStream( public PersistentSubscriptionResult SubscribeToStream( string streamName, string groupName, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) { if (streamName == null) { @@ -235,12 +83,14 @@ public PersistentSubscriptionResult SubscribeToStream( throw new ArgumentException($"{nameof(groupName)} may not be empty.", nameof(groupName)); } + options ??= new SubscribeToPersistentSubscriptionOptions(); + if (options.BufferSize <= 0) { throw new ArgumentOutOfRangeException(nameof(options.BufferSize)); } var readOptions = new ReadReq.Types.Options { - BufferSize = options.BufferSize, + BufferSize = options.BufferSize ?? DefaultBufferSize, GroupName = groupName, UuidOption = new ReadReq.Types.Options.Types.UUIDOption { Structured = new Empty() } }; @@ -274,35 +124,13 @@ public PersistentSubscriptionResult SubscribeToStream( ); } - /// - /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged - /// - public Task SubscribeToAllAsync( - string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, - int bufferSize = 10, - CancellationToken cancellationToken = default - ) => - SubscribeToAllAsync( - groupName, - PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToPersistentSubscriptionOptions { - BufferSize = bufferSize, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - /// /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged /// public Task SubscribeToAllAsync( string groupName, PersistentSubscriptionListener listener, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) => SubscribeToStreamAsync( @@ -312,53 +140,17 @@ public Task SubscribeToAllAsync( options, cancellationToken ); - - /// - /// 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, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => - SubscribeToStream( - SystemStreams.AllStream, - groupName, - new SubscribeToPersistentSubscriptionOptions { - BufferSize = bufferSize, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// 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( + public Task SubscribeToAllAsync( string groupName, - UserCredentials? userCredentials, + Func eventAppeared, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) => - SubscribeToStream( - SystemStreams.AllStream, + SubscribeToAllAsync( groupName, - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, + PersistentSubscriptionListener.Handle(eventAppeared), + options, cancellationToken ); @@ -371,7 +163,7 @@ public PersistentSubscriptionResult SubscribeToAll( /// public PersistentSubscriptionResult SubscribeToAll( string groupName, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) => SubscribeToStream( @@ -703,81 +495,279 @@ public async IAsyncEnumerator GetAsyncEnumerator( } } } + + public class SubscribeToPersistentSubscriptionOptions { + /// + /// The size of the buffer. + /// + public int? BufferSize { get; set; } + + /// + /// The optional user credentials to perform operation with. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Allows to customize or disable the automatic deserialization + /// + public OperationSerializationSettings? SerializationSettings { get; set; } + } + + public class PersistentSubscriptionListener { + /// + /// A handler called when a new event is received over the subscription. + /// +#if NET48 + public Func EventAppeared { get; set; } = + null!; +#else + public required Func EventAppeared { + get; + set; + } +#endif + /// + /// A handler called if the subscription is dropped. + /// + public Action? SubscriptionDropped { get; set; } + + /// + /// Returns the subscription listener with configured handlers + /// + /// Handler invoked when a new event is received over the subscription. + /// A handler invoked if the subscription is dropped. + /// A handler called when a checkpoint is reached. + /// Set the checkpointInterval in subscription filter options to define how often this method is called. + /// + /// + public static PersistentSubscriptionListener Handle( + Func eventAppeared, + Action? subscriptionDropped = null + ) => + new PersistentSubscriptionListener { + EventAppeared = eventAppeared, + SubscriptionDropped = subscriptionDropped + }; + } + + public static class ObsoleteKurrentDBClientPersistentSubscriptionsExtensions { + /// + /// Subscribes to a persistent subscription. + /// + /// + /// + /// + [Obsolete("SubscribeAsync is no longer supported. Use SubscribeToStream with manual acks instead.", false)] + public static async Task SubscribeAsync( + this KurrentDBPersistentSubscriptionsClient dbClient, + string streamName, + string groupName, + Func eventAppeared, + Action? subscriptionDropped = null, + UserCredentials? userCredentials = null, + int bufferSize = DefaultBufferSize, + 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 dbClient.SubscribeToStreamAsync( + streamName, + groupName, + PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + BufferSize = bufferSize, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + } - public static class KurrentDBClientPersistentSubscriptionsExtensions { /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged /// /// /// /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static Task SubscribeToStreamAsync( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string streamName, string groupName, - PersistentSubscriptionListener listener, + Func eventAppeared, + Action? subscriptionDropped = null, + UserCredentials? userCredentials = null, + int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( + dbClient.SubscribeToStreamAsync( streamName, groupName, - listener, - new SubscribeToPersistentSubscriptionOptions(), + PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + BufferSize = bufferSize, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// /// 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 . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToStream( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string streamName, string groupName, + int bufferSize, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStream( + dbClient.SubscribeToStream( streamName, groupName, - new SubscribeToPersistentSubscriptionOptions(), + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferSize, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + + /// + /// 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 . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToStream( + this KurrentDBPersistentSubscriptionsClient dbClient, + string streamName, + string groupName, + UserCredentials? userCredentials, + CancellationToken cancellationToken = default + ) => + dbClient.SubscribeToStream( + streamName, + groupName, + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static Task SubscribeToAllAsync( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string groupName, - PersistentSubscriptionListener listener, + Func eventAppeared, + Action? subscriptionDropped = null, + UserCredentials? userCredentials = null, + int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToAllAsync( + dbClient.SubscribeToAllAsync( groupName, - listener, - new SubscribeToPersistentSubscriptionOptions(), + PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferSize, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + + /// + /// 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 . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToAll( + this KurrentDBPersistentSubscriptionsClient dbClient, + string groupName, + int bufferSize, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.SubscribeToStream( + SystemStreams.AllStream, + groupName, + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferSize, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// /// 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 . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToAll( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string groupName, + UserCredentials? userCredentials, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToAll( + dbClient.SubscribeToStream( + SystemStreams.AllStream, groupName, - new SubscribeToPersistentSubscriptionOptions(), + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs index 0509bc9cb..28d72d3cd 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs @@ -47,8 +47,8 @@ public StreamSubscriptionResult SubscribeToAll( new ReadReq { Options = new ReadReq.Types.Options { ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = options.ResolveLinkTos, - All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(options.Start), + ResolveLinks = options.ResolveLinkTos ?? false, + All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(options.Start ?? FromAll.Start), Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), Filter = GetFilterOptions(options.FilterOptions)!, UuidOption = new() { Structured = new() } @@ -101,10 +101,10 @@ public StreamSubscriptionResult SubscribeToStream( new ReadReq { Options = new ReadReq.Types.Options { ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = options.ResolveLinkTos, + ResolveLinks = options.ResolveLinkTos ?? false, Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition( streamName, - options.Start + options.Start ?? FromStream.Start ), Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), UuidOption = new() { Structured = new() } @@ -312,12 +312,12 @@ public class SubscribeToAllOptions { /// /// A (exclusive of) to start the subscription from. /// - public FromAll Start { get; set; } = FromAll.Start; + public FromAll? Start { get; set; } /// /// Whether to resolve LinkTo events automatically. /// - public bool ResolveLinkTos { get; set; } + public bool? ResolveLinkTos { get; set; } /// /// The optional to apply. @@ -392,12 +392,12 @@ public class SubscribeToStreamOptions { /// /// A (exclusive of) to start the subscription from. /// - public FromStream Start { get; set; } = FromStream.Start; + public FromStream? Start { get; set; } /// /// Whether to resolve LinkTo events automatically. /// - public bool ResolveLinkTos { get; set; } + public bool? ResolveLinkTos { get; set; } /// /// The optional user credentials to perform operation with. diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs index dac2dde80..b0f404070 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs @@ -1,5 +1,4 @@ using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.PersistentSubscriptions; @@ -30,7 +29,8 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { + new PersistentSubscriptionListener{ + EventAppeared = async (subscription, e, r, ct) => { if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { await subscription.Ack(e); return; @@ -39,11 +39,11 @@ await Fixture.Subscriptions.CreateToAllAsync( firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); }, - (subscription, reason, ex) => { + SubscriptionDropped = (subscription, reason, ex) => { if (reason != SubscriptionDroppedReason.Disposed) firstNonSystemEventSource.TrySetException(ex!); - }, - TestCredentials.Root + }}, + new SubscribeToPersistentSubscriptionOptions{ UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync(() => firstNonSystemEventSource.Task.WithTimeout()); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs index 04794ea81..58b8b1379 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_with_start_from_set_to_end_position() { @@ -21,24 +24,30 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + 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)) { + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); - return; + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); } - - firstNonSystemEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstNonSystemEventSource.TrySetException(ex!); }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync(() => firstNonSystemEventSource.Task.WithTimeout()); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs index 0333b6093..1904e830b 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs @@ -26,7 +26,7 @@ await Assert.ThrowsAsync( using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: user + new SubscribeToPersistentSubscriptionOptions { UserCredentials = user } ); } ); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs index 853ecfd14..74d3aa60a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs @@ -44,7 +44,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await subscription.Messages .OfType() @@ -113,7 +116,11 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var appearedEvents = await subscription.Messages.OfType() .Take(10) diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs index 1e1769d48..9777e5db3 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs @@ -89,7 +89,7 @@ await Subscriptions.SubscribeToAllAsync( return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); }; } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs index f0bfb0a5e..9ba4201a7 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs @@ -30,7 +30,7 @@ public async Task throws_persistent_subscription_not_found() { using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs index 4b9763e69..d0d483a8f 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs @@ -19,7 +19,7 @@ await Fixture.Subscriptions.CreateToAllAsync( using var first = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); var ex = await Assert.ThrowsAsync( @@ -27,7 +27,7 @@ await Fixture.Subscriptions.CreateToAllAsync( using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); @@ -45,9 +45,11 @@ public async Task connect_to_existing_with_permissions() { 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 + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (s, reason, ex) => dropped.TrySetResult((reason, ex)), + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); Assert.NotNull(subscription); @@ -82,15 +84,17 @@ await Fixture.Subscriptions.CreateToAllAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var resolvedEvent = await firstEventSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); @@ -118,20 +122,22 @@ await Fixture.Streams.AppendToStreamAsync( 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)) { + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); - return; + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); } - - firstNonSystemEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstNonSystemEventSource.TrySetException(ex!); }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -166,20 +172,22 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); - return; + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); } - - firstNonSystemEventSource.TrySetResult(e); - await subscription.Ack(e); }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstNonSystemEventSource.TrySetException(ex!); - }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -204,9 +212,11 @@ await Fixture.Subscriptions.CreateToAllAsync( 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 + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => await subscription.Ack(e), + SubscriptionDropped = (subscription, reason, ex) => { dropped.TrySetResult((reason, ex)); } + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var (reason, exception) = await dropped.Task.WithTimeout(); @@ -234,15 +244,17 @@ await Fixture.Subscriptions.CreateToAllAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var resolvedEvent = await firstEventSource.Task.WithTimeout(); @@ -267,23 +279,25 @@ await Fixture.Subscriptions.CreateToAllAsync( // 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 - ); + new PersistentSubscriptionListener { + EventAppeared = 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 + ); + } + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + retryCountSource.TrySetException(ex!); } }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - retryCountSource.TrySetException(ex!); - }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); // Assert @@ -300,9 +314,11 @@ public async Task deleting_existing_with_subscriber() { 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 + new PersistentSubscriptionListener { + EventAppeared = async (s, e, i, ct) => await s.Ack(e), + SubscriptionDropped = (s, r, e) => dropped.TrySetResult((r, e)), + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); // todo: investigate why this test is flaky without this delay @@ -427,18 +443,22 @@ await Fixture.Subscriptions.CreateToAllAsync( 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); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + if (e.OriginalStreamId.StartsWith("test-") + && Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) { @@ -470,16 +490,18 @@ await Fixture.Subscriptions.CreateToAllAsync( using var firstSubscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, r, ct) => { - appearedEvents.Add(e); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + appearedEvents.Add(e); - if (appearedEvents.Count == events.Length) - appeared.TrySetResult(true); + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); - await s.Ack(e); + await s.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), }, - (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task, WaitForCheckpoint().WithTimeout()); @@ -491,18 +513,20 @@ await Fixture.Subscriptions.CreateToAllAsync( 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); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + s.Dispose(); + }, + SubscriptionDropped = (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in events) @@ -560,16 +584,18 @@ await Fixture.Subscriptions.CreateToAllAsync( await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, r, ct) => { - appearedEvents.Add(e); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + appearedEvents.Add(e); - if (appearedEvents.Count == events.Length) - appeared.TrySetResult(true); + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); - await s.Ack(e); + await s.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), }, - (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task, Checkpointed()).WithTimeout(); @@ -581,18 +607,20 @@ await Fixture.Subscriptions.SubscribeToAllAsync( 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); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + s.Dispose(); + }, + SubscriptionDropped = (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + }, }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in events) @@ -636,9 +664,11 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = Fixture.Subscriptions.SubscribeToAllAsync( group, - delegate { return Task.CompletedTask; }, - (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); // todo: investigate why this test is flaky without this delay @@ -695,7 +725,7 @@ await Fixture.Subscriptions.CreateToAllAsync( await s.Ack(e); }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task, WaitForCheckpoints().WithTimeout()); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs index cb19a7807..e59a772fe 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs @@ -31,7 +31,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -48,7 +48,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var sub = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var resolvedEvent = await sub.Messages diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs index 1ba566fb8..27165f482 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectToExistingWithStartFromNotSetTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectToExistingWithStartFromNotSetTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_with_start_from_not_set() { @@ -19,7 +22,10 @@ await Fixture.Streams.AppendToStreamAsync( ); await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Assert.ThrowsAsync( () => subscription.Messages diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs index e85a1ff1e..c1c7fa49d 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs @@ -1,10 +1,12 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_with_start_from_set_to_end_position() { @@ -19,9 +21,16 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End), + userCredentials: TestCredentials.Root + ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Assert.ThrowsAsync( () => subscription.Messages diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs index b12fb8646..1ec4aa4df 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs @@ -1,10 +1,12 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectWithoutReadPermissionsTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectWithoutReadPermissionsTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_without_read_all_permissions() { @@ -23,7 +25,11 @@ await Fixture.DbUsers.CreateUserWithRetry( await Assert.ThrowsAsync( async () => { - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: user); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = user } + ); + await subscription.Messages.AnyAsync().AsTask().WithTimeout(); } ); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs index 028ed270f..7673f58b3 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs @@ -46,7 +46,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await subscription.Messages .OfType() @@ -115,7 +118,8 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll(group, new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root }); var appearedEvents = await subscription.Messages.OfType() .Take(10) diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs index 0942fcebd..166583611 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs @@ -55,7 +55,10 @@ await fixture.Subscriptions.GetInfoToAllAsync(group, userCredentials: TestCreden [RetryFact] public async Task returns_result_with_normal_user_credentials() { - var result = await fixture.Subscriptions.GetInfoToAllAsync(fixture.Group, userCredentials: TestCredentials.Root); + var result = await fixture.Subscriptions.GetInfoToAllAsync( + fixture.Group, + userCredentials: TestCredentials.Root + ); Assert.Equal("$all", result.EventSource); } @@ -80,7 +83,10 @@ await Streams.AppendToStreamAsync( var counter = 0; - await using var subscription = Subscriptions.SubscribeToAll(Group, userCredentials: TestCredentials.Root); + await using var subscription = Subscriptions.SubscribeToAll( + Group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var enumerator = subscription.Messages.GetAsyncEnumerator(); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs index 314d71887..34d575db1 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs @@ -1,9 +1,10 @@ -using KurrentDB.Client; - namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllNoDefaultCredentialsTests(ITestOutputHelper output, SubscribeToAllNoDefaultCredentialsTests.CustomFixture fixture) +public class SubscribeToAllNoDefaultCredentialsTests( + ITestOutputHelper output, + SubscribeToAllNoDefaultCredentialsTests.CustomFixture fixture +) : KurrentPermanentTests(output, fixture) { [RetryFact] public async Task connect_to_existing_without_permissions() { @@ -23,7 +24,10 @@ await Assert.ThrowsAsync( public async Task throws_persistent_subscription_not_found() { var group = Fixture.GetGroupName(); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -34,7 +38,9 @@ public async Task throws_persistent_subscription_not_found() { [RetryFact] public async Task deleting_without_permissions() { - await Assert.ThrowsAsync(() => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString())); + await Assert.ThrowsAsync( + () => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString()) + ); } [RetryFact] diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs index ede5c91ef..c5411ad9c 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs @@ -44,7 +44,10 @@ await Fixture.Subscriptions.CreateToAllAsync( async Task Subscribe() { await using var subscription = - Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await subscription.Messages.AnyAsync(); } @@ -58,7 +61,10 @@ public async Task connect_to_existing_with_permissions() { // Act await using var subscription = - Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); // Assert subscription.Messages @@ -90,7 +96,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await subscription.Messages.OfType() .Select(e => e.ResolvedEvent) @@ -116,7 +125,10 @@ await Fixture.Streams.AppendToStreamAsync( } await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -150,7 +162,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -177,8 +192,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - var enumerator = subscription.Messages.GetAsyncEnumerator(); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); var ex = await Assert.ThrowsAsync( @@ -206,7 +225,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await subscription!.Messages.OfType() .Select(e => e.ResolvedEvent) @@ -230,7 +252,11 @@ await Fixture.Subscriptions.CreateToAllAsync( ); // Act - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var retryCount = await subscription.Messages.OfType() .SelectAwait( async e => { @@ -332,7 +358,10 @@ public async Task deleting_existing_with_subscriber() { await Fixture.Subscriptions.DeleteToAllAsync(group, userCredentials: TestCredentials.Root); await using var subscription = - Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -410,8 +439,8 @@ await Fixture.Subscriptions.CreateToAllAsync( var subscription = Fixture.Subscriptions.SubscribeToAll( group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions + { BufferSize = bufferCount, UserCredentials = TestCredentials.Root } ); await subscription.Messages.OfType() @@ -440,8 +469,8 @@ await Fixture.Subscriptions.CreateToAllAsync( var subscription = Fixture.Subscriptions.SubscribeToAll( group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions + { BufferSize = bufferCount, UserCredentials = TestCredentials.Root } ); await subscription!.Messages.OfType() @@ -466,8 +495,10 @@ await Fixture.Subscriptions.CreateToAllAsync( var subscription = Fixture.Subscriptions.SubscribeToAll( group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) { @@ -539,8 +570,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - var enumerator = subscription.Messages.GetAsyncEnumerator(); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoint().WithTimeout()); @@ -556,7 +591,11 @@ await Fixture.Subscriptions.CreateToAllAsync( await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, new[] { e }); } - await using var sub = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var resumed = await sub.Messages.OfType() .Select(e => e.ResolvedEvent) .Take(1) @@ -585,7 +624,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -617,8 +656,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - var enumerator = subscription.Messages.GetAsyncEnumerator(); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoint().WithTimeout()); @@ -634,7 +677,11 @@ await Fixture.Subscriptions.CreateToAllAsync( await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, [e]); } - await using var sub = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var resumed = await sub.Messages.OfType() .Select(e => e.ResolvedEvent) .Take(1) @@ -663,7 +710,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -714,7 +761,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -756,7 +806,10 @@ await Fixture.Subscriptions.CreateToAllAsync( ); await using var subscription = - Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -793,7 +846,7 @@ async Task WaitForCheckpoints() { bool firstCheckpointSet = false; await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs index 051a151c6..4b5ee1e4a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs @@ -23,7 +23,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -36,7 +40,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); - await using var sub = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await sub.Messages .OfType() diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs index 19a73cc39..7fddd698a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs @@ -183,7 +183,7 @@ await Subscriptions.SubscribeToStreamAsync( return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); for (var i = 0; i < 15; i++) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs index 89f775e2e..79e60bc90 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -22,7 +22,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( stream, group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); var ex = await Assert.ThrowsAsync( @@ -31,7 +31,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( stream, group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); @@ -56,9 +56,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - delegate { return Task.CompletedTask; }, - (s, reason, ex) => dropped.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (s, reason, ex) => dropped.TrySetResult((reason, ex)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); Assert.NotNull(subscription); @@ -86,15 +88,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var firstEvent = firstEventSource.Task; @@ -125,15 +129,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -162,15 +168,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var firstEvent = firstEventSource.Task; @@ -198,15 +206,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -238,15 +248,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var firstEvent = firstEventSource.Task; @@ -273,15 +285,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -312,15 +326,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -353,15 +369,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -393,15 +411,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -433,15 +453,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -464,7 +486,7 @@ public async Task connect_to_non_existing_with_permissions() { stream, group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); @@ -493,23 +515,25 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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 - ); + new PersistentSubscriptionListener { + EventAppeared = 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 + ); + } + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + retryCountSource.TrySetException(ex!); } }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - retryCountSource.TrySetException(ex!); - }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -537,15 +561,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -573,9 +599,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - (_, _, _, _) => Task.CompletedTask, - (_, r, e) => dropped.TrySetResult((r, e)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = (_, _, _, _) => Task.CompletedTask, + SubscriptionDropped = (_, r, e) => dropped.TrySetResult((r, e)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.DeleteToStreamAsync( @@ -608,9 +636,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - (_, _, _, _) => Task.CompletedTask, - (_, r, e) => _dropped.TrySetResult((r, e)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = (_, _, _, _) => Task.CompletedTask, + SubscriptionDropped = (_, r, e) => _dropped.TrySetResult((r, e)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.DeleteToStreamAsync( @@ -636,7 +666,7 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { const int bufferCount = 10; const int eventWriteCount = bufferCount * 2; - MessageData[] events; + MessageData[] events; TaskCompletionSource eventsReceived = new(); int eventReceivedCount = 0; @@ -664,18 +694,22 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Ack(e); + new PersistentSubscriptionListener { + EventAppeared = 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); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await eventsReceived.Task.WithTimeout(); @@ -707,18 +741,22 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Ack(e); + new PersistentSubscriptionListener { + EventAppeared = 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); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await eventsReceived.Task.WithTimeout(); @@ -746,17 +784,21 @@ await Fixture.Subscriptions.CreateToStreamAsync( 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); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) @@ -772,11 +814,11 @@ public async Task update_existing_with_check_point() { StreamPosition checkPoint = default; - TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); - TaskCompletionSource resumedSource = new(); - TaskCompletionSource appeared = new(); + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + TaskCompletionSource resumedSource = new(); + TaskCompletionSource appeared = new(); List appearedEvents = []; - MessageData[] events = Fixture.CreateTestEvents(5).ToArray(); + MessageData[] events = Fixture.CreateTestEvents(5).ToArray(); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); await Fixture.Subscriptions.CreateToStreamAsync( @@ -795,15 +837,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (s, e, _, _) => { - appearedEvents.Add(e); - await s.Ack(e); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, _, _) => { + appearedEvents.Add(e); + await s.Ack(e); - if (appearedEvents.Count == events.Length) - appeared.TrySetResult(true); + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); + }, + SubscriptionDropped = (_, reason, ex) => droppedSource.TrySetResult((reason, ex)) }, - (_, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task.WithTimeout(), Checkpointed()); @@ -821,17 +865,19 @@ await Fixture.Subscriptions.UpdateToStreamAsync( 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); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, _, _) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + }, + SubscriptionDropped = (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); @@ -844,7 +890,7 @@ await Fixture.Subscriptions.SubscribeToStreamAsync( async Task Checkpointed() { await using var subscription = Fixture.Streams.SubscribeToStream( checkPointStream, - new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -878,9 +924,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - delegate { return Task.CompletedTask; }, - (_, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (_, reason, ex) => droppedSource.TrySetResult((reason, ex)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.UpdateToStreamAsync( @@ -925,18 +973,22 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Nack(PersistentSubscriptionNakEventAction.Park, "fail", e); + new PersistentSubscriptionListener { + EventAppeared = 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); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = TestCredentials.Root, + BufferSize = bufferCount + } ); foreach (var e in events) diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs index 9380a1cc2..ee99f9f4a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs @@ -23,7 +23,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync( diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs index 41f82f039..3b3d08101 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs @@ -172,8 +172,13 @@ await Subscriptions.CreateToStreamAsync( ); var counter = 0; - Subscription = Subscriptions.SubscribeToStream(Stream, Group, userCredentials: TestCredentials.Root); - Enumerator = Subscription.Messages.GetAsyncEnumerator(); + Subscription = Subscriptions.SubscribeToStream( + Stream, + Group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + Enumerator = Subscription.Messages.GetAsyncEnumerator(); for (var i = 0; i < 15; i++) { await Streams.AppendToStreamAsync( diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs index f1e683a43..e560a66ed 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs @@ -39,14 +39,21 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - var ex = await Assert.ThrowsAsync(() => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout())); + var ex = await Assert.ThrowsAsync( + () => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout()) + ); Assert.Equal(stream, ex.StreamName); Assert.Equal(group, ex.GroupName); return; async Task Subscribe() { - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + await subscription.Messages.AnyAsync(); } } @@ -58,9 +65,16 @@ public async Task connect_to_existing_with_permissions() { await Fixture.Subscriptions.CreateToStreamAsync(stream, group, new(), userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); - Assert.True(await subscription.Messages.FirstAsync().AsTask().WithTimeout() is PersistentSubscriptionMessage.SubscriptionConfirmation); + Assert.True( + await subscription.Messages.FirstAsync().AsTask().WithTimeout() is PersistentSubscriptionMessage + .SubscriptionConfirmation + ); } [RetryFact] @@ -77,7 +91,12 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var resolvedEvent = await subscription.Messages.OfType() .Select(e => e.ResolvedEvent) .FirstOrDefaultAsync().AsTask().WithTimeout(); @@ -88,9 +107,9 @@ await Fixture.Subscriptions.CreateToStreamAsync( [RetryFact] public async Task connect_to_existing_with_start_from_beginning_and_no_stream() { - var stream = Fixture.GetStreamName(); - var group = Fixture.GetGroupName(); - var events = Fixture.CreateTestEvents().ToArray(); + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + var events = Fixture.CreateTestEvents().ToArray(); var messageId = events.Single().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( @@ -100,7 +119,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -129,7 +152,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -159,11 +182,12 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync( - () => subscription.Messages.AnyAsync(message => message is PersistentSubscriptionMessage.Event).AsTask().WithTimeout(TimeSpan.FromMilliseconds(250)) + () => subscription.Messages.AnyAsync(message => message is PersistentSubscriptionMessage.Event).AsTask() + .WithTimeout(TimeSpan.FromMilliseconds(250)) ); } @@ -181,7 +205,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.TestUser1); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.TestUser1 } + ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); } @@ -204,7 +232,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -234,7 +262,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -265,7 +293,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -295,7 +323,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -313,7 +341,12 @@ public async Task connect_to_non_existing_with_permissions() { var stream = Fixture.GetStreamName(); var group = Fixture.GetGroupName(); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + Assert.True( await subscription.Messages.OfType() .AnyAsync() @@ -336,7 +369,12 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); var retryCount = await subscription.Messages.OfType() .SelectAwait( @@ -376,7 +414,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -491,7 +533,11 @@ public async Task deleting_existing_with_subscriber() { await Fixture.Subscriptions.DeleteToStreamAsync(stream, group, userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -541,8 +587,10 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await subscription!.Messages.OfType() @@ -562,7 +610,7 @@ public async Task happy_case_catching_up_to_normal_events_manual_ack() { var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); foreach (var e in events) - await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { e }); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -574,8 +622,10 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await subscription!.Messages.OfType() @@ -604,8 +654,10 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) @@ -651,8 +703,13 @@ public async Task update_existing_with_subscribers() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); await Fixture.Subscriptions.CreateToStreamAsync(stream, group, new(), userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); - await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); @@ -700,7 +757,14 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } + ); foreach (var e in events) await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 1394d2b91..3fe1fb0e2 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -81,8 +81,15 @@ public async Task subscribe_to_stream_without_options_does_NOT_deserialize_resol // When var group = await CreateToStreamSubscription(stream); +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Subscriptions - .SubscribeToStream(stream, group, int.MaxValue).Take(2) + .SubscribeToStream( + stream, + group, + bufferSize: int.MaxValue, + userCredentials: TestCredentials.Root + ).Take(2) +#pragma warning restore CS0618 // Type or member is obsolete .ToListAsync(); // Then @@ -96,8 +103,13 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved // When var group = await CreateToAllSubscription(stream); +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Subscriptions - .SubscribeToAll(group, int.MaxValue) + .SubscribeToAll( + group, + int.MaxValue + ) +#pragma warning restore CS0618 // Type or member is obsolete .Take(2) .ToListAsync(); @@ -381,8 +393,9 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = + var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.Any, messages); + Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 7a18109a7..c846c1541 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -72,8 +72,10 @@ public async Task subscribe_to_stream_without_options_does_NOT_deserialize_resol var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Streams - .SubscribeToStream(stream).Take(2) + .SubscribeToStream(stream, FromStream.Start).Take(2) +#pragma warning restore CS0618 // Type or member is obsolete .ToListAsync(); // Then @@ -86,10 +88,10 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Streams - .SubscribeToAll( - new SubscribeToAllOptions().WithFilter(new SubscriptionFilterOptions(StreamFilter.Prefix(stream))) - ) + .SubscribeToAll(FromAll.Start, filterOptions: new SubscriptionFilterOptions(StreamFilter.Prefix(stream))) +#pragma warning restore CS0618 // Type or member is obsolete .Take(2) .ToListAsync(); From b135a832e7bcb40e4141f047b061c7d331a14d55 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Tue, 25 Mar 2025 10:22:04 +0100 Subject: [PATCH 18/23] [DEVEX-222] Added samples showcasing auto-serialization usage --- samples/appending-events/Program.cs | 97 +++++++++++++++++++++++++++-- samples/quick-start/Program.cs | 18 ++---- samples/reading-events/Program.cs | 38 ++++++++--- 3 files changed, 128 insertions(+), 25 deletions(-) diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index b934119ad..16936d536 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -1,6 +1,4 @@ -using KurrentDB.Client; - -#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS8321 // Local function is declared but never used var settings = KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false"); @@ -9,9 +7,12 @@ await using var client = new KurrentDBClient(settings); await AppendToStream(client); +await AppendToStreamWithAutoSerialization(client); +await AppendToStreamWithMetadataAndAutoSerialization(client); await AppendWithConcurrencyCheck(client); await AppendWithNoStream(client); await AppendWithSameId(client); +await AppendWithSameIdAndAutoSerialization(client); return; @@ -32,12 +33,56 @@ await client.AppendToStreamAsync( #endregion append-to-stream } +static async Task AppendToStreamWithAutoSerialization(KurrentDBClient client) { + #region append-to-stream-with-auto-serialization + + var shoppingCartId = Guid.NewGuid(); + + var @event = new ProductItemAddedToShoppingCart( + shoppingCartId, + new PricedProductItem("t-shirt", 1, 99.99m) + ); + + await client.AppendToStreamAsync( + $"shopping_cart-{shoppingCartId}", + StreamState.NoStream, + [@event] + ); + + #endregion append-to-stream-with-auto-serialization +} + +static async Task AppendToStreamWithMetadataAndAutoSerialization(KurrentDBClient client) { + #region append-to-stream-with-metadata-and-auto-serialization + + var shoppingCartId = Guid.NewGuid(); + var clientId = Guid.NewGuid().ToString(); + + var @event = new ProductItemAddedToShoppingCart( + shoppingCartId, + new PricedProductItem("t-shirt", 1, 99.99m) + ); + + var metadata = new ShoppingCartMetadata(clientId); + + var message = Message.From(@event, metadata); + + await client.AppendToStreamAsync( + $"shopping_cart-{shoppingCartId}", + StreamState.NoStream, + [message] + ); + + #endregion append-to-stream-with-metadata-and-auto-serialization +} + static async Task AppendWithSameId(KurrentDBClient client) { #region append-duplicate-event var eventData = MessageData.From( "some-event", - "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray(), + messageId: Uuid.NewUuid() ); await client.AppendToStreamAsync( @@ -56,6 +101,37 @@ await client.AppendToStreamAsync( #endregion append-duplicate-event } +static async Task AppendWithSameIdAndAutoSerialization(KurrentDBClient client) { + #region append-duplicate-event-with-serialization + + var shoppingCartId = Guid.NewGuid(); + + var @event = new ProductItemAddedToShoppingCart( + shoppingCartId, + new PricedProductItem("t-shirt", 1, 99.99m) + ); + + var message = Message.From( + @event, + messageId: Uuid.NewUuid() + ); + + await client.AppendToStreamAsync( + "same-event-stream", + StreamState.Any, + [message] + ); + + // attempt to append the same event again + await client.AppendToStreamAsync( + "same-event-stream", + StreamState.Any, + [message] + ); + + #endregion append-duplicate-event-with-serialization +} + static async Task AppendWithNoStream(KurrentDBClient client) { #region append-with-no-stream @@ -144,3 +220,16 @@ await client.AppendToStreamAsync( #endregion overriding-user-credentials } + +public record PricedProductItem( + string ProductId, + int Quantity, + decimal UnitPrice +); + +public record ProductItemAddedToShoppingCart( + Guid CartId, + PricedProductItem ProductItem +); + +public record ShoppingCartMetadata(string ClientId); diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 6170072f3..bfb35f386 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -1,7 +1,4 @@ -using System.Text.Json; -using KurrentDB.Client; - -var tokenSource = new CancellationTokenSource(); +var tokenSource = new CancellationTokenSource(); var cancellationToken = tokenSource.Token; #region createClient @@ -21,11 +18,6 @@ ImportantData = "I wrote my first event!" }; -var eventData = MessageData.From( - "TestEvent", - JsonSerializer.SerializeToUtf8Bytes(evt) -); - #endregion createEvent #region appendEvents @@ -33,7 +25,7 @@ await client.AppendToStreamAsync( "some-stream", StreamState.Any, - [eventData], + [evt], cancellationToken: cancellationToken ); @@ -44,7 +36,7 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "some-stream", StreamState.Any, - [eventData], + [evt], new AppendToStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken ); @@ -58,7 +50,9 @@ await client.AppendToStreamAsync( cancellationToken: cancellationToken ); -var events = await result.ToListAsync(cancellationToken); +var events = await result + .DeserializedData() + .ToListAsync(cancellationToken); #endregion readStream diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index 513c48ecb..71be81186 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -1,15 +1,13 @@ -using KurrentDB.Client; - -#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS8321 // Local function is declared but never used await using var client = new KurrentDBClient(KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false")); var events = Enumerable.Range(0, 20) .Select( - r => MessageData.From( - "some-event", - Encoding.UTF8.GetBytes($"{{\"id\": \"{r}\" \"value\": \"some value\"}}") - ) + _ => new TestEvent { + EntityId = Guid.NewGuid().ToString(), + ImportantData = Guid.NewGuid().ToString() + } ); await client.AppendToStreamAsync( @@ -19,6 +17,7 @@ await client.AppendToStreamAsync( ); await ReadFromStream(client); +await ReadFromStreamWithDisabledAutoSerialization(client); return; @@ -31,7 +30,8 @@ static async Task ReadFromStream(KurrentDBClient client) { #region iterate-stream - await foreach (var @event in events) Console.WriteLine(@event.DeserializedData); + await foreach (var @event in events) + Console.WriteLine(@event.DeserializedData); #endregion iterate-stream @@ -43,6 +43,21 @@ static async Task ReadFromStream(KurrentDBClient client) { #endregion } +static async Task ReadFromStreamWithDisabledAutoSerialization(KurrentDBClient client) { + #region read-from-stream-with-disabled-auto-serialization + + var events = client.ReadStreamAsync("some-stream", new ReadStreamOptions().DisableAutoSerialization()); + + #endregion read-from-stream-with-disabled-auto-serialization + + #region iterate-stream-with-disabled-auto-serialization + + await foreach (var resolvedEvent in events) + Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + + #endregion iterate-stream-with-disabled-auto-serialization +} + static async Task ReadFromStreamMessages(KurrentDBClient client) { #region read-from-stream-messages @@ -65,7 +80,7 @@ static async Task ReadFromStreamMessages(KurrentDBClient client) { return; case StreamMessage.Event(var resolvedEvent): - Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + Console.WriteLine(resolvedEvent.DeserializedData); break; case StreamMessage.FirstStreamPosition(var sp): @@ -295,3 +310,8 @@ static void ReadAllResolvingLinkTos(KurrentDBClient client, CancellationToken ca #endregion read-from-all-stream-resolving-link-Tos } + +public class TestEvent { + public string? EntityId { get; set; } + public string? ImportantData { get; set; } +} From 3a70a552187702e63c33df3d2a052d6cbe0453f5 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Wed, 26 Mar 2025 14:08:06 +0100 Subject: [PATCH 19/23] [DEVEX-222] Reshaped type resolution strategy to take EventRecord instead of just raw string. That should enable current users to override their strategy if they used something more than just message type name from EventRecord. It needs to be EventRecord and not ResolvedEvent, as resolution happens before creating ResolvedEvent. --- .../Core/Serialization/MessageSerializer.cs | 4 +- .../MessageTypeResolutionStrategy.cs | 33 +++++++------- .../Core/Serialization/SchemaRegistry.cs | 12 ++--- .../Core/Serialization/SchemaRegistryTests.cs | 45 +++++++++++++------ ...ializationTests.PersistentSubscriptions.cs | 11 ++--- .../SerializationTests.Subscriptions.cs | 12 ++--- .../Serialization/SerializationTests.cs | 11 ++--- 7 files changed, 74 insertions(+), 54 deletions(-) diff --git a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs index 302d904a4..3f0ff0e69 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs @@ -66,7 +66,7 @@ public bool TryDeserialize(EventRecord record, out Message? deserialized) { #else public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized) { #endif - if (!schemaRegistry.TryResolveClrType(record.EventType, out var clrType)) { + if (!schemaRegistry.TryResolveClrType(record, out var clrType)) { deserialized = null; return false; } @@ -81,7 +81,7 @@ public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? } object? metadata = record.Metadata.Length > 0 - && schemaRegistry.TryResolveClrMetadataType(record.EventType, out var clrMetadataType) + && schemaRegistry.TryResolveClrMetadataType(record, out var clrMetadataType) ? _metadataSerializer.Deserialize(record.Metadata, clrMetadataType!) : null; diff --git a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs index d60749fb1..4de679d85 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs @@ -7,15 +7,15 @@ public interface IMessageTypeNamingStrategy { string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext); #if NET48 - bool TryResolveClrType(string messageTypeName, out Type? type); + bool TryResolveClrType(EventRecord record, out Type? type); #else - bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type); + bool TryResolveClrType(EventRecord messageTypeName, [NotNullWhen(true)] out Type? type); #endif #if NET48 - bool TryResolveClrMetadataType(string messageTypeName, out Type? type); + bool TryResolveClrMetadataType(EventRecord record, out Type? type); #else - bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type); + bool TryResolveClrMetadataType(EventRecord messageTypeName, [NotNullWhen(true)] out Type? type); #endif } @@ -36,13 +36,13 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(EventRecord record, out Type? type) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif type = messageTypeRegistry.GetOrAddClrType( - messageTypeName, - _ => messageTypeNamingStrategy.TryResolveClrType(messageTypeName, out var resolvedType) + record.EventType, + _ => messageTypeNamingStrategy.TryResolveClrType(record, out var resolvedType) ? resolvedType : null ); @@ -51,13 +51,13 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif type = messageTypeRegistry.GetOrAddClrType( - $"{messageTypeName}-metadata", - _ => messageTypeNamingStrategy.TryResolveClrMetadataType(messageTypeName, out var resolvedType) + $"{record}-metadata", + _ => messageTypeNamingStrategy.TryResolveClrMetadataType(record, out var resolvedType) ? resolvedType : null ); @@ -73,10 +73,11 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte $"{resolutionContext.CategoryName}-{messageType.FullName}"; #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(EventRecord record, out Type? type) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif + var messageTypeName = record.EventType; var categorySeparatorIndex = messageTypeName.IndexOf('-'); if (categorySeparatorIndex == -1 || categorySeparatorIndex == messageTypeName.Length - 1) { @@ -92,9 +93,9 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif type = _defaultMetadataType; return true; diff --git a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs index dc27c4530..445a74697 100644 --- a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs @@ -37,18 +37,18 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte messageTypeNamingStrategy.ResolveTypeName(messageType, resolutionContext); #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) => + public bool TryResolveClrType(EventRecord record, out Type? type) => #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) => + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) => #endif - messageTypeNamingStrategy.TryResolveClrType(messageTypeName, out type); + messageTypeNamingStrategy.TryResolveClrType(record, out type); #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) => + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) => #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) => + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) => #endif - messageTypeNamingStrategy.TryResolveClrMetadataType(messageTypeName, out type); + messageTypeNamingStrategy.TryResolveClrMetadataType(record, out type); public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) { var messageTypeNamingStrategy = diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs index e23b699cd..bbbeaf7f2 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs @@ -109,10 +109,10 @@ public void From_WithMessageTypeMap_RegistersTypes() { // Then // Verify types can be resolved - Assert.True(registry.TryResolveClrType("test-event-1", out var type1)); + Assert.True(registry.TryResolveClrType(TestRecord("test-event-1"), out var type1)); Assert.Equal(typeof(TestEvent1), type1); - Assert.True(registry.TryResolveClrType("test-event-2", out var type2)); + Assert.True(registry.TryResolveClrType(TestRecord("test-event-2"), out var type2)); Assert.Equal(typeof(TestEvent2), type2); } @@ -165,13 +165,13 @@ public void From_WithCategoryMessageTypesMap_WithDefaultMessageAutoRegistration( Assert.Equal(expectedTypeName3, typeName3); // Verify types can be resolved by the type names - Assert.True(registry.TryResolveClrType(typeName1, out var resolvedType1)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName1), out var resolvedType1)); Assert.Equal(typeof(TestEvent1), resolvedType1); - Assert.True(registry.TryResolveClrType(typeName2, out var resolvedType2)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName2), out var resolvedType2)); Assert.Equal(typeof(TestEvent2), resolvedType2); - Assert.True(registry.TryResolveClrType(typeName3, out var resolvedType3)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName3), out var resolvedType3)); Assert.Equal(typeof(TestEvent3), resolvedType3); } @@ -205,13 +205,13 @@ public void From_WithCategoryMessageTypesMap_RegistersTypesWithCategories() { ); // Verify types can be resolved by the type names - Assert.True(registry.TryResolveClrType(typeName1, out var resolvedType1)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName1), out var resolvedType1)); Assert.Equal(typeof(TestEvent1), resolvedType1); - Assert.True(registry.TryResolveClrType(typeName2, out var resolvedType2)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName2), out var resolvedType2)); Assert.Equal(typeof(TestEvent2), resolvedType2); - Assert.True(registry.TryResolveClrType(typeName3, out var resolvedType3)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName3), out var resolvedType3)); Assert.Equal(typeof(TestEvent3), resolvedType3); } @@ -250,10 +250,27 @@ public void From_WithNoMessageTypeNamingStrategy_UsesDefaultStrategy() { // Then // The wrapped default strategy should use our metadata type - Assert.True(registry.TryResolveClrMetadataType("some-type", out var defaultMetadataType)); + Assert.True(registry.TryResolveClrMetadataType(TestRecord("some-type"), out var defaultMetadataType)); Assert.Equal(typeof(TestMetadata), defaultMetadataType); } + + static EventRecord TestRecord( + string eventType + ) => + new( + Uuid.NewUuid().ToString(), + Uuid.NewUuid(), + StreamPosition.FromInt64(0), + new Position(1, 1), + new Dictionary { + { Constants.Metadata.Type, eventType }, + { Constants.Metadata.Created, DateTime.UtcNow.ToTicksSinceEpoch().ToString() }, + { Constants.Metadata.ContentType, Constants.Metadata.ContentTypes.ApplicationJson } + }, + ReadOnlyMemory.Empty, + ReadOnlyMemory.Empty + ); // Custom naming strategy for testing class TestNamingStrategy : IMessageTypeNamingStrategy { @@ -261,13 +278,13 @@ public string ResolveTypeName(Type type, MessageTypeNamingResolutionContext cont return $"Custom-{type.Name}-{context.CategoryName}"; } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? clrType) + public bool TryResolveClrType(EventRecord record, out Type? clrType) #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? clrType) + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? clrType) #endif { // Simple implementation for testing - clrType = messageTypeName.StartsWith("Custom-TestEvent1") + clrType = record.EventType.StartsWith("Custom-TestEvent1") ? typeof(TestEvent1) : null; @@ -275,9 +292,9 @@ public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Ty } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? clrType) + public bool TryResolveClrMetadataType(EventRecord record, out Type? clrType) #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? clrType) + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? clrType) #endif { clrType = typeof(TestMetadata); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 3fe1fb0e2..5fc4c76c8 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -209,20 +209,21 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(EventRecord record, out Type? type) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; + var messageTypeName = record.EventType; + var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; type = Type.GetType(typeName); return type != null; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif type = null; return false; diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index c846c1541..db76cda86 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; -using KurrentDB.Client; using KurrentDB.Client.Core.Serialization; using Kurrent.Diagnostics.Tracing; @@ -176,20 +175,21 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(EventRecord record, out Type? type) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; + var messageTypeName = record.EventType; + var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; type = Type.GetType(typeName); return type != null; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif type = null; return false; diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index af87e6fd2..5dbb817e5 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -222,20 +222,21 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrType(EventRecord record, out Type? type) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; + var messageTypeName = record.EventType; + var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; type = Type.GetType(typeName); return type != null; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif type = null; return false; From 27e1c6114b2f96db0461d2a9aa89a4ef9a1a749e Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Wed, 26 Mar 2025 15:56:36 +0100 Subject: [PATCH 20/23] [DEVEX-222] Changed message type registration to allow multiple message type names mapping to the same CLR class --- .../KurrentDBClientSerializationSettings.cs | 12 ++++++------ .../Core/Serialization/MessageTypeRegistry.cs | 8 ++++---- .../Core/Serialization/SchemaRegistry.cs | 6 +++--- .../Serialization/MessageTypeRegistryTests.cs | 18 +++++++++--------- ...rializationTests.PersistentSubscriptions.cs | 4 ++-- .../SerializationTests.Subscriptions.cs | 4 ++-- .../Serialization/SerializationTests.cs | 4 ++-- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs index 567a38003..c85245fc1 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs @@ -41,7 +41,7 @@ public class KurrentDBClientSerializationSettings { /// /// Allows to register mapping of CLR message types to their corresponding message type names used in serialized messages. /// - public IDictionary MessageTypeMap { get; set; } = new Dictionary(); + public IDictionary MessageTypeMap { get; set; } = new Dictionary(); /// /// Registers CLR message types that can be appended to the specific stream category. @@ -233,7 +233,7 @@ public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(strin /// /// public KurrentDBClientSerializationSettings RegisterMessageType(string typeName) => - RegisterMessageType(typeof(T), typeName); + RegisterMessageType(typeName, typeof(T)); /// /// Registers a message type with a specific type name. @@ -241,8 +241,8 @@ public KurrentDBClientSerializationSettings RegisterMessageType(string typeNa /// The message type to register. /// The type name to register for the message type. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageType(Type type, string typeName) { - MessageTypeMap[type] = typeName; + public KurrentDBClientSerializationSettings RegisterMessageType(string typeName, Type type) { + MessageTypeMap[typeName] = type; return this; } @@ -252,7 +252,7 @@ public KurrentDBClientSerializationSettings RegisterMessageType(Type type, strin /// /// Dictionary mapping types to their type names. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageTypes(IDictionary typeMap) { + public KurrentDBClientSerializationSettings RegisterMessageTypes(IDictionary typeMap) { foreach (var map in typeMap) { MessageTypeMap[map.Key] = map.Value; } @@ -301,7 +301,7 @@ internal KurrentDBClientSerializationSettings Clone() { BytesSerializer = BytesSerializer, JsonSerializer = JsonSerializer, DefaultContentType = DefaultContentType, - MessageTypeMap = new Dictionary(MessageTypeMap), + MessageTypeMap = new Dictionary(MessageTypeMap), CategoryMessageTypesMap = new Dictionary(CategoryMessageTypesMap), MessageTypeNamingStrategy = MessageTypeNamingStrategy }; diff --git a/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs b/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs index 885bcca1c..cff13b690 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs @@ -3,7 +3,7 @@ namespace KurrentDB.Client.Core.Serialization; interface IMessageTypeRegistry { - void Register(Type messageType, string messageTypeName); + void Register(string messageTypeName, Type messageType); string? GetTypeName(Type messageType); string GetOrAddTypeName(Type clrType, Func getTypeName); Type? GetClrType(string messageTypeName); @@ -14,7 +14,7 @@ class MessageTypeRegistry : IMessageTypeRegistry { readonly ConcurrentDictionary _typeMap = new(); readonly ConcurrentDictionary _typeNameMap = new(); - public void Register(Type messageType, string messageTypeName) { + public void Register(string messageTypeName, Type messageType) { _typeNameMap.AddOrUpdate(messageType, messageTypeName, (_, _) => messageTypeName); _typeMap.AddOrUpdate(messageTypeName, messageType, (_, type) => type); } @@ -63,9 +63,9 @@ public string GetOrAddTypeName(Type clrType, Func getTypeName) => static class MessageTypeRegistryExtensions { public static void Register(this IMessageTypeRegistry messageTypeRegistry, string messageTypeName) => - messageTypeRegistry.Register(typeof(T), messageTypeName); + messageTypeRegistry.Register(messageTypeName, typeof(T)); - public static void Register(this IMessageTypeRegistry messageTypeRegistry, IDictionary typeMap) { + public static void Register(this IMessageTypeRegistry messageTypeRegistry, IDictionary typeMap) { foreach (var map in typeMap) { messageTypeRegistry.Register(map.Key, map.Value); } diff --git a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs index 445a74697..0520d0992 100644 --- a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs @@ -82,7 +82,7 @@ public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) ); } - static Dictionary ResolveMessageTypeUsingNamingStrategy( + static Dictionary ResolveMessageTypeUsingNamingStrategy( IDictionary categoryMessageTypesMap, IMessageTypeNamingStrategy messageTypeNamingStrategy ) => @@ -100,7 +100,7 @@ IMessageTypeNamingStrategy messageTypeNamingStrategy ) ) .ToDictionary( - ks => ks.Type, - vs => vs.TypeName + ks => ks.TypeName, + vs => vs.Type ); } diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs index 4c92b4668..9dc9d6921 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs @@ -11,7 +11,7 @@ public void Register_StoresTypeAndTypeName() { const string typeName = "test-event-1"; // When - registry.Register(type, typeName); + registry.Register(typeName, type); // Then Assert.Equal(typeName, registry.GetTypeName(type)); @@ -19,7 +19,7 @@ public void Register_StoresTypeAndTypeName() { } [Fact] - public void Register_CalledTwiceForTheSameTypeOverridesExistingRegistration() { + public void Register_CalledTwiceForTheSameTypeOverridesExistingTypeRegistration() { // Given var registry = new MessageTypeRegistry(); var type = typeof(TestEvent1); @@ -27,8 +27,8 @@ public void Register_CalledTwiceForTheSameTypeOverridesExistingRegistration() { const string updatedTypeName = "updated-name"; // When - registry.Register(type, originalTypeName); - registry.Register(type, updatedTypeName); + registry.Register(originalTypeName, type); + registry.Register(updatedTypeName, type); // Then Assert.Equal(updatedTypeName, registry.GetTypeName(type)); @@ -69,7 +69,7 @@ public void GetOrAddTypeName_ReturnsExistingTypeName() { var type = typeof(TestEvent1); const string existingTypeName = "existing-type-name"; - registry.Register(type, existingTypeName); + registry.Register(existingTypeName, type); var typeResolutionCount = 0; // When @@ -116,7 +116,7 @@ public void GetOrAddClrType_ReturnsExistingClrType() { var registry = new MessageTypeRegistry(); var type = typeof(TestEvent1); const string typeName = "test-event-name"; - registry.Register(type, typeName); + registry.Register(typeName, type); var typeResolutionCount = 0; // When @@ -190,9 +190,9 @@ public void RegisterGeneric_RegistersTypeWithTypeName() { public void RegisterDictionary_RegistersMultipleTypes() { // Given var registry = new MessageTypeRegistry(); - var typeMap = new Dictionary { - { typeof(TestEvent1), "test-event-1" }, - { typeof(TestEvent2), "test-event-2" } + var typeMap = new Dictionary { + { "test-event-1", typeof(TestEvent1) }, + { "test-event-2", typeof(TestEvent2) } }; // When diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 5fc4c76c8..9838989e9 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -122,9 +122,9 @@ public static TheoryData> C (settings, typeName) => settings.RegisterMessageType(typeName), (settings, typeName) => - settings.RegisterMessageType(typeof(UserRegistered), typeName), + settings.RegisterMessageType(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeof(UserRegistered), typeName } }) + settings.RegisterMessageTypes(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index db76cda86..1dd848898 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -103,9 +103,9 @@ public static TheoryData> C (settings, typeName) => settings.RegisterMessageType(typeName), (settings, typeName) => - settings.RegisterMessageType(typeof(UserRegistered), typeName), + settings.RegisterMessageType(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeof(UserRegistered), typeName } }) + settings.RegisterMessageTypes(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index 5dbb817e5..04957b7ca 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -150,9 +150,9 @@ public static TheoryData> C (settings, typeName) => settings.RegisterMessageType(typeName), (settings, typeName) => - settings.RegisterMessageType(typeof(UserRegistered), typeName), + settings.RegisterMessageType(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeof(UserRegistered), typeName } }) + settings.RegisterMessageTypes(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } From dba4342b4c136f2d82eb571d981e42f94434503e Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 27 Mar 2025 10:52:41 +0100 Subject: [PATCH 21/23] [DEVEX-222] Nested Message type mapping into settings object --- .../KurrentDBClientSerializationSettings.cs | 259 ++++++++++-------- .../Core/Serialization/SchemaRegistry.cs | 14 +- .../Serialization/MessageSerializerTests.cs | 9 +- .../Core/Serialization/SchemaRegistryTests.cs | 15 +- ...ializationTests.PersistentSubscriptions.cs | 12 +- .../SerializationTests.Subscriptions.cs | 9 +- .../Serialization/SerializationTests.cs | 10 +- 7 files changed, 180 insertions(+), 148 deletions(-) diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs index c85245fc1..555368362 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs @@ -38,23 +38,7 @@ public class KurrentDBClientSerializationSettings { /// public IMessageTypeNamingStrategy? MessageTypeNamingStrategy { get; set; } - /// - /// Allows to register mapping of CLR message types to their corresponding message type names used in serialized messages. - /// - public IDictionary MessageTypeMap { get; set; } = new Dictionary(); - - /// - /// Registers CLR message types that can be appended to the specific stream category. - /// Types will have message type names resolved based on the used - /// - public IDictionary CategoryMessageTypesMap { get; set; } = new Dictionary(); - - /// - /// Specifies the CLR type that should be used when deserializing metadata for all events. - /// When set, the client will attempt to deserialize event metadata into this type. - /// If not provided, will be used. - /// - public Type? DefaultMetadataType { get; set; } + public MessageTypeMappingSettings MessageTypeMapping { get; set; } = new MessageTypeMappingSettings(); /// /// Creates a new instance of serialization settings with either default values or custom configuration. @@ -181,113 +165,24 @@ IMessageTypeNamingStrategy messageTypeNamingStrategy } /// - /// Associates a message type with a specific stream category to enable automatic deserialization. - /// In event sourcing, streams are often prefixed with a category (e.g., "user-123", "order-456"). - /// This method tells the client which message types can appear in streams of a given category. - /// - /// The event or message type that can appear in the category's streams. - /// The category prefix (e.g., "user", "order", "account"). - /// The current instance for method chaining. - /// - /// - /// // Register event types that can appear in user streams - /// settings.RegisterMessageTypeForCategory<UserCreated>("user") - /// .RegisterMessageTypeForCategory<UserUpdated>("user") - /// .RegisterMessageTypeForCategory<UserDeleted>("user"); - /// - /// - public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(string categoryName) => - RegisterMessageTypeForCategory(categoryName, typeof(T)); - - /// - /// Registers multiple message types for a specific stream category. - /// - /// The category name to register the types with. - /// The message types to register. - /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(string categoryName, params Type[] types) { - CategoryMessageTypesMap[categoryName] = CategoryMessageTypesMap.TryGetValue(categoryName, out var current) - ? [..current, ..types] - : types; - - return this; - } - - /// - /// Maps a .NET type to a specific message type name that will be stored in the message metadata. - /// This mapping is used during automatic deserialization, as it tells the client which CLR type - /// to instantiate when encountering a message with a particular type name in the database. - /// - /// The .NET type to register (typically a message class). - /// The string identifier to use for this type in the database. - /// The current instance for method chaining. - /// - /// The type name is often different from the .NET type name to support versioning and evolution - /// of your domain model without breaking existing stored messages. - /// - /// - /// - /// // Register me types with their corresponding type identifiers - /// settings.RegisterMessageType<UserCreated>("user-created-v1") - /// .RegisterMessageType<OrderPlaced>("order-placed-v2"); - /// - /// - public KurrentDBClientSerializationSettings RegisterMessageType(string typeName) => - RegisterMessageType(typeName, typeof(T)); - - /// - /// Registers a message type with a specific type name. - /// - /// The message type to register. - /// The type name to register for the message type. - /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageType(string typeName, Type type) { - MessageTypeMap[typeName] = type; - - return this; - } - - /// - /// Registers multiple message types with their corresponding type names. + /// Configures which serialization format (JSON or binary) is used by default when writing messages + /// where the content type isn't explicitly specified. The default content type is "application/json" /// - /// Dictionary mapping types to their type names. + /// The serialization format content type /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageTypes(IDictionary typeMap) { - foreach (var map in typeMap) { - MessageTypeMap[map.Key] = map.Value; - } + public KurrentDBClientSerializationSettings UseContentType(ContentType contentType) { + DefaultContentType = contentType; return this; } /// - /// Configures a strongly-typed metadata class for all mes in the system. - /// This enables accessing metadata properties in a type-safe manner rather than using dynamic objects. - /// - /// The metadata class type containing properties matching the expected metadata fields. - /// The current instance for method chaining. - public KurrentDBClientSerializationSettings UseMetadataType() => - UseMetadataType(typeof(T)); - - /// - /// Configures a strongly-typed metadata class for all mes in the system. - /// This enables accessing metadata properties in a type-safe manner rather than using dynamic objects. - /// - /// The metadata class type containing properties matching the expected metadata fields. - /// The current instance for method chaining. - public KurrentDBClientSerializationSettings UseMetadataType(Type type) { - DefaultMetadataType = type; - - return this; - } - /// - /// Configures which serialization format (JSON or binary) is used by default when writing messages - /// where the content type isn't explicitly specified. The default content type is "application/json" + /// Allows to configure message type mapping /// - /// The serialization format content type + /// /// The current instance for method chaining. - public KurrentDBClientSerializationSettings UseContentType(ContentType contentType) { - DefaultContentType = contentType; + public KurrentDBClientSerializationSettings ConfigureTypeMap(Action configure) { + configure(MessageTypeMapping); return this; } @@ -301,8 +196,7 @@ internal KurrentDBClientSerializationSettings Clone() { BytesSerializer = BytesSerializer, JsonSerializer = JsonSerializer, DefaultContentType = DefaultContentType, - MessageTypeMap = new Dictionary(MessageTypeMap), - CategoryMessageTypesMap = new Dictionary(CategoryMessageTypesMap), + MessageTypeMapping = MessageTypeMapping.Clone(), MessageTypeNamingStrategy = MessageTypeNamingStrategy }; } @@ -369,3 +263,134 @@ public enum AutomaticDeserialization { /// Enabled = 1 } + +/// +/// Represents message type mapping settings +/// +public class MessageTypeMappingSettings { + /// + /// Allows to register mapping of CLR message types to their corresponding message type names used in serialized messages. + /// + public IDictionary TypeMap { get; set; } = new Dictionary(); + + /// + /// Registers CLR message types that can be appended to the specific stream category. + /// Types will have message type names resolved based on the used + /// + public IDictionary CategoryTypesMap { get; set; } = new Dictionary(); + + /// + /// Specifies the CLR type that should be used when deserializing metadata for all events. + /// When set, the client will attempt to deserialize event metadata into this type. + /// If not provided, will be used. + /// + public Type? DefaultMetadataType { get; set; } + + /// + /// Associates a message type with a specific stream category to enable automatic deserialization. + /// In event sourcing, streams are often prefixed with a category (e.g., "user-123", "order-456"). + /// This method tells the client which message types can appear in streams of a given category. + /// + /// The event or message type that can appear in the category's streams. + /// The category prefix (e.g., "user", "order", "account"). + /// The current instance for method chaining. + /// + /// + /// // Register event types that can appear in user streams + /// settings.RegisterMessageTypeForCategory<UserCreated>("user") + /// .RegisterMessageTypeForCategory<UserUpdated>("user") + /// .RegisterMessageTypeForCategory<UserDeleted>("user"); + /// + /// + public MessageTypeMappingSettings RegisterForCategory(string categoryName) => + RegisterForCategory(categoryName, typeof(T)); + + /// + /// Registers multiple message types for a specific stream category. + /// + /// The category name to register the types with. + /// The message types to register. + /// The current instance for method chaining. + public MessageTypeMappingSettings RegisterForCategory(string categoryName, params Type[] types) { + CategoryTypesMap[categoryName] = + CategoryTypesMap.TryGetValue(categoryName, out var current) + ? [..current, ..types] + : types; + + return this; + } + + /// + /// Maps a .NET type to a specific message type name that will be stored in the message metadata. + /// This mapping is used during automatic deserialization, as it tells the client which CLR type + /// to instantiate when encountering a message with a particular type name in the database. + /// + /// The .NET type to register (typically a message class). + /// The string identifier to use for this type in the database. + /// The current instance for method chaining. + /// + /// The type name is often different from the .NET type name to support versioning and evolution + /// of your domain model without breaking existing stored messages. + /// + /// + /// + /// // Register me types with their corresponding type identifiers + /// settings.RegisterMessageType<UserCreated>("user-created-v1") + /// .RegisterMessageType<OrderPlaced>("order-placed-v2"); + /// + /// + public MessageTypeMappingSettings Register(string typeName) => + Register(typeName, typeof(T)); + + /// + /// Registers a message type with a specific type name. + /// + /// The message type to register. + /// The type name to register for the message type. + /// The current instance for method chaining. + public MessageTypeMappingSettings Register(string typeName, Type type) { + TypeMap[typeName] = type; + + return this; + } + + /// + /// Registers multiple message types with their corresponding type names. + /// + /// Dictionary mapping types to their type names. + /// The current instance for method chaining. + public MessageTypeMappingSettings Register(IDictionary typeMap) { + foreach (var map in typeMap) { + TypeMap[map.Key] = map.Value; + } + + return this; + } + + /// + /// Configures a strongly-typed metadata class for all mes in the system. + /// This enables accessing metadata properties in a type-safe manner rather than using dynamic objects. + /// + /// The metadata class type containing properties matching the expected metadata fields. + /// The current instance for method chaining. + public MessageTypeMappingSettings UseMetadataType() => + UseMetadataType(typeof(T)); + + /// + /// Configures a strongly-typed metadata class for all mes in the system. + /// This enables accessing metadata properties in a type-safe manner rather than using dynamic objects. + /// + /// The metadata class type containing properties matching the expected metadata fields. + /// The current instance for method chaining. + public MessageTypeMappingSettings UseMetadataType(Type type) { + DefaultMetadataType = type; + + return this; + } + + internal MessageTypeMappingSettings Clone() => + new MessageTypeMappingSettings { + TypeMap = new Dictionary(TypeMap), + CategoryTypesMap = new Dictionary(CategoryTypesMap), + }; +} diff --git a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs index 0520d0992..a2c5e52f4 100644 --- a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs @@ -52,15 +52,16 @@ public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] ou public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) { var messageTypeNamingStrategy = - settings.MessageTypeNamingStrategy ?? new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType); + settings.MessageTypeNamingStrategy + ?? new DefaultMessageTypeNamingStrategy(settings.MessageTypeMapping.DefaultMetadataType); var categoriesTypeMap = ResolveMessageTypeUsingNamingStrategy( - settings.CategoryMessageTypesMap, + settings.MessageTypeMapping, messageTypeNamingStrategy ); var messageTypeRegistry = new MessageTypeRegistry(); - messageTypeRegistry.Register(settings.MessageTypeMap); + messageTypeRegistry.Register(settings.MessageTypeMapping.TypeMap); messageTypeRegistry.Register(categoriesTypeMap); var serializers = new Dictionary { @@ -77,16 +78,17 @@ public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) serializers, new MessageTypeNamingStrategyWrapper( messageTypeRegistry, - settings.MessageTypeNamingStrategy ?? new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType) + settings.MessageTypeNamingStrategy + ?? new DefaultMessageTypeNamingStrategy(settings.MessageTypeMapping.DefaultMetadataType) ) ); } static Dictionary ResolveMessageTypeUsingNamingStrategy( - IDictionary categoryMessageTypesMap, + MessageTypeMappingSettings messageTypeMappingSettings, IMessageTypeNamingStrategy messageTypeNamingStrategy ) => - categoryMessageTypesMap + messageTypeMappingSettings.CategoryTypesMap .SelectMany( categoryTypes => categoryTypes.Value.Select( type => diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs index 2c1b67daf..b1b93fe73 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs @@ -276,7 +276,7 @@ public void With_ConfigureSettings_CreatesNewMessageSerializer() { var operationSettings = OperationSerializationSettings.Configure( s => - s.RegisterMessageType("CustomMessageName") + s.MessageTypeMapping.Register("CustomMessageName") ); // When @@ -309,9 +309,10 @@ public void Serialize_WithMultipleMessages_ReturnsArrayOfMessageData() { static KurrentDBClientSerializationSettings CreateTestSettings() { var settings = new KurrentDBClientSerializationSettings(); - settings.RegisterMessageType("UserRegistered"); - settings.RegisterMessageType("UserAssignedToRole"); - settings.UseMetadataType(); + settings.MessageTypeMapping + .Register("UserRegistered") + .Register("UserAssignedToRole") + .UseMetadataType(); return settings; } diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs index bbbeaf7f2..e3879abb5 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs @@ -101,8 +101,8 @@ public void From_WithCustomBytesSerializer_UsesProvidedSerializer() { public void From_WithMessageTypeMap_RegistersTypes() { // Given var settings = new KurrentDBClientSerializationSettings(); - settings.RegisterMessageType("test-event-1"); - settings.RegisterMessageType("test-event-2"); + settings.MessageTypeMapping.Register("test-event-1"); + settings.MessageTypeMapping.Register("test-event-2"); // When var registry = SchemaRegistry.From(settings); @@ -120,7 +120,7 @@ public void From_WithMessageTypeMap_RegistersTypes() { public void From_WithCategoryMessageTypesMap_WithDefaultMessageAutoRegistration() { // Given var settings = new KurrentDBClientSerializationSettings(); - var defaultMessageTypeNamingStrategy = new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType); + var defaultMessageTypeNamingStrategy = new DefaultMessageTypeNamingStrategy(settings.MessageTypeMapping.DefaultMetadataType); // When var registry = SchemaRegistry.From(settings); @@ -179,9 +179,9 @@ public void From_WithCategoryMessageTypesMap_WithDefaultMessageAutoRegistration( public void From_WithCategoryMessageTypesMap_RegistersTypesWithCategories() { // Given var settings = new KurrentDBClientSerializationSettings(); - settings.RegisterMessageTypeForCategory("category1"); - settings.RegisterMessageTypeForCategory("category1"); - settings.RegisterMessageTypeForCategory("category2"); + settings.MessageTypeMapping.RegisterForCategory("category1"); + settings.MessageTypeMapping.RegisterForCategory("category1"); + settings.MessageTypeMapping.RegisterForCategory("category2"); // When var registry = SchemaRegistry.From(settings); @@ -242,8 +242,7 @@ public void From_WithNoMessageTypeNamingStrategy_UsesDefaultStrategy() { // Given var settings = new KurrentDBClientSerializationSettings { MessageTypeNamingStrategy = null, - DefaultMetadataType = typeof(TestMetadata) - }; + }.ConfigureTypeMap(setting => setting.DefaultMetadataType = typeof(TestMetadata)); // When var registry = SchemaRegistry.From(settings); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 9838989e9..1ee3811dd 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -31,9 +31,11 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => serialization.UseMetadataType()); + await using var client = + NewClientWith(serialization => serialization.MessageTypeMapping.UseMetadataType()); + await using var subscriptionsClient = - NewSubscriptionsClientWith(serialization => serialization.UseMetadataType()); + NewSubscriptionsClientWith(serialization => serialization.MessageTypeMapping.UseMetadataType()); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -120,11 +122,11 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved public static TheoryData> CustomTypeMappings() { return [ (settings, typeName) => - settings.RegisterMessageType(typeName), + settings.MessageTypeMapping.Register(typeName), (settings, typeName) => - settings.RegisterMessageType(typeName, typeof(UserRegistered)), + settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeName, typeof(UserRegistered) } }) + settings.MessageTypeMapping.Register(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 1dd848898..52eec9314 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -28,7 +28,8 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => serialization.UseMetadataType()); + await using var client = NewClientWith(serialization => + serialization.MessageTypeMapping.UseMetadataType()); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -101,11 +102,11 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved public static TheoryData> CustomTypeMappings() { return [ (settings, typeName) => - settings.RegisterMessageType(typeName), + settings.MessageTypeMapping.Register(typeName), (settings, typeName) => - settings.RegisterMessageType(typeName, typeof(UserRegistered)), + settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeName, typeof(UserRegistered) } }) + settings.MessageTypeMapping.Register(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index 04957b7ca..fbb7c21d8 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -54,7 +54,9 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => serialization.UseMetadataType()); + await using var client = NewClientWith( + serialization => serialization.MessageTypeMapping.UseMetadataType() + ); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -148,11 +150,11 @@ public async Task read_all_with_disabled_autoserialization_does_NOT_deserialize_ public static TheoryData> CustomTypeMappings() { return [ (settings, typeName) => - settings.RegisterMessageType(typeName), + settings.MessageTypeMapping.Register(typeName), (settings, typeName) => - settings.RegisterMessageType(typeName, typeof(UserRegistered)), + settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeName, typeof(UserRegistered) } }) + settings.MessageTypeMapping.Register(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } From 7e765ee2fa8ca3febeb4f3640315d12880e67803 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 28 Mar 2025 11:16:40 +0100 Subject: [PATCH 22/23] [DEVEX-222] Added nested message type mapping --- .../KurrentDBClientSerializationSettings.cs | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs index 555368362..e9e286a40 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs @@ -49,9 +49,9 @@ public class KurrentDBClientSerializationSettings { /// /// /// var settings = KurrentDBClientSerializationSettings.Default(options => { - /// options.RegisterMessageType<UserCreated>("user-created"); - /// options.RegisterMessageType<UserUpdated>("user-updated"); - /// options.RegisterMessageTypeForCategory<UserCreated>("user"); + /// options.MessageTypeMapping.Register<UserRegistered>("user_registered"); + /// options.MessageTypeMapping.Register<RoleAssigned>("role_assigned"); + /// options.MessageTypeMapping.RegisterForCategory<UserRegistered>("user_onboarding"); /// }); /// /// @@ -196,7 +196,7 @@ internal KurrentDBClientSerializationSettings Clone() { BytesSerializer = BytesSerializer, JsonSerializer = JsonSerializer, DefaultContentType = DefaultContentType, - MessageTypeMapping = MessageTypeMapping.Clone(), + MessageTypeMapping = MessageTypeMapping.Clone(), MessageTypeNamingStrategy = MessageTypeNamingStrategy }; } @@ -264,6 +264,25 @@ public enum AutomaticDeserialization { Enabled = 1 } +/// +/// Controls whether the KurrentDB client should automatically register +/// message type names based on the CLR type using naming convention. +/// By default, it's enabled. +/// +public enum AutomaticTypeMappingRegistration { + /// + /// Enables automatic type registration. + /// The messages will be automatically discovered and resolved using registered naming resolution strategy. + /// + Enabled = 0, + + /// + /// Disables automatic type registration. If you use this setting, you need to register all type mappings manually. + /// If the type mapping is not registered for the specific message, the exception will be thrown. + /// + Disabled = 1 +} + /// /// Represents message type mapping settings /// @@ -277,7 +296,7 @@ public class MessageTypeMappingSettings { /// Registers CLR message types that can be appended to the specific stream category. /// Types will have message type names resolved based on the used /// - public IDictionary CategoryTypesMap { get; set; } = new Dictionary(); + internal IDictionary CategoryTypesMap { get; set; } = new Dictionary(); /// /// Specifies the CLR type that should be used when deserializing metadata for all events. @@ -286,6 +305,13 @@ public class MessageTypeMappingSettings { /// public Type? DefaultMetadataType { get; set; } + /// + /// Controls whether the KurrentDB client should automatically register + /// message type names based on the CLR type using naming convention. + /// By default, it's enabled. + /// + public AutomaticTypeMappingRegistration? AutomaticTypeMappingRegistration { get; set; } + /// /// Associates a message type with a specific stream category to enable automatic deserialization. /// In event sourcing, streams are often prefixed with a category (e.g., "user-123", "order-456"). @@ -297,9 +323,9 @@ public class MessageTypeMappingSettings { /// /// /// // Register event types that can appear in user streams - /// settings.RegisterMessageTypeForCategory<UserCreated>("user") - /// .RegisterMessageTypeForCategory<UserUpdated>("user") - /// .RegisterMessageTypeForCategory<UserDeleted>("user"); + /// settings.RegisterForCategory<UserRegistered>("user") + /// .RegisterForCategory<RoleAssigned>("user") + /// .RegisterForCategory<UserDeleted>("user"); /// /// public MessageTypeMappingSettings RegisterForCategory(string categoryName) => @@ -335,8 +361,8 @@ public MessageTypeMappingSettings RegisterForCategory(string categoryName, param /// /// /// // Register me types with their corresponding type identifiers - /// settings.RegisterMessageType<UserCreated>("user-created-v1") - /// .RegisterMessageType<OrderPlaced>("order-placed-v2"); + /// settings.Register<UserRegistered>("user_registered-v1") + /// .Register<OrderPlaced>("order-placed-v2"); /// /// public MessageTypeMappingSettings Register(string typeName) => @@ -387,6 +413,29 @@ public MessageTypeMappingSettings UseMetadataType(Type type) { return this; } + + /// + /// Disables automatic deserialization. Messages will be returned in their raw serialized form, + /// requiring manual deserialization by the application. Use this when you need direct access to the raw data + /// or when working with messages that don't have registered type mappings. + /// + /// The current instance for method chaining. + public MessageTypeMappingSettings DisableAutomaticRegistration() { + AutomaticTypeMappingRegistration = Client.AutomaticTypeMappingRegistration.Disabled; + + return this; + } + + /// + /// Disables automatic type registration. If you use this setting, you need to register all type mappings manually. + /// If the type mapping is not registered for the specific message, the exception will be thrown. + /// + /// The current instance for method chaining. + public MessageTypeMappingSettings EnableAutomaticRegistration() { + AutomaticTypeMappingRegistration = Client.AutomaticTypeMappingRegistration.Enabled; + + return this; + } internal MessageTypeMappingSettings Clone() => new MessageTypeMappingSettings { From a789d34f756aad31954799abe4ce02c41d1e269a Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 28 Mar 2025 15:55:22 +0100 Subject: [PATCH 23/23] [DEVEX-222] Replaced clr type with clr type name in naming resolution Tha makes naming strategy focused on just mapping types back and forth and allowing to disable auto registration --- .../Core/Serialization/MessageSerializer.cs | 4 +- .../MessageTypeResolutionStrategy.cs | 70 ++++--------------- .../Core/Serialization/SchemaRegistry.cs | 66 +++++++++++++---- .../Core/Serialization/SchemaRegistryTests.cs | 16 +++-- ...ializationTests.PersistentSubscriptions.cs | 15 ++-- .../SerializationTests.Subscriptions.cs | 23 +++--- .../Serialization/SerializationTests.cs | 17 ++--- 7 files changed, 107 insertions(+), 104 deletions(-) diff --git a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs index 3f0ff0e69..95d845a8b 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs @@ -66,14 +66,14 @@ public bool TryDeserialize(EventRecord record, out Message? deserialized) { #else public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized) { #endif - if (!schemaRegistry.TryResolveClrType(record, out var clrType)) { + if (!schemaRegistry.TryResolveClrType(record, out var clrTypeName)) { deserialized = null; return false; } var data = schemaRegistry .GetSerializer(FromMessageContentType(record.ContentType)) - .Deserialize(record.Data, clrType!); + .Deserialize(record.Data, clrTypeName!); if (data == null) { deserialized = null; diff --git a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs index 4de679d85..8dce8e021 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs @@ -7,15 +7,15 @@ public interface IMessageTypeNamingStrategy { string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext); #if NET48 - bool TryResolveClrType(EventRecord record, out Type? type); + bool TryResolveClrTypeName(EventRecord record, out string? clrTypeName); #else - bool TryResolveClrType(EventRecord messageTypeName, [NotNullWhen(true)] out Type? type); + bool TryResolveClrTypeName(EventRecord messageTypeName, [NotNullWhen(true)] out string? clrTypeName); #endif #if NET48 - bool TryResolveClrMetadataType(EventRecord record, out Type? type); + bool TryResolveClrMetadataTypeName(EventRecord record, out string? clrTypeName); #else - bool TryResolveClrMetadataType(EventRecord messageTypeName, [NotNullWhen(true)] out Type? type); + bool TryResolveClrMetadataTypeName(EventRecord messageTypeName, [NotNullWhen(true)] out string? clrTypeName); #endif } @@ -24,48 +24,6 @@ public static MessageTypeNamingResolutionContext FromStreamName(string streamNam new(streamName.Split('-').FirstOrDefault() ?? "no_stream_category"); } -class MessageTypeNamingStrategyWrapper( - IMessageTypeRegistry messageTypeRegistry, - IMessageTypeNamingStrategy messageTypeNamingStrategy -) : IMessageTypeNamingStrategy { - public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) { - return messageTypeRegistry.GetOrAddTypeName( - messageType, - _ => messageTypeNamingStrategy.ResolveTypeName(messageType, resolutionContext) - ); - } - -#if NET48 - public bool TryResolveClrType(EventRecord record, out Type? type) { -#else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { -#endif - type = messageTypeRegistry.GetOrAddClrType( - record.EventType, - _ => messageTypeNamingStrategy.TryResolveClrType(record, out var resolvedType) - ? resolvedType - : null - ); - - return type != null; - } - -#if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { -#else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { -#endif - type = messageTypeRegistry.GetOrAddClrType( - $"{record}-metadata", - _ => messageTypeNamingStrategy.TryResolveClrMetadataType(record, out var resolvedType) - ? resolvedType - : null - ); - - return type != null; - } -} - public class DefaultMessageTypeNamingStrategy(Type? defaultMetadataType) : IMessageTypeNamingStrategy { readonly Type _defaultMetadataType = defaultMetadataType ?? typeof(TracingMetadata); @@ -73,31 +31,29 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte $"{resolutionContext.CategoryName}-{messageType.FullName}"; #if NET48 - public bool TryResolveClrType(EventRecord record, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? clrTypeName) { #else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? clrTypeName) { #endif var messageTypeName = record.EventType; var categorySeparatorIndex = messageTypeName.IndexOf('-'); if (categorySeparatorIndex == -1 || categorySeparatorIndex == messageTypeName.Length - 1) { - type = null; + clrTypeName = null; return false; } - var clrTypeName = messageTypeName[(categorySeparatorIndex + 1)..]; - - type = TypeProvider.GetTypeByFullName(clrTypeName); + clrTypeName = messageTypeName[(categorySeparatorIndex + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? clrTypeName) { #else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? clrTypeName) { #endif - type = _defaultMetadataType; - return true; + clrTypeName = _defaultMetadataType.FullName; + return clrTypeName != null; } } diff --git a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs index a2c5e52f4..731d076cc 100644 --- a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs @@ -28,27 +28,68 @@ public static string ToMessageContentType(this ContentType contentType) => class SchemaRegistry( IDictionary serializers, - IMessageTypeNamingStrategy messageTypeNamingStrategy + IMessageTypeNamingStrategy messageTypeNamingStrategy, + IMessageTypeRegistry messageTypeRegistry, + AutomaticTypeMappingRegistration automaticTypeMappingRegistration ) { public ISerializer GetSerializer(ContentType schemaType) => serializers[schemaType]; public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) => + messageTypeRegistry.GetTypeName(messageType) ?? messageTypeNamingStrategy.ResolveTypeName(messageType, resolutionContext); #if NET48 - public bool TryResolveClrType(EventRecord record, out Type? type) => + public bool TryResolveClrType(EventRecord record, out Type? type) { #else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) => + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif - messageTypeNamingStrategy.TryResolveClrType(record, out type); + type = messageTypeRegistry.GetClrType(record.EventType); + + if (type != null) + return true; + + if (automaticTypeMappingRegistration == AutomaticTypeMappingRegistration.Disabled) + return false; + + if (!messageTypeNamingStrategy.TryResolveClrTypeName(record, out var clrTypeName) || clrTypeName == null) + return false; + + type = TypeProvider.GetTypeByFullName(clrTypeName); + + if (type == null) + return false; + + messageTypeRegistry.Register(record.EventType, type); + + return true; + } #if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? type) => + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { #else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) => + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { #endif - messageTypeNamingStrategy.TryResolveClrMetadataType(record, out type); + type = messageTypeRegistry.GetClrType($"{record.EventType}-metadata"); + + if (type != null) + return true; + + if (automaticTypeMappingRegistration == AutomaticTypeMappingRegistration.Disabled) + return false; + + if (!messageTypeNamingStrategy.TryResolveClrMetadataTypeName(record, out var clrTypeName) || clrTypeName == null) + return false; + + type = TypeProvider.GetTypeByFullName(clrTypeName); + + if (type == null) + return false; + + messageTypeRegistry.Register($"{record.EventType}-metadata", type); + + return true; + } public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) { var messageTypeNamingStrategy = @@ -60,6 +101,9 @@ public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) messageTypeNamingStrategy ); + var automaticTypeMappingRegistration = settings.MessageTypeMapping.AutomaticTypeMappingRegistration + ?? AutomaticTypeMappingRegistration.Enabled; + var messageTypeRegistry = new MessageTypeRegistry(); messageTypeRegistry.Register(settings.MessageTypeMapping.TypeMap); messageTypeRegistry.Register(categoriesTypeMap); @@ -76,11 +120,9 @@ public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) return new SchemaRegistry( serializers, - new MessageTypeNamingStrategyWrapper( - messageTypeRegistry, - settings.MessageTypeNamingStrategy - ?? new DefaultMessageTypeNamingStrategy(settings.MessageTypeMapping.DefaultMetadataType) - ) + messageTypeNamingStrategy, + messageTypeRegistry, + automaticTypeMappingRegistration ); } diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs index e3879abb5..a9eaa663f 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs @@ -27,7 +27,9 @@ public void GetSerializer_ReturnsCorrectSerializer() { var registry = new SchemaRegistry( serializers, - new DefaultMessageTypeNamingStrategy(typeof(TestMetadata)) + new DefaultMessageTypeNamingStrategy(typeof(TestMetadata)), + new MessageTypeRegistry(), + AutomaticTypeMappingRegistration.Enabled ); // When @@ -277,26 +279,26 @@ public string ResolveTypeName(Type type, MessageTypeNamingResolutionContext cont return $"Custom-{type.Name}-{context.CategoryName}"; } #if NET48 - public bool TryResolveClrType(EventRecord record, out Type? clrType) + public bool TryResolveClrTypeName(EventRecord record, out string? clrType) #else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? clrType) + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? clrType) #endif { // Simple implementation for testing clrType = record.EventType.StartsWith("Custom-TestEvent1") - ? typeof(TestEvent1) + ? typeof(TestEvent1).FullName : null; return clrType != null; } #if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? clrType) + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? clrType) #else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? clrType) + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? clrType) #endif { - clrType = typeof(TestMetadata); + clrType = typeof(TestMetadata).FullName!; return true; } } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 1ee3811dd..013c717df 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -211,23 +211,22 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(EventRecord record, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif var messageTypeName = record.EventType; - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - type = Type.GetType(typeName); + typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif - type = null; + typeName = null; return false; } } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 52eec9314..8070b03aa 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -28,8 +28,10 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => - serialization.MessageTypeMapping.UseMetadataType()); + await using var client = NewClientWith( + serialization => + serialization.MessageTypeMapping.UseMetadataType() + ); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -106,7 +108,9 @@ public static TheoryData> C (settings, typeName) => settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.MessageTypeMapping.Register(new Dictionary { { typeName, typeof(UserRegistered) } }) + settings.MessageTypeMapping.Register( + new Dictionary { { typeName, typeof(UserRegistered) } } + ) ]; } @@ -176,21 +180,20 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(EventRecord record, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif var messageTypeName = record.EventType; - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - type = Type.GetType(typeName); + typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? type) { #else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? type) { #endif type = null; return false; diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index fbb7c21d8..9330785e0 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -154,7 +154,9 @@ public static TheoryData> C (settings, typeName) => settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.MessageTypeMapping.Register(new Dictionary { { typeName, typeof(UserRegistered) } }) + settings.MessageTypeMapping.Register( + new Dictionary { { typeName, typeof(UserRegistered) } } + ) ]; } @@ -224,21 +226,20 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(EventRecord record, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif var messageTypeName = record.EventType; - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - type = Type.GetType(typeName); + typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? type) { #else - public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? type) { #endif type = null; return false;