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); } }