< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Languages.ParameterForInjectionInfo
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Languages/ParameterForInjectionInfo.cs
Tag: Kestrun/Kestrun@eeafbe813231ed23417e7b339e170e307b2c86f9
Line coverage
47%
Covered lines: 570
Uncovered lines: 632
Coverable lines: 1202
Total lines: 3458
Line coverage: 47.4%
Branch coverage
35%
Covered branches: 363
Total branches: 1016
Branch coverage: 35.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/12/2025 - 17:27:19 Line coverage: 2.1% (4/182) Branch coverage: 1.2% (2/160) Total lines: 437 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/14/2025 - 20:04:52 Line coverage: 2.1% (4/187) Branch coverage: 1% (2/182) Total lines: 458 Tag: Kestrun/Kestrun@a05ac8de57c6207e227b92ba360e9d58869ac80a01/02/2026 - 00:16:25 Line coverage: 6.1% (11/180) Branch coverage: 2.7% (5/184) Total lines: 434 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d001/08/2026 - 08:19:25 Line coverage: 70% (126/180) Branch coverage: 46.7% (86/184) Total lines: 438 Tag: Kestrun/Kestrun@6ab94ca7560634c2ac58b36c2b98e2a9b1bf305d01/17/2026 - 04:33:35 Line coverage: 63.6% (249/391) Branch coverage: 43% (174/404) Total lines: 973 Tag: Kestrun/Kestrun@aca34ea8d284564e2f9f6616dc937668dce926ba01/21/2026 - 17:07:46 Line coverage: 62.7% (364/580) Branch coverage: 46.5% (254/546) Total lines: 1506 Tag: Kestrun/Kestrun@3f6f61710c7ef7d5953cab578fe699c1e5e01a3602/05/2026 - 00:28:18 Line coverage: 50% (561/1120) Branch coverage: 37% (362/978) Total lines: 3289 Tag: Kestrun/Kestrun@d9261bd752e45afa789d10bc0c82b7d5724d958902/18/2026 - 08:33:07 Line coverage: 47.4% (570/1202) Branch coverage: 35.7% (363/1016) Total lines: 3458 Tag: Kestrun/Kestrun@bf8a937cfb7e8936c225b9df4608f8ddd85558b1 12/12/2025 - 17:27:19 Line coverage: 2.1% (4/182) Branch coverage: 1.2% (2/160) Total lines: 437 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/14/2025 - 20:04:52 Line coverage: 2.1% (4/187) Branch coverage: 1% (2/182) Total lines: 458 Tag: Kestrun/Kestrun@a05ac8de57c6207e227b92ba360e9d58869ac80a01/02/2026 - 00:16:25 Line coverage: 6.1% (11/180) Branch coverage: 2.7% (5/184) Total lines: 434 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d001/08/2026 - 08:19:25 Line coverage: 70% (126/180) Branch coverage: 46.7% (86/184) Total lines: 438 Tag: Kestrun/Kestrun@6ab94ca7560634c2ac58b36c2b98e2a9b1bf305d01/17/2026 - 04:33:35 Line coverage: 63.6% (249/391) Branch coverage: 43% (174/404) Total lines: 973 Tag: Kestrun/Kestrun@aca34ea8d284564e2f9f6616dc937668dce926ba01/21/2026 - 17:07:46 Line coverage: 62.7% (364/580) Branch coverage: 46.5% (254/546) Total lines: 1506 Tag: Kestrun/Kestrun@3f6f61710c7ef7d5953cab578fe699c1e5e01a3602/05/2026 - 00:28:18 Line coverage: 50% (561/1120) Branch coverage: 37% (362/978) Total lines: 3289 Tag: Kestrun/Kestrun@d9261bd752e45afa789d10bc0c82b7d5724d958902/18/2026 - 08:33:07 Line coverage: 47.4% (570/1202) Branch coverage: 35.7% (363/1016) Total lines: 3458 Tag: Kestrun/Kestrun@bf8a937cfb7e8936c225b9df4608f8ddd85558b1

Coverage delta

Coverage delta 64 -64

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
get_KeyPattern()100%210%
get_SchemaTypeName()100%210%
IsMatch(...)100%210%
Validate(...)100%11100%
.ctor(...)37.5%8881.81%
.ctor(...)72.22%1818100%
InjectParameters(...)100%88100%
.cctor()100%1164.7%
ShouldConvertBody(...)50%66100%
TryConvertBodyByContentType(...)0%342180%
InjectSingleParameter(...)75%8881.25%
CoerceFormPayloadForParameter(...)50%121072.72%
LogCoerceFormPayload(...)70%101088.88%
TryHandleExactPayloadType(...)25%7440%
TryBindFormModel(...)25%6450%
TryBindMultipartModel(...)0%2040%
TryBindFormDataModel(...)0%2040%
TryCoerceToKrFormPayload(...)100%22100%
TryCoerceToKrFormData(...)70%101088.88%
TryCoerceToKrMultipart(...)70%111080%
TryPopulateKrMultipartDerivedModel(...)50%2255.55%
TryPopulateMultipartObjectProperties(...)83.33%131281.81%
TryPopulateFormDataObjectProperties(...)100%210%
GetFormDataBindableProperties(...)0%620%
ApplyFormFieldValues(...)0%7280%
ApplyFormFileValues(...)0%7280%
BuildFieldPropertyValue(...)0%272160%
BuildFilePropertyValue(...)0%272160%
CreateArrayFromValues(...)0%620%
TryPopulateMultipartStorage(...)0%7280%
TryBuildValueForMultipartProperty(...)90%101092.3%
TryBuildSingleMultipartValue(...)100%44100%
TryBuildNestedMultipartValue(...)75%4475%
TryBuildJsonMultipartValue(...)31.25%371656.25%
IsJsonPart(...)100%11100%
TryParseJsonToHashtable(...)100%1140%
LogNestedMultipartCreateFailure(...)0%7280%
LogJsonAdditionalPropertiesFailure(...)0%7280%
LogJsonInstanceCreateFailure(...)0%7280%
TryReadPartAsString(...)50%5457.14%
TryCreateObjectInstance(...)83.33%66100%
TryCreatePowerShellClassInstance(...)25%6450%
TryCreateStandardInstance(...)0%3228.57%
TryCreateUninitializedInstance(...)0%4260%
TryCreateObjectInstanceInRunspace(...)42.85%231464.28%
TrySetPropertyValue(...)25%8436.36%
TrySetPropertyBackingFieldValue(...)0%7280%
TrySetAdditionalPropertiesBag(...)50%18846.66%
NormalizeAdditionalPropertiesBag(...)16.66%19628.57%
FilterPatternedAdditionalProperties(...)0%7280%
ConvertAdditionalPropertiesByType(...)0%2040%
ConvertAdditionalPropertyValue(...)0%420200%
TryGetAdditionalPropertiesMetadata(...)14.28%491443.75%
ResolveAdditionalPropertiesType(...)0%7280%
TryResolveAlias(...)0%2970540%
TryResolveByTypeName(...)100%210%
TryResolveInAssembly(...)0%620%
TryResolveInLoadedAssemblies(...)0%2040%
TrySetAdditionalPropertiesBackingField(...)0%7280%
IsPowerShellClassType(...)75%44100%
TryCreateMultipartInstance(...)50%3250%
TryCreateFormDataInstance(...)50%3250%
TryCreateMultipartInstanceInRunspace(...)50%141064.7%
TryCreateFormDataInstanceInRunspace(...)0%110100%
CopyFormData(...)100%44100%
EscapePowerShellString(...)100%11100%
LogInjectingParameter(...)50%101090.9%
GetConvertedParameterValue(...)50%44100%
ConvertBodyParameterIfNeeded(...)25%13418.18%
LogAddingParameter(...)50%9880%
StoreResolvedParameter(...)100%22100%
GetParameterValueFromContext(...)70%121073.33%
GetRawValue(...)36.84%371962.96%
NormalizeRaw(...)40%151062.5%
ConvertValue(...)29.41%4183430.76%
ConvertBodyBasedOnContentType(...)75%4477.77%
ConvertByCanonicalMediaType(...)58.33%423683.33%
ConvertYamlToHashtable(...)50%2275%
ConvertJsonToHashtable(...)50%2280%
JsonElementToClr(...)36.36%261150%
ToHashtable(...)100%22100%
ToArray(...)0%620%
ConvertXmlBodyToParameterType(...)77.77%461855.88%
ExtractRootMapForBinding(...)66.66%221258.33%
NormalizeWrappedArrays(...)100%1010100%
TryGetXmlMetadataProperties(...)100%44100%
TryGetWrappedArrayMetadata(...)70%121072.72%
TryGetWrapperHashtable(...)50%9655.55%
TryUnwrapWrapper(...)25%44100%
ConvertHashtableToObject(...)50%4471.42%
TryConvertHashtableToParameterType(...)25%38822.22%
PopulateObjectFromHashtable(...)41.66%491236.66%
ValidateRequiredMembers(...)16.66%24620%
GetRequiredMemberNames(...)25%971631.81%
HasAdditionalProperties(...)0%620%
SanitizePsValidation(...)0%2040%
GetWritableProperties(...)50%22100%
GetPublicFields(...)100%11100%
NormalizeMemberKey(...)50%22100%
TrySetMemberValue(...)25%7444.44%
ApplyAdditionalPropertiesExtras(...)0%7280%
ConvertToTargetType(...)75%8890%
UnwrapNullableTargetType(...)100%22100%
TryConvertHashtableValue(...)25%7442.85%
TryConvertListOrArrayValue(...)75%4475%
ConvertScalarValue(...)50%44100%
TryConvertScalarByType(...)25%10428.57%
TryConvertPrimitiveScalar(...)25%551233.33%
TryParseInt32(...)50%22100%
TryParseInt64(...)0%620%
TryParseDouble(...)0%620%
TryParseDecimal(...)50%22100%
TryParseBoolean(...)0%620%
TryParseEnum(...)0%620%
TryChangeType(...)100%210%
ConvertEnumerableToTargetType(...)33.33%651847.36%
TryGetHashtableValue(...)83.33%7671.42%
ConvertFormToHashtable(...)0%4260%
ConvertFormToValue(...)0%506220%
ConvertBsonToHashtable(...)50%7671.42%
BsonValueToClr(...)20.83%1232444.44%
BsonDocumentToHashtable(...)100%22100%
BsonArrayToClrArray(...)0%620%
ConvertCborToHashtable(...)50%7671.42%
CborToClr(...)62.5%8887.5%
ConvertCborMapToHashtable(...)100%22100%
ConvertCborArrayToClrArray(...)0%620%
ConvertCborScalarToClr(...)50%281458.82%
GetCborMapKeyString(...)37.5%88100%
ConvertCsvToHashtable(...)83.33%121295.83%
DecodeBodyStringToBytes(...)50%12860%
TryDecodeBase64(...)59.37%563271.42%
TryDecodeHex(...)0%600240%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Languages/ParameterForInjectionInfo.cs

