Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/Compat/IsExternalInit.cs

This file was deleted.

4 changes: 2 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
<None Include="$(RepoRoot)assets\RequestFlowIcon-256x256.png" Pack="true" PackagePath="icon.png" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net462'">
<Compile Include="$(MSBuildThisFileDirectory)Compat\*.cs" LinkBase="Compat" />
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.16.0" PrivateAssets="all" />
</ItemGroup>

</Project>
9 changes: 3 additions & 6 deletions src/RequestFlow.Cqrs/CqrsDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,23 @@ internal sealed class CqrsDispatcher(IRequestDispatcher dispatcher) : ICommandDi
/// <inheritdoc />
public Task<TResponse> SendAsync<TResponse>(ICommand<TResponse> command, CancellationToken cancellationToken = default)
{
if (command is null)
throw new ArgumentNullException(nameof(command));
ThrowHelper.ThrowIfNull(command);

return _dispatcher.SendAsync(command, cancellationToken);
}

/// <inheritdoc />
public Task SendAsync(ICommand command, CancellationToken cancellationToken = default)
{
if (command is null)
throw new ArgumentNullException(nameof(command));
ThrowHelper.ThrowIfNull(command);

return _dispatcher.SendAsync(command, cancellationToken);
}

/// <inheritdoc />
public Task<TResponse> SendAsync<TResponse>(IQuery<TResponse> query, CancellationToken cancellationToken = default)
{
if (query is null)
throw new ArgumentNullException(nameof(query));
ThrowHelper.ThrowIfNull(query);

return _dispatcher.SendAsync(query, cancellationToken);
}
Expand Down
3 changes: 1 addition & 2 deletions src/RequestFlow.Cqrs/CqrsStreamDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ internal sealed class CqrsStreamDispatcher(IStreamDispatcher dispatcher) : IStre
/// <inheritdoc />
public IAsyncEnumerable<TItem> Stream<TItem>(IStreamQuery<TItem> query, CancellationToken cancellationToken = default)
{
if (query is null)
throw new ArgumentNullException(nameof(query));
ThrowHelper.ThrowIfNull(query);

return _dispatcher.Stream(query, cancellationToken);
}
Expand Down
9 changes: 3 additions & 6 deletions src/RequestFlow.Cqrs/CqrsValueDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ public ValueTask<TResponse> SendAsync<TResponse>(
IValueCommand<TResponse> command,
CancellationToken cancellationToken = default)
{
if (command is null)
throw new ArgumentNullException(nameof(command));
ThrowHelper.ThrowIfNull(command);

return _dispatcher.SendAsync(command, cancellationToken);
}
Expand All @@ -26,8 +25,7 @@ public ValueTask SendAsync(
IValueCommand command,
CancellationToken cancellationToken = default)
{
if (command is null)
throw new ArgumentNullException(nameof(command));
ThrowHelper.ThrowIfNull(command);

return _dispatcher.SendAsync(command, cancellationToken);
}
Expand All @@ -37,8 +35,7 @@ public ValueTask<TResponse> SendAsync<TResponse>(
IValueQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
if (query is null)
throw new ArgumentNullException(nameof(query));
ThrowHelper.ThrowIfNull(query);

return _dispatcher.SendAsync(query, cancellationToken);
}
Expand Down
21 changes: 21 additions & 0 deletions src/RequestFlow.Cqrs/ThrowHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace RequestFlow.Cqrs;

internal static class ThrowHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfNull(
[NotNull] object? argument,
[CallerArgumentExpression(nameof(argument))] string? paramName = null)
{
if (argument is null)
ArgumentNull(paramName);
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ArgumentNull(string? paramName) => throw new ArgumentNullException(paramName);
}
3 changes: 2 additions & 1 deletion src/RequestFlow/Dispatch/DispatchMap.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
#if NET8_0_OR_GREATER
using System.Collections.Frozen;
Expand All @@ -16,6 +17,6 @@ internal sealed class DispatchMap(Dictionary<Type, RequestPlanBase> plans)
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetPlanFor(Type requestType, out RequestPlanBase? plan)
public bool TryGetPlanFor(Type requestType, [NotNullWhen(true)] out RequestPlanBase? plan)
=> _plans.TryGetValue(requestType, out plan);
}
8 changes: 4 additions & 4 deletions src/RequestFlow/Dispatch/NullTaskGuard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ internal static class NullTaskGuard
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<TResponse> FromHandler<TResponse>(Task<TResponse> task, Type requestType)
=> task ?? throw new HandlerNullTaskException(requestType);
=> task ?? ThrowHelper.HandlerNullTask<Task<TResponse>>(requestType);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<TResponse> FromStage<TResponse>(Task<TResponse> task, Type stageType)
=> task ?? throw new StageNullTaskException(stageType);
=> task ?? ThrowHelper.StageNullTask<Task<TResponse>>(stageType);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task FromEventHandler(Task task, Type eventType, Type handlerType)
=> task ?? throw new EventHandlerNullTaskException(eventType, handlerType);
=> task ?? ThrowHelper.EventHandlerNullTask<Task>(eventType, handlerType);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task FromStrategy(Task task, Type strategyType)
=> task ?? throw new EventStrategyNullTaskException(strategyType);
=> task ?? ThrowHelper.EventStrategyNullTask<Task>(strategyType);
}
11 changes: 5 additions & 6 deletions src/RequestFlow/Dispatch/RequestDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,24 @@ internal sealed class RequestDispatcher(DispatchMap map, IServiceProvider servic
/// <inheritdoc />
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
ThrowHelper.ThrowIfNull(request);

Type requestType = request.GetType();

if (!_map.TryGetPlanFor(requestType, out RequestPlanBase? plan))
throw new HandlerNotFoundException(requestType);
return ThrowHelper.HandlerNotFound<Task<TResponse>>(requestType);

if (plan is not RequestPlan<TResponse> typedPlan)
throw new ResponseTypeMismatchException(requestType, expected: plan!.ResponseType, actual: typeof(TResponse));
return ThrowHelper.ResponseTypeMismatch<Task<TResponse>>(
requestType, expected: plan.ResponseType, actual: typeof(TResponse));

return typedPlan.ExecuteAsync(request, _services, cancellationToken);
}

/// <inheritdoc />
public Task SendAsync(IRequest request, CancellationToken cancellationToken = default)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
ThrowHelper.ThrowIfNull(request);

return SendAsync<NoResult>(request, cancellationToken);
}
Expand Down
4 changes: 2 additions & 2 deletions src/RequestFlow/Dispatch/Streaming/NullStreamGuard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ internal static class NullStreamGuard
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IAsyncEnumerable<TItem> FromHandler<TItem>(IAsyncEnumerable<TItem> stream, Type requestType)
=> stream ?? throw new HandlerNullStreamException(requestType);
=> stream ?? ThrowHelper.HandlerNullStream<IAsyncEnumerable<TItem>>(requestType);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IAsyncEnumerable<TItem> FromStage<TItem>(IAsyncEnumerable<TItem> stream, Type stageType)
=> stream ?? throw new StageNullStreamException(stageType);
=> stream ?? ThrowHelper.StageNullStream<IAsyncEnumerable<TItem>>(stageType);
}
24 changes: 19 additions & 5 deletions src/RequestFlow/Dispatch/Streaming/StreamDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,32 @@ internal sealed class StreamDispatcher(DispatchMap map, IServiceProvider service
public IAsyncEnumerable<TItem> Stream<TItem>(
IStreamRequest<TItem> request, CancellationToken cancellationToken = default)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
ThrowHelper.ThrowIfNull(request);

Type requestType = request.GetType();

if (!_map.TryGetPlanFor(requestType, out RequestPlanBase? plan))
throw new HandlerNotFoundException(requestType);
return ThrowHelper.HandlerNotFound<IAsyncEnumerable<TItem>>(requestType);

if (plan is not StreamPlan<TItem> streamPlan)
throw new ResponseTypeMismatchException(requestType, expected: plan!.ResponseType, actual: typeof(TItem));
return ThrowHelper.ResponseTypeMismatch<IAsyncEnumerable<TItem>>(
requestType, expected: plan.ResponseType, actual: typeof(TItem));

return Walk(streamPlan, request, _services, cancellationToken);
return cancellationToken.CanBeCanceled
? Walk(streamPlan, request, _services, cancellationToken)
: WalkWithoutDispatchToken(streamPlan, request, _services);
}

private static async IAsyncEnumerable<TItem> WalkWithoutDispatchToken<TItem>(
StreamPlan<TItem> plan,
object request,
IServiceProvider services,
[EnumeratorCancellation] CancellationToken iterationToken = default)
{
await foreach (TItem item in plan.Execute(request, services, iterationToken).ConfigureAwait(false))
{
yield return item;
}
}

// The dispatch token arrives as a parameter and the iteration token through the attribute, so
Expand Down
57 changes: 57 additions & 0 deletions src/RequestFlow/Dispatch/ThrowHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace RequestFlow;

internal static class ThrowHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfNull(
[NotNull] object? argument,
[CallerArgumentExpression(nameof(argument))] string? paramName = null)
{
if (argument is null)
ArgumentNull(paramName);
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ArgumentNull(string? paramName) => throw new ArgumentNullException(paramName);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult HandlerNotFound<TResult>(Type requestType)
=> throw new HandlerNotFoundException(requestType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult ResponseTypeMismatch<TResult>(Type requestType, Type expected, Type actual)
=> throw new ResponseTypeMismatchException(requestType, expected, actual);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult EventNotRegistered<TResult>(Type eventType)
=> throw new EventNotRegisteredException(eventType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult HandlerNullTask<TResult>(Type requestType)
=> throw new HandlerNullTaskException(requestType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult StageNullTask<TResult>(Type stageType)
=> throw new StageNullTaskException(stageType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult EventHandlerNullTask<TResult>(Type eventType, Type handlerType)
=> throw new EventHandlerNullTaskException(eventType, handlerType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult EventStrategyNullTask<TResult>(Type strategyType)
=> throw new EventStrategyNullTaskException(strategyType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult HandlerNullStream<TResult>(Type requestType)
=> throw new HandlerNullStreamException(requestType);

[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult StageNullStream<TResult>(Type stageType)
=> throw new StageNullStreamException(stageType);
}
18 changes: 8 additions & 10 deletions src/RequestFlow/Dispatch/ValueTask/ValueRequestDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ public ValueTask<TResponse> SendAsync<TResponse>(
IValueRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
ThrowHelper.ThrowIfNull(request);

Type requestType = request.GetType();

if (!_map.TryGetPlanFor(requestType, out RequestPlanBase? plan))
throw new HandlerNotFoundException(requestType);
return ThrowHelper.HandlerNotFound<ValueTask<TResponse>>(requestType);

if (plan is not ValueRequestPlan<TResponse> typedPlan)
throw new ResponseTypeMismatchException(
return ThrowHelper.ResponseTypeMismatch<ValueTask<TResponse>>(
requestType,
expected: plan!.ResponseType,
expected: plan.ResponseType,
actual: typeof(TResponse));

return typedPlan.ExecuteAsync(request, _services, cancellationToken);
Expand All @@ -37,13 +36,12 @@ public ValueTask SendAsync(
IValueRequest request,
CancellationToken cancellationToken = default)
{
if (request is null)
throw new ArgumentNullException(nameof(request));
ThrowHelper.ThrowIfNull(request);

Type requestType = request.GetType();

if (!_map.TryGetPlanFor(requestType, out RequestPlanBase? plan))
throw new HandlerNotFoundException(requestType);
return ThrowHelper.HandlerNotFound<ValueTask>(requestType);

if (plan is ValueVoidRequestPlan voidPlan)
return voidPlan.ExecuteVoidAsync(request, _services, cancellationToken);
Expand All @@ -52,9 +50,9 @@ public ValueTask SendAsync(
return ValueNoResultBridge.Discard(
typedPlan.ExecuteAsync(request, _services, cancellationToken));

throw new ResponseTypeMismatchException(
return ThrowHelper.ResponseTypeMismatch<ValueTask>(
requestType,
expected: plan!.ResponseType,
expected: plan.ResponseType,
actual: typeof(NoResult));
}
}
3 changes: 2 additions & 1 deletion src/RequestFlow/Events/EventMap.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
#if NET8_0_OR_GREATER
using System.Collections.Frozen;
Expand All @@ -16,6 +17,6 @@ internal sealed class EventMap(Dictionary<Type, EventPlan> plans)
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetPlanFor(Type eventType, out EventPlan? plan)
public bool TryGetPlanFor(Type eventType, [NotNullWhen(true)] out EventPlan? plan)
=> _plans.TryGetValue(eventType, out plan);
}
4 changes: 3 additions & 1 deletion src/RequestFlow/Events/EventPlanFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ private static EventPlan CreatePlan(
if (strategyType == typeof(SequentialPublishStrategy))
return new SequentialEventPlan(@event, entries);
if (strategyType == typeof(ParallelPublishStrategy))
return new ParallelEventPlan(@event, entries);
return entries.Length == 1
? new SequentialEventPlan(@event, entries)
: new ParallelEventPlan(@event, entries);
if (strategyType == typeof(FailFastPublishStrategy))
return new SequentialEventPlan(@event, entries, stopOnFirstFailure: true);

Expand Down
7 changes: 3 additions & 4 deletions src/RequestFlow/Events/EventPublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ internal sealed class EventPublisher(EventMap map, IServiceProvider services) :

public Task PublishAsync(IEvent @event, CancellationToken cancellationToken = default)
{
if (@event is null)
throw new ArgumentNullException(nameof(@event));
ThrowHelper.ThrowIfNull(@event, nameof(@event));

Type eventType = @event.GetType();
if (!_map.TryGetPlanFor(eventType, out EventPlan? plan))
throw new EventNotRegisteredException(eventType);
return ThrowHelper.EventNotRegistered<Task>(eventType);

return plan!.ExecuteAsync(@event, _services, cancellationToken);
return plan.ExecuteAsync(@event, _services, cancellationToken);
}
}
5 changes: 0 additions & 5 deletions tests/Compat/IsExternalInit.cs

This file was deleted.

4 changes: 2 additions & 2 deletions tests/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<Compile Include="$(MSBuildThisFileDirectory)Compat\*.cs" LinkBase="Compat" />
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.16.0" PrivateAssets="all" />
</ItemGroup>

</Project>
Loading