< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.OpenApi.CallbackOperationId
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/OpenApi/CallbackOperationId.cs
Tag: Kestrun/Kestrun@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
0%
Covered lines: 0
Uncovered lines: 56
Coverable lines: 56
Total lines: 178
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 44
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/27/2025 - 20:05:22 Line coverage: 0% (0/56) Branch coverage: 0% (0/44) Total lines: 178 Tag: Kestrun/Kestrun@dec745d62965b14e1ed62c0f3ec815e60e53366f 12/27/2025 - 20:05:22 Line coverage: 0% (0/56) Branch coverage: 0% (0/44) Total lines: 178 Tag: Kestrun/Kestrun@dec745d62965b14e1ed62c0f3ec815e60e53366f

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
From(...)0%7280%
FromLastSegment(...)0%156120%
BuildCallbackKey(...)0%600240%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/OpenApi/CallbackOperationId.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2using Microsoft.OpenApi;
 3
 4namespace Kestrun.OpenApi;
 5
 6/// <summary>
 7/// Utility class to generate operation IDs for OpenAPI callbacks.
 8/// </summary>
 9internal static partial class CallbackOperationId
 10{
 011    private static readonly Regex NonIdChars = NonAlphanumericCharsRegex();
 012    private static readonly Regex MultiUnderscore = MultipleUnderscoresRegex();
 13
 14    /// <summary>
 15    /// Generates a standardized operation ID for an OpenAPI callback based on the callback name, HTTP verb, and route p
 16    /// </summary>
 17    /// <param name="callbackName">The name of the callback.</param>
 18    /// <param name="httpVerb">The HTTP verb associated with the callback.</param>
 19    /// <param name="pattern">The route pattern for the callback.</param>
 20    /// <returns>A standardized operation ID string.</returns>
 21    /// <exception cref="ArgumentException">Thrown when any of the input parameters are null or whitespace.</exception>
 22    internal static string From(string callbackName, string httpVerb, string pattern)
 23    {
 024        if (string.IsNullOrWhiteSpace(callbackName))
 25        {
 026            throw new ArgumentException("callbackName is required", nameof(callbackName));
 27        }
 28
 029        if (string.IsNullOrWhiteSpace(httpVerb))
 30        {
 031            throw new ArgumentException("httpVerb is required", nameof(httpVerb));
 32        }
 33
 034        if (string.IsNullOrWhiteSpace(pattern))
 35        {
 036            throw new ArgumentException("pattern is required", nameof(pattern));
 37        }
 38
 039        var verb = httpVerb.Trim().ToLowerInvariant();
 40
 41        // Example: "/order/status/{orderId}" -> "order_status_orderId"
 042        var suffix = pattern.Trim();
 043        suffix = suffix.StartsWith('/') ? suffix[1..] : suffix;
 044        suffix = suffix.Replace('/', '_')
 045                       .Replace("{", "")
 046                       .Replace("}", "");
 47
 048        suffix = NonIdChars.Replace(suffix, "_");
 049        suffix = MultiUnderscore.Replace(suffix, "_").Trim('_');
 50
 51        // If you want *exactly* "...__status" for "/order/status/{orderId}",
 52        // you can optionally take a specific segment (see helper below).
 053        return $"{callbackName}__{verb}__{suffix}";
 54    }
 55
 56    /// <summary>
 57    /// Generates a standardized operation ID for an OpenAPI callback based on the callback name, HTTP verb, and route p
 58    /// using only the last non-parameter segment of the route pattern.
 59    /// </summary>
 60    /// <param name="callbackName">The name of the callback.</param>
 61    /// <param name="httpVerb">The HTTP verb associated with the callback.</param>
 62    /// <param name="pattern">The route pattern for the callback.</param>
 63    /// <returns>A standardized operation ID string.</returns>
 64    internal static string FromLastSegment(string callbackName, string httpVerb, string pattern)
 65    {
 066        if (string.IsNullOrWhiteSpace(callbackName))
 67        {
 068            throw new ArgumentException("callbackName is required", nameof(callbackName));
 69        }
 70
 071        if (string.IsNullOrWhiteSpace(httpVerb))
 72        {
 073            throw new ArgumentException("httpVerb is required", nameof(httpVerb));
 74        }
 75
 076        if (string.IsNullOrWhiteSpace(pattern))
 77        {
 078            throw new ArgumentException("pattern is required", nameof(pattern));
 79        }
 80
 081        var verb = httpVerb.Trim().ToLowerInvariant();
 082        var parts = pattern.Trim('/').Split('/', StringSplitOptions.RemoveEmptyEntries);
 83
 084        var last = "op";
 085        if (parts.Length > 0)
 86        {
 087            last = parts[^1];
 088            for (var i = parts.Length - 1; i >= 0; i--)
 89            {
 090                if (!parts[i].StartsWith('{'))
 91                {
 092                    last = parts[i];
 093                    break;
 94                }
 95            }
 96        }
 97
 098        last = last.Trim('{', '}');
 099        last = NonIdChars.Replace(last, "_");
 0100        last = MultiUnderscore.Replace(last, "_").Trim('_');
 101
 0102        return $"{callbackName}__{verb}__{last}";
 103    }
 104
 105    /// <summary>
 106    /// Builds the callback key from the runtime expression and the pattern.
 107    /// </summary>
 108    /// <param name="expression">The runtime expression for the callback.</param>
 109    /// <param name="pattern">The route pattern for the callback.</param>
 110    /// <returns>A combined callback key as a RuntimeExpression.</returns>
 111    /// <exception cref="ArgumentException">Thrown when any of the input parameters are null or whitespace.</exception>
 112    internal static RuntimeExpression BuildCallbackKey(string expression, string pattern)
 113    {
 0114        if (string.IsNullOrWhiteSpace(expression))
 115        {
 0116            throw new ArgumentException("expression required", nameof(expression));
 117        }
 118
 0119        if (string.IsNullOrWhiteSpace(pattern))
 120        {
 0121            throw new ArgumentException("pattern required", nameof(pattern));
 122        }
 123
 0124        expression = expression.Trim();
 0125        pattern = pattern.Trim();
 126
 127        // Normalize expression into braced form: {$request...}
 0128        if (expression.StartsWith('{'))
 129        {
 130            // If user gave "{...}", ensure the inner begins with '$'
 131            // Example: "{request.body#/x}" -> "{$request.body#/x}"
 0132            if (expression.Length >= 2 && expression[1] != '$')
 133            {
 134                // Prefer to explicitly preserve the closing brace, if present.
 135                // Well-formed case: "{inner}" -> "{$inner}"
 0136                if (expression[^1] == '}')
 137                {
 0138                    var inner = expression.Length > 2 ? expression[1..^1] : string.Empty;
 0139                    expression = "{$" + inner + "}";
 140                }
 141                else
 142                {
 143                    // Malformed case without trailing '}' – fall back to prior behavior
 0144                    expression = "{$" + expression[1..];
 145                }
 146            }
 147        }
 148        else
 149        {
 150            // If user gave "$request..." or "request...", normalize to "$request..."
 0151            if (!expression.StartsWith('$'))
 152            {
 0153                expression = "$" + expression;
 154            }
 155
 0156            expression = "{" + expression + "}";
 157        }
 158
 159        // Ensure pattern starts with '/'
 0160        if (!pattern.StartsWith('/'))
 161        {
 0162            pattern = "/" + pattern;
 163        }
 164
 165        // If expression ends with '/', avoid "//"
 0166        if (expression.EndsWith('/') && pattern.Length > 0 && pattern[0] == '/')
 167        {
 0168            pattern = pattern[1..];
 169        }
 170
 0171        return RuntimeExpression.Build(expression + pattern);
 172    }
 173
 174    [GeneratedRegex(@"[^A-Za-z0-9_]+", RegexOptions.Compiled)]
 175    private static partial Regex NonAlphanumericCharsRegex();
 176    [GeneratedRegex(@"_+", RegexOptions.Compiled)]
 177    private static partial Regex MultipleUnderscoresRegex();
 178}