#LineLine coverage
 1using System.Collections;
 2using System.Globalization;
 3using System.Management.Automation;
 4using System.Reflection;
 5using System.Text;
 6using System.Text.Json;
 7using System.Text.RegularExpressions;
 8using System.Xml;
 9using System.Xml.Linq;
 10using CsvHelper;
 11using CsvHelper.Configuration;
 12using System.Runtime.Serialization;
 13using Kestrun.Forms;
 14using Kestrun.Logging;
 15using Kestrun.Models;
 16using Kestrun.Utilities;
 17using Microsoft.Extensions.Primitives;
 18using Microsoft.OpenApi;
 19using MongoDB.Bson;
 20using MongoDB.Bson.Serialization;
 21using PeterO.Cbor;
 22using YamlDotNet.Serialization;
 23using YamlDotNet.Serialization.NamingConventions;
 24using Kestrun.KrException;
 25
 26namespace Kestrun.Languages;
 27
 28/// <summary>
 29/// Information about a parameter to be injected into a script.
 30/// </summary>
 31public class ParameterForInjectionInfo : ParameterForInjectionInfoBase
 32{
 33    /// <summary>
 34    /// Content types associated with the parameter.
 35    /// </summary>
 36    /// <remarks>Typically used for body parameters.</remarks>
 037    private sealed class PatternPropertyRule(string keyPattern, string? schemaTypeName)
 38    {
 39        /// <summary>
 40        /// Regex pattern to match property keys.
 41        /// </summary>
 042        public string KeyPattern { get; } = keyPattern;
 43
 44        /// <summary>
 45        /// Type name of the schema for matched properties.
 46        /// </summary>
 047        public string? SchemaTypeName { get; } = schemaTypeName;
 48
 49        /// <summary>
 50        /// Determines if the given key matches the pattern.
 51        /// </summary>
 52        public bool IsMatch(string key)
 053            => Regex.IsMatch(key, KeyPattern, RegexOptions.CultureInvariant);
 54    }
 55
 56    /// <summary>
 57    /// Validates the ParameterMetadata instance.
 58    /// </summary>
 59    /// <param name="paramInfo">The parameter metadata to validate.</param>
 60    /// <returns>The validated ParameterMetadata.</returns>
 61    private static ParameterMetadata Validate(ParameterMetadata? paramInfo)
 62    {
 3763        ArgumentNullException.ThrowIfNull(paramInfo);
 3664        return paramInfo;
 65    }
 66
 67    /// <summary>
 68    /// Constructs a ParameterForInjectionInfo from an OpenApiParameter.
 69    /// </summary>
 70    /// <param name="paramInfo">The parameter metadata.</param>
 71    /// <param name="parameter">The OpenApiParameter to construct from.</param>
 72    public ParameterForInjectionInfo(ParameterMetadata paramInfo, OpenApiParameter? parameter) :
 973        base(Validate(paramInfo).Name, Validate(paramInfo).ParameterType)
 74    {
 875        ArgumentNullException.ThrowIfNull(parameter);
 776        Type = parameter.Schema?.Type;
 777        DefaultValue = parameter.Schema?.Default;
 778        In = parameter.In;
 779        if (parameter.Content is not null)
 80        {
 081            foreach (var key in parameter.Content.Keys)
 82            {
 083                ContentTypes.Add(key);
 84            }
 85        }
 86        else
 87        {
 788            Explode = parameter.Explode;
 789            Style = parameter.Style;
 90        }
 791    }
 92    /// <summary>
 93    /// Constructs a ParameterForInjectionInfo from an OpenApiRequestBody.
 94    /// </summary>
 95    /// <param name="paramInfo">The parameter metadata.</param>
 96    /// <param name="requestBody">The OpenApiRequestBody to construct from.</param>
 97    /// <param name="formOptions">The form options for handling form data.</param>
 98    public ParameterForInjectionInfo(ParameterMetadata paramInfo, OpenApiRequestBody requestBody, KrFormOptions? formOpt
 1099        base(Validate(paramInfo).Name, Validate(paramInfo).ParameterType)
 100    {
 10101        ArgumentNullException.ThrowIfNull(requestBody);
 10102        Type = requestBody.Content?.Values.FirstOrDefault()?.Schema?.Type;
 10103        var schema = requestBody.Content?.Values.FirstOrDefault()?.Schema;
 10104        if (schema is OpenApiSchemaReference)
 105        {
 1106            Type = JsonSchemaType.Object;
 107        }
 9108        else if (schema is OpenApiSchema sch)
 109        {
 9110            Type = sch.Type;
 9111            DefaultValue = sch.Default;
 112        }
 10113        In = null;
 10114        if (requestBody.Content is not null)
 115        {
 40116            foreach (var key in requestBody.Content.Keys)
 117            {
 10118                ContentTypes.Add(key);
 119            }
 120        }
 121        // Default to "form" style with explode for request bodies.
 10122        FormOptions = formOptions;
 10123    }
 124
 125    /// <summary>
 126    /// Adds parameters from the HTTP context to the PowerShell instance.
 127    /// </summary>
 128    /// <param name="context">The current HTTP context.</param>
 129    /// <param name="ps">The PowerShell instance to which parameters will be added.</param>
 130    internal static void InjectParameters(KestrunContext context, PowerShell ps)
 131    {
 17132        if (context.HttpContext.GetEndpoint()?
 17133               .Metadata
 28134               .FirstOrDefault(m => m is List<ParameterForInjectionInfo>) is not List<ParameterForInjectionInfo> paramet
 135        {
 6136            return;
 137        }
 138
 11139        var logger = context.Host.Logger;
 11140        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 141        {
 11142            logger.Debug("Injecting {Count} parameters into PowerShell script.", parameters.Count);
 143        }
 144
 44145        foreach (var param in parameters)
 146        {
 11147            InjectSingleParameter(context, ps, param);
 148        }
 11149    }
 150
 151    /// <summary>
 152    /// Mapping of content types to body conversion functions.
 153    /// </summary>
 1154    private static readonly IReadOnlyDictionary<string, Func<KestrunContext, string, object?>> BodyConverters =
 1155        new Dictionary<string, Func<KestrunContext, string, object?>>(StringComparer.OrdinalIgnoreCase)
 1156        {
 0157            ["application/json"] = (_, raw) => ConvertJsonToHashtable(raw),
 0158            ["application/yaml"] = (_, raw) => ConvertYamlToHashtable(raw),
 1159            // XML conversion needs to consider OpenAPI XML modeling; handled in the callers that have ParameterType.
 0160            ["application/bson"] = (_, raw) => ConvertBsonToHashtable(raw),
 0161            ["application/cbor"] = (_, raw) => ConvertCborToHashtable(raw),
 0162            ["text/csv"] = (_, raw) => ConvertCsvToHashtable(raw),
 1163
 1164            // This one typically needs the request form, not the raw string.
 0165            ["application/x-www-form-urlencoded"] = (ctx, _) => ConvertFormToHashtable(ctx.Request.Form),
 1166        };
 167
 168    /// <summary>
 169    /// Determines whether the body parameter should be converted based on its type information.
 170    /// </summary>
 171    /// <param name="param">The parameter information.</param>
 172    /// <param name="converted">The converted object.</param>
 173    /// <returns>True if the body should be converted; otherwise, false.</returns>
 174    private static bool ShouldConvertBody(ParameterForInjectionInfo param, object? converted) =>
 11175    converted is string && param.Type is null && param.ParameterType is not null && param.ParameterType != typeof(string
 176
 177    /// <summary>
 178    /// Tries to convert the body parameter based on the content types specified.
 179    /// </summary>
 180    /// <param name="context">The current Kestrun context.</param>
 181    /// <param name="param">The parameter information.</param>
 182    /// <param name="rawString">The raw body string.</param>
 183    private static object? TryConvertBodyByContentType(KestrunContext context, ParameterForInjectionInfo param, string r
 184    {
 185        // Collect canonical content types once
 0186        var canonicalTypes = param.ContentTypes
 0187            .Select(MediaTypeHelper.Canonicalize)
 0188            .Where(ct => !string.IsNullOrWhiteSpace(ct))
 0189            .Distinct(StringComparer.OrdinalIgnoreCase);
 190        // Try each content type in order
 0191        foreach (var ct in canonicalTypes)
 192        {
 0193            if (ct.Equals("application/xml", StringComparison.OrdinalIgnoreCase))
 194            {
 0195                return ConvertXmlBodyToParameterType(rawString, param.ParameterType);
 196            }
 197
 0198            if (BodyConverters.TryGetValue(ct, out var converter))
 199            {
 200                // Special-case: form-url-encoded conversion only makes sense with explode/form style.
 0201                if (ct.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) &&
 0202                    !(param.Explode || param.Style == ParameterStyle.Form))
 203                {
 204                    continue;
 205                }
 206                // Use the converter
 0207                return converter(context, rawString);
 208            }
 209        }
 210
 211        // If it's "form" style explode, you can still treat it as a hashtable even without explicit content-type.
 0212        return param.Style == ParameterStyle.Form && param.Explode && context.Request.Form is not null
 0213            ? ConvertFormToHashtable(context.Request.Form)
 0214            : (object?)null;
 0215    }
 216
 217    /// <summary>
 218    /// Injects a single parameter into the PowerShell instance based on its location and type.
 219    /// </summary>
 220    /// <param name="context">The current Kestrun context.</param>
 221    /// <param name="ps">The PowerShell instance to inject parameters into.</param>
 222    /// <param name="param">The parameter information to inject.</param>
 223    private static void InjectSingleParameter(KestrunContext context, PowerShell ps, ParameterForInjectionInfo param)
 224    {
 11225        var logger = context.Host.Logger;
 11226        var name = param.Name;
 227        object? converted;
 11228        LogInjectingParameter(logger, param);
 11229        if (param.FormOptions is not null)
 230        {
 0231            converted = KrFormParser.Parse(context.HttpContext, param.FormOptions, context.Ct);
 0232            converted = CoerceFormPayloadForParameter(param.ParameterType, converted, logger, ps);
 233        }
 234        else
 235        {
 11236            converted = GetConvertedParameterValue(context, param, out var shouldLog);
 11237            converted = ConvertBodyParameterIfNeeded(context, param, converted);
 11238            LogAddingParameter(logger, name, converted, shouldLog);
 239        }
 240
 11241        if (converted is Hashtable ht && param.ParameterType is not null)
 242        {
 7243            var coerced = TryConvertHashtableToParameterType(param.ParameterType, ht, logger, ps);
 7244            if (coerced is not null)
 245            {
 0246                converted = coerced;
 247            }
 248        }
 11249        _ = ps.AddParameter(name, converted);
 11250        StoreResolvedParameter(context, param, name, converted);
 11251    }
 252
 253    /// <summary>
 254    /// Coerces a parsed form payload to the expected parameter type when possible.
 255    /// This supports parameters typed as <see cref="IKrFormPayload"/> as well as PowerShell
 256    /// classes derived from <see cref="KrMultipart"/>.
 257    /// </summary>
 258    /// <param name="parameterType">The parameter type to satisfy.</param>
 259    /// <param name="payload">The parsed form payload.</param>
 260    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 261    /// <param name="ps">The current PowerShell instance, when available.</param>
 262    /// <returns>The original payload or a new instance compatible with the parameter type.</returns>
 263    internal static object? CoerceFormPayloadForParameter(Type? parameterType, object? payload, Serilog.ILogger logger, 
 264    {
 3265        if (parameterType is null || payload is not IKrFormPayload formPayload)
 266        {
 0267            return payload;
 268        }
 269
 3270        LogCoerceFormPayload(logger, parameterType, payload, ps);
 271
 3272        var exactMatch = TryHandleExactPayloadType(parameterType, formPayload, logger, ps);
 3273        if (exactMatch is not null)
 274        {
 0275            return exactMatch;
 276        }
 277
 3278        var boundModel = TryBindFormModel(parameterType, formPayload, logger, ps);
 3279        if (boundModel is not null)
 280        {
 0281            return boundModel;
 282        }
 283
 3284        var coerced = TryCoerceToKrFormPayload(parameterType, formPayload, logger, ps);
 3285        return coerced ?? formPayload;
 286    }
 287
 288    /// <summary>
 289    /// Logs form payload coercion details when debug logging is enabled.
 290    /// </summary>
 291    /// <param name="logger">The logger to use.</param>
 292    /// <param name="parameterType">The parameter type being targeted.</param>
 293    /// <param name="payload">The raw payload instance.</param>
 294    /// <param name="ps">The current PowerShell instance, when available.</param>
 295    private static void LogCoerceFormPayload(
 296        Serilog.ILogger logger,
 297        Type parameterType,
 298        object? payload,
 299        PowerShell? ps)
 300    {
 3301        if (!logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 302        {
 0303            return;
 304        }
 305
 3306        var runspaceId = ps?.Runspace?.InstanceId.ToString() ?? "<null>";
 3307        logger.Debug(
 3308            "CoerceFormPayloadForParameter: parameterType={ParameterType} payloadType={PayloadType} runspaceId={Runspace
 3309            parameterType,
 3310            payload?.GetType(),
 3311            runspaceId);
 3312    }
 313
 314    /// <summary>
 315    /// Handles the case where the parsed payload already matches the parameter type.
 316    /// </summary>
 317    /// <param name="parameterType">The parameter type to satisfy.</param>
 318    /// <param name="formPayload">The parsed form payload.</param>
 319    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 320    /// <param name="ps">The current PowerShell instance, when available.</param>
 321    /// <returns>The payload instance when it already matches the target type; otherwise null.</returns>
 322    private static object? TryHandleExactPayloadType(
 323        Type parameterType,
 324        IKrFormPayload formPayload,
 325        Serilog.ILogger logger,
 326        PowerShell? ps)
 327    {
 3328        if (!parameterType.IsInstanceOfType(formPayload))
 329        {
 3330            return null;
 331        }
 332
 333        // The parser may have already constructed the exact derived payload type.
 334        // Still populate any declared KrMultipart-derived properties (outer/nested/etc) from Parts.
 0335        if (formPayload is KrMultipart mp)
 336        {
 0337            TryPopulateKrMultipartDerivedModel(mp, logger, ps);
 338        }
 339
 0340        return formPayload;
 341    }
 342
 343    /// <summary>
 344    /// Attempts to bind a KrBindForm model that does not inherit from payload base classes.
 345    /// </summary>
 346    /// <param name="parameterType">The parameter type to satisfy.</param>
 347    /// <param name="formPayload">The parsed form payload.</param>
 348    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 349    /// <param name="ps">The current PowerShell instance, when available.</param>
 350    /// <returns>A bound model instance when binding succeeds; otherwise null.</returns>
 351    private static object? TryBindFormModel(
 352        Type parameterType,
 353        IKrFormPayload formPayload,
 354        Serilog.ILogger logger,
 355        PowerShell? ps)
 356    {
 357        // Support KrBindForm on types that do not inherit KrMultipart/KrFormData.
 358        // The form kind is inferred from MaxNestingDepth (multipart when > 0; otherwise form-data).
 3359        var bindAttr = parameterType.GetCustomAttribute<KrBindFormAttribute>(inherit: true);
 3360        if (bindAttr is null)
 361        {
 3362            return null;
 363        }
 364        // Decide which binding strategy to use based on MaxNestingDepth.
 0365        return bindAttr.MaxNestingDepth > 0
 0366            ? TryBindMultipartModel(parameterType, formPayload as KrMultipart, logger, ps)
 0367            : TryBindFormDataModel(parameterType, formPayload as KrFormData, logger, ps);
 368    }
 369
 370    /// <summary>
 371    /// Attempts to bind a multipart model from a multipart payload.
 372    /// </summary>
 373    /// <param name="parameterType">The parameter type to satisfy.</param>
 374    /// <param name="multipartPayload">The multipart payload, if available.</param>
 375    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 376    /// <param name="ps">The current PowerShell instance, when available.</param>
 377    /// <returns>A bound model instance when binding succeeds; otherwise null.</returns>
 378    private static object? TryBindMultipartModel(
 379        Type parameterType,
 380        KrMultipart? multipartPayload,
 381        Serilog.ILogger logger,
 382        PowerShell? ps)
 383    {
 0384        if (multipartPayload is null)
 385        {
 0386            return null;
 387        }
 388
 0389        var instance = TryCreateObjectInstance(parameterType, logger, ps);
 0390        if (instance is null)
 391        {
 0392            logger.Warning("Failed to create form payload instance for parameter type {ParameterType}.", parameterType);
 0393            return null;
 394        }
 395
 0396        var instanceType = instance.GetType();
 0397        TryPopulateMultipartStorage(instance, multipartPayload, logger);
 0398        TryPopulateMultipartObjectProperties(instance, multipartPayload, instanceType, logger, ps, depth: 0);
 0399        return instance;
 400    }
 401
 402    /// <summary>
 403    /// Attempts to bind a form-data model from a form-data payload.
 404    /// </summary>
 405    /// <param name="parameterType">The parameter type to satisfy.</param>
 406    /// <param name="formDataPayload">The form-data payload, if available.</param>
 407    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 408    /// <param name="ps">The current PowerShell instance, when available.</param>
 409    /// <returns>A bound model instance when binding succeeds; otherwise null.</returns>
 410    private static object? TryBindFormDataModel(
 411        Type parameterType,
 412        KrFormData? formDataPayload,
 413        Serilog.ILogger logger,
 414        PowerShell? ps)
 415    {
 0416        if (formDataPayload is null)
 417        {
 0418            return null;
 419        }
 420
 0421        var instance = TryCreateObjectInstance(parameterType, logger, ps);
 0422        if (instance is null)
 423        {
 0424            logger.Warning("Failed to create form payload instance for parameter type {ParameterType}.", parameterType);
 0425            return null;
 426        }
 427
 0428        var instanceType = instance.GetType();
 0429        TryPopulateFormDataObjectProperties(instance, formDataPayload, instanceType, logger);
 0430        return instance;
 431    }
 432
 433    /// <summary>
 434    /// Attempts to coerce a parsed payload into KrFormData or KrMultipart derived types.
 435    /// </summary>
 436    /// <param name="parameterType">The parameter type to satisfy.</param>
 437    /// <param name="formPayload">The parsed form payload.</param>
 438    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 439    /// <param name="ps">The current PowerShell instance, when available.</param>
 440    /// <returns>The coerced payload instance when conversion succeeds; otherwise null.</returns>
 441    private static object? TryCoerceToKrFormPayload(
 442        Type parameterType,
 443        IKrFormPayload formPayload,
 444        Serilog.ILogger logger,
 445        PowerShell? ps)
 446    {
 3447        var formDataResult = TryCoerceToKrFormData(parameterType, formPayload, logger, ps);
 3448        if (formDataResult is not null)
 449        {
 1450            return formDataResult;
 451        }
 452        // Try multipart next
 2453        return TryCoerceToKrMultipart(parameterType, formPayload, logger, ps);
 454    }
 455
 456    /// <summary>
 457    /// Attempts to coerce a parsed payload into a <see cref="KrFormData"/> derived type.
 458    /// </summary>
 459    /// <param name="parameterType">The parameter type to satisfy.</param>
 460    /// <param name="formPayload">The parsed form payload.</param>
 461    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 462    /// <param name="ps">The current PowerShell instance, when available.</param>
 463    /// <returns>A derived form-data instance when conversion succeeds; otherwise null.</returns>
 464    private static KrFormData? TryCoerceToKrFormData(
 465        Type parameterType,
 466        IKrFormPayload formPayload,
 467        Serilog.ILogger logger,
 468        PowerShell? ps)
 469    {
 3470        if (formPayload is not KrFormData formData || !typeof(KrFormData).IsAssignableFrom(parameterType))
 471        {
 2472            return null;
 473        }
 474
 1475        var instance = IsPowerShellClassType(parameterType) && ps is not null
 1476            ? TryCreateFormDataInstanceInRunspace(ps, parameterType, logger)
 1477            : TryCreateFormDataInstance(parameterType, logger);
 478
 1479        if (instance is null)
 480        {
 0481            return null;
 482        }
 483
 1484        CopyFormData(instance, formData);
 1485        return instance;
 486    }
 487
 488    /// <summary>
 489    /// Attempts to coerce a parsed payload into a <see cref="KrMultipart"/> derived type.
 490    /// </summary>
 491    /// <param name="parameterType">The parameter type to satisfy.</param>
 492    /// <param name="formPayload">The parsed form payload.</param>
 493    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 494    /// <param name="ps">The current PowerShell instance, when available.</param>
 495    /// <returns>A derived multipart instance when conversion succeeds; otherwise null.</returns>
 496    private static KrMultipart? TryCoerceToKrMultipart(
 497        Type parameterType,
 498        IKrFormPayload formPayload,
 499        Serilog.ILogger logger,
 500        PowerShell? ps)
 501    {
 2502        if (formPayload is not KrMultipart multipart || !typeof(KrMultipart).IsAssignableFrom(parameterType))
 503        {
 0504            return null;
 505        }
 506
 2507        var instance = IsPowerShellClassType(parameterType) && ps is not null
 2508            ? TryCreateMultipartInstanceInRunspace(ps, parameterType, logger)
 2509            : TryCreateMultipartInstance(parameterType, logger);
 510
 2511        if (instance is null)
 512        {
 0513            return null;
 514        }
 515
 2516        instance.Parts.AddRange(multipart.Parts);
 2517        TryPopulateKrMultipartDerivedModel(instance, logger, ps);
 2518        return instance;
 519    }
 520
 521    /// <summary>
 522    /// Attempts to populate properties declared on a <see cref="KrMultipart"/>-derived model
 523    /// from the underlying ordered <see cref="KrMultipart.Parts"/> list.
 524    /// </summary>
 525    /// <remarks>
 526    /// <para>
 527    /// The multipart parser produces a runtime container (<see cref="KrMultipart"/>) with ordered parts.
 528    /// This helper performs a best-effort mapping for properties decorated with <see cref="KrPartAttribute"/>
 529    /// so PowerShell model classes (e.g., <c>NestedMultipartRequest : KrMultipart</c>) expose populated
 530    /// <c>$outer</c>/<c>$nested</c> properties in addition to the raw <c>Parts</c> list.
 531    /// </para>
 532    /// <para>
 533    /// Mapping is intentionally conservative: unknown shapes are skipped rather than causing request failure.
 534    /// Validation/limits are still enforced by the parser via rules.
 535    /// </para>
 536    /// </remarks>
 537    /// <param name="model">The model instance to populate.</param>
 538    /// <param name="logger">Logger for warnings.</param>
 539    /// <param name="ps">Current runspace PowerShell, used for PowerShell class instantiation when needed.</param>
 540    private static void TryPopulateKrMultipartDerivedModel(KrMultipart model, Serilog.ILogger logger, PowerShell? ps)
 541    {
 542        try
 543        {
 544            // Only attempt to populate properties declared on the derived type.
 2545            var t = model.GetType();
 2546            if (t == typeof(KrMultipart))
 547            {
 0548                return;
 549            }
 550
 2551            TryPopulateMultipartObjectProperties(model, model, t, logger, ps, depth: 0);
 2552        }
 0553        catch (Exception ex)
 554        {
 0555            logger.Warning(ex, "Failed to populate KrMultipart-derived model properties for type {ParameterType}.", mode
 0556        }
 2557    }
 558
 559    /// <summary>
 560    /// Populates KrPart-decorated properties declared on <paramref name="declaringType"/> from a multipart payload.
 561    /// </summary>
 562    /// <param name="target">The object instance to populate.</param>
 563    /// <param name="source">The multipart payload providing parts.</param>
 564    /// <param name="declaringType">The type whose declared properties should be considered.</param>
 565    /// <param name="logger">Logger for warnings.</param>
 566    /// <param name="ps">Current PowerShell runspace, used to instantiate PowerShell class types.</param>
 567    /// <param name="depth">Recursion depth for nested multipart binding.</param>
 568    private static void TryPopulateMultipartObjectProperties(
 569        object target,
 570        KrMultipart source,
 571        Type declaringType,
 572        Serilog.ILogger logger,
 573        PowerShell? ps,
 574        int depth)
 575    {
 3576        if (depth > 4)
 577        {
 0578            return;
 579        }
 580
 581        // Build a case-insensitive map of public instance properties.
 582        // PowerShell-emitted classes can surface properties in ways that make DeclaringType/CanWrite checks unreliable.
 583        // We bind by part name (Content-Disposition: name="...") to property name.
 3584        var props = declaringType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
 6585            .Where(static p => p.GetIndexParameters().Length == 0)
 6586            .Where(static p => !string.Equals(p.Name, "Parts", StringComparison.OrdinalIgnoreCase))
 7587            .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
 588
 14589        foreach (var group in source.Parts
 5590                     .Where(static p => !string.IsNullOrWhiteSpace(p.Name))
 7591                     .GroupBy(p => p.Name!, StringComparer.OrdinalIgnoreCase))
 592        {
 4593            if (!props.TryGetValue(group.Key, out var prop))
 594            {
 595                continue;
 596            }
 597
 598            // Avoid stomping values that were already set.
 599            try
 600            {
 4601                var existing = prop.GetValue(target);
 4602                if (existing is not null)
 603                {
 0604                    continue;
 605                }
 4606            }
 0607            catch
 608            {
 609                // Ignore read failures; we can still attempt to set.
 0610            }
 611
 4612            var matches = group.ToList();
 4613            if (matches.Count == 0)
 614            {
 615                continue;
 616            }
 617
 4618            var value = TryBuildValueForMultipartProperty(prop.PropertyType, matches, logger, ps, depth);
 4619            if (value is null)
 620            {
 621                continue;
 622            }
 623
 4624            TrySetPropertyValue(target, prop, value, logger);
 625        }
 3626    }
 627
 628    /// <summary>
 629    /// Populates simple form-data properties (fields/files) on a model that does not inherit <see cref="KrFormData"/>.
 630    /// </summary>
 631    /// <param name="target">The object instance to populate.</param>
 632    /// <param name="source">The parsed form-data payload.</param>
 633    /// <param name="declaringType">The type whose declared properties should be considered.</param>
 634    /// <param name="logger">Logger for warnings.</param>
 635    private static void TryPopulateFormDataObjectProperties(
 636        object target,
 637        KrFormData source,
 638        Type declaringType,
 639        Serilog.ILogger logger)
 640    {
 0641        var props = GetFormDataBindableProperties(declaringType);
 0642        ApplyFormFieldValues(target, source.Fields, props, logger);
 0643        ApplyFormFileValues(target, source.Files, props, logger);
 0644    }
 645
 646    /// <summary>
 647    /// Builds a case-insensitive map of bindable public instance properties for form data binding.
 648    /// </summary>
 649    /// <param name="declaringType">The type whose properties should be considered.</param>
 650    /// <returns>The property map keyed by property name.</returns>
 651    private static Dictionary<string, PropertyInfo> GetFormDataBindableProperties(Type declaringType)
 652    {
 0653        var props = new Dictionary<string, PropertyInfo>(StringComparer.OrdinalIgnoreCase);
 0654        foreach (var prop in declaringType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
 0655                     .Where(static p => p.GetIndexParameters().Length == 0))
 656        {
 657            // PowerShell-emitted classes can surface duplicate properties that differ only by case.
 658            // Keep the first occurrence and ignore duplicates to avoid CLS conflicts.
 0659            _ = props.TryAdd(prop.Name, prop);
 660        }
 661
 0662        return props;
 663    }
 664
 665    /// <summary>
 666    /// Applies form field values to matching properties.
 667    /// </summary>
 668    /// <param name="target">The object instance to populate.</param>
 669    /// <param name="fields">The form field dictionary.</param>
 670    /// <param name="props">The property map.</param>
 671    /// <param name="logger">Logger for warnings.</param>
 672    private static void ApplyFormFieldValues(
 673        object target,
 674        IReadOnlyDictionary<string, string[]> fields,
 675        IReadOnlyDictionary<string, PropertyInfo> props,
 676        Serilog.ILogger logger)
 677    {
 0678        foreach (var kvp in fields)
 679        {
 0680            if (!props.TryGetValue(kvp.Key, out var prop))
 681            {
 682                continue;
 683            }
 684
 0685            var value = BuildFieldPropertyValue(prop.PropertyType, kvp.Value ?? []);
 0686            if (value is null)
 687            {
 688                continue;
 689            }
 690
 0691            TrySetPropertyValue(target, prop, value, logger);
 692        }
 0693    }
 694
 695    /// <summary>
 696    /// Applies form file values to matching properties.
 697    /// </summary>
 698    /// <param name="target">The object instance to populate.</param>
 699    /// <param name="files">The form file dictionary.</param>
 700    /// <param name="props">The property map.</param>
 701    /// <param name="logger">Logger for warnings.</param>
 702    private static void ApplyFormFileValues(
 703        object target,
 704        IReadOnlyDictionary<string, KrFilePart[]> files,
 705        IReadOnlyDictionary<string, PropertyInfo> props,
 706        Serilog.ILogger logger)
 707    {
 0708        foreach (var kvp in files)
 709        {
 0710            if (!props.TryGetValue(kvp.Key, out var prop))
 711            {
 712                continue;
 713            }
 714
 0715            var value = BuildFilePropertyValue(prop.PropertyType, kvp.Value ?? []);
 0716            if (value is null)
 717            {
 718                continue;
 719            }
 720
 0721            TrySetPropertyValue(target, prop, value, logger);
 722        }
 0723    }
 724
 725    /// <summary>
 726    /// Builds a value for a field-backed property.
 727    /// </summary>
 728    /// <param name="propertyType">The target property type.</param>
 729    /// <param name="values">The field values.</param>
 730    /// <returns>The resolved value or <c>null</c> when no match exists.</returns>
 731    private static object? BuildFieldPropertyValue(Type propertyType, string[] values)
 732    {
 0733        if (propertyType == typeof(string))
 734        {
 0735            return values.FirstOrDefault();
 736        }
 737
 0738        if (propertyType == typeof(string[]))
 739        {
 0740            return values;
 741        }
 742
 0743        if (propertyType == typeof(object))
 744        {
 0745            return values.Length == 1 ? values[0] : values;
 746        }
 747
 0748        if (!propertyType.IsArray)
 749        {
 0750            return null;
 751        }
 752
 0753        var elementType = propertyType.GetElementType();
 0754        return elementType is null || (elementType != typeof(string) && elementType != typeof(object))
 0755            ? null
 0756            : (object)CreateArrayFromValues(values, elementType);
 757    }
 758
 759    /// <summary>
 760    /// Builds a value for a file-backed property.
 761    /// </summary>
 762    /// <param name="propertyType">The target property type.</param>
 763    /// <param name="files">The file values.</param>
 764    /// <returns>The resolved value or <c>null</c> when no match exists.</returns>
 765    private static object? BuildFilePropertyValue(Type propertyType, KrFilePart[] files)
 766    {
 0767        if (propertyType == typeof(KrFilePart))
 768        {
 0769            return files.FirstOrDefault();
 770        }
 771
 0772        if (propertyType == typeof(KrFilePart[]))
 773        {
 0774            return files;
 775        }
 776
 0777        if (propertyType == typeof(object))
 778        {
 0779            return files.Length == 1 ? files[0] : files;
 780        }
 781
 0782        if (!propertyType.IsArray)
 783        {
 0784            return null;
 785        }
 786
 0787        var elementType = propertyType.GetElementType();
 0788        return elementType is null || (elementType != typeof(KrFilePart) && elementType != typeof(object))
 0789            ? null
 0790            : (object)CreateArrayFromValues(files, elementType);
 791    }
 792
 793    /// <summary>
 794    /// Creates a strongly-typed array from the provided values.
 795    /// </summary>
 796    /// <param name="values">The values to copy into the array.</param>
 797    /// <param name="elementType">The target element type.</param>
 798    /// <returns>A populated array instance.</returns>
 799    private static Array CreateArrayFromValues(IReadOnlyList<object?> values, Type elementType)
 800    {
 0801        var array = Array.CreateInstance(elementType, values.Count);
 0802        for (var i = 0; i < values.Count; i++)
 803        {
 0804            array.SetValue(values[i], i);
 805        }
 806
 0807        return array;
 808    }
 809
 810    /// <summary>
 811    /// Populates the raw multipart storage list (Parts) when the target exposes it.
 812    /// </summary>
 813    /// <param name="target">The object instance to populate.</param>
 814    /// <param name="source">The parsed multipart payload.</param>
 815    /// <param name="logger">Logger for warnings.</param>
 816    private static void TryPopulateMultipartStorage(object target, KrMultipart source, Serilog.ILogger logger)
 817    {
 0818        if (target is KrMultipart typedTarget)
 819        {
 0820            typedTarget.Parts.AddRange(source.Parts);
 0821            return;
 822        }
 823
 824        try
 825        {
 0826            var prop = target.GetType().GetProperty(nameof(KrMultipart.Parts), BindingFlags.Public | BindingFlags.Instan
 0827            if (prop?.GetValue(target) is IList list)
 828            {
 0829                foreach (var part in source.Parts)
 830                {
 0831                    _ = list.Add(part);
 832                }
 833            }
 0834        }
 0835        catch (Exception ex)
 836        {
 0837            logger.Debug(ex, "Failed to populate multipart storage Parts for type {Type}.", target.GetType());
 0838        }
 0839    }
 840
 841    /// <summary>
 842    /// Builds a value for a multipart model property from matching parts.
 843    /// </summary>
 844    private static object? TryBuildValueForMultipartProperty(
 845        Type propertyType,
 846        List<KrRawPart> matchingParts,
 847        Serilog.ILogger logger,
 848        PowerShell? ps,
 849        int depth)
 850    {
 4851        if (propertyType.IsArray)
 852        {
 1853            var elementType = propertyType.GetElementType();
 1854            if (elementType is null)
 855            {
 0856                return null;
 857            }
 858
 1859            var items = new List<object?>();
 4860            foreach (var part in matchingParts)
 861            {
 1862                items.Add(TryBuildSingleMultipartValue(elementType, part, logger, ps, depth));
 863            }
 864
 1865            var typed = Array.CreateInstance(elementType, items.Count);
 4866            for (var i = 0; i < items.Count; i++)
 867            {
 1868                if (items[i] is not null)
 869                {
 1870                    typed.SetValue(items[i], i);
 871                }
 872            }
 873
 1874            return typed;
 875        }
 876
 3877        return TryBuildSingleMultipartValue(propertyType, matchingParts[0], logger, ps, depth);
 878    }
 879
 880    /// <summary>
 881    /// Builds a single value for a multipart model property from one matching part.
 882    /// </summary>
 883    /// <param name="targetType">The target property type.</param>
 884    /// <param name="part">The matching multipart part.</param>
 885    /// <param name="logger">Logger for warnings.</param>
 886    /// <param name="ps">Current PowerShell runspace, used to instantiate PowerShell class types.</param>
 887    /// <param name="depth">Recursion depth for nested multipart binding.</param>
 888    /// <returns>The built value, or null if building failed.</returns>
 889    private static object? TryBuildSingleMultipartValue(
 890        Type targetType,
 891        KrRawPart part,
 892        Serilog.ILogger logger,
 893        PowerShell? ps,
 894        int depth)
 895    {
 4896        if (targetType == typeof(string))
 897        {
 2898            return TryReadPartAsString(part, logger);
 899        }
 900
 2901        var nestedResult = TryBuildNestedMultipartValue(targetType, part, logger, ps, depth);
 2902        return nestedResult is not null ? nestedResult : TryBuildJsonMultipartValue(targetType, part, logger, ps);
 903    }
 904
 905    /// <summary>
 906    /// Attempts to build a multipart value from a nested multipart payload.
 907    /// </summary>
 908    /// <param name="targetType">The target property type.</param>
 909    /// <param name="part">The matching multipart part.</param>
 910    /// <param name="logger">Logger for warnings.</param>
 911    /// <param name="ps">Current PowerShell runspace, used to instantiate PowerShell class types.</param>
 912    /// <param name="depth">Recursion depth for nested multipart binding.</param>
 913    /// <returns>The nested instance or <c>null</c> when the part is not nested or cannot be bound.</returns>
 914    private static object? TryBuildNestedMultipartValue(
 915        Type targetType,
 916        KrRawPart part,
 917        Serilog.ILogger logger,
 918        PowerShell? ps,
 919        int depth)
 920    {
 2921        if (part.NestedPayload is not KrMultipart nested)
 922        {
 1923            return null;
 924        }
 925
 1926        var nestedInstance = TryCreateObjectInstance(targetType, logger, ps);
 1927        if (nestedInstance is null)
 928        {
 0929            LogNestedMultipartCreateFailure(logger, targetType, part, ps);
 0930            return null;
 931        }
 932
 1933        TryPopulateMultipartObjectProperties(nestedInstance, nested, targetType, logger, ps, depth + 1);
 1934        return nestedInstance;
 935    }
 936
 937    /// <summary>
 938    /// Attempts to build a multipart value from a JSON part payload.
 939    /// </summary>
 940    /// <param name="targetType">The target property type.</param>
 941    /// <param name="part">The matching multipart part.</param>
 942    /// <param name="logger">Logger for warnings.</param>
 943    /// <param name="ps">Current PowerShell runspace, used to instantiate PowerShell class types.</param>
 944    /// <returns>The resolved value or <c>null</c> when no match exists.</returns>
 945    private static object? TryBuildJsonMultipartValue(
 946        Type targetType,
 947        KrRawPart part,
 948        Serilog.ILogger logger,
 949        PowerShell? ps)
 950    {
 1951        if (!IsJsonPart(part))
 952        {
 0953            return null;
 954        }
 955
 1956        var json = TryReadPartAsString(part, logger);
 1957        if (string.IsNullOrWhiteSpace(json))
 958        {
 0959            return null;
 960        }
 961
 1962        var ht = TryParseJsonToHashtable(json, part, logger);
 1963        if (ht is null)
 964        {
 0965            return targetType == typeof(object) ? ConvertJsonToHashtable(json) : null;
 966        }
 967
 1968        var obj = TryCreateObjectInstance(targetType, logger, ps);
 1969        if (obj is not null)
 970        {
 1971            if (TrySetAdditionalPropertiesBag(obj, ht, logger))
 972            {
 1973                return obj;
 974            }
 975
 0976            LogJsonAdditionalPropertiesFailure(logger, targetType, part, ps);
 977        }
 978
 0979        if (obj is null)
 980        {
 0981            LogJsonInstanceCreateFailure(logger, targetType, part, ps);
 982        }
 983
 0984        return targetType == typeof(object) ? ht : null;
 985    }
 986
 987    /// <summary>
 988    /// Determines whether a multipart part contains JSON content.
 989    /// </summary>
 990    /// <param name="part">The multipart part.</param>
 991    /// <returns><c>true</c> if the part is JSON; otherwise <c>false</c>.</returns>
 992    private static bool IsJsonPart(KrRawPart part)
 1993        => MediaTypeHelper.Canonicalize(part.ContentType).Equals("application/json", StringComparison.OrdinalIgnoreCase)
 994
 995    /// <summary>
 996    /// Attempts to parse JSON text into a hashtable, throwing a form exception on invalid JSON.
 997    /// </summary>
 998    /// <param name="json">The JSON string.</param>
 999    /// <param name="part">The multipart part.</param>
 1000    /// <param name="logger">Logger for warnings.</param>
 1001    /// <returns>The parsed hashtable or <c>null</c> when parsing yields a non-hashtable.</returns>
 1002    private static Hashtable? TryParseJsonToHashtable(string json, KrRawPart part, Serilog.ILogger logger)
 1003    {
 1004        try
 1005        {
 11006            return ConvertJsonToHashtable(json) as Hashtable;
 1007        }
 01008        catch (JsonException ex)
 1009        {
 01010            logger.Warning(ex, "Invalid JSON payload for multipart part '{PartName}'.", part.Name);
 01011            throw new KrFormException("Invalid JSON payload for form part.", StatusCodes.Status400BadRequest);
 1012        }
 11013    }
 1014
 1015    /// <summary>
 1016    /// Logs a failure to create a nested multipart instance when debug logging is enabled.
 1017    /// </summary>
 1018    /// <param name="logger">Logger for warnings.</param>
 1019    /// <param name="targetType">The target property type.</param>
 1020    /// <param name="part">The multipart part.</param>
 1021    /// <param name="ps">Current PowerShell runspace, if available.</param>
 1022    private static void LogNestedMultipartCreateFailure(
 1023        Serilog.ILogger logger,
 1024        Type targetType,
 1025        KrRawPart part,
 1026        PowerShell? ps)
 1027    {
 01028        if (!logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1029        {
 01030            return;
 1031        }
 1032
 01033        logger.Debug(
 01034            "Multipart bind: failed to create nested instance for targetType={TargetType} partName={PartName} contentTyp
 01035            targetType,
 01036            part.Name,
 01037            part.ContentType,
 01038            ps?.Runspace?.InstanceId.ToString() ?? "<null>");
 01039    }
 1040
 1041    /// <summary>
 1042    /// Logs a failure to set AdditionalProperties for JSON parts when debug logging is enabled.
 1043    /// </summary>
 1044    /// <param name="logger">Logger for warnings.</param>
 1045    /// <param name="targetType">The target property type.</param>
 1046    /// <param name="part">The multipart part.</param>
 1047    /// <param name="ps">Current PowerShell runspace, if available.</param>
 1048    private static void LogJsonAdditionalPropertiesFailure(
 1049        Serilog.ILogger logger,
 1050        Type targetType,
 1051        KrRawPart part,
 1052        PowerShell? ps)
 1053    {
 01054        if (!logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1055        {
 01056            return;
 1057        }
 1058
 01059        logger.Debug(
 01060            "Multipart bind: created instance for json part but could not set AdditionalProperties for targetType={Targe
 01061            targetType,
 01062            part.Name,
 01063            ps?.Runspace?.InstanceId.ToString() ?? "<null>");
 01064    }
 1065
 1066    /// <summary>
 1067    /// Logs a failure to create an instance for a JSON part when debug logging is enabled.
 1068    /// </summary>
 1069    /// <param name="logger">Logger for warnings.</param>
 1070    /// <param name="targetType">The target property type.</param>
 1071    /// <param name="part">The multipart part.</param>
 1072    /// <param name="ps">Current PowerShell runspace, if available.</param>
 1073    private static void LogJsonInstanceCreateFailure(
 1074        Serilog.ILogger logger,
 1075        Type targetType,
 1076        KrRawPart part,
 1077        PowerShell? ps)
 1078    {
 01079        if (!logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1080        {
 01081            return;
 1082        }
 1083
 01084        logger.Debug(
 01085            "Multipart bind: failed to create instance for json part targetType={TargetType} partName={PartName} runspac
 01086            targetType,
 01087            part.Name,
 01088            ps?.Runspace?.InstanceId.ToString() ?? "<null>");
 01089    }
 1090
 1091    /// <summary>
 1092    /// Reads a stored part payload as UTF-8 text.
 1093    /// </summary>
 1094    /// <param name="part">The multipart part to read.</param>
 1095    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1096    /// <returns>The part payload as a string, or null if reading failed.</returns>
 1097    private static string? TryReadPartAsString(KrRawPart part, Serilog.ILogger logger)
 1098    {
 1099        try
 1100        {
 31101            return string.IsNullOrWhiteSpace(part.TempPath) || !File.Exists(part.TempPath)
 31102                ? null
 31103                : File.ReadAllText(part.TempPath, Encoding.UTF8);
 1104        }
 01105        catch (Exception ex)
 1106        {
 01107            logger.Warning(ex, "Failed to read multipart part payload from '{TempPath}'.", part.TempPath);
 01108            return null;
 1109        }
 31110    }
 1111
 1112    /// <summary>
 1113    /// Attempts to create an instance of the requested type, using the current PowerShell runspace
 1114    /// for PowerShell-emitted class types.
 1115    /// </summary>
 1116    /// <param name="type">The type to instantiate.</param>
 1117    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1118    /// <param name="ps">The current PowerShell instance, when available.</param>
 1119    /// <returns>The created instance, or null if instantiation failed.</returns>
 1120    private static object? TryCreateObjectInstance(Type type, Serilog.ILogger logger, PowerShell? ps)
 1121    {
 41122        return IsPowerShellClassType(type) && ps?.Runspace is not null
 41123            ? TryCreatePowerShellClassInstance(type, logger, ps)
 41124            : TryCreateStandardInstance(type, logger);
 1125    }
 1126
 1127    /// <summary>
 1128    /// Attempts to create a PowerShell-emitted class instance using the current runspace.
 1129    /// </summary>
 1130    /// <param name="type">The type to instantiate.</param>
 1131    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1132    /// <param name="ps">The current PowerShell instance.</param>
 1133    /// <returns>The created instance, or null if instantiation failed.</returns>
 1134    private static object? TryCreatePowerShellClassInstance(Type type, Serilog.ILogger logger, PowerShell ps)
 1135    {
 1136        // For PowerShell-emitted classes, create instances inside the current runspace only.
 1137        // This avoids cross-runspace type identity mismatches.
 21138        var runspaceInstance = TryCreateObjectInstanceInRunspace(ps, type, logger);
 21139        if (runspaceInstance is not null)
 1140        {
 21141            return runspaceInstance;
 1142        }
 1143
 01144        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1145        {
 01146            logger.Debug("Failed to create PowerShell class instance in the current runspace for type {Type}.", type);
 1147        }
 1148
 01149        return TryCreateUninitializedInstance(type, logger, "PowerShell class type");
 1150    }
 1151
 1152    /// <summary>
 1153    /// Attempts to create a standard instance via Activator with a fallback to an uninitialized instance.
 1154    /// </summary>
 1155    /// <param name="type">The type to instantiate.</param>
 1156    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1157    /// <returns>The created instance, or null if instantiation failed.</returns>
 1158    private static object? TryCreateStandardInstance(Type type, Serilog.ILogger logger)
 1159    {
 1160        try
 1161        {
 21162            return Activator.CreateInstance(type, nonPublic: true);
 1163        }
 01164        catch (Exception ex)
 1165        {
 01166            if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1167            {
 01168                logger.Debug(ex, "Failed to create instance via Activator for type {Type}.", type);
 1169            }
 01170        }
 1171
 1172        // PowerShell-emitted types sometimes fail Activator-based construction even though they are otherwise usable.
 1173        // As a fallback, create an uninitialized instance so we can still populate properties.
 01174        return TryCreateUninitializedInstance(type, logger, "type");
 21175    }
 1176
 1177    /// <summary>
 1178    /// Attempts to create an uninitialized instance when the type is a non-abstract reference type.
 1179    /// </summary>
 1180    /// <param name="type">The type to instantiate.</param>
 1181    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1182    /// <param name="typeDescription">A short description of the type for logging.</param>
 1183    /// <returns>The uninitialized instance, or null if instantiation failed.</returns>
 1184    private static object? TryCreateUninitializedInstance(Type type, Serilog.ILogger logger, string typeDescription)
 1185    {
 01186        if (type.IsValueType || type.IsAbstract)
 1187        {
 01188            return null;
 1189        }
 1190
 1191        try
 1192        {
 1193#pragma warning disable SYSLIB0050
 01194            return FormatterServices.GetUninitializedObject(type);
 1195#pragma warning restore SYSLIB0050
 1196        }
 01197        catch (Exception ex)
 1198        {
 01199            if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1200            {
 01201                logger.Debug(ex, "Failed to create uninitialized instance for {TypeDescription} {Type}.", typeDescriptio
 1202            }
 01203        }
 1204
 01205        return null;
 01206    }
 1207
 1208    /// <summary>
 1209    /// Creates an instance of <paramref name="type"/> using the current PowerShell runspace.
 1210    /// </summary>
 1211    /// <param name="ps">The current PowerShell instance.</param>
 1212    /// <param name="type">The type to instantiate.</param>
 1213    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1214    /// <returns>The created instance, or null if instantiation failed.</returns>
 1215    private static object? TryCreateObjectInstanceInRunspace(PowerShell ps, Type type, Serilog.ILogger logger)
 1216    {
 1217        try
 1218        {
 21219            if (ps.Runspace is null)
 1220            {
 01221                if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1222                {
 01223                    logger.Debug("Runspace instance creation skipped: PowerShell.Runspace is null for type {ParameterTyp
 1224                }
 01225                return null;
 1226            }
 1227
 21228            using var localPs = PowerShell.Create();
 21229            localPs.Runspace = ps.Runspace;
 1230
 21231            var candidates = new[]
 21232            {
 21233                type.Name,
 21234                type.FullName,
 21235                type.AssemblyQualifiedName
 21236            };
 1237
 81238            foreach (var candidate in candidates.Where(static c => !string.IsNullOrWhiteSpace(c)).Distinct(StringCompare
 1239            {
 21240                localPs.Commands.Clear();
 21241                localPs.Streams.ClearStreams();
 1242
 21243                var escapedName = EscapePowerShellString(candidate!);
 21244                _ = localPs.AddScript($"[Activator]::CreateInstance([type]'{escapedName}')");
 1245
 21246                var results = localPs.Invoke();
 21247                if (!localPs.HadErrors && results.Count > 0)
 1248                {
 21249                    return results[0]?.BaseObject;
 1250                }
 1251
 1252                // Keep trying other names; some PowerShell types resolve better via FullName/Name than AQN.
 01253                if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1254                {
 01255                    logger.Debug("Failed to create instance for type {ParameterType} using type name '{TypeName}'.", typ
 1256                }
 1257            }
 1258
 01259            logger.Warning("Failed to resolve PowerShell class type {ParameterType} in the current runspace.", type);
 01260            return null;
 1261        }
 01262        catch (Exception ex)
 1263        {
 01264            logger.Warning(ex, "Failed to create PowerShell class instance for type {ParameterType}.", type);
 01265            return null;
 1266        }
 21267    }
 1268
 1269    /// <summary>
 1270    /// Attempts to assign <paramref name="value"/> to the given property.
 1271    /// </summary>
 1272    private static void TrySetPropertyValue(object target, PropertyInfo prop, object value, Serilog.ILogger logger)
 1273    {
 1274        try
 1275        {
 41276            if (prop.PropertyType.IsInstanceOfType(value))
 1277            {
 41278                prop.SetValue(target, value);
 41279                return;
 1280            }
 1281
 1282            // Allow assigning any value into [object].
 01283            if (prop.PropertyType == typeof(object))
 1284            {
 01285                prop.SetValue(target, value);
 1286            }
 01287        }
 01288        catch (Exception ex)
 1289        {
 01290            logger.Warning(ex, "Failed to set property {PropertyName} on type {Type}.", prop.Name, target.GetType());
 1291
 1292            // Best-effort fallback for dynamically-emitted types: try writing the auto-property backing field.
 01293            _ = TrySetPropertyBackingFieldValue(target, prop, value, logger);
 01294        }
 41295    }
 1296
 1297    /// <summary>
 1298    /// Attempts to set an auto-property backing field for a property on a target instance.
 1299    /// </summary>
 1300    private static bool TrySetPropertyBackingFieldValue(object target, PropertyInfo prop, object value, Serilog.ILogger 
 1301    {
 1302        try
 1303        {
 01304            var t = target.GetType();
 01305            var field = t.GetField($"<{prop.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)
 01306                        ?? t.GetField(prop.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 1307
 01308            if (field is null)
 1309            {
 01310                return false;
 1311            }
 1312
 01313            if (field.FieldType.IsInstanceOfType(value) || field.FieldType == typeof(object))
 1314            {
 01315                field.SetValue(target, value);
 01316                return true;
 1317            }
 1318
 01319            return false;
 1320        }
 01321        catch (Exception ex)
 1322        {
 01323            logger.Debug(ex, "Failed to set backing field for property {PropertyName} on type {Type}.", prop.Name, targe
 01324            return false;
 1325        }
 01326    }
 1327
 1328    /// <summary>
 1329    /// Attempts to set an <c>AdditionalProperties</c> bag (or similarly named) on a model instance.
 1330    /// </summary>
 1331    private static bool TrySetAdditionalPropertiesBag(object target, Hashtable ht, Serilog.ILogger logger)
 1332    {
 1333        try
 1334        {
 11335            ht = NormalizeAdditionalPropertiesBag(target, ht, logger);
 11336            var prop = target.GetType().GetProperty("AdditionalProperties", BindingFlags.Public | BindingFlags.Instance)
 11337            if (prop is null)
 1338            {
 01339                return TrySetAdditionalPropertiesBackingField(target, ht, logger);
 1340            }
 1341
 11342            if (prop.CanWrite && (prop.PropertyType == typeof(object) || prop.PropertyType.IsInstanceOfType(ht)))
 1343            {
 1344                try
 1345                {
 11346                    prop.SetValue(target, ht);
 11347                    return true;
 1348                }
 01349                catch (Exception ex)
 1350                {
 1351                    // Some dynamically-emitted types have setters that can throw; fall back to backing field.
 01352                    logger.Debug(ex, "Failed to set AdditionalProperties via setter on type {Type}; trying backing field
 01353                    return TrySetAdditionalPropertiesBackingField(target, ht, logger);
 1354                }
 1355            }
 1356
 1357            // Some PowerShell-emitted properties can be effectively read-only to reflection.
 01358            return TrySetAdditionalPropertiesBackingField(target, ht, logger);
 1359        }
 01360        catch (Exception ex)
 1361        {
 01362            logger.Warning(ex, "Failed to set AdditionalProperties on type {Type}.", target.GetType());
 01363            return false;
 1364        }
 11365    }
 1366
 1367    /// <summary>
 1368    /// Attempts to set an <c>AdditionalProperties</c> backing field on a model instance.
 1369    /// </summary>
 1370    /// <param name="target">The target instance.</param>
 1371    /// <param name="ht">The hashtable to assign.</param>
 1372    /// <param name="logger">Logger for diagnostics.</param>
 1373    /// <returns>True if the backing field was set; otherwise false.</returns>
 1374    private static Hashtable NormalizeAdditionalPropertiesBag(object target, Hashtable ht, Serilog.ILogger logger)
 1375    {
 11376        if (!TryGetAdditionalPropertiesMetadata(target, out var additionalTypeName, out var patterns))
 1377        {
 11378            return ht;
 1379        }
 1380        // If there are pattern rules, apply those first.
 01381        if (patterns.Count > 0)
 1382        {
 01383            return FilterPatternedAdditionalProperties(target, ht, patterns, logger);
 1384        }
 1385        // Otherwise, if there's a single type name, convert all properties to that type.
 01386        return string.IsNullOrWhiteSpace(additionalTypeName)
 01387            ? ht
 01388            : ConvertAdditionalPropertiesByType(target, ht, additionalTypeName, logger);
 1389    }
 1390
 1391    /// <summary>
 1392    /// Filters and converts additional properties based on pattern rules.
 1393    /// </summary>
 1394    /// <param name="target">The target instance.</param>
 1395    /// <param name="ht">The original properties hashtable.</param>
 1396    /// <param name="patterns">The pattern rules to apply.</param>
 1397    /// <param name="logger">Logger for diagnostics.</param>
 1398    /// <returns>The filtered and converted properties.</returns>
 1399    private static Hashtable FilterPatternedAdditionalProperties(
 1400        object target,
 1401        Hashtable ht,
 1402        IReadOnlyList<PatternPropertyRule> patterns,
 1403        Serilog.ILogger logger)
 1404    {
 01405        var filtered = new Hashtable(StringComparer.OrdinalIgnoreCase);
 01406        foreach (DictionaryEntry entry in ht)
 1407        {
 01408            if (entry.Key is not string key)
 1409            {
 1410                continue;
 1411            }
 1412
 01413            var match = patterns.FirstOrDefault(p => p.IsMatch(key));
 01414            if (match is null)
 1415            {
 1416                continue;
 1417            }
 1418
 01419            var targetType = ResolveAdditionalPropertiesType(target.GetType(), match.SchemaTypeName);
 01420            filtered[key] = targetType is null
 01421                ? entry.Value
 01422                : ConvertAdditionalPropertyValue(entry.Value, targetType, logger);
 1423        }
 1424
 01425        return filtered;
 1426    }
 1427
 1428    /// <summary>
 1429    /// Converts additional properties using a single resolved target type.
 1430    /// </summary>
 1431    /// <param name="target">The target instance.</param>
 1432    /// <param name="ht">The original properties hashtable.</param>
 1433    /// <param name="typeName">The additional properties type name.</param>
 1434    /// <param name="logger">Logger for diagnostics.</param>
 1435    /// <returns>The converted properties.</returns>
 1436    private static Hashtable ConvertAdditionalPropertiesByType(
 1437        object target,
 1438        Hashtable ht,
 1439        string typeName,
 1440        Serilog.ILogger logger)
 1441    {
 01442        var targetType = ResolveAdditionalPropertiesType(target.GetType(), typeName);
 01443        if (targetType is null)
 1444        {
 01445            return ht;
 1446        }
 1447
 01448        var converted = new Hashtable(StringComparer.OrdinalIgnoreCase);
 01449        foreach (DictionaryEntry entry in ht)
 1450        {
 01451            converted[entry.Key] = ConvertAdditionalPropertyValue(entry.Value, targetType, logger);
 1452        }
 1453
 01454        return converted;
 1455    }
 1456
 1457    private static object ConvertAdditionalPropertyValue(object? value, Type targetType, Serilog.ILogger logger)
 1458    {
 01459        if (value is null)
 1460        {
 01461            return value!;
 1462        }
 1463
 01464        if (targetType.IsInstanceOfType(value))
 1465        {
 01466            return value;
 1467        }
 1468
 01469        if (targetType == typeof(object))
 1470        {
 01471            return value;
 1472        }
 1473
 01474        if (value is Hashtable ht)
 1475        {
 01476            return ConvertHashtableToObject(ht, targetType, depth: 0, ps: null) ?? value;
 1477        }
 1478
 01479        if (targetType == typeof(string))
 1480        {
 01481            return value.ToString() ?? string.Empty;
 1482        }
 1483
 01484        if (targetType.IsEnum && value is string s)
 1485        {
 1486            try
 1487            {
 01488                return Enum.Parse(targetType, s, ignoreCase: true);
 1489            }
 01490            catch
 1491            {
 01492                return value;
 1493            }
 1494        }
 1495
 1496        try
 1497        {
 01498            return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture) ?? value;
 1499        }
 01500        catch (Exception ex)
 1501        {
 01502            logger.Debug(ex, "Failed to convert additional property to {TargetType}.", targetType);
 01503            return value;
 1504        }
 01505    }
 1506
 1507    private static bool TryGetAdditionalPropertiesMetadata(
 1508        object target,
 1509        out string? additionalTypeName,
 1510        out List<PatternPropertyRule> patterns)
 1511    {
 11512        additionalTypeName = null;
 11513        patterns = [];
 1514
 11515        var metaProp = target.GetType().GetProperty(
 11516            "AdditionalPropertiesMetadata",
 11517            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
 1518
 11519        if (metaProp?.GetValue(null) is not Hashtable meta)
 1520        {
 11521            return false;
 1522        }
 1523
 01524        additionalTypeName = meta["AdditionalPropertiesType"] as string;
 1525
 01526        if (meta["PatternProperties"] is IEnumerable list)
 1527        {
 01528            foreach (var entry in list)
 1529            {
 01530                if (entry is not Hashtable pattern)
 1531                {
 1532                    continue;
 1533                }
 1534
 01535                var keyPattern = pattern["KeyPattern"] as string;
 01536                var schemaType = pattern["SchemaType"] as string;
 01537                if (string.IsNullOrWhiteSpace(keyPattern))
 1538                {
 1539                    continue;
 1540                }
 1541
 01542                patterns.Add(new PatternPropertyRule(keyPattern, schemaType));
 1543            }
 1544        }
 1545
 01546        return !string.IsNullOrWhiteSpace(additionalTypeName) || patterns.Count > 0;
 1547    }
 1548
 1549    /// <summary>
 1550    /// Resolves a type for additional properties based on a type name or alias.
 1551    /// </summary>
 1552    /// <param name="targetType">The target type requesting the additional properties type.</param>
 1553    /// <param name="typeName">The type name or alias to resolve.</param>
 1554    /// <returns>The resolved type, or null if resolution failed.</returns>
 1555    private static Type? ResolveAdditionalPropertiesType(Type targetType, string? typeName)
 1556    {
 01557        if (string.IsNullOrWhiteSpace(typeName))
 1558        {
 01559            return null;
 1560        }
 1561
 01562        var trimmed = typeName.Trim();
 1563
 01564        return TryResolveAlias(trimmed)
 01565            ?? TryResolveByTypeName(trimmed)
 01566            ?? TryResolveInAssembly(targetType.Assembly, trimmed)
 01567            ?? TryResolveInLoadedAssemblies(trimmed);
 1568    }
 1569
 1570    /// <summary>
 1571    /// Attempts to resolve a known type alias (e.g., string, int32).
 1572    /// </summary>
 1573    /// <param name="typeName">The candidate alias.</param>
 1574    /// <returns>The resolved CLR type, or null if no alias matched.</returns>
 1575    private static Type? TryResolveAlias(string typeName)
 1576    {
 01577        return typeName.ToLowerInvariant() switch
 01578        {
 01579            "string" => typeof(string),
 01580            "int" or "int32" => typeof(int),
 01581            "long" or "int64" => typeof(long),
 01582            "double" => typeof(double),
 01583            "float" => typeof(float),
 01584            "decimal" => typeof(decimal),
 01585            "bool" or "boolean" => typeof(bool),
 01586            "object" => typeof(object),
 01587            "hashtable" => typeof(Hashtable),
 01588            _ => null
 01589        };
 1590    }
 1591
 1592    /// <summary>
 1593    /// Attempts to resolve a type using <see cref="Type.GetType(string, bool, bool)"/>.
 1594    /// </summary>
 1595    /// <param name="typeName">The fully qualified type name.</param>
 1596    /// <returns>The resolved CLR type, or null when resolution failed.</returns>
 1597    private static Type? TryResolveByTypeName(string typeName)
 01598        => System.Type.GetType(typeName, throwOnError: false, ignoreCase: true);
 1599
 1600    /// <summary>
 1601    /// Attempts to resolve a type by searching within the provided assembly.
 1602    /// </summary>
 1603    /// <param name="assembly">The assembly to search.</param>
 1604    /// <param name="typeName">The type name or full name to match.</param>
 1605    /// <returns>The resolved CLR type, or null if no match exists.</returns>
 1606    private static Type? TryResolveInAssembly(Assembly assembly, string typeName)
 1607    {
 01608        return assembly.GetTypes()
 01609            .FirstOrDefault(t => string.Equals(t.FullName, typeName, StringComparison.OrdinalIgnoreCase)
 01610                              || string.Equals(t.Name, typeName, StringComparison.OrdinalIgnoreCase));
 1611    }
 1612
 1613    /// <summary>
 1614    /// Attempts to resolve a type by searching all loaded assemblies.
 1615    /// </summary>
 1616    /// <param name="typeName">The type name or full name to match.</param>
 1617    /// <returns>The resolved CLR type, or null if no match exists.</returns>
 1618    private static Type? TryResolveInLoadedAssemblies(string typeName)
 1619    {
 01620        foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
 1621        {
 01622            var match = TryResolveInAssembly(asm, typeName);
 01623            if (match is not null)
 1624            {
 01625                return match;
 1626            }
 1627        }
 1628
 01629        return null;
 1630    }
 1631
 1632    /// <summary>
 1633    /// Attempts to set a backing field for an <c>AdditionalProperties</c> auto-property.
 1634    /// </summary>
 1635    private static bool TrySetAdditionalPropertiesBackingField(object target, Hashtable ht, Serilog.ILogger logger)
 1636    {
 1637        try
 1638        {
 01639            var t = target.GetType();
 01640            var field = t.GetField("<AdditionalProperties>k__BackingField", BindingFlags.Instance | BindingFlags.NonPubl
 01641                        ?? t.GetField("AdditionalProperties", BindingFlags.Instance | BindingFlags.Public | BindingFlags
 1642
 01643            if (field is null)
 1644            {
 01645                return false;
 1646            }
 1647
 01648            if (field.FieldType == typeof(object) || field.FieldType.IsInstanceOfType(ht))
 1649            {
 01650                field.SetValue(target, ht);
 01651                return true;
 1652            }
 1653
 01654            return false;
 1655        }
 01656        catch (Exception ex)
 1657        {
 01658            logger.Debug(ex, "Failed to set AdditionalProperties backing field on type {Type}.", target.GetType());
 01659            return false;
 1660        }
 01661    }
 1662
 1663    /// <summary>
 1664    /// Returns true when the type is a PowerShell class emitted in a dynamic assembly.
 1665    /// </summary>
 1666    /// <param name="type">The type to inspect.</param>
 1667    /// <returns>True when the type originates from a PowerShell class assembly.</returns>
 1668    private static bool IsPowerShellClassType(Type type)
 1669    {
 71670        var asm = type.Assembly;
 71671        if (!asm.IsDynamic)
 1672        {
 41673            return false;
 1674        }
 1675
 31676        var fullName = asm.FullName;
 31677        return fullName is not null && fullName.StartsWith("PowerShell Class Assembly", StringComparison.Ordinal);
 1678    }
 1679
 1680    /// <summary>
 1681    /// Attempts to create a <see cref="KrMultipart"/> instance for a derived parameter type.
 1682    /// </summary>
 1683    /// <param name="parameterType">The parameter type to construct.</param>
 1684    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1685    /// <returns>A new multipart instance or null if creation failed.</returns>
 1686    private static KrMultipart? TryCreateMultipartInstance(Type parameterType, Serilog.ILogger logger)
 1687    {
 1688        try
 1689        {
 11690            var instance = Activator.CreateInstance(parameterType, nonPublic: true) as KrMultipart;
 11691            if (instance is null)
 1692            {
 01693                logger.Warning("Failed to create form payload instance for parameter type {ParameterType}.", parameterTy
 1694            }
 11695            return instance;
 1696        }
 01697        catch (Exception ex)
 1698        {
 01699            logger.Warning(ex, "Failed to create form payload instance for parameter type {ParameterType}.", parameterTy
 01700            return null;
 1701        }
 11702    }
 1703
 1704    /// <summary>
 1705    /// Attempts to create a <see cref="KrFormData"/> instance for a derived parameter type.
 1706    /// </summary>
 1707    /// <param name="parameterType">The parameter type to construct.</param>
 1708    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1709    /// <returns>A new form data instance or null if creation failed.</returns>
 1710    private static KrFormData? TryCreateFormDataInstance(Type parameterType, Serilog.ILogger logger)
 1711    {
 1712        try
 1713        {
 11714            var instance = Activator.CreateInstance(parameterType, nonPublic: true) as KrFormData;
 11715            if (instance is null)
 1716            {
 01717                logger.Warning("Failed to create form payload instance for parameter type {ParameterType}.", parameterTy
 1718            }
 11719            return instance;
 1720        }
 01721        catch (Exception ex)
 1722        {
 01723            logger.Warning(ex, "Failed to create form payload instance for parameter type {ParameterType}.", parameterTy
 01724            return null;
 1725        }
 11726    }
 1727
 1728    /// <summary>
 1729    /// Attempts to create a derived <see cref="KrMultipart"/> instance using the current PowerShell runspace.
 1730    /// </summary>
 1731    /// <param name="ps">The current PowerShell instance.</param>
 1732    /// <param name="parameterType">The parameter type to construct.</param>
 1733    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1734    /// <returns>A new multipart instance or null if creation failed.</returns>
 1735    private static KrMultipart? TryCreateMultipartInstanceInRunspace(PowerShell ps, Type parameterType, Serilog.ILogger 
 1736    {
 1737        try
 1738        {
 11739            using var localPs = PowerShell.Create();
 11740            localPs.Runspace = ps.Runspace;
 1741
 11742            var typeName = parameterType.FullName ?? parameterType.Name;
 11743            var escapedName = EscapePowerShellString(typeName);
 11744            _ = localPs.AddScript($"[Activator]::CreateInstance([type]'{escapedName}')");
 1745
 11746            var results = localPs.Invoke();
 11747            if (localPs.HadErrors || results.Count == 0)
 1748            {
 01749                logger.Warning("Failed to resolve PowerShell class type {ParameterType} in the current runspace.", param
 01750                return null;
 1751            }
 1752
 11753            var instance = results[0]?.BaseObject as KrMultipart;
 11754            if (instance is null)
 1755            {
 01756                logger.Warning("Resolved PowerShell class type {ParameterType} is not a KrMultipart instance.", paramete
 1757            }
 1758
 11759            return instance;
 1760        }
 01761        catch (Exception ex)
 1762        {
 01763            logger.Warning(ex, "Failed to create PowerShell class instance for parameter type {ParameterType}.", paramet
 01764            return null;
 1765        }
 11766    }
 1767
 1768    /// <summary>
 1769    /// Attempts to create a derived <see cref="KrFormData"/> instance using the current PowerShell runspace.
 1770    /// </summary>
 1771    /// <param name="ps">The current PowerShell instance.</param>
 1772    /// <param name="parameterType">The parameter type to construct.</param>
 1773    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 1774    /// <returns>A new form data instance or null if creation failed.</returns>
 1775    private static KrFormData? TryCreateFormDataInstanceInRunspace(PowerShell ps, Type parameterType, Serilog.ILogger lo
 1776    {
 1777        try
 1778        {
 01779            using var localPs = PowerShell.Create();
 01780            localPs.Runspace = ps.Runspace;
 1781
 01782            var typeName = parameterType.FullName ?? parameterType.Name;
 01783            var escapedName = EscapePowerShellString(typeName);
 01784            _ = localPs.AddScript($"[Activator]::CreateInstance([type]'{escapedName}')");
 1785
 01786            var results = localPs.Invoke();
 01787            if (localPs.HadErrors || results.Count == 0)
 1788            {
 01789                logger.Warning("Failed to resolve PowerShell class type {ParameterType} in the current runspace.", param
 01790                return null;
 1791            }
 1792
 01793            var instance = results[0]?.BaseObject as KrFormData;
 01794            if (instance is null)
 1795            {
 01796                logger.Warning("Resolved PowerShell class type {ParameterType} is not a KrFormData instance.", parameter
 1797            }
 1798
 01799            return instance;
 1800        }
 01801        catch (Exception ex)
 1802        {
 01803            logger.Warning(ex, "Failed to create PowerShell class instance for parameter type {ParameterType}.", paramet
 01804            return null;
 1805        }
 01806    }
 1807
 1808    /// <summary>
 1809    /// Copies fields and files from a parsed form payload into a derived <see cref="KrFormData"/> instance.
 1810    /// </summary>
 1811    /// <param name="target">The instance to populate.</param>
 1812    /// <param name="source">The parsed payload to copy from.</param>
 1813    private static void CopyFormData(KrFormData target, KrFormData source)
 1814    {
 41815        foreach (var kvp in source.Fields)
 1816        {
 11817            target.Fields[kvp.Key] = kvp.Value;
 1818        }
 1819
 41820        foreach (var kvp in source.Files)
 1821        {
 11822            target.Files[kvp.Key] = kvp.Value;
 1823        }
 11824    }
 1825
 1826    /// <summary>
 1827    /// Escapes a string for inclusion in a single-quoted PowerShell string literal.
 1828    /// </summary>
 1829    /// <param name="value">The raw string value.</param>
 1830    /// <returns>An escaped string safe for single-quoted PowerShell literals.</returns>
 1831    private static string EscapePowerShellString(string value)
 31832        => value.Replace("'", "''", StringComparison.Ordinal);
 1833
 1834    /// <summary>
 1835    /// Logs the injection of a parameter when debug logging is enabled.
 1836    /// </summary>
 1837    /// <param name="logger">The host logger.</param>
 1838    /// <param name="param">The parameter being injected.</param>
 1839    private static void LogInjectingParameter(Serilog.ILogger logger, ParameterForInjectionInfo param)
 1840    {
 111841        if (!logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1842        {
 01843            return;
 1844        }
 1845
 111846        var schemaType = param.Type?.ToString() ?? "<none>";
 111847        var clrType = param.ParameterType?.FullName ?? "<unknown>";
 111848        logger.Debug(
 111849            "Injecting parameter '{Name}' schemaType='{SchemaType}' clrType='{ClrType}' from '{In}'.",
 111850            param.Name,
 111851            schemaType,
 111852            clrType,
 111853            param.In);
 111854    }
 1855
 1856    /// <summary>
 1857    /// Gets the converted parameter value from the current request context.
 1858    /// </summary>
 1859    /// <param name="context">The current Kestrun context.</param>
 1860    /// <param name="param">The parameter metadata.</param>
 1861    /// <param name="shouldLog">Whether the value should be logged.</param>
 1862    /// <returns>The converted parameter value.</returns>
 1863    private static object? GetConvertedParameterValue(KestrunContext context, ParameterForInjectionInfo param, out bool 
 1864    {
 111865        shouldLog = true;
 1866
 111867        return context.Request.Form is not null && context.Request.HasFormContentType
 111868            ? ConvertFormToValue(context.Request.Form, param)
 111869            : GetParameterValueFromContext(context, param, out shouldLog);
 1870    }
 1871
 1872    /// <summary>
 1873    /// Converts a request-body parameter from a raw string to a structured object when possible.
 1874    /// </summary>
 1875    /// <param name="context">The current Kestrun context.</param>
 1876    /// <param name="param">The parameter metadata.</param>
 1877    /// <param name="converted">The current converted value.</param>
 1878    /// <returns>The updated value, possibly converted to an object/hashtable.</returns>
 1879    private static object? ConvertBodyParameterIfNeeded(KestrunContext context, ParameterForInjectionInfo param, object?
 1880    {
 111881        if (!ShouldConvertBody(param, converted))
 1882        {
 111883            return converted;
 1884        }
 1885
 01886        var rawString = (string)converted!;
 01887        var bodyObj = TryConvertBodyByContentType(context, param, rawString);
 1888
 01889        if (bodyObj is not null)
 1890        {
 01891            return bodyObj;
 1892        }
 1893
 01894        context.Logger.WarningSanitized(
 01895            "Unable to convert body parameter '{Name}' with content types: {ContentTypes}. Using raw string value.",
 01896            param.Name,
 01897            param.ContentTypes);
 1898
 01899        return converted;
 1900    }
 1901
 1902    /// <summary>
 1903    /// Logs the addition of a parameter to the PowerShell invocation when requested and debug logging is enabled.
 1904    /// </summary>
 1905    /// <param name="logger">The host logger.</param>
 1906    /// <param name="name">The parameter name.</param>
 1907    /// <param name="value">The value to be added.</param>
 1908    /// <param name="shouldLog">Whether logging should be performed.</param>
 1909    private static void LogAddingParameter(Serilog.ILogger logger, string name, object? value, bool shouldLog)
 1910    {
 111911        if (!shouldLog || !logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1912        {
 01913            return;
 1914        }
 1915
 111916        var valueType = value?.GetType().FullName ?? "<null>";
 111917        logger.DebugSanitized("Adding parameter '{Name}' ({ValueType}): {ConvertedValue}", name, valueType, value);
 111918    }
 1919
 1920    /// <summary>
 1921    /// Stores the resolved parameter on the request context, either as the request body or a named parameter.
 1922    /// </summary>
 1923    /// <param name="context">The current Kestrun context.</param>
 1924    /// <param name="param">The parameter metadata.</param>
 1925    /// <param name="name">The parameter name.</param>
 1926    /// <param name="value">The resolved value.</param>
 1927    private static void StoreResolvedParameter(KestrunContext context, ParameterForInjectionInfo param, string name, obj
 1928    {
 111929        var resolved = new ParameterForInjectionResolved(param, value);
 111930        if (param.IsRequestBody)
 1931        {
 91932            context.Parameters.Body = resolved;
 91933            return;
 1934        }
 1935
 21936        context.Parameters.Parameters[name] = resolved;
 21937    }
 1938
 1939    /// <summary>
 1940    /// Retrieves and converts the parameter value from the HTTP context.
 1941    /// </summary>
 1942    /// <param name="context">The current HTTP context.</param>
 1943    /// <param name="param">The parameter information.</param>
 1944    /// <param name="shouldLog">Indicates whether logging should be performed.</param>
 1945    /// <returns>The converted parameter value.</returns>
 1946    private static object? GetParameterValueFromContext(KestrunContext context, ParameterForInjectionInfo param, out boo
 1947    {
 111948        shouldLog = true;
 111949        var logger = context.Host.Logger;
 111950        var raw = GetRawValue(param, context);
 1951
 111952        if (raw is null)
 1953        {
 11954            if (param.DefaultValue is not null)
 1955            {
 11956                raw = param.DefaultValue.GetValue<object>();
 1957            }
 1958            else
 1959            {
 01960                shouldLog = false;
 01961                return null;
 1962            }
 1963        }
 1964
 111965        if (logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 1966        {
 111967            logger.Debug("Raw value for parameter '{Name}': {RawValue}", param.Name, raw);
 1968        }
 1969
 111970        var (singleValue, multiValue) = NormalizeRaw(raw);
 1971
 111972        if (singleValue is null && multiValue is null)
 1973        {
 01974            shouldLog = false;
 01975            return null;
 1976        }
 1977
 111978        return ConvertValue(context, param, singleValue, multiValue);
 1979    }
 1980
 1981    /// <summary>
 1982    /// Retrieves the raw value of a parameter from the HTTP context based on its location.
 1983    /// </summary>
 1984    /// <param name="param">The parameter information.</param>
 1985    /// <param name="context">The current HTTP context.</param>
 1986    /// <returns>The raw value of the parameter.</returns>
 1987    private static object? GetRawValue(ParameterForInjectionInfo param, KestrunContext context)
 1988    {
 111989        return param.In switch
 111990        {
 111991            ParameterLocation.Path =>
 01992            context.Request.RouteValues.TryGetValue(param.Name, out var routeVal)
 01993                ? routeVal
 01994                : null,
 111995
 111996            ParameterLocation.Query =>
 21997                context.Request.Query.TryGetValue(param.Name, out var queryVal)
 21998                    ? (string?)queryVal
 21999                    : null,
 112000
 112001            ParameterLocation.Header =>
 02002                context.Request.Headers.TryGetValue(param.Name, out var headerVal)
 02003                    ? (string?)headerVal
 02004                    : null,
 112005
 112006            ParameterLocation.Cookie =>
 02007                context.Request.Cookies.TryGetValue(param.Name, out var cookieVal)
 02008                    ? cookieVal
 02009                    : null,
 92010            null => (context.Request.Form is not null && context.Request.HasFormContentType) ?
 92011                    context.Request.Form :
 92012                    context.Request.Body,
 112013
 02014            _ => null,
 112015        };
 2016    }
 2017
 2018    /// <summary>
 2019    /// Normalizes the raw parameter value into single and multi-value forms.
 2020    /// </summary>
 2021    /// <param name="raw">The raw parameter value.</param>
 2022    /// <returns>A tuple containing the single and multi-value forms of the parameter.</returns>
 2023    private static (string? single, string?[]? multi) NormalizeRaw(object raw)
 2024    {
 112025        string?[]? multiValue = null;
 2026
 2027        string? singleValue;
 2028        switch (raw)
 2029        {
 2030            case StringValues sv:
 02031                multiValue = [.. sv];
 02032                singleValue = sv.Count > 0 ? sv[0] : null;
 02033                break;
 2034
 2035            case string s:
 102036                singleValue = s;
 102037                break;
 2038
 2039            default:
 12040                singleValue = raw?.ToString();
 2041                break;
 2042        }
 2043
 112044        return (singleValue, multiValue);
 2045    }
 2046
 2047    /// <summary>
 2048    /// Converts the parameter value to the appropriate type based on the JSON schema type.
 2049    /// </summary>
 2050    /// <param name="context">The current HTTP context.</param>
 2051    /// <param name="param">The parameter information.</param>
 2052    /// <param name="singleValue">The single value of the parameter.</param>
 2053    /// <param name="multiValue">The multi-value of the parameter.</param>
 2054    /// <returns>The converted parameter value.</returns>
 2055    private static object? ConvertValue(KestrunContext context, ParameterForInjectionInfo param,
 2056    string? singleValue, string?[]? multiValue)
 2057    {
 2058        try
 2059        {
 2060            // Convert based on schema type
 112061            return param.Type switch
 112062            {
 22063                JsonSchemaType.Integer => int.TryParse(singleValue, out var i) ? (int?)i : null,
 02064                JsonSchemaType.Number => double.TryParse(singleValue, out var d) ? (double?)d : null,
 02065                JsonSchemaType.Boolean => bool.TryParse(singleValue, out var b) ? (bool?)b : null,
 02066                JsonSchemaType.Array => multiValue ?? (singleValue is not null ? new[] { singleValue } : null), // keep 
 92067                JsonSchemaType.Object => param.IsRequestBody
 92068                                        ? ConvertBodyBasedOnContentType(context, singleValue ?? "", param)
 92069                                        : singleValue,
 02070                JsonSchemaType.String => singleValue,
 02071                _ => singleValue,
 112072            };
 2073        }
 02074        catch (Exception ex)
 2075        {
 02076            context.Host.Logger.WarningSanitized(
 02077                ex,
 02078                "Failed to convert parameter '{Name}' (schemaType={SchemaType}, requestBody={IsBody}). RawValue='{Value}
 02079                param.Name,
 02080                param.Type,
 02081                param.IsRequestBody,
 02082                singleValue,
 02083                multiValue?.Length ?? 0);
 2084
 2085            // Minimal client message (no raw value)
 02086            var clientMsg = param.IsRequestBody
 02087                ? "Invalid request body."
 02088                : $"Invalid value for parameter '{param.Name}'.";
 2089
 02090            throw new ParameterForInjectionException(clientMsg, 400);
 2091        }
 112092    }
 2093
 2094    /// <summary>
 2095    /// Converts the request body based on the Content-Type header.
 2096    /// </summary>
 2097    /// <param name="context">The current Kestrun context.</param>
 2098    /// <param name="rawBodyString">The raw body string from the request.</param>
 2099    /// <param name="param">The parameter information.</param>
 2100    /// <returns>The converted body object.</returns>
 2101    /// <exception cref="InvalidOperationException">Thrown when the Content-Type header is missing and cannot convert bo
 2102    private static object? ConvertBodyBasedOnContentType(
 2103        KestrunContext context,
 2104        string rawBodyString,
 2105        ParameterForInjectionInfo param)
 2106    {
 92107        var isSingleContentType = param.ContentTypes.Count == 1;
 2108
 92109        var requestMediaType = MediaTypeHelper.Canonicalize(context.Request.ContentType);
 2110
 92111        if (string.IsNullOrEmpty(requestMediaType))
 2112        {
 12113            if (!isSingleContentType)
 2114            {
 02115                throw new InvalidOperationException(
 02116                    "Content-Type header is missing; cannot convert body to object.");
 2117            }
 2118
 12119            var inferred = MediaTypeHelper.Canonicalize(param.ContentTypes[0]);
 12120            return ConvertByCanonicalMediaType(inferred, context, rawBodyString, param);
 2121        }
 2122
 82123        return ConvertByCanonicalMediaType(requestMediaType, context, rawBodyString, param);
 2124    }
 2125
 2126    /// <summary>
 2127    /// Converts the body string to an object based on the canonical media type.
 2128    /// </summary>
 2129    /// <param name="canonicalMediaType">   The canonical media type of the request body.</param>
 2130    /// <param name="context"> The current Kestrun context.</param>
 2131    /// <param name="rawBodyString"> The raw body string from the request.</param>
 2132    /// <param name="param">The parameter information.</param>
 2133    /// <returns> The converted body object.</returns>
 2134    private static object? ConvertByCanonicalMediaType(
 2135        string canonicalMediaType,
 2136        KestrunContext context,
 2137        string rawBodyString,
 2138        ParameterForInjectionInfo param)
 2139    {
 92140        return canonicalMediaType switch
 92141        {
 22142            "application/json" => ConvertJsonToHashtable(rawBodyString),
 12143            "application/yaml" => ConvertYamlToHashtable(rawBodyString),
 22144            "application/xml" => ConvertXmlBodyToParameterType(rawBodyString, param.ParameterType),
 12145            "application/bson" => ConvertBsonToHashtable(rawBodyString),
 12146            "application/cbor" => ConvertCborToHashtable(rawBodyString),
 22147            "text/csv" => ConvertCsvToHashtable(rawBodyString),
 92148            "application/x-www-form-urlencoded" =>
 02149                ConvertFormToHashtable(context.Request.Form),
 02150            _ => rawBodyString,
 92151        };
 2152    }
 2153
 2154    /// <summary>
 2155    /// CBOR deserializer instance.
 2156    /// </summary>
 12157    private static readonly IDeserializer YamlDeserializer =
 12158    new DeserializerBuilder()
 12159        .WithNamingConvention(CamelCaseNamingConvention.Instance)
 12160        .Build();
 2161
 2162    private static Hashtable? ConvertYamlToHashtable(string yaml)
 2163    {
 12164        if (string.IsNullOrWhiteSpace(yaml))
 2165        {
 02166            return null;
 2167        }
 2168
 2169        // Top-level YAML mapping → Hashtable
 12170        var ht = YamlDeserializer.Deserialize<Hashtable>(yaml);
 12171        return ht;
 2172    }
 2173    private static object? ConvertJsonToHashtable(string? json)
 2174    {
 32175        if (string.IsNullOrWhiteSpace(json))
 2176        {
 02177            return null;
 2178        }
 2179
 32180        using var doc = JsonDocument.Parse(json);
 32181        return JsonElementToClr(doc.RootElement);
 32182    }
 2183
 2184    private static object? JsonElementToClr(JsonElement element)
 2185    {
 82186        return element.ValueKind switch
 82187        {
 32188            JsonValueKind.Object => ToHashtable(element),
 02189            JsonValueKind.Array => ToArray(element),
 32190            JsonValueKind.String => element.GetString(),
 22191            JsonValueKind.Number => element.TryGetInt64(out var l) ? l : element.GetDouble(),
 02192            JsonValueKind.True => true,
 02193            JsonValueKind.False => false,
 02194            JsonValueKind.Null => null,
 02195            JsonValueKind.Undefined => null,
 02196            _ => null
 82197        };
 2198    }
 2199
 2200    private static Hashtable ToHashtable(JsonElement element)
 2201    {
 32202        var ht = new Hashtable(StringComparer.OrdinalIgnoreCase);
 162203        foreach (var prop in element.EnumerateObject())
 2204        {
 52205            ht[prop.Name] = JsonElementToClr(prop.Value);
 2206        }
 32207        return ht;
 2208    }
 2209
 2210    private static object?[] ToArray(JsonElement element)
 2211    {
 02212        var list = new List<object?>();
 02213        foreach (var item in element.EnumerateArray())
 2214        {
 02215            list.Add(JsonElementToClr(item));
 2216        }
 02217        return [.. list];
 2218    }
 2219
 2220    private const int MaxObjectBindingDepth = 32;
 2221
 2222    private static object? ConvertXmlBodyToParameterType(string xml, Type parameterType)
 2223    {
 32224        if (string.IsNullOrWhiteSpace(xml))
 2225        {
 02226            return null;
 2227        }
 2228
 2229        XElement root;
 2230        try
 2231        {
 2232            // Clients often include an XML declaration with an encoding (e.g. UTF-8). When parsing from a .NET
 2233            // string (already decoded), some parsers can reject mismatched/pointless encoding declarations.
 2234            // Strip the declaration if present.
 32235            var cleaned = xml.TrimStart('\uFEFF', '\u200B', '\u0000', ' ', '\t', '\r', '\n');
 32236            if (cleaned.StartsWith("<?xml", StringComparison.OrdinalIgnoreCase))
 2237            {
 22238                var endDecl = cleaned.IndexOf("?>", StringComparison.Ordinal);
 22239                if (endDecl >= 0)
 2240                {
 22241                    cleaned = cleaned[(endDecl + 2)..].TrimStart();
 2242                }
 2243            }
 2244
 2245            XDocument doc;
 2246            try
 2247            {
 32248                doc = XDocument.Parse(cleaned);
 32249            }
 02250            catch
 2251            {
 02252                var settings = new XmlReaderSettings
 02253                {
 02254                    DtdProcessing = DtdProcessing.Prohibit,
 02255                    XmlResolver = null,
 02256                };
 2257
 02258                using var reader = XmlReader.Create(new StringReader(cleaned), settings);
 02259                doc = XDocument.Load(reader);
 02260            }
 2261
 32262            root = doc.Root ?? throw new InvalidOperationException("XML document has no root element.");
 32263        }
 02264        catch
 2265        {
 02266            return null;
 2267        }
 2268
 2269        // If the parameter expects a string, don't attempt to parse.
 32270        if (parameterType == typeof(string))
 2271        {
 02272            return xml;
 2273        }
 2274
 32275        var xmlMetadata = XmlHelper.GetOpenApiXmlMetadataForType(parameterType);
 32276        var wrapped = XmlHelper.ToHashtable(root, xmlMetadata);
 2277
 2278        // Normalize from XmlHelper's { RootName = { ... } } shape into the element map itself.
 32279        var rootMap = ExtractRootMapForBinding(wrapped, root.Name.LocalName);
 32280        if (rootMap is null)
 2281        {
 02282            return null;
 2283        }
 2284
 32285        NormalizeWrappedArrays(rootMap, xmlMetadata);
 2286
 2287        // For PowerShell script classes, the runtime may produce a new (dynamic) type in the request runspace.
 2288        // Creating an instance here (using a Type captured during route registration) can produce a type-identity
 2289        // mismatch at parameter-binding time ("cannot convert Product to Product").
 2290        // Returning a hashtable lets PowerShell perform the conversion to the *current* runspace type.
 32291        if (parameterType == typeof(object) || typeof(IDictionary).IsAssignableFrom(parameterType) || parameterType.Asse
 2292        {
 12293            return rootMap;
 2294        }
 2295        // Otherwise, attempt to convert to the target type.
 22296        return ConvertHashtableToObject(rootMap, parameterType, depth: 0, ps: null);
 02297    }
 2298
 2299    private static Hashtable? ExtractRootMapForBinding(Hashtable wrapped, string rootLocalName)
 2300    {
 2301        // XmlHelper.ToHashtable returns { rootName = childMap } plus any mapped attributes at the same level.
 32302        if (!TryGetHashtableValue(wrapped, rootLocalName, out var rootObj))
 2303        {
 2304            // Fallback: if there's exactly one entry and it's a hashtable, use it.
 02305            if (wrapped.Count == 1)
 2306            {
 02307                var only = wrapped.Values.Cast<object?>().FirstOrDefault();
 02308                return only as Hashtable;
 2309            }
 02310            return wrapped;
 2311        }
 2312
 32313        if (rootObj is not Hashtable rootMap)
 2314        {
 02315            return null;
 2316        }
 2317
 2318        // Merge any sibling keys (e.g., metadata-guided attributes) into the root map.
 162319        foreach (DictionaryEntry entry in wrapped)
 2320        {
 52321            if (entry.Key is not string key)
 2322            {
 2323                continue;
 2324            }
 2325
 52326            if (string.Equals(key, rootLocalName, StringComparison.OrdinalIgnoreCase))
 2327            {
 2328                continue;
 2329            }
 2330
 22331            rootMap[key] = entry.Value;
 2332        }
 2333
 32334        return rootMap;
 2335    }
 2336
 2337    /// <summary>
 2338    /// Normalizes wrapped arrays in the root map based on XML metadata.
 2339    /// </summary>
 2340    /// <param name="rootMap">The root hashtable map.</param>
 2341    /// <param name="xmlMetadata">The XML metadata hashtable.</param>
 2342    private static void NormalizeWrappedArrays(Hashtable rootMap, Hashtable? xmlMetadata)
 2343    {
 32344        if (!TryGetXmlMetadataProperties(xmlMetadata, out var propsHash))
 2345        {
 12346            return;
 2347        }
 2348
 202349        foreach (DictionaryEntry entry in propsHash)
 2350        {
 82351            if (!TryGetWrappedArrayMetadata(entry, out var propertyName, out var xmlName))
 2352            {
 2353                continue;
 2354            }
 2355
 22356            if (!TryGetWrapperHashtable(rootMap, propertyName, xmlName, out var wrapper))
 2357            {
 2358                continue;
 2359            }
 2360
 22361            var unwrapped = TryUnwrapWrapper(wrapper);
 22362            if (unwrapped is not null)
 2363            {
 22364                rootMap[propertyName] = unwrapped;
 2365            }
 2366        }
 22367    }
 2368
 2369    /// <summary>
 2370    /// Attempts to retrieve the <c>Properties</c> hashtable from XML metadata.
 2371    /// </summary>
 2372    /// <param name="xmlMetadata">XML metadata hashtable.</param>
 2373    /// <param name="properties">The properties hashtable when present.</param>
 2374    /// <returns><c>true</c> when the properties hashtable exists; otherwise <c>false</c>.</returns>
 2375    private static bool TryGetXmlMetadataProperties(Hashtable? xmlMetadata, out Hashtable properties)
 2376    {
 32377        if (xmlMetadata?["Properties"] is Hashtable propsHash)
 2378        {
 22379            properties = propsHash;
 22380            return true;
 2381        }
 2382
 12383        properties = default!;
 12384        return false;
 2385    }
 2386
 2387    /// <summary>
 2388    /// Extracts metadata for a wrapped array property from a single <see cref="DictionaryEntry"/>.
 2389    /// </summary>
 2390    /// <param name="entry">Entry from <c>xmlMetadata.Properties</c>.</param>
 2391    /// <param name="propertyName">The CLR property name.</param>
 2392    /// <param name="xmlName">The XML element name (or the CLR name when not overridden).</param>
 2393    /// <returns><c>true</c> when the entry describes a wrapped property; otherwise <c>false</c>.</returns>
 2394    private static bool TryGetWrappedArrayMetadata(DictionaryEntry entry, out string propertyName, out string xmlName)
 2395    {
 82396        if (entry.Key is not string propName || entry.Value is not Hashtable propMeta)
 2397        {
 02398            propertyName = default!;
 02399            xmlName = default!;
 02400            return false;
 2401        }
 2402
 82403        if (propMeta["Wrapped"] is not bool wrapped || !wrapped)
 2404        {
 62405            propertyName = default!;
 62406            xmlName = default!;
 62407            return false;
 2408        }
 2409
 22410        propertyName = propName;
 22411        xmlName = propMeta["Name"] as string ?? propName;
 22412        return true;
 2413    }
 2414
 2415    /// <summary>
 2416    /// Attempts to find a wrapper hashtable for a wrapped array property in the root map.
 2417    /// </summary>
 2418    /// <param name="rootMap">The root map produced by XML parsing.</param>
 2419    /// <param name="propertyName">CLR property name to search.</param>
 2420    /// <param name="xmlName">XML element name to search (fallback).</param>
 2421    /// <param name="wrapper">The wrapper hashtable if found.</param>
 2422    /// <returns><c>true</c> when a wrapper hashtable is found; otherwise <c>false</c>.</returns>
 2423    private static bool TryGetWrapperHashtable(Hashtable rootMap, string propertyName, string xmlName, out Hashtable wra
 2424    {
 22425        if (!TryGetHashtableValue(rootMap, propertyName, out var raw)
 22426            && !TryGetHashtableValue(rootMap, xmlName, out raw))
 2427        {
 02428            wrapper = default!;
 02429            return false;
 2430        }
 2431
 22432        if (raw is Hashtable wrapperHash)
 2433        {
 22434            wrapper = wrapperHash;
 22435            return true;
 2436        }
 2437
 02438        wrapper = default!;
 02439        return false;
 2440    }
 2441
 2442    /// <summary>
 2443    /// Unwraps a wrapper hashtable into an item list/value when possible.
 2444    /// </summary>
 2445    /// <param name="wrapper">Wrapper hashtable.</param>
 2446    /// <returns>The unwrapped value, or <c>null</c> if it cannot be unwrapped.</returns>
 2447    private static object? TryUnwrapWrapper(Hashtable wrapper)
 2448    {
 22449        return TryGetHashtableValue(wrapper, "Item", out var itemValue)
 22450            ? itemValue
 22451            : wrapper.Count == 1
 22452                ? wrapper.Values.Cast<object?>().FirstOrDefault()
 22453                : null;
 2454    }
 2455
 2456    /// <summary>
 2457    /// Converts a hashtable into an object of the specified target type.
 2458    /// </summary>
 2459    /// <param name="data">The hashtable data to convert.</param>
 2460    /// <param name="targetType">The target type to convert to.</param>
 2461    /// <param name="depth">The current recursion depth.</param>
 2462    /// <param name="ps">The current PowerShell instance, if available.</param>
 2463    /// <returns>The converted object, or null if conversion is not possible.</returns>
 2464    private static object? ConvertHashtableToObject(Hashtable data, Type targetType, int depth, PowerShell? ps)
 2465    {
 22466        if (depth >= MaxObjectBindingDepth)
 2467        {
 02468            return null;
 2469        }
 2470
 22471        var instance = TryCreateObjectInstance(targetType, Serilog.Log.Logger, ps);
 22472        if (instance is null)
 2473        {
 02474            return null;
 2475        }
 2476
 22477        PopulateObjectFromHashtable(instance, targetType, data, depth, Serilog.Log.Logger, ps);
 22478        return instance;
 2479    }
 2480
 2481    /// <summary>
 2482    /// Attempts to convert a hashtable into an instance of the specified parameter type.
 2483    /// </summary>
 2484    /// <param name="parameterType">The target parameter type.</param>
 2485    /// <param name="data">The hashtable data to convert.</param>
 2486    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 2487    /// <param name="ps">The current PowerShell instance, if available.</param>
 2488    /// <returns>The converted object, or null if conversion is not possible.</returns>
 2489    private static object? TryConvertHashtableToParameterType(
 2490        Type parameterType,
 2491        Hashtable data,
 2492        Serilog.ILogger logger,
 2493        PowerShell? ps)
 2494    {
 72495        if (parameterType == typeof(object) || typeof(IDictionary).IsAssignableFrom(parameterType))
 2496        {
 72497            return null;
 2498        }
 2499
 02500        if (!parameterType.IsClass)
 2501        {
 02502            return null;
 2503        }
 2504
 02505        var instance = TryCreateObjectInstance(parameterType, logger, ps);
 02506        if (instance is null)
 2507        {
 02508            return null;
 2509        }
 2510
 02511        PopulateObjectFromHashtable(instance, instance.GetType(), data, depth: 0, logger, ps);
 02512        return instance;
 2513    }
 2514
 2515    /// <summary>
 2516    /// Populates an object's properties and fields from a hashtable.
 2517    /// </summary>
 2518    /// <param name="instance">The object instance to populate.</param>
 2519    /// <param name="targetType">The target type of the object.</param>
 2520    /// <param name="data">The hashtable data to populate from.</param>
 2521    /// <param name="depth">The current recursion depth.</param>
 2522    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 2523    /// <param name="ps">The current PowerShell instance, if available.</param>
 2524    private static void PopulateObjectFromHashtable(
 2525        object instance,
 2526        Type targetType,
 2527        Hashtable data,
 2528        int depth,
 2529        Serilog.ILogger logger,
 2530        PowerShell? ps)
 2531    {
 22532        var props = GetWritableProperties(targetType);
 22533        var fields = GetPublicFields(targetType);
 22534        var extras = new Hashtable(StringComparer.OrdinalIgnoreCase);
 2535
 202536        foreach (DictionaryEntry entry in data)
 2537        {
 2538            try
 2539            {
 82540                if (entry.Key is not string rawKey)
 2541                {
 02542                    continue;
 2543                }
 2544
 82545                var key = NormalizeMemberKey(rawKey);
 82546                if (TrySetMemberValue(instance, entry.Value, key, props, fields, depth, ps))
 2547                {
 82548                    continue;
 2549                }
 2550
 02551                extras[key] = entry.Value;
 02552            }
 02553            catch (Exception ex)
 2554            {
 02555                logger.Warning(ex,
 02556                    "Failed to populate object of type {TargetType} from hashtable data. Member={Member}.",
 02557                    targetType, entry.Key);
 02558                var clientMsg = ex.InnerException is ValidationMetadataException vme
 02559                    ? $"Validation failed for member '{entry.Key}': {SanitizePsValidation(vme.Message)}"
 02560                    : $"Invalid value for member '{entry.Key}'.";
 02561                throw new ParameterForInjectionException(clientMsg, 422);
 2562            }
 2563        }
 2564
 22565        ValidateRequiredMembers(targetType, data);
 2566
 2567        // If there are unmatched entries and the target type doesn't support additional properties, log a warning and t
 22568        if (extras.Count > 0)
 2569        {
 02570            if (!HasAdditionalProperties(instance))
 2571            {
 02572                logger.WarningSanitized(
 02573                "Hashtable data contains {ExtraCount} unmatched entries for type {TargetType}. ExtraKeys: {ExtraKeys}",
 02574                extras.Count, targetType, extras.Keys.Cast<object?>().ToArray());
 2575
 2576                // Client-safe, still actionable:
 02577                var extraKeys = string.Join(", ", extras.Keys.Cast<object?>().Where(k => k is not null));
 02578                var clientMsg = $"Additional properties are not allowed: {extraKeys}.";
 02579                throw new ParameterForInjectionException(clientMsg, 422);
 2580            }
 2581
 02582            ApplyAdditionalPropertiesExtras(instance, data, extras, logger);
 2583        }
 22584    }
 2585
 2586    /// <summary>
 2587    /// Validates that required members declared by OpenApiSchemaComponent are present in the input payload.
 2588    /// </summary>
 2589    /// <param name="targetType">The target type being populated.</param>
 2590    /// <param name="data">The incoming payload data.</param>
 2591    /// <exception cref="ParameterForInjectionException">Thrown when one or more required members are missing.</exceptio
 2592    private static void ValidateRequiredMembers(Type targetType, Hashtable data)
 2593    {
 22594        var required = GetRequiredMemberNames(targetType);
 22595        if (required.Count == 0)
 2596        {
 22597            return;
 2598        }
 2599
 02600        var provided = new HashSet<string>(
 02601            data.Keys
 02602                .OfType<string>()
 02603                .Select(NormalizeMemberKey),
 02604            StringComparer.OrdinalIgnoreCase);
 2605
 02606        var missing = required.Where(name => !provided.Contains(name)).ToList();
 02607        if (missing.Count == 0)
 2608        {
 02609            return;
 2610        }
 2611
 02612        var msg = missing.Count == 1
 02613            ? $"{missing[0]} is required."
 02614            : $"The following fields are required: {string.Join(", ", missing)}.";
 2615
 02616        throw new ParameterForInjectionException(msg, 422);
 2617    }
 2618
 2619    /// <summary>
 2620    /// Gets required member names declared by OpenApiSchemaComponent.RequiredProperties on the target type.
 2621    /// </summary>
 2622    /// <param name="targetType">The target type to inspect.</param>
 2623    /// <returns>A de-duplicated list of required member names.</returns>
 2624    private static List<string> GetRequiredMemberNames(Type targetType)
 2625    {
 22626        var staticRequired = targetType.GetProperty("RequiredProperties", BindingFlags.Public | BindingFlags.NonPublic |
 22627            ?.GetValue(null) as IEnumerable;
 22628        if (staticRequired is not null)
 2629        {
 02630            var staticNames = staticRequired
 02631                .Cast<object?>()
 02632                .Select(v => v?.ToString())
 02633                .Where(v => !string.IsNullOrWhiteSpace(v))
 02634                .Cast<string>()
 02635                .Distinct(StringComparer.OrdinalIgnoreCase)
 02636                .ToList();
 2637
 02638            if (staticNames.Count > 0)
 2639            {
 02640                return staticNames;
 2641            }
 2642        }
 2643
 22644        var schemaAttr = targetType.GetCustomAttributes(inherit: true)
 62645            .FirstOrDefault(a => a.GetType().Name.Equals("OpenApiSchemaComponent", StringComparison.OrdinalIgnoreCase));
 2646
 22647        if (schemaAttr is null)
 2648        {
 22649            return [];
 2650        }
 2651
 02652        var required = schemaAttr.GetType().GetProperty("RequiredProperties")?.GetValue(schemaAttr) as string[];
 02653        return required is not { Length: > 0 }
 02654            ? []
 02655            : [.. required
 02656            .Where(r => !string.IsNullOrWhiteSpace(r))
 02657            .Distinct(StringComparer.OrdinalIgnoreCase)];
 2658    }
 2659
 2660    /// <summary>
 2661    ///  Determines whether the specified object has an "AdditionalProperties" property, indicating support for extra da
 2662    /// </summary>
 2663    /// <param name="obj"> The object to check for an "AdditionalProperties" property.</param>
 2664    /// <returns>True if the object has an "AdditionalProperties" property; otherwise, false.</returns>
 2665    private static bool HasAdditionalProperties(object obj)
 2666    {
 02667        return obj is not null && obj.GetType()
 02668                  .GetProperty("AdditionalProperties",
 02669                      BindingFlags.Instance | BindingFlags.Public) != null;
 2670    }
 2671    /// <summary>
 2672    ///  Sanitizes a PowerShell validation error message for safe inclusion in client-facing error responses.
 2673    /// </summary>
 2674    /// <param name="message">The original PowerShell validation error message.</param>
 2675    /// <returns>A sanitized version of the message suitable for client display.</returns>
 2676    private static string SanitizePsValidation(string message)
 2677    {
 02678        if (string.IsNullOrWhiteSpace(message))
 2679        {
 02680            return "Validation failed.";
 2681        }
 2682
 02683        message = message.Trim();
 2684
 2685        // Remove line breaks (PS sometimes adds them)
 02686        message = message.Replace("\r", " ").Replace("\n", " ");
 2687
 2688        // Take only the first sentence
 02689        var dotIndex = message.IndexOf('.');
 02690        if (dotIndex > 0)
 2691        {
 02692            message = message[..(dotIndex + 1)];
 2693        }
 2694
 02695        return message;
 2696    }
 2697
 2698    /// <summary>
 2699    /// Builds a case-insensitive map of writable public instance properties.
 2700    /// </summary>
 2701    /// <param name="targetType">The target type.</param>
 2702    /// <returns>The property map keyed by property name.</returns>
 2703    private static Dictionary<string, PropertyInfo> GetWritableProperties(Type targetType)
 2704    {
 22705        return targetType
 22706            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
 82707            .Where(p => p.CanWrite && p.SetMethod is not null)
 102708            .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
 2709    }
 2710
 2711    /// <summary>
 2712    /// Builds a case-insensitive map of public instance fields.
 2713    /// </summary>
 2714    /// <param name="targetType">The target type.</param>
 2715    /// <returns>The field map keyed by field name.</returns>
 2716    private static Dictionary<string, FieldInfo> GetPublicFields(Type targetType)
 2717    {
 22718        return targetType
 22719            .GetFields(BindingFlags.Public | BindingFlags.Instance)
 22720            .ToDictionary(f => f.Name, StringComparer.OrdinalIgnoreCase);
 2721    }
 2722
 2723    /// <summary>
 2724    /// Normalizes a member key by trimming the PowerShell @ prefix when present.
 2725    /// </summary>
 2726    /// <param name="rawKey">The raw key.</param>
 2727    /// <returns>The normalized key.</returns>
 2728    private static string NormalizeMemberKey(string rawKey)
 82729        => rawKey.StartsWith('@') ? rawKey[1..] : rawKey;
 2730
 2731    /// <summary>
 2732    /// Attempts to set a property or field value on the target instance.
 2733    /// </summary>
 2734    /// <param name="instance">The target instance.</param>
 2735    /// <param name="value">The value to assign.</param>
 2736    /// <param name="key">The member name.</param>
 2737    /// <param name="props">The property map.</param>
 2738    /// <param name="fields">The field map.</param>
 2739    /// <param name="depth">The current recursion depth.</param>
 2740    /// <param name="ps">The current PowerShell instance, if available.</param>
 2741    /// <returns><c>true</c> if a property or field was set; otherwise <c>false</c>.</returns>
 2742    private static bool TrySetMemberValue(
 2743        object instance,
 2744        object? value,
 2745        string key,
 2746        IReadOnlyDictionary<string, PropertyInfo> props,
 2747        IReadOnlyDictionary<string, FieldInfo> fields,
 2748        int depth,
 2749        PowerShell? ps)
 2750    {
 82751        if (props.TryGetValue(key, out var prop))
 2752        {
 82753            var converted = ConvertToTargetType(value, prop.PropertyType, depth + 1, ps);
 82754            prop.SetValue(instance, converted);
 82755            return true;
 2756        }
 2757
 02758        if (fields.TryGetValue(key, out var field))
 2759        {
 02760            var converted = ConvertToTargetType(value, field.FieldType, depth + 1, ps);
 02761            field.SetValue(instance, converted);
 02762            return true;
 2763        }
 2764
 02765        return false;
 2766    }
 2767
 2768    /// <summary>
 2769    /// Applies unmatched entries to the AdditionalProperties bag when present.
 2770    /// </summary>
 2771    /// <param name="instance">The target instance.</param>
 2772    /// <param name="data">The original hashtable data.</param>
 2773    /// <param name="extras">Unmatched entries to apply.</param>
 2774    /// <param name="logger">The logger to use for diagnostic warnings.</param>
 2775    private static void ApplyAdditionalPropertiesExtras(
 2776        object instance,
 2777        Hashtable data,
 2778        Hashtable extras,
 2779        Serilog.ILogger logger)
 2780    {
 02781        if (extras.Count == 0)
 2782        {
 02783            return;
 2784        }
 2785
 02786        if (TryGetHashtableValue(data, "AdditionalProperties", out var existingBag)
 02787            && existingBag is Hashtable existingHash)
 2788        {
 02789            foreach (DictionaryEntry entry in extras)
 2790            {
 02791                existingHash[entry.Key] = entry.Value;
 2792            }
 2793
 02794            _ = TrySetAdditionalPropertiesBag(instance, existingHash, logger);
 02795            return;
 2796        }
 2797
 02798        _ = TrySetAdditionalPropertiesBag(instance, extras, logger);
 02799    }
 2800
 2801    /// <summary>
 2802    /// Converts a value to the specified target type, handling complex objects and collections.
 2803    /// </summary>
 2804    /// <param name="value">The value to convert.</param>
 2805    /// <param name="targetType">The target type to convert to.</param>
 2806    /// <param name="depth">The current recursion depth.</param>
 2807    /// <param name="ps">The current PowerShell instance, if available.</param>
 2808    /// <returns>The converted value, or null if conversion is not possible.</returns>
 2809    private static object? ConvertToTargetType(object? value, Type targetType, int depth, PowerShell? ps)
 2810    {
 142811        if (value is null)
 2812        {
 02813            return null;
 2814        }
 2815
 142816        targetType = UnwrapNullableTargetType(targetType);
 2817
 142818        return targetType.IsInstanceOfType(value)
 142819            ? value
 142820            : TryConvertHashtableValue(value, targetType, depth, ps, out var convertedFromHashtable)
 142821                ? convertedFromHashtable
 142822                : TryConvertListOrArrayValue(value, targetType, depth, ps, out var convertedFromEnumerable)
 142823                    ? convertedFromEnumerable
 142824                    : ConvertScalarValue(value, targetType);
 2825    }
 2826
 2827    /// <summary>
 2828    /// Unwraps a nullable target type to its underlying non-nullable type.
 2829    /// </summary>
 2830    /// <param name="targetType">The target type.</param>
 2831    /// <returns>The underlying non-nullable type, or the original type when not nullable.</returns>
 2832    private static Type UnwrapNullableTargetType(Type targetType)
 142833        => Nullable.GetUnderlyingType(targetType) ?? targetType;
 2834
 2835    /// <summary>
 2836    /// Converts a hashtable value into the target type when applicable.
 2837    /// </summary>
 2838    /// <param name="value">Value to convert.</param>
 2839    /// <param name="targetType">Target type.</param>
 2840    /// <param name="depth">Current recursion depth.</param>
 2841    /// <param name="ps">The current PowerShell instance, if available.</param>
 2842    /// <param name="converted">Converted result.</param>
 2843    /// <returns><c>true</c> when the value was handled; otherwise <c>false</c>.</returns>
 2844    private static bool TryConvertHashtableValue(object value, Type targetType, int depth, PowerShell? ps, out object? c
 2845    {
 62846        if (value is not Hashtable ht)
 2847        {
 62848            converted = null;
 62849            return false;
 2850        }
 2851
 02852        converted = typeof(IDictionary).IsAssignableFrom(targetType)
 02853            ? ht
 02854            : ConvertHashtableToObject(ht, targetType, depth, ps);
 02855        return true;
 2856    }
 2857
 2858    /// <summary>
 2859    /// Converts list/array values into the target type when applicable.
 2860    /// </summary>
 2861    /// <param name="value">Value to convert.</param>
 2862    /// <param name="targetType">Target type.</param>
 2863    /// <param name="depth">Current recursion depth.</param>
 2864    /// <param name="ps">The current PowerShell instance, if available.</param>
 2865    /// <param name="converted">Converted result.</param>
 2866    /// <returns><c>true</c> when the value was handled; otherwise <c>false</c>.</returns>
 2867    private static bool TryConvertListOrArrayValue(object value, Type targetType, int depth, PowerShell? ps, out object?
 2868    {
 62869        if (value is List<object?> list)
 2870        {
 22871            converted = ConvertEnumerableToTargetType(list, targetType, depth, ps);
 22872            return true;
 2873        }
 2874
 42875        if (value is object?[] arr)
 2876        {
 02877            converted = ConvertEnumerableToTargetType(arr, targetType, depth, ps);
 02878            return true;
 2879        }
 2880
 42881        converted = null;
 42882        return false;
 2883    }
 2884
 2885    /// <summary>
 2886    /// Converts a scalar (non-hashtable, non-collection) value into the target type.
 2887    /// </summary>
 2888    /// <param name="value">Scalar value to convert.</param>
 2889    /// <param name="targetType">Target type.</param>
 2890    /// <returns>The converted value, or <c>null</c> when conversion fails.</returns>
 2891    private static object? ConvertScalarValue(object value, Type targetType)
 2892    {
 42893        var str = value as string ?? Convert.ToString(value, CultureInfo.InvariantCulture);
 2894
 42895        return TryConvertScalarByType(str, targetType, out var converted)
 42896            ? converted
 42897            : TryChangeType(value, targetType);
 2898    }
 2899
 2900    /// <summary>
 2901    /// Attempts to convert a scalar string representation to common primitive target types.
 2902    /// </summary>
 2903    /// <param name="str">String representation of the value.</param>
 2904    /// <param name="targetType">Target type.</param>
 2905    /// <param name="converted">Converted result.</param>
 2906    /// <returns><c>true</c> when converted; otherwise <c>false</c>.</returns>
 2907    private static bool TryConvertScalarByType(string? str, Type targetType, out object? converted)
 2908    {
 42909        if (TryConvertPrimitiveScalar(str, targetType, out converted))
 2910        {
 42911            return true;
 2912        }
 2913
 02914        if (targetType.IsEnum)
 2915        {
 02916            converted = TryParseEnum(targetType, str);
 02917            return converted is not null;
 2918        }
 2919
 02920        converted = null;
 02921        return false;
 2922    }
 2923
 2924    /// <summary>
 2925    /// Attempts to convert a scalar string representation into a primitive CLR type.
 2926    /// </summary>
 2927    /// <param name="str">String representation of the value.</param>
 2928    /// <param name="targetType">Target type.</param>
 2929    /// <param name="converted">Converted result.</param>
 2930    /// <returns><c>true</c> when converted; otherwise <c>false</c>.</returns>
 2931    private static bool TryConvertPrimitiveScalar(string? str, Type targetType, out object? converted)
 2932    {
 42933        switch (System.Type.GetTypeCode(targetType))
 2934        {
 2935            case TypeCode.String:
 02936                converted = str;
 02937                return true;
 2938            case TypeCode.Int32:
 22939                converted = TryParseInt32(str);
 22940                return converted is not null;
 2941            case TypeCode.Int64:
 02942                converted = TryParseInt64(str);
 02943                return converted is not null;
 2944            case TypeCode.Double:
 02945                converted = TryParseDouble(str);
 02946                return converted is not null;
 2947            case TypeCode.Decimal:
 22948                converted = TryParseDecimal(str);
 22949                return converted is not null;
 2950            case TypeCode.Boolean:
 02951                converted = TryParseBoolean(str);
 02952                return converted is not null;
 2953            default:
 02954                converted = null;
 02955                return false;
 2956        }
 2957    }
 2958
 2959    /// <summary>
 2960    /// Attempts to parse an <see cref="int"/> from a string.
 2961    /// </summary>
 2962    /// <param name="str">String representation.</param>
 2963    /// <returns>The parsed value, or <c>null</c> when parsing fails.</returns>
 2964    private static int? TryParseInt32(string? str)
 22965        => int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) ? i : null;
 2966
 2967    /// <summary>
 2968    /// Attempts to parse a <see cref="long"/> from a string.
 2969    /// </summary>
 2970    /// <param name="str">String representation.</param>
 2971    /// <returns>The parsed value, or <c>null</c> when parsing fails.</returns>
 2972    private static long? TryParseInt64(string? str)
 02973        => long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out var l) ? l : null;
 2974
 2975    /// <summary>
 2976    /// Attempts to parse a <see cref="double"/> from a string.
 2977    /// </summary>
 2978    /// <param name="str">String representation.</param>
 2979    /// <returns>The parsed value, or <c>null</c> when parsing fails.</returns>
 2980    private static double? TryParseDouble(string? str)
 02981        => double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out var d) ? d : null;
 2982
 2983    /// <summary>
 2984    /// Attempts to parse a <see cref="decimal"/> from a string.
 2985    /// </summary>
 2986    /// <param name="str">String representation.</param>
 2987    /// <returns>The parsed value, or <c>null</c> when parsing fails.</returns>
 2988    private static decimal? TryParseDecimal(string? str)
 22989        => decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec) ? dec : null;
 2990
 2991    /// <summary>
 2992    /// Attempts to parse a <see cref="bool"/> from a string.
 2993    /// </summary>
 2994    /// <param name="str">String representation.</param>
 2995    /// <returns>The parsed value, or <c>null</c> when parsing fails.</returns>
 2996    private static bool? TryParseBoolean(string? str)
 02997        => bool.TryParse(str, out var b) ? b : null;
 2998
 2999    /// <summary>
 3000    /// Attempts to parse an enum value from a string.
 3001    /// </summary>
 3002    /// <param name="targetType">Enum type.</param>
 3003    /// <param name="str">String representation.</param>
 3004    /// <returns>The parsed enum value, or <c>null</c> when parsing fails.</returns>
 3005    private static object? TryParseEnum(Type targetType, string? str)
 3006    {
 03007        if (str is null)
 3008        {
 03009            return null;
 3010        }
 3011
 3012        try
 3013        {
 03014            return Enum.Parse(targetType, str, ignoreCase: true);
 3015        }
 03016        catch
 3017        {
 03018            return null;
 3019        }
 03020    }
 3021
 3022    /// <summary>
 3023    /// Attempts a generic scalar conversion via <see cref="Convert.ChangeType(object, Type, IFormatProvider)"/>.
 3024    /// </summary>
 3025    /// <param name="value">Source value.</param>
 3026    /// <param name="targetType">Target type.</param>
 3027    /// <returns>The converted value, or <c>null</c> when conversion fails.</returns>
 3028    private static object? TryChangeType(object value, Type targetType)
 3029    {
 3030        try
 3031        {
 03032            return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
 3033        }
 03034        catch
 3035        {
 03036            return null;
 3037        }
 03038    }
 3039
 3040    private static object? ConvertEnumerableToTargetType(IEnumerable enumerable, Type targetType, int depth, PowerShell?
 3041    {
 23042        if (targetType.IsArray)
 3043        {
 23044            var elementType = targetType.GetElementType() ?? typeof(object);
 23045            var items = new List<object?>();
 163046            foreach (var item in enumerable)
 3047            {
 63048                items.Add(ConvertToTargetType(item, elementType, depth + 1, ps));
 3049            }
 3050
 23051            var arr = Array.CreateInstance(elementType, items.Count);
 163052            for (var i = 0; i < items.Count; i++)
 3053            {
 63054                arr.SetValue(items[i], i);
 3055            }
 3056
 23057            return arr;
 3058        }
 3059
 3060        // Default to List<T> for generic IEnumerable targets.
 03061        if (targetType.IsGenericType)
 3062        {
 03063            var genDef = targetType.GetGenericTypeDefinition();
 03064            if (genDef == typeof(List<>) || genDef == typeof(IList<>) || genDef == typeof(IEnumerable<>))
 3065            {
 03066                var elementType = targetType.GetGenericArguments()[0];
 03067                var listType = typeof(List<>).MakeGenericType(elementType);
 03068                var list = (IList)Activator.CreateInstance(listType)!;
 03069                foreach (var item in enumerable)
 3070                {
 03071                    _ = list.Add(ConvertToTargetType(item, elementType, depth + 1, ps));
 3072                }
 03073                return list;
 3074            }
 3075        }
 3076
 03077        return null;
 3078    }
 3079
 3080    private static bool TryGetHashtableValue(Hashtable table, string key, out object? value)
 3081    {
 253082        foreach (DictionaryEntry entry in table)
 3083        {
 93084            if (entry.Key is string s && string.Equals(s, key, StringComparison.OrdinalIgnoreCase))
 3085            {
 73086                value = entry.Value;
 73087                return true;
 3088            }
 3089        }
 3090
 03091        value = null;
 03092        return false;
 73093    }
 3094
 3095    /// <summary>
 3096    /// Converts a form dictionary to a hashtable.
 3097    /// </summary>
 3098    /// <param name="form">The form dictionary to convert.</param>
 3099    /// <returns>A hashtable representing the form data.</returns>
 3100    private static Hashtable? ConvertFormToHashtable(Dictionary<string, string>? form)
 3101    {
 03102        if (form is null || form.Count == 0)
 3103        {
 03104            return null;
 3105        }
 3106
 03107        var ht = new Hashtable(StringComparer.OrdinalIgnoreCase);
 3108
 03109        foreach (var kvp in form)
 3110        {
 3111            // x-www-form-urlencoded in your case has a single value per key
 03112            ht[kvp.Key] = kvp.Value;
 3113        }
 3114
 03115        return ht;
 3116    }
 3117
 3118    private static object? ConvertFormToValue(Dictionary<string, string>? form, ParameterForInjectionInfo param)
 3119    {
 03120        if (form is null || form.Count == 0)
 3121        {
 03122            return null;
 3123        }
 3124
 3125        // If the parameter is a simple type, return the first key if there's only one key-value pair
 3126        // and it's a simple type (not an object or array)
 03127        return param.Type is JsonSchemaType.Integer or JsonSchemaType.Number or JsonSchemaType.Boolean or JsonSchemaType
 03128            ? form.Count == 1 ? form.First().Key : null
 03129            : ConvertFormToHashtable(form);
 3130    }
 3131
 3132    private static object? ConvertBsonToHashtable(string bson)
 3133    {
 13134        if (string.IsNullOrWhiteSpace(bson))
 3135        {
 03136            return null;
 3137        }
 3138
 13139        var bytes = DecodeBodyStringToBytes(bson);
 13140        if (bytes is null || bytes.Length == 0)
 3141        {
 03142            return null;
 3143        }
 3144
 13145        var doc = BsonSerializer.Deserialize<BsonDocument>(bytes);
 13146        return BsonValueToClr(doc);
 3147    }
 3148
 3149    private static object? BsonValueToClr(BsonValue value)
 3150    {
 33151        return value is null || value.IsBsonNull
 33152            ? null
 33153            : value.BsonType switch
 33154            {
 13155                BsonType.Document => BsonDocumentToHashtable(value.AsBsonDocument),
 03156                BsonType.Array => BsonArrayToClrArray(value.AsBsonArray),
 03157                BsonType.Boolean => value.AsBoolean,
 13158                BsonType.Int32 => value.AsInt32,
 03159                BsonType.Int64 => value.AsInt64,
 03160                BsonType.Double => value.AsDouble,
 03161                BsonType.Decimal128 => value.AsDecimal,
 13162                BsonType.String => value.AsString,
 03163                BsonType.DateTime => value.ToUniversalTime(),
 03164                BsonType.ObjectId => value.AsObjectId.ToString(),
 03165                BsonType.Binary => value.AsBsonBinaryData.Bytes,
 03166                BsonType.Null => null,
 03167                _ => value.ToString(),
 33168            };
 3169    }
 3170
 3171    private static Hashtable BsonDocumentToHashtable(BsonDocument doc)
 3172    {
 13173        var ht = new Hashtable(StringComparer.OrdinalIgnoreCase);
 63174        foreach (var element in doc.Elements)
 3175        {
 23176            ht[element.Name] = BsonValueToClr(element.Value);
 3177        }
 3178
 13179        return ht;
 3180    }
 3181
 3182    private static object?[] BsonArrayToClrArray(BsonArray arr)
 3183    {
 03184        var list = new object?[arr.Count];
 03185        for (var i = 0; i < arr.Count; i++)
 3186        {
 03187            list[i] = BsonValueToClr(arr[i]);
 3188        }
 3189
 03190        return list;
 3191    }
 3192
 3193    private static object? ConvertCborToHashtable(string cbor)
 3194    {
 13195        if (string.IsNullOrWhiteSpace(cbor))
 3196        {
 03197            return null;
 3198        }
 3199
 13200        var bytes = DecodeBodyStringToBytes(cbor);
 13201        if (bytes is null || bytes.Length == 0)
 3202        {
 03203            return null;
 3204        }
 3205
 13206        var obj = CBORObject.DecodeFromBytes(bytes);
 13207        return CborToClr(obj);
 3208    }
 3209
 3210    /// <summary>
 3211    /// Converts a CBORObject to a CLR object (Hashtable, array, or scalar).
 3212    /// </summary>
 3213    /// <param name="obj">The CBORObject to convert.</param>
 3214    /// <returns>A CLR object representation of the CBORObject.</returns>
 3215    private static object? CborToClr(CBORObject obj)
 3216    {
 33217        return obj is null || obj.IsNull
 33218            ? null
 33219            : obj.Type switch
 33220            {
 13221                CBORType.Map => ConvertCborMapToHashtable(obj),
 03222                CBORType.Array => ConvertCborArrayToClrArray(obj),
 23223                _ => ConvertCborScalarToClr(obj),
 33224            };
 3225    }
 3226
 3227    /// <summary>
 3228    /// Converts a CBOR map into a CLR <see cref="Hashtable"/>.
 3229    /// </summary>
 3230    /// <param name="map">The CBOR object expected to be of type <see cref="CBORType.Map"/>.</param>
 3231    /// <returns>A case-insensitive hashtable representing the map.</returns>
 3232    private static Hashtable ConvertCborMapToHashtable(CBORObject map)
 3233    {
 13234        var ht = new Hashtable(StringComparer.OrdinalIgnoreCase);
 63235        foreach (var key in map.Keys)
 3236        {
 23237            var keyString = GetCborMapKeyString(key);
 23238            ht[keyString] = CborToClr(map[key]);
 3239        }
 3240
 13241        return ht;
 3242    }
 3243
 3244    /// <summary>
 3245    /// Converts a CBOR array into a CLR object array.
 3246    /// </summary>
 3247    /// <param name="array">The CBOR object expected to be of type <see cref="CBORType.Array"/>.</param>
 3248    /// <returns>An array of converted elements.</returns>
 3249    private static object?[] ConvertCborArrayToClrArray(CBORObject array)
 3250    {
 03251        var list = new object?[array.Count];
 03252        for (var i = 0; i < array.Count; i++)
 3253        {
 03254            list[i] = CborToClr(array[i]);
 3255        }
 3256
 03257        return list;
 3258    }
 3259
 3260    /// <summary>
 3261    /// Converts a CBOR scalar value (number, string, boolean, byte string, etc.) into a CLR value.
 3262    /// </summary>
 3263    /// <param name="scalar">The CBOR scalar to convert.</param>
 3264    /// <returns>The converted CLR value.</returns>
 3265    private static object? ConvertCborScalarToClr(CBORObject scalar)
 3266    {
 23267        if (scalar.IsNumber)
 3268        {
 3269            // Prefer integral if representable; else double/decimal as available.
 13270            var number = scalar.AsNumber();
 13271            if (number.CanFitInInt64())
 3272            {
 13273                return number.ToInt64Checked();
 3274            }
 3275
 03276            if (number.CanFitInDouble())
 3277            {
 03278                return scalar.ToObject<double>();
 3279            }
 3280
 3281            // For extremely large/precise numbers, keep a string representation.
 03282            return number.ToString();
 3283        }
 3284
 13285        if (scalar.Type == CBORType.Boolean)
 3286        {
 03287            return scalar.AsBoolean();
 3288        }
 3289
 13290        if (scalar.Type == CBORType.ByteString)
 3291        {
 03292            return scalar.GetByteString();
 3293        }
 3294
 3295        // TextString, SimpleValue, etc.
 13296        return scalar.Type switch
 13297        {
 13298            CBORType.TextString => scalar.AsString(),
 03299            CBORType.SimpleValue => scalar.ToString(),
 03300            _ => scalar.ToString(),
 13301        };
 3302    }
 3303
 3304    /// <summary>
 3305    /// Converts a CBOR map key into a CLR string key.
 3306    /// </summary>
 3307    /// <param name="key">The CBOR key object.</param>
 3308    /// <returns>A best-effort string representation of the key.</returns>
 3309    private static string GetCborMapKeyString(CBORObject? key)
 3310    {
 23311        return key is not null && key.Type == CBORType.TextString
 23312            ? key.AsString()
 23313            : (key?.ToString() ?? string.Empty);
 3314    }
 3315
 3316    /// <summary>
 3317    /// Converts a CSV string to a hashtable or array of hashtables.
 3318    /// </summary>
 3319    /// <param name="csv">The CSV string to convert.</param>
 3320    /// <returns>A hashtable if one record is present, an array of hashtables if multiple records are present, or null i
 3321    private static object? ConvertCsvToHashtable(string csv)
 3322    {
 23323        if (string.IsNullOrWhiteSpace(csv))
 3324        {
 03325            return null;
 3326        }
 3327
 23328        using var reader = new StringReader(csv);
 23329        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
 23330        {
 23331            HasHeaderRecord = true,
 23332            BadDataFound = null,
 23333            MissingFieldFound = null,
 23334            HeaderValidated = null,
 23335            IgnoreBlankLines = true,
 23336            TrimOptions = TrimOptions.Trim,
 23337        };
 3338
 23339        using var csvReader = new CsvReader(reader, config);
 23340        var records = new List<Hashtable>();
 3341
 103342        foreach (var rec in csvReader.GetRecords<dynamic>())
 3343        {
 33344            if (rec is not IDictionary<string, object?> dict)
 3345            {
 3346                continue;
 3347            }
 3348
 33349            var ht = new Hashtable(StringComparer.OrdinalIgnoreCase);
 183350            foreach (var kvp in dict)
 3351            {
 63352                ht[kvp.Key] = kvp.Value;
 3353            }
 3354
 33355            records.Add(ht);
 3356        }
 3357
 23358        return records.Count == 0
 23359            ? null
 23360            : (records.Count == 1 ? records[0] : records.Cast<object?>().ToArray());
 23361    }
 3362
 3363    private static byte[]? DecodeBodyStringToBytes(string body)
 3364    {
 23365        if (string.IsNullOrWhiteSpace(body))
 3366        {
 03367            return null;
 3368        }
 3369
 23370        var trimmed = body.Trim();
 23371        if (trimmed.StartsWith("base64:", StringComparison.OrdinalIgnoreCase))
 3372        {
 23373            trimmed = trimmed["base64:".Length..].Trim();
 3374        }
 3375
 23376        if (TryDecodeBase64(trimmed, out var base64Bytes))
 3377        {
 23378            return base64Bytes;
 3379        }
 3380
 03381        if (TryDecodeHex(trimmed, out var hexBytes))
 3382        {
 03383            return hexBytes;
 3384        }
 3385
 3386        // Fallback: interpret as UTF-8 text (best-effort).
 03387        return Encoding.UTF8.GetBytes(trimmed);
 3388    }
 3389
 3390    private static bool TryDecodeBase64(string input, out byte[] bytes)
 3391    {
 23392        bytes = [];
 3393
 3394        // Quick reject for non-base64 strings.
 23395        if (input.Length < 4 || (input.Length % 4) != 0)
 3396        {
 03397            return false;
 3398        }
 3399
 3400        // Avoid throwing on clearly non-base64 content.
 843401        for (var i = 0; i < input.Length; i++)
 3402        {
 403403            var c = input[i];
 403404            var isValid =
 403405                c is (>= 'A' and <= 'Z') or (>= 'a' and <= 'z') or (>= '0' and <= '9') or '+' or '/' or '=' or '\r' or '
 3406
 403407            if (!isValid)
 3408            {
 03409                return false;
 3410            }
 3411        }
 3412
 3413        try
 3414        {
 23415            bytes = Convert.FromBase64String(input);
 23416            return true;
 3417        }
 03418        catch (FormatException)
 3419        {
 03420            return false;
 3421        }
 23422    }
 3423
 3424    private static bool TryDecodeHex(string input, out byte[] bytes)
 3425    {
 03426        bytes = [];
 03427        var s = input.Trim();
 3428
 03429        if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
 3430        {
 03431            s = s[2..];
 3432        }
 3433
 03434        if (s.Length < 2 || (s.Length % 2) != 0)
 3435        {
 03436            return false;
 3437        }
 3438
 03439        for (var i = 0; i < s.Length; i++)
 3440        {
 03441            var c = s[i];
 03442            var isHex = c is (>= '0' and <= '9') or (>= 'a' and <= 'f') or (>= 'A' and <= 'F');
 3443
 03444            if (!isHex)
 3445            {
 03446                return false;
 3447            }
 3448        }
 3449
 03450        bytes = new byte[s.Length / 2];
 03451        for (var i = 0; i < bytes.Length; i++)
 3452        {
 03453            bytes[i] = byte.Parse(s.AsSpan(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
 3454        }
 3455
 03456        return true;
 3457    }
 3458}

Methods/Properties

.ctor(System.String,System.String)
get_KeyPattern()
get_SchemaTypeName()
IsMatch(System.String)
Validate(System.Management.Automation.ParameterMetadata)
.ctor(System.Management.Automation.ParameterMetadata,Microsoft.OpenApi.OpenApiParameter)
.ctor(System.Management.Automation.ParameterMetadata,Microsoft.OpenApi.OpenApiRequestBody,Kestrun.Forms.KrFormOptions)
InjectParameters(Kestrun.Models.KestrunContext,System.Management.Automation.PowerShell)
.cctor()
ShouldConvertBody(Kestrun.Languages.ParameterForInjectionInfo,System.Object)
TryConvertBodyByContentType(Kestrun.Models.KestrunContext,Kestrun.Languages.ParameterForInjectionInfo,System.String)
InjectSingleParameter(Kestrun.Models.KestrunContext,System.Management.Automation.PowerShell,Kestrun.Languages.ParameterForInjectionInfo)
CoerceFormPayloadForParameter(System.Type,System.Object,Serilog.ILogger,System.Management.Automation.PowerShell)
LogCoerceFormPayload(Serilog.ILogger,System.Type,System.Object,System.Management.Automation.PowerShell)
TryHandleExactPayloadType(System.Type,Kestrun.Forms.IKrFormPayload,Serilog.ILogger,System.Management.Automation.PowerShell)
TryBindFormModel(System.Type,Kestrun.Forms.IKrFormPayload,Serilog.ILogger,System.Management.Automation.PowerShell)
TryBindMultipartModel(System.Type,Kestrun.Forms.KrMultipart,Serilog.ILogger,System.Management.Automation.PowerShell)
TryBindFormDataModel(System.Type,Kestrun.Forms.KrFormData,Serilog.ILogger,System.Management.Automation.PowerShell)
TryCoerceToKrFormPayload(System.Type,Kestrun.Forms.IKrFormPayload,Serilog.ILogger,System.Management.Automation.PowerShell)
TryCoerceToKrFormData(System.Type,Kestrun.Forms.IKrFormPayload,Serilog.ILogger,System.Management.Automation.PowerShell)
TryCoerceToKrMultipart(System.Type,Kestrun.Forms.IKrFormPayload,Serilog.ILogger,System.Management.Automation.PowerShell)
TryPopulateKrMultipartDerivedModel(Kestrun.Forms.KrMultipart,Serilog.ILogger,System.Management.Automation.PowerShell)
TryPopulateMultipartObjectProperties(System.Object,Kestrun.Forms.KrMultipart,System.Type,Serilog.ILogger,System.Management.Automation.PowerShell,System.Int32)
TryPopulateFormDataObjectProperties(System.Object,Kestrun.Forms.KrFormData,System.Type,Serilog.ILogger)
GetFormDataBindableProperties(System.Type)
ApplyFormFieldValues(System.Object,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String[]>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Reflection.PropertyInfo>,Serilog.ILogger)
ApplyFormFileValues(System.Object,System.Collections.Generic.IReadOnlyDictionary`2<System.String,Kestrun.Forms.KrFilePart[]>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Reflection.PropertyInfo>,Serilog.ILogger)
BuildFieldPropertyValue(System.Type,System.String[])
BuildFilePropertyValue(System.Type,Kestrun.Forms.KrFilePart[])
CreateArrayFromValues(System.Collections.Generic.IReadOnlyList`1<System.Object>,System.Type)
TryPopulateMultipartStorage(System.Object,Kestrun.Forms.KrMultipart,Serilog.ILogger)
TryBuildValueForMultipartProperty(System.Type,System.Collections.Generic.List`1<Kestrun.Forms.KrRawPart>,Serilog.ILogger,System.Management.Automation.PowerShell,System.Int32)
TryBuildSingleMultipartValue(System.Type,Kestrun.Forms.KrRawPart,Serilog.ILogger,System.Management.Automation.PowerShell,System.Int32)
TryBuildNestedMultipartValue(System.Type,Kestrun.Forms.KrRawPart,Serilog.ILogger,System.Management.Automation.PowerShell,System.Int32)
TryBuildJsonMultipartValue(System.Type,Kestrun.Forms.KrRawPart,Serilog.ILogger,System.Management.Automation.PowerShell)
IsJsonPart(Kestrun.Forms.KrRawPart)
TryParseJsonToHashtable(System.String,Kestrun.Forms.KrRawPart,Serilog.ILogger)
LogNestedMultipartCreateFailure(Serilog.ILogger,System.Type,Kestrun.Forms.KrRawPart,System.Management.Automation.PowerShell)
LogJsonAdditionalPropertiesFailure(Serilog.ILogger,System.Type,Kestrun.Forms.KrRawPart,System.Management.Automation.PowerShell)
LogJsonInstanceCreateFailure(Serilog.ILogger,System.Type,Kestrun.Forms.KrRawPart,System.Management.Automation.PowerShell)
TryReadPartAsString(Kestrun.Forms.KrRawPart,Serilog.ILogger)
TryCreateObjectInstance(System.Type,Serilog.ILogger,System.Management.Automation.PowerShell)
TryCreatePowerShellClassInstance(System.Type,Serilog.ILogger,System.Management.Automation.PowerShell)
TryCreateStandardInstance(System.Type,Serilog.ILogger)
TryCreateUninitializedInstance(System.Type,Serilog.ILogger,System.String)
TryCreateObjectInstanceInRunspace(System.Management.Automation.PowerShell,System.Type,Serilog.ILogger)
TrySetPropertyValue(System.Object,System.Reflection.PropertyInfo,System.Object,Serilog.ILogger)
TrySetPropertyBackingFieldValue(System.Object,System.Reflection.PropertyInfo,System.Object,Serilog.ILogger)
TrySetAdditionalPropertiesBag(System.Object,System.Collections.Hashtable,Serilog.ILogger)
NormalizeAdditionalPropertiesBag(System.Object,System.Collections.Hashtable,Serilog.ILogger)
FilterPatternedAdditionalProperties(System.Object,System.Collections.Hashtable,System.Collections.Generic.IReadOnlyList`1<Kestrun.Languages.ParameterForInjectionInfo/PatternPropertyRule>,Serilog.ILogger)
ConvertAdditionalPropertiesByType(System.Object,System.Collections.Hashtable,System.String,Serilog.ILogger)
ConvertAdditionalPropertyValue(System.Object,System.Type,Serilog.ILogger)
TryGetAdditionalPropertiesMetadata(System.Object,System.String&,System.Collections.Generic.List`1<Kestrun.Languages.ParameterForInjectionInfo/PatternPropertyRule>&)
ResolveAdditionalPropertiesType(System.Type,System.String)
TryResolveAlias(System.String)
TryResolveByTypeName(System.String)
TryResolveInAssembly(System.Reflection.Assembly,System.String)
TryResolveInLoadedAssemblies(System.String)
TrySetAdditionalPropertiesBackingField(System.Object,System.Collections.Hashtable,Serilog.ILogger)
IsPowerShellClassType(System.Type)
TryCreateMultipartInstance(System.Type,Serilog.ILogger)
TryCreateFormDataInstance(System.Type,Serilog.ILogger)
TryCreateMultipartInstanceInRunspace(System.Management.Automation.PowerShell,System.Type,Serilog.ILogger)
TryCreateFormDataInstanceInRunspace(System.Management.Automation.PowerShell,System.Type,Serilog.ILogger)
CopyFormData(Kestrun.Forms.KrFormData,Kestrun.Forms.KrFormData)
EscapePowerShellString(System.String)
LogInjectingParameter(Serilog.ILogger,Kestrun.Languages.ParameterForInjectionInfo)
GetConvertedParameterValue(Kestrun.Models.KestrunContext,Kestrun.Languages.ParameterForInjectionInfo,System.Boolean&)
ConvertBodyParameterIfNeeded(Kestrun.Models.KestrunContext,Kestrun.Languages.ParameterForInjectionInfo,System.Object)
LogAddingParameter(Serilog.ILogger,System.String,System.Object,System.Boolean)
StoreResolvedParameter(Kestrun.Models.KestrunContext,Kestrun.Languages.ParameterForInjectionInfo,System.String,System.Object)
GetParameterValueFromContext(Kestrun.Models.KestrunContext,Kestrun.Languages.ParameterForInjectionInfo,System.Boolean&)
GetRawValue(Kestrun.Languages.ParameterForInjectionInfo,Kestrun.Models.KestrunContext)
NormalizeRaw(System.Object)
ConvertValue(Kestrun.Models.KestrunContext,Kestrun.Languages.ParameterForInjectionInfo,System.String,System.String[])
ConvertBodyBasedOnContentType(Kestrun.Models.KestrunContext,System.String,Kestrun.Languages.ParameterForInjectionInfo)
ConvertByCanonicalMediaType(System.String,Kestrun.Models.KestrunContext,System.String,Kestrun.Languages.ParameterForInjectionInfo)
ConvertYamlToHashtable(System.String)
ConvertJsonToHashtable(System.String)
JsonElementToClr(System.Text.Json.JsonElement)
ToHashtable(System.Text.Json.JsonElement)
ToArray(System.Text.Json.JsonElement)
ConvertXmlBodyToParameterType(System.String,System.Type)
ExtractRootMapForBinding(System.Collections.Hashtable,System.String)
NormalizeWrappedArrays(System.Collections.Hashtable,System.Collections.Hashtable)
TryGetXmlMetadataProperties(System.Collections.Hashtable,System.Collections.Hashtable&)
TryGetWrappedArrayMetadata(System.Collections.DictionaryEntry,System.String&,System.String&)
TryGetWrapperHashtable(System.Collections.Hashtable,System.String,System.String,System.Collections.Hashtable&)
TryUnwrapWrapper(System.Collections.Hashtable)
ConvertHashtableToObject(System.Collections.Hashtable,System.Type,System.Int32,System.Management.Automation.PowerShell)
TryConvertHashtableToParameterType(System.Type,System.Collections.Hashtable,Serilog.ILogger,System.Management.Automation.PowerShell)
PopulateObjectFromHashtable(System.Object,System.Type,System.Collections.Hashtable,System.Int32,Serilog.ILogger,System.Management.Automation.PowerShell)
ValidateRequiredMembers(System.Type,System.Collections.Hashtable)
GetRequiredMemberNames(System.Type)
HasAdditionalProperties(System.Object)
SanitizePsValidation(System.String)
GetWritableProperties(System.Type)
GetPublicFields(System.Type)
NormalizeMemberKey(System.String)
TrySetMemberValue(System.Object,System.Object,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Reflection.PropertyInfo>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Reflection.FieldInfo>,System.Int32,System.Management.Automation.PowerShell)
ApplyAdditionalPropertiesExtras(System.Object,System.Collections.Hashtable,System.Collections.Hashtable,Serilog.ILogger)
ConvertToTargetType(System.Object,System.Type,System.Int32,System.Management.Automation.PowerShell)
UnwrapNullableTargetType(System.Type)
TryConvertHashtableValue(System.Object,System.Type,System.Int32,System.Management.Automation.PowerShell,System.Object&)
TryConvertListOrArrayValue(System.Object,System.Type,System.Int32,System.Management.Automation.PowerShell,System.Object&)
ConvertScalarValue(System.Object,System.Type)
TryConvertScalarByType(System.String,System.Type,System.Object&)
TryConvertPrimitiveScalar(System.String,System.Type,System.Object&)
TryParseInt32(System.String)
TryParseInt64(System.String)
TryParseDouble(System.String)
TryParseDecimal(System.String)
TryParseBoolean(System.String)
TryParseEnum(System.Type,System.String)
TryChangeType(System.Object,System.Type)
ConvertEnumerableToTargetType(System.Collections.IEnumerable,System.Type,System.Int32,System.Management.Automation.PowerShell)
TryGetHashtableValue(System.Collections.Hashtable,System.String,System.Object&)
ConvertFormToHashtable(System.Collections.Generic.Dictionary`2<System.String,System.String>)
ConvertFormToValue(System.Collections.Generic.Dictionary`2<System.String,System.String>,Kestrun.Languages.ParameterForInjectionInfo)
ConvertBsonToHashtable(System.String)
BsonValueToClr(MongoDB.Bson.BsonValue)
BsonDocumentToHashtable(MongoDB.Bson.BsonDocument)
BsonArrayToClrArray(MongoDB.Bson.BsonArray)
ConvertCborToHashtable(System.String)
CborToClr(PeterO.Cbor.CBORObject)
ConvertCborMapToHashtable(PeterO.Cbor.CBORObject)
ConvertCborArrayToClrArray(PeterO.Cbor.CBORObject)
ConvertCborScalarToClr(PeterO.Cbor.CBORObject)
GetCborMapKeyString(PeterO.Cbor.CBORObject)
ConvertCsvToHashtable(System.String)
DecodeBodyStringToBytes(System.String)
TryDecodeBase64(System.String,System.Byte[]&)
TryDecodeHex(System.String,System.Byte[]&)