< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Callback.CallbackRuntimeContextFactory
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Callback/CallbackRuntimeContextFactory.cs
Tag: Kestrun/Kestrun@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
97%
Covered lines: 42
Uncovered lines: 1
Coverable lines: 43
Total lines: 100
Line coverage: 97.6%
Branch coverage
90%
Covered branches: 18
Total branches: 20
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 01/02/2026 - 00:16:25 Line coverage: 97.6% (42/43) Branch coverage: 90% (18/20) Total lines: 100 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d0 01/02/2026 - 00:16:25 Line coverage: 97.6% (42/43) Branch coverage: 90% (18/20) Total lines: 100 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
ExtractTemplateParams(...)83.33%6687.5%
FromHttpContext(...)92.85%1414100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Callback/CallbackRuntimeContextFactory.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2using Kestrun.Models;
 3
 4namespace Kestrun.Callback;
 5
 6/// <summary>
 7/// Factory for creating <see cref="CallbackRuntimeContext"/> instances from HTTP context.
 8/// </summary>
 9public static partial class CallbackRuntimeContextFactory
 10{
 11    // Matches {id} and {id:int} etc; ignores {$request.body#/...} because of / and #
 112    private static readonly Regex TemplateParamRegex =
 113        TemplateParameterRegex();
 14
 15    private static HashSet<string> ExtractTemplateParams(string urlTemplate)
 16    {
 217        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 218        if (string.IsNullOrWhiteSpace(urlTemplate))
 19        {
 020            return set;
 21        }
 22
 823        foreach (Match m in TemplateParamRegex.Matches(urlTemplate))
 24        {
 225            var name = m.Groups["name"].Value.Trim();
 226            if (!string.IsNullOrEmpty(name))
 27            {
 228                _ = set.Add(name);
 29            }
 30        }
 31
 232        return set;
 33    }
 34    /// <summary>
 35    /// Creates a <see cref="CallbackRuntimeContext"/> from the given <see cref="HttpContext"/>.
 36    /// </summary>
 37    /// <param name="ctx">The HTTP context from which to create the callback runtime context.</param>
 38    /// <param name="urlTemplate">An optional URL template to extract template parameters for idempotency key generation
 39    /// <returns>A new instance of <see cref="CallbackRuntimeContext"/> populated with data from the HTTP context.</retu
 40    public static CallbackRuntimeContext FromHttpContext(KestrunContext ctx, string? urlTemplate = null)
 41    {
 542        ArgumentNullException.ThrowIfNull(ctx);
 43
 544        var correlationId = ctx.TraceIdentifier;
 45
 46        // Vars come from resolved OpenAPI parameters (this is the key fix)
 547        var vars = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
 48
 1649        foreach (var kv in ctx.Parameters.Parameters)
 50        {
 351            vars[kv.Key] = kv.Value.Value;
 52        }
 53
 54        // Typed body is already resolved
 555        var requestBody = ctx.Parameters.Body?.Value;
 56
 57        // Idempotency seed: derived from placeholders in the callback URL template (if provided)
 58        string idempotencySeed;
 559        if (!string.IsNullOrWhiteSpace(urlTemplate))
 60        {
 261            var names = ExtractTemplateParams(urlTemplate);
 62
 263            var seedParts = names
 264                .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
 265                .Select(n =>
 266                {
 267                    if (!vars.TryGetValue(n, out var v) || v is null)
 268                    {
 169                        return null;
 270                    }
 271
 172                    var s = v.ToString();
 173                    return string.IsNullOrWhiteSpace(s) ? null : $"{n}={s}";
 274                })
 275                .Where(x => x is not null)
 276                .ToArray();
 77
 278            idempotencySeed = seedParts.Length > 0
 279                ? string.Join("&", seedParts)
 280                : correlationId;
 81        }
 82        else
 83        {
 84            // No template => don't guess which param matters
 385            idempotencySeed = correlationId;
 86        }
 87
 588        return new CallbackRuntimeContext(
 589            CorrelationId: correlationId,
 590            IdempotencyKeySeed: idempotencySeed,
 591            DefaultBaseUri: null,
 592            Vars: vars,
 593            CallbackPayload: requestBody    // <-- typed body goes here
 594        );
 95    }
 96
 97    [GeneratedRegex(@"\{(?<name>[^{}:/\?]+)(?:[:][^{}]+)?\}", RegexOptions.Compiled)]
 98    private static partial Regex TemplateParameterRegex();
 99}
 100