< Summary

Information
Class: NexMediator.Core.Internal.NexRequestExecutor
Assembly: NexMediator.Core
File(s): /home/runner/work/NexMediator/NexMediator/src/NexMediator.Core/Internal/NexRequestExecutor.cs
Line coverage
96%
Covered lines: 64
Uncovered lines: 2
Coverable lines: 66
Total lines: 118
Line coverage: 96.9%
Branch coverage
50%
Covered branches: 1
Total branches: 2
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Dispatch()50%2291.66%
BuildFullPipelineInvoker(...)100%11100%
BuildPipelineGeneric(...)100%1197.61%

File(s)

/home/runner/work/NexMediator/NexMediator/src/NexMediator.Core/Internal/NexRequestExecutor.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Microsoft.Extensions.DependencyInjection;
 7using NexMediator.Abstractions.Exceptions;
 8using NexMediator.Abstractions.Interfaces;
 9
 10namespace NexMediator.Core.Internal;
 11
 12/// <summary>
 13/// Builds and caches a pipeline invoker per request and response type.
 14/// Supports strong typing of behaviors and correct ordering.
 15/// </summary>
 16internal static class NexRequestExecutor
 17{
 218    private static readonly ConcurrentDictionary<
 219        (Type Request, Type Response),
 220        Func<object, CancellationToken, Task<object>>>
 221      _pipelineCache = new();
 22
 23    /// <summary>
 24    /// Dispatches a request through a cached pipeline and returns the typed response.
 25    /// </summary>
 26    /// <typeparam name="TResponse">The expected response type.</typeparam>
 27    /// <param name="request">The request to send.</param>
 28    /// <param name="provider">The root service provider.</param>
 29    /// <param name="cancellationToken">A token to cancel the operation.</param>
 30    /// <returns>The response from the handler.</returns>
 31    /// <exception cref="ArgumentNullException">Thrown if request is null.</exception>
 32    public static async Task<TResponse> Dispatch<TResponse>(
 33        INexRequest<TResponse> request,
 34        IServiceProvider provider,
 35        CancellationToken cancellationToken)
 836    {
 837        if (request is null)
 038            throw new ArgumentNullException(nameof(request));
 39
 840        var reqType = request.GetType();
 841        var resType = typeof(TResponse);
 842        var key = (reqType, resType);
 43
 844        var invoker = _pipelineCache.GetOrAdd(
 845            key,
 1246            _ => BuildFullPipelineInvoker(reqType, resType, provider));
 47
 848        var result = await invoker(request!, cancellationToken)
 849                           .ConfigureAwait(false);
 150        return (TResponse)result!;
 151    }
 52
 53    /// <summary>
 54    /// Builds a generic pipeline invoker by reflecting to a generic method.
 55    /// </summary>
 56    private static Func<object, CancellationToken, Task<object>>
 57    BuildFullPipelineInvoker(
 58        Type reqType,
 59        Type resType,
 60        IServiceProvider provider)
 461    {
 462        var method = typeof(NexRequestExecutor)
 463            .GetMethod(nameof(BuildPipelineGeneric), System.Reflection.BindingFlags.NonPublic | System.Reflection.Bindin
 464        var generic = method.MakeGenericMethod(reqType, resType);
 465        return (Func<object, CancellationToken, Task<object>>)
 466            generic.Invoke(null, new object[] { provider })!;
 467    }
 68
 69    /// <summary>
 70    /// Builds a strongly typed pipeline for TRequest and TResponse.
 71    /// </summary>
 72    private static Func<object, CancellationToken, Task<object>>
 73    BuildPipelineGeneric<TRequest, TResponse>(IServiceProvider provider)
 74        where TRequest : INexRequest<TResponse>
 475    {
 476        var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
 77
 478        return async (object reqObj, CancellationToken ct) =>
 879        {
 880            using var scope = scopeFactory.CreateScope();
 881            var sp = scope.ServiceProvider;
 482
 483            // Resolve options and handler
 884            var options = sp.GetRequiredService<NexMediatorOptions>();
 885            var handler = sp.GetRequiredService<INexRequestHandler<TRequest, TResponse>>();
 486
 487            // Base pipeline calls the handler
 488            async Task<TResponse> HandlerPipe() =>
 189                await handler.Handle((TRequest)reqObj, ct)
 190                             .ConfigureAwait(false);
 491
 792            RequestHandlerDelegate<TResponse> pipeline = HandlerPipe;
 493
 494            // Resolve, order, and wrap behaviors
 795            var behaviors = sp.GetServices<INexPipelineBehavior<TRequest, TResponse>>();
 796            var ordered = options.OrderBehaviors(behaviors.Cast<object>());
 497
 3398            foreach (var behaviorObj in ordered.Reverse())
 699            {
 6100                var behavior = (INexPipelineBehavior<TRequest, TResponse>)behaviorObj;
 6101                var next = pipeline;
 4102
 4103                async Task<TResponse> BehaviorPipe() =>
 6104                    await behavior.Handle((TRequest)reqObj, next, ct)
 6105                                  .ConfigureAwait(false);
 4106
 6107                pipeline = BehaviorPipe;
 6108            }
 4109
 4110            // Execute and return
 7111            var response = await pipeline().ConfigureAwait(false);
 1112            if (response is null)
 0113                throw new NexMediatorException("Handler returned null response.");
 4114
 1115            return (object)response!;
 5116        };
 4117    }
 118}