< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Models.KestrunRequest
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Models/KestrunRequest.cs
Tag: Kestrun/Kestrun@eeafbe813231ed23417e7b339e170e307b2c86f9
Line coverage
100%
Covered lines: 61
Uncovered lines: 0
Coverable lines: 61
Total lines: 194
Line coverage: 100%
Branch coverage
76%
Covered branches: 23
Total branches: 30
Branch coverage: 76.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 09/12/2025 - 13:32:05 Line coverage: 100% (49/49) Branch coverage: 64.2% (9/14) Total lines: 160 Tag: Kestrun/Kestrun@63ea5841fe73fd164406accba17a956e8c08357f09/14/2025 - 21:23:16 Line coverage: 100% (51/51) Branch coverage: 62.5% (10/16) Total lines: 169 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422302/05/2026 - 00:28:18 Line coverage: 100% (61/61) Branch coverage: 76.6% (23/30) Total lines: 194 Tag: Kestrun/Kestrun@d9261bd752e45afa789d10bc0c82b7d5724d9589 09/12/2025 - 13:32:05 Line coverage: 100% (49/49) Branch coverage: 64.2% (9/14) Total lines: 160 Tag: Kestrun/Kestrun@63ea5841fe73fd164406accba17a956e8c08357f09/14/2025 - 21:23:16 Line coverage: 100% (51/51) Branch coverage: 62.5% (10/16) Total lines: 169 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422302/05/2026 - 00:28:18 Line coverage: 100% (61/61) Branch coverage: 76.6% (23/30) Total lines: 194 Tag: Kestrun/Kestrun@d9261bd752e45afa789d10bc0c82b7d5724d9589

Coverage delta

Coverage delta 15 -15

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%1212100%
get_HttpContext()100%11100%
get_Request()100%11100%
get_Method()100%11100%
get_Host()100%11100%
get_QueryString()100%11100%
get_ContentType()100%11100%
get_Protocol()100%11100%
get_IsHttps()100%11100%
get_ContentLength()100%11100%
get_HasFormContentType()100%11100%
get_Scheme()100%11100%
get_Path()100%11100%
get_PathBase()100%11100%
get_Query()100%11100%
get_Headers()100%11100%
get_Body()100%11100%
get_Authorization()100%11100%
get_Cookies()100%11100%
get_Form()100%11100%
get_RouteValues()100%11100%
NewRequest()94.44%1818100%
NewRequestSync(...)100%11100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Models/KestrunRequest.cs

#LineLine coverage
 1using System.Text;
 2using Microsoft.Net.Http.Headers;
 3
 4namespace Kestrun.Models;
 5
 6/// <summary>
 7/// Represents a request model for Kestrun, containing HTTP method, path, query, headers, body, authorization, cookies, 
 8/// </summary>
 9public partial class KestrunRequest
 10{
 18111    private KestrunRequest(HttpContext context, Dictionary<string, string>? formDict, string body)
 12    {
 18113        HttpContext = context ?? throw new ArgumentNullException(nameof(context));
 18114        Request = context.Request;
 18115        Query = Request.Query
 20316                            .ToDictionary(x => x.Key, x => x.Value.ToString() ?? string.Empty);
 18117        Headers = Request.Headers
 38118                            .ToDictionary(x => x.Key, x => x.Value.ToString() ?? string.Empty);
 18119        Cookies = Request.Cookies
 18720                            .ToDictionary(x => x.Key, x => x.Value.ToString() ?? string.Empty);
 18121        Form = formDict;
 18122        Body = body;
 18123        RouteValues = Request.RouteValues
 18324                            .ToDictionary(x => x.Key, x => x.Value?.ToString() ?? string.Empty);
 18125    }
 26
 27    /// <summary>
 28    /// Gets the <see cref="Microsoft.AspNetCore.Http.HttpContext"/> associated with the request.
 29    /// </summary>
 35630    public HttpContext HttpContext { get; init; }
 31
 32    /// <summary>
 33    /// Gets the <see cref="HttpRequest"/> associated with the request.
 34    /// </summary>
 129635    public HttpRequest Request { get; init; }
 36    /// <summary>
 37    /// Gets the HTTP method for the request (e.g., GET, POST).
 38    /// </summary>
 34539    public string Method => Request.Method;
 40
 41    /// <summary>
 42    /// Gets the host header value for the request.
 43    /// </summary>
 144    public HostString Host => Request.Host;
 45
 46    /// <summary>
 47    /// Gets the query string for the request (e.g., "?id=123").
 48    /// </summary>
 149    public string QueryString => Request.QueryString.ToUriComponent();
 50
 51    /// <summary>
 52    /// Gets the content type of the request (e.g., "application/json").
 53    /// </summary>
 1054    public string? ContentType => Request.ContentType;
 55
 56    /// <summary>
 57    /// Gets the protocol used for the request (e.g., "HTTP/1.1").
 58    /// </summary>
 159    public string Protocol => Request.Protocol;
 60
 61    /// <summary>
 62    /// Gets a value indicating whether the request is made over HTTPS.
 63    /// </summary>
 164    public bool IsHttps => Request.IsHttps;
 65
 66    /// <summary>
 67    /// Gets the content length of the request, if available.
 68    /// </summary>
 269    public long? ContentLength => Request.ContentLength;
 70
 71    /// <summary>
 72    /// Gets a value indicating whether the request has a form content type.
 73    /// </summary>
 174    public bool HasFormContentType => Request.HasFormContentType;
 75
 76    /// <summary>
 77    /// Gets the request scheme (e.g., "http", "https").
 78    /// </summary>
 179    public string Scheme => Request.Scheme;
 80
 81    /// <summary>
 82    /// Gets the request path (e.g., "/api/resource").
 83    /// </summary>
 2084    public string Path => Request.Path.ToString();
 85
 86    /// <summary>
 87    /// Gets the base path for the request (e.g., "/api").
 88    /// </summary>
 189    public string PathBase => Request.PathBase.ToString();
 90
 91    /// <summary>
 92    /// Gets the query parameters for the request as a dictionary of key-value pairs.
 93    /// </summary>
 18994    public Dictionary<string, string> Query { get; init; }
 95
 96    /// <summary>
 97    /// Gets the headers for the request as a dictionary of key-value pairs.
 98    /// </summary>
 38799    public Dictionary<string, string> Headers { get; init; }
 100
 101    /// <summary>
 102    /// Gets the body content of the request as a string.
 103    /// </summary>
 197104    public string Body { get; init; }
 105
 106    /// <summary>
 107    /// Gets the authorization header value for the request, if present.
 108    /// </summary>
 7109    public string? Authorization => Request.Headers.Authorization.ToString();
 110    /// <summary>
 111    /// Gets the cookies for the request as an <see cref="IRequestCookieCollection"/>, if present.
 112    /// </summary>
 185113    public Dictionary<string, string> Cookies { get; init; }
 114
 115    /// <summary>
 116    /// Gets the form data for the request as a dictionary of key-value pairs, if present.
 117    /// </summary>
 206118    public Dictionary<string, string>? Form { get; init; }
 119
 120    /// <summary>
 121    /// Gets the route values for the request as a <see cref="RouteValueDictionary"/>, if present.
 122    /// </summary>
 182123    public Dictionary<string, string> RouteValues { get; init; }
 124
 125    /// <summary>
 126    /// Creates a new <see cref="KestrunRequest"/> instance from the specified <see cref="Microsoft.AspNetCore.Http.Http
 127    /// </summary>
 128    /// <param name="context">The HTTP context containing the request information.</param>
 129    /// <returns>A task that represents the asynchronous operation. The task result contains the constructed <see cref="
 130    public static async Task<KestrunRequest> NewRequest(HttpContext context)
 131    {
 132        // If request decompression runs later in the pipeline (after the PowerShell middleware),
 133        // the body is still encoded here. Avoid reading/parsing forms at this stage in that case.
 134        // Also avoid enabling buffering up-front for encoded bodies; the decompression middleware
 135        // expects to read the raw encoded stream from position 0.
 181136        var contentEncoding = context.Request.Headers[HeaderNames.ContentEncoding].ToString();
 137
 138        // Allow the body to be read multiple times for non-encoded requests.
 181139        if (string.IsNullOrWhiteSpace(contentEncoding))
 140        {
 181141            context.Request.EnableBuffering();
 142        }
 143
 181144        var contentType = context.Request.ContentType;
 181145        var hasFormContentType = context.Request.HasFormContentType;
 181146        var isMultipart = contentType?.StartsWith("multipart/", StringComparison.OrdinalIgnoreCase) ?? false;
 147
 148        // ② Read the raw body into a string (best-effort), then rewind.
 149        // IMPORTANT: Avoid reading encoded bodies and multipart payloads here.
 150        // - For Content-Encoding (e.g. gzip), RequestDecompression will decode later in the pipeline.
 151        // - For multipart, KrFormParser handles parsing/streaming.
 152        // Reading those payloads as UTF-8 here can interfere with later middleware/parsers.
 181153        var body = string.Empty;
 181154        if (!isMultipart && string.IsNullOrWhiteSpace(contentEncoding))
 155        {
 181156            using var reader = new StreamReader(
 181157                context.Request.Body,
 181158                encoding: Encoding.UTF8,
 181159                detectEncodingFromByteOrderMarks: false,
 181160                leaveOpen: true);
 161
 181162            body = await reader.ReadToEndAsync().ConfigureAwait(false);
 181163            context.Request.Body.Position = 0;
 181164        }
 165
 166        // ③ If it's a form, read it safely
 181167        Dictionary<string, string>? formDict = null;
 181168        if (hasFormContentType)
 169        {
 170            // Only parse application/x-www-form-urlencoded here.
 171            // Multipart (form-data/mixed/etc) is handled by KrFormParser / Add-KrFormRoute.
 172            // Also skip parsing when a non-empty Content-Encoding header is present; in that case
 173            // request decompression middleware may not have run yet.
 4174            if (string.IsNullOrWhiteSpace(contentEncoding)
 4175                && (context.Request.ContentType?.StartsWith("application/x-www-form-urlencoded", StringComparison.Ordina
 176            {
 4177                formDict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 4178                var form = await context.Request.ReadFormAsync().ConfigureAwait(false);
 18179                foreach (var kv in form)
 180                {
 5181                    formDict[kv.Key] = kv.Value.ToString();
 182                }
 183            }
 184        }
 185
 181186        return new KestrunRequest(context: context, formDict: formDict, body: body);
 181187    }
 188
 189    /// <summary>
 190    /// Synchronous helper for tests and simple call sites that prefer not to use async/await.
 191    /// Avoid in ASP.NET request pipelines; intended for unit tests only.
 192    /// </summary>
 175193    public static KestrunRequest NewRequestSync(HttpContext context) => NewRequest(context).GetAwaiter().GetResult();
 194}