using Microsoft.Extensions.DependencyInjection;
using Prefab.Handler.Decorators;
namespace Prefab.Handler;
///
/// Provides a mechanism to execute handler pipelines with support for applying a sequence of handler decorators. This
/// class enables the composition of cross-cutting concerns around handler execution.
///
/// HandlerInvoker is typically used to execute handlers with additional behaviors, such as logging,
/// validation, or exception handling, by composing the provided decorators. The order of decorators affects the
/// execution flow: the first-registered decorator is invoked first and wraps subsequent decorators and the handler
/// itself. This class is thread-safe for concurrent handler executions.
/// The collection of handler decorators to apply to each handler invocation. Decorators are applied in the order they
/// are provided, with the first decorator wrapping the outermost layer of the pipeline. Cannot be null.
public sealed class HandlerInvoker(IEnumerable decorators, IServiceProvider serviceProvider)
{
private readonly IHandlerDecorator[] _decorators = decorators.Reverse().ToArray();
public Task Execute(CancellationToken cancellationToken)
{
var inner = serviceProvider.GetRequiredService>();
// No decorators? Call the handler directly.
if (_decorators.Length == 0)
return inner.Execute(cancellationToken);
var noRequest = new NoRequestAdapter(inner);
var pipeline = BuildPipeline(noRequest);
return pipeline(NoRequest.Instance, cancellationToken);
}
///
/// Executes the specified handler for the given request within the provided context and cancellation token,
/// returning the handler's response asynchronously.
///
/// The type of the request to be handled. Must not be null.
/// The type of the response returned by the handler.
/// The request object to be processed by the handler.
/// A cancellation token that can be used to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains the response produced by the
/// handler.
public Task Execute(TRequest request, CancellationToken cancellationToken) where TRequest : notnull
{
var handler = serviceProvider.GetRequiredService>();
// No decorators? Call the handler directly.
if (_decorators.Length == 0)
return handler.Execute(request, cancellationToken);
var pipeline = BuildPipeline(handler);
return pipeline(request, cancellationToken);
}
///
/// Builds a delegate representing the request handling pipeline, applying all registered decorators to the
/// specified handler.
///
/// Decorators are applied in the order they were registered, with the first-registered decorator
/// wrapping the outermost layer of the pipeline. The returned delegate can be invoked with a request, handler
/// context, and cancellation token to execute the full pipeline.
/// The type of the request message to be handled.
/// The type of the response message returned by the handler.
/// The core handler to which decorators will be applied. Cannot be null.
/// A delegate that processes a request through all registered decorators and the specified handler.
private Func> BuildPipeline(IHandler handler)
{
// Start from the final operation: the handler itself.
Func> pipeline = handler.Execute;
// Wrap in reverse registration order so the first-registered decorator runs outermost.
foreach (var decorator in _decorators)
{
var next = pipeline;
pipeline = (request, cancellationToken) =>
decorator.Invoke(next, request, cancellationToken);
}
return pipeline;
}
///
/// Provides an adapter that allows an existing handler to be used where a handler for a request type of NoRequest
/// is required.
///
/// This class is useful for scenarios where a handler expects no input request data but must
/// conform to an interface requiring a request parameter. It delegates execution to the specified inner handler,
/// ignoring the NoRequest parameter.
/// The type of the response returned by the handler.
/// The handler that processes requests and produces a response of type TResponse.
private sealed class NoRequestAdapter(IHandler innerHandler) : IHandler
{
public Task Execute(NoRequest _, CancellationToken cancellationToken)
=> innerHandler.Execute(cancellationToken);
}
}