< Summary

Information
Class: NanoRoute.ValueParserRegistration
Assembly: NanoRoute.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/ValueParser.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 171
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
ValueParserRegistration(...)20

File(s)

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

#LineLine coverage
 1/********************************************************************************
 2* ValueParser.cs                                                                *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Collections.Generic;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace NanoRoute
 12{
 13    /// <summary>
 14    /// Binds raw parser arguments to an opaque object that is cached with the route definition.
 15    /// </summary>
 16    /// <param name="rawArgs">
 17    /// The raw parser arguments as parsed from the route template, keyed case-insensitively.
 18    /// </param>
 19    /// <returns>
 20    /// A parser-specific object that will later be exposed through <see cref="ValueParserContext.Arguments"/>.
 21    /// Return <see langword="null"/> when the parser does not need a bound payload.
 22    /// </returns>
 23    /// <remarks>
 24    /// This delegate runs during route registration, not during request processing. It is the right place to
 25    /// validate parser arguments, parse numeric limits, or precompile regular expressions once.
 26    /// Exceptions thrown by the delegate are reported while the route containing the parser arguments is registered.
 27    /// </remarks>
 28    /// <example>
 29    /// <code>
 30    /// routerBuilder.AddValueParser
 31    /// (
 32    ///     "int",
 33    ///     static rawArgs =&gt;
 34    ///     (
 35    ///         Min: rawArgs.TryGetValue("min", out string? min) ? int.Parse(min) : null,
 36    ///         Max: rawArgs.TryGetValue("max", out string? max) ? int.Parse(max) : null
 37    ///     ),
 38    ///     static context =&gt;
 39    ///     {
 40    ///         var args = ((int? Min, int? Max)) context.Arguments!;
 41    ///         return ValueTask.FromResult(new ValueParseResult(true, context.Segment));
 42    ///     }
 43    /// );
 44    /// </code>
 45    /// </example>
 46    public delegate object? BindArgumentsDelegate(IReadOnlyDictionary<string, string> rawArgs);
 47
 48    /// <summary>
 49    /// Tries to parse a single route segment into a value that can optionally be stored in <see cref="RequestContext.Pa
 50    /// </summary>
 51    /// <param name="context">
 52    /// The parser context, including the decoded route segment, request services, and the linked pipeline cancellation 
 53    /// </param>
 54    /// <returns>A <see cref="ValueParseResult"/> that describes whether the segment matched and what value it produced.
 55    /// <remarks>
 56    /// Exceptions thrown by the delegate propagate during request processing for matching routes that use the parser.
 57    /// </remarks>
 58    /// <example>
 59    /// <code>
 60    /// routerBuilder.AddValueParser("user", static async (ValueParserContext context) =&gt;
 61    /// {
 62    ///     if (!Guid.TryParse(context.Segment.ToString(), out Guid userId))
 63    ///         return ValueParseResult.False;
 64    ///
 65    ///     object? user = await context
 66    ///         .Services
 67    ///         .GetRequiredService&lt;IUserRepository&gt;()
 68    ///         .TryGetAsync(userId, context.Cancellation);
 69    ///
 70    ///     return new ValueParseResult(user is not null, user);
 71    /// });
 72    /// </code>
 73    /// </example>
 74    public delegate ValueTask<ValueParseResult> ValueParserDelegate(ValueParserContext context);
 75
 76    /// <summary>
 77    /// Carries the current route segment and request-scoped services into an asynchronous value parser.
 78    /// </summary>
 79    /// <example>
 80    /// <code>
 81    /// builder.AddValueParser("user", static async context =&gt;
 82    /// {
 83    ///     Guid id = Guid.Parse(context.Segment.ToString());
 84    ///     object? user = await context.Services.GetRequiredService&lt;IUserStore&gt;().FindAsync(id, context.Cancellat
 85    ///     return new ValueParseResult(user is not null, user);
 86    /// });
 87    /// </code>
 88    /// </example>
 89    public readonly struct ValueParserContext
 90    {
 91        /// <summary>
 92        /// Gets the decoded route segment.
 93        /// </summary>
 94        /// <example>
 95        /// <code>
 96        /// string value = context.Segment.ToString();
 97        /// </code>
 98        /// </example>
 99        public required ReadOnlyMemory<char> Segment { get; init; }
 100
 101        /// <summary>
 102        /// Gets the request-scoped service provider.
 103        /// </summary>
 104        /// <example>
 105        /// <code>
 106        /// IUserStore store = context.Services.GetRequiredService&lt;IUserStore&gt;();
 107        /// </code>
 108        /// </example>
 109        public required IServiceProvider Services { get; init; }
 110
 111        /// <summary>
 112        /// Gets the parser arguments that were bound during route registration.
 113        /// </summary>
 114        /// <example>
 115        /// <code>
 116        /// ParserLimits? limits = (ParserLimits?) context.Arguments;
 117        /// </code>
 118        /// </example>
 119        public object? Arguments { get; init; }
 120
 121        /// <summary>
 122        /// Gets the linked pipeline cancellation token.
 123        /// </summary>
 124        /// <example>
 125        /// <code>
 126        /// context.Cancellation.ThrowIfCancellationRequested();
 127        /// </code>
 128        /// </example>
 129        public CancellationToken Cancellation { get; init; }
 130    }
 131
 132    /// <summary>
 133    /// Represents the outcome of a value parser.
 134    /// </summary>
 135    /// <param name="Success"><see langword="true"/> when the segment is accepted by the parser; otherwise <see langword
 136    /// <param name="Parsed">The parsed value when <paramref name="Success"/> is <see langword="true"/>; otherwise <see 
 137    /// <example>
 138    /// <code>
 139    /// return new ValueParseResult(true, parsedValue);
 140    /// </code>
 141    /// </example>
 142    public readonly record struct ValueParseResult(bool Success, object? Parsed)
 143    {
 144        /// <summary>
 145        /// Gets a failed parser result with <see cref="Success"/> set to <see langword="false"/> and
 146        /// <see cref="Parsed"/> set to <see langword="null"/>.
 147        /// </summary>
 148        /// <remarks>
 149        /// Return this value when the current route branch should be treated as a non-match.
 150        /// </remarks>
 151        /// <example>
 152        /// <code>
 153        /// if (!Guid.TryParse(context.Segment.ToString(), out Guid id))
 154        ///     return ValueParseResult.False;
 155        ///
 156        /// return new ValueParseResult(true, id);
 157        /// </code>
 158        /// </example>
 159        public static ValueParseResult False { get; } = new ValueParseResult(false, null);
 160    }
 161
 162    /// <summary>
 163    /// Stores a named value parser together with its argument binder.
 164    /// </summary>
 165    /// <example>
 166    /// <code>
 167    /// ValueParserRegistration registration = builder.ValueParsers["int"];
 168    /// </code>
 169    /// </example>
 1170    public sealed record ValueParserRegistration(string Name, ValueParserDelegate Parse, BindArgumentsDelegate BindArgum
 171}