< Summary

Information
Class: NanoRoute.Router
Assembly: NanoRoute.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/Router.cs
Line coverage
100%
Covered lines: 49
Uncovered lines: 0
Coverable lines: 49
Total lines: 199
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBlocks covered Blocks not covered
Router(...)60
Handle(...)100
Router(...)30
Router()30
CreateBuilder()30

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/Router.cs

#LineLine coverage
 1/********************************************************************************
 2* Router.cs                                                                     *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Linq.Expressions;
 9using System.Net.Http;
 10using System.Reflection;
 11using System.Runtime.CompilerServices;
 12using System.Threading;
 13using System.Threading.Tasks;
 14
 15namespace NanoRoute
 16{
 17    using Internals;
 18
 19    /// <summary>
 20    /// Executes the route matching pipeline built by <see cref="RouteScopeBuilder"/>.
 21    /// </summary>
 22    /// <remarks>
 23    /// A router is created from a builder snapshot. Matching walks the configured route tree, attaches bound parameters
 24    /// handlers in order until one returns a response without delegating further.
 25    /// </remarks>
 26    /// <example>
 27    /// <code>
 28    /// Router router = MyRouter
 29    ///     .CreateBuilder()
 30    ///     .AddDefaultValueParsers()
 31    ///     .AddHandler("GET", "/health/", (context, _) =&gt; Results.Ok())
 32    ///     .CreateRouter();
 33    /// </code>
 34    /// </example>
 35    public abstract class Router
 36    {
 37        private readonly RequestPipeline _requestPipeline;
 38
 39        /// <summary>
 40        /// The request property key that stores the trace identifier associated with the current request.
 41        /// </summary>
 42        /// <example>
 43        /// <code>
 44        /// request.Properties[Router.TraceIdName] = traceId;
 45        /// </code>
 46        /// </example>
 47        public const string TraceIdName = "TraceId";
 48
 49        /// <summary>
 50        /// The request property key that stores the original transport-specific request object.
 51        /// </summary>
 52        /// <example>
 53        /// <code>
 54        /// request.Properties[Router.OriginalRequestName] = listenerRequest;
 55        /// </code>
 56        /// </example>
 57        public const string OriginalRequestName = "OriginalRequest";
 58
 59        /// <summary>
 60        /// Creates a new <see cref="Router"/> instance.
 61        /// </summary>
 62        /// <param name="routeScopeBuilder">The builder scope whose registered routes are captured by the router.</param
 63        /// <param name="config">The configuration assigned to the router instance.</param>
 64        /// <exception cref="ArgumentNullException">
 65        /// Thrown when <paramref name="routeScopeBuilder"/> or <paramref name="config"/> is <see langword="null"/>.
 66        /// </exception>
 267        protected Router(RouteScopeBuilder routeScopeBuilder, RouterConfig config)
 268        {
 269            Ensure.NotNull(routeScopeBuilder);
 270            Ensure.NotNull(config);
 71
 272            _requestPipeline = new RequestPipeline(routeScopeBuilder.CreateSnapshot(), config);
 273            Config = config;
 274        }
 75
 76        /// <summary>
 77        /// Routes an <see cref="HttpRequestMessage"/> through the configured handler pipeline.
 78        /// </summary>
 79        /// <param name="request">The request to process.</param>
 80        /// <param name="services">The service provider exposed to value parsers and handlers.</param>
 81        /// <param name="cancellation">A token that can cancel request processing.</param>
 82        /// <returns>The <see cref="HttpResponseMessage"/> produced by the matching handlers.</returns>
 83        /// <remarks>
 84        /// Prefix routes can participate in the same pipeline as exact routes. Consecutive <c>/</c> separators in the
 85        /// request path are treated as a single separator during matching. When several handlers match, NanoRoute
 86        /// evaluates compatible matches from shorter prefixes toward more specific matches and honors
 87        /// <see cref="MatchingPrecedence"/> when both literal and parameterized segments are available at the same dept
 88        /// Once a branch is selected at a given depth, NanoRoute does not return to sibling branches later in the pipel
 89        /// </remarks>
 90        /// <exception cref="HttpRequestException">Thrown when no handler matches the request path.</exception>
 91        /// <exception cref="ArgumentNullException">
 92        /// Thrown when <paramref name="request"/> or <paramref name="services"/> is <see langword="null"/>.
 93        /// </exception>
 94        /// <exception cref="ArgumentException">Thrown when the request uses an unsupported HTTP method.</exception>
 95        /// <exception cref="OperationCanceledException">
 96        /// Thrown when the caller cancels the <paramref name="cancellation"/>.
 97        /// </exception>
 98        /// <example>
 99        /// <code>
 100        /// using HttpResponseMessage response = await Handle(request, services, cancellation);
 101        /// </code>
 102        /// </example>
 103#if DEBUG
 104        internal
 105#endif
 106        protected Task<HttpResponseMessage> Handle(HttpRequestMessage request, IServiceProvider services, CancellationTo
 2107        {
 2108            Ensure.NotNull(request);
 2109            Ensure.NotNull(services);
 110
 2111            RouterEventSource.Info.Write("RequestProcessingStarted", static request => new
 2112            {
 2113                RequestUri = request.RequestUri.OriginalString,
 2114                Verb = request.Method.Method
 2115            }, request);
 116
 2117            return _requestPipeline.RunAsync(request, services, cancellation);
 2118        }
 119
 120        /// <summary>
 121        /// Configuration assigned to this instance.
 122        /// </summary>
 123        public RouterConfig Config { get; }
 124    }
 125
 126    /// <summary>
 127    /// Provides the self-typed base for concrete router implementations with strongly typed configuration.
 128    /// </summary>
 129    /// <typeparam name="TDescendant">The concrete router type produced by <see cref="CreateBuilder"/>.</typeparam>
 130    /// <typeparam name="TConfig">The configuration type exposed by <see cref="Config"/>.</typeparam>
 131    /// <param name="builder">The builder whose route snapshot and configuration initialize the router.</param>
 132    /// <remarks>
 133    /// Concrete routers derive from this type to inherit <see cref="CreateBuilder"/> and a typed
 134    /// <see cref="Config"/> property. The concrete router must expose a public or non-public constructor that accepts
 135    /// <see cref="RouterBuilder{TRouter, TConfig}"/> so the generated factory can create immutable router snapshots.
 136    /// </remarks>
 137    /// <example>
 138    /// <code>
 139    /// public sealed class MyRouter : Router&lt;MyRouter, RouterConfig&gt;
 140    /// {
 141    ///     private MyRouter(RouterBuilder&lt;MyRouter, RouterConfig&gt; builder) : base(builder)
 142    ///     {
 143    ///     }
 144    /// }
 145    /// </code>
 146    /// </example>
 2147    public abstract class Router<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors | Dyna
 148    {
 2149        private static readonly Lazy<RouterFactoryDelegate<TDescendant, TConfig>> s_factory = new
 2150        (
 2151            static () =>
 2152            {
 2153                ParameterExpression builder = Expression.Parameter(typeof(RouterBuilder<TDescendant, TConfig>), nameof(b
 2154
 2155                return Expression
 2156                    .Lambda<RouterFactoryDelegate<TDescendant, TConfig>>
 2157                    (
 2158                        Expression.New
 2159                        (
 2160                            typeof(TDescendant).GetConstructor
 2161                            (
 2162                                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
 2163                                null,
 2164                                [typeof(RouterBuilder<TDescendant, TConfig>)],
 2165                                []
 2166                            ) ?? throw new MissingMethodException(),
 2167                            builder
 2168                        ),
 2169                        builder
 2170                    )
 2171                    .Compile
 2172                    (
 2173                        preferInterpretation: !RuntimeFeature.IsDynamicCodeSupported
 2174                    );
 2175            },
 2176            isThreadSafe: true
 2177        );
 178
 179        /// <summary>
 180        /// Configuration assigned to this instance.
 181        /// </summary>
 1182        public new TConfig Config => (TConfig) base.Config;
 183
 184        /// <summary>
 185        /// Creates a strongly typed builder.
 186        /// </summary>
 187        /// <returns>A builder that can register handlers, value parsers, and router configuration.</returns>
 188        /// <exception cref="MissingMethodException">
 189        /// Thrown when <typeparamref name="TDescendant"/> does not declare a public or non-public constructor that
 190        /// accepts <see cref="RouterBuilder{TRouter, TConfig}"/>.
 191        /// </exception>
 192        /// <example>
 193        /// <code>
 194        /// RouterBuilder&lt;MyRouter, RouterConfig&gt; builder = MyRouter.CreateBuilder();
 195        /// </code>
 196        /// </example>
 2197        public static RouterBuilder<TDescendant, TConfig> CreateBuilder() => new(s_factory.Value);
 198    }
 199}