< Summary - Kestrun — Combined Coverage

Line coverage
36%
Covered lines: 507
Uncovered lines: 864
Coverable lines: 1371
Total lines: 3660
Line coverage: 36.9%
Branch coverage
30%
Covered branches: 347
Total branches: 1128
Branch coverage: 30.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: 37% (507/1370) Branch coverage: 30.7% (347/1130) Total lines: 3659 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/14/2025 - 20:04:52 Line coverage: 36.9% (507/1371) Branch coverage: 30.7% (347/1130) Total lines: 3661 Tag: Kestrun/Kestrun@a05ac8de57c6207e227b92ba360e9d58869ac80a12/15/2025 - 18:44:50 Line coverage: 36.9% (507/1371) Branch coverage: 30.7% (347/1130) Total lines: 3660 Tag: Kestrun/Kestrun@6b9e56ea2de904fc3597033ef0f9bc7839d5d61812/18/2025 - 21:41:58 Line coverage: 36.9% (507/1371) Branch coverage: 30.7% (347/1128) Total lines: 3660 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff 12/12/2025 - 17:27:19 Line coverage: 37% (507/1370) Branch coverage: 30.7% (347/1130) Total lines: 3659 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/14/2025 - 20:04:52 Line coverage: 36.9% (507/1371) Branch coverage: 30.7% (347/1130) Total lines: 3661 Tag: Kestrun/Kestrun@a05ac8de57c6207e227b92ba360e9d58869ac80a12/15/2025 - 18:44:50 Line coverage: 36.9% (507/1371) Branch coverage: 30.7% (347/1130) Total lines: 3660 Tag: Kestrun/Kestrun@6b9e56ea2de904fc3597033ef0f9bc7839d5d61812/18/2025 - 21:41:58 Line coverage: 36.9% (507/1371) Branch coverage: 30.7% (347/1128) Total lines: 3660 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: LoadAnnotatedFunctions(...)0%620%
File 1: ProcessFunction(...)0%2040%
File 1: ProcessFunctionAttributes(...)0%210140%
File 1: ApplyPathAttribute(...)0%110100%
File 1: ChooseFirstNonEmpty(...)0%2040%
File 1: NormalizeNewlines(...)0%620%
File 1: ApplyResponseRefAttribute(...)0%7280%
File 1: ApplyResponseAttribute(...)0%2040%
File 1: ApplyPropertyAttribute(...)0%342180%
File 1: ApplyAuthorizationAttribute(...)0%620%
File 1: BuildPolicyList(...)0%620%
File 1: ProcessParameters(...)0%210140%
File 1: ApplyParameterAttribute(...)0%272160%
File 1: ApplyParameterRefAttribute(...)0%110100%
File 1: ApplyRequestBodyRefAttribute(...)0%2040%
File 1: ResolveRequestBodyReferenceId(...)0%156120%
File 1: ApplyRequestBodyAttribute(...)0%4260%
File 1: ApplyPreferredRequestBody(...)0%2040%
File 1: EnsureDefaultResponses(...)0%2040%
File 1: FinalizeRouteOptions(...)0%2040%
File 1: CreateRequestBodyFromAttribute(...)0%7280%
File 2: BuildPathsFromRegisteredRoutes(...)0%110100%
File 2: ProcessRouteGroup(...)0%2040%
File 2: GetOrCreatePathItem(...)0%4260%
File 2: ProcessRouteOperation(...)0%156120%
File 2: ApplyPathLevelMetadata(...)0%620%
File 2: ApplyPathLevelServers(...)0%420200%
File 2: ApplyPathLevelParameters(...)0%420200%
File 2: BuildOperationFromMetadata(...)0%7280%
File 2: ApplyTags(...)0%4260%
File 2: ApplyServers(...)0%420200%
File 2: ApplyParameters(...)0%506220%
File 2: ApplyCallbacks(...)0%2040%
File 2: ApplySecurity(...)0%210140%
File 3: BuildSchema(...)100%66100%
File 3: BuildPropertySchema(...)44.44%201881.25%
File 3: BuildComplexTypeSchema(...)100%210%
File 3: BuildEnumSchema(...)100%210%
File 3: BuildArraySchema(...)0%2040%
File 3: BuildPrimitiveSchema(...)100%11100%
File 4: BuildExamples(...)85.71%1414100%
File 4: CreateExampleFromAttribute(...)50%121286.66%
File 4: ApplyExampleRefAttribute(...)75%4480%
File 4: ApplyInlineExampleAttribute(...)50%6684.61%
File 5: BuildHeaders(...)75%88100%
File 5: GetHeaderComponentDefaults(...)50%88100%
File 5: GetHeaderAttributes(...)83.33%66100%
File 5: BuildHeader(...)100%66100%
File 5: ApplyDefaultDescription(...)50%5466.66%
File 5: BuildHeaderKey(...)50%22100%
File 5: CreateHeaderFromAttribute(...)83.33%6687.5%
File 5: ApplyHeaderAttribute(...)75%44100%
File 6: GetSchema(...)0%7280%
File 6: GetParameter(...)0%7280%
File 6: GetRequestBody(...)0%7280%
File 6: GetHeader(...)0%7280%
File 6: GetResponse(...)0%7280%
File 6: ComponentSchemasExists(...)50%44100%
File 6: ComponentRequestBodiesExists(...)0%2040%
File 6: ComponentResponsesExists(...)0%2040%
File 6: ComponentParametersExists(...)0%2040%
File 6: ComponentExamplesExists(...)0%2040%
File 6: ComponentHeadersExists(...)0%2040%
File 6: ComponentCallbacksExists(...)0%2040%
File 6: ComponentLinksExists(...)0%2040%
File 6: ComponentPathItemsExists(...)0%2040%
File 7: BuildParameters(...)0%2040%
File 7: GetClassLevelMetadata(...)0%110100%
File 7: ProcessPropertyForParameter(...)0%2040%
File 7: GetParameterAttributes(...)0%4260%
File 7: ApplyParameterAttributes(...)0%7280%
File 7: FinalizeAndRegisterParameter(...)0%110100%
File 7: CreatePropertySchema(...)0%7280%
File 7: CreateEnumSchema(...)0%2040%
File 7: CreateArraySchema(...)0%110100%
File 7: CreateComplexSchema(...)100%210%
File 7: CreatePrimitiveSchema(...)0%7280%
File 7: TryApplyDefaultValue(...)0%2040%
File 7: CreateParameterFromAttribute(...)0%2040%
File 7: ApplyParameterAttribute(...)0%156120%
File 7: ApplyExampleRefAttribute(...)0%156120%
File 8: BuildRequestBodies(...)66.66%6690.9%
File 8: ApplyRequestBodyComponent(...)91.66%121293.75%
File 8: ApplyRequestBodyExampleRef(...)75%121292.3%
File 8: ResolveExampleContentTypes(...)37.5%88100%
File 8: CloneExampleOrThrow(...)50%88100%
File 9: BuildResponses(...)75%44100%
File 9: GetClassLevelResponseMetadata(...)80%101085.71%
File 9: ProcessPropertyForResponse(...)75%4487.5%
File 9: GetPropertyResponseAttributes(...)87.5%88100%
File 9: ApplyPropertyAttributesToResponse(...)87.5%8888.88%
File 9: RegisterResponse(...)50%8883.33%
File 9: GetAttributeValue(...)50%12861.53%
File 9: GetEnumSchema(...)0%2040%
File 9: GetArraySchema(...)0%7280%
File 9: GetComplexSchema(...)100%210%
File 9: GetPrimitiveSchema(...)33.33%7675%
File 9: GetKeyOverride(...)50%22100%
File 9: CreateResponseFromAttribute(...)62.5%10870%
File 9: ApplyResponseAttribute(...)100%11100%
File 9: ApplyDescription(...)100%22100%
File 9: ResolveResponseSchema(...)25%20842.85%
File 9: ResolveSchemaRef(...)100%22100%
File 9: ApplySchemaToContentTypes(...)100%88100%
File 9: ApplyHeaderRefAttribute(...)0%620%
File 9: ApplyLinkRefAttribute(...)0%620%
File 9: ApplyExampleRefAttribute(...)54.54%252281.25%
File 9: GetOrAddMediaType(...)100%44100%
File 9: CloneSchemaOrThrow(...)50%8660%
File 10: GetSchemaIdentity(...)0%620%
File 10: BuildSchemaForType(...)92.85%141490.47%
File 10: BuildBaseTypeSchema(...)58.33%121290.9%
File 10: BuildOpenApiTypeSchema(...)0%620%
File 10: BuildCustomBaseTypeSchema(...)33.33%7670%
File 10: BuildPropertyFromAttribute(...)0%620%
File 10: RegisterEnumSchema(...)0%2040%
File 10: ApplyTypeAttributes(...)71.42%311455.55%
File 10: ProcessTypeProperties(...)83.33%66100%
File 10: TryCreateTypeInstance(...)100%11100%
File 10: CapturePropertyDefault(...)100%9877.77%
File 10: IsIntrinsicDefault(...)75%191676.47%
File 10: MergeSchemaAttributes(...)0%7280%
File 10: MergeStringProperties(...)0%156120%
File 10: MergeEnumAndCollections(...)0%110100%
File 10: MergeNumericProperties(...)0%110100%
File 10: MergeBooleanProperties(...)100%210%
File 10: MergeTypeAndRequired(...)0%7280%
File 10: MergeCustomFields(...)0%620%
File 10: InferPrimitiveSchema(...)16.66%19628.57%
File 10: InferArraySchema(...)0%4260%
File 10: InferPowerShellClassSchema(...)0%4260%
File 10: .cctor()100%1126.66%
File 10: ApplySchemaAttr(...)50%8662.5%
File 10: ApplyConcreteSchemaAttributes(...)100%11100%
File 10: ApplyTitleAndDescription(...)100%44100%
File 10: ApplySchemaType(...)14.28%1141420%
File 10: ApplyFormatAndNumericBounds(...)50%461238.46%
File 10: ApplyLengthAndPattern(...)50%9657.14%
File 10: ApplyCollectionConstraints(...)50%191054.54%
File 10: ApplyFlags(...)100%11100%
File 10: ApplyExamplesAndDefaults(...)43.75%581645.45%
File 10: ApplyReferenceSchemaAttributes(...)0%2040%
File 10: IsPrimitiveLike(...)100%1818100%
File 10: ToNode(...)77.5%994066.66%
File 10: ToJsonObject(...)0%4260%
File 10: ToJsonArray(...)0%620%
File 10: ToNodeFromPocoOrString(...)55.55%181891.66%
File 10: EnsureSchemaComponent(...)0%4260%
File 11: ApplySecurityScheme(...)78.57%161476.92%
File 11: GetSecurityScheme(...)50%22100%
File 11: GetSecurityScheme(...)0%2040%
File 11: GetSecurityScheme(...)0%156120%
File 11: GetSecurityScheme(...)100%11100%
File 11: GetSecurityScheme(...)100%11100%
File 11: GetSecurityScheme(...)100%11100%
File 11: GetSecurityScheme(...)100%11100%
File 11: AddSecurityComponent(...)25%4483.33%
File 12: get_Host()100%11100%
File 12: get_DocumentId()100%11100%
File 12: get_Document()100%11100%
File 12: get_SecurityRequirement()100%11100%
File 12: .ctor(...)100%11100%
File 12: get_HasBeenGenerated()100%11100%
File 12: GenerateComponents(...)50%22100%
File 12: ProcessComponentTypes(...)100%66100%
File 12: GenerateComponents()100%210%
File 12: GenerateDoc()100%210%
File 12: ReadAndDiagnose(...)100%210%
File 12: ToJson(...)100%210%
File 12: ToYaml(...)100%210%

File(s)

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

#LineLine coverage
 1using System.Management.Automation;
 2using System.Management.Automation.Internal;
 3using System.Management.Automation.Language;
 4using Kestrun.Hosting;
 5using Kestrun.Hosting.Options;
 6using Kestrun.Languages;
 7using Kestrun.Utilities;
 8using Microsoft.OpenApi;
 9
 10namespace Kestrun.OpenApi;
 11
 12public partial class OpenApiDocDescriptor
 13{
 14    /// <summary>
 15    /// Enumerates all in-session PowerShell functions in the given runspace,
 16    /// detects those annotated with [OpenApiPath], and maps them into the provided KestrunHost.
 17    /// </summary>
 18    /// <param name="cmdInfos">List of FunctionInfo objects representing PowerShell functions.</param>
 19    public void LoadAnnotatedFunctions(List<FunctionInfo> cmdInfos)
 20    {
 021        ArgumentNullException.ThrowIfNull(cmdInfos);
 22
 023        foreach (var func in cmdInfos)
 24        {
 025            ProcessFunction(func);
 26        }
 027    }
 28
 29    /// <summary>
 30    /// Processes a single PowerShell function, extracting OpenAPI annotations and configuring the host accordingly.
 31    /// </summary>
 32    /// <param name="func">The function information.</param>
 33    private void ProcessFunction(FunctionInfo func)
 34    {
 35        try
 36        {
 037            var help = func.GetHelp();
 038            var sb = func.ScriptBlock;
 039            if (sb is null)
 40            {
 041                return;
 42            }
 43
 044            var attrs = sb.Attributes;
 045            if (attrs.Count == 0)
 46            {
 047                return;
 48            }
 49
 050            var openApiMetadata = new OpenAPIMetadata();
 051            var routeOptions = new MapRouteOptions();
 052            var parsedVerb = ProcessFunctionAttributes(func, help!, attrs, routeOptions, openApiMetadata);
 53
 054            ProcessParameters(func, help!, routeOptions, openApiMetadata);
 55
 056            EnsureDefaultResponses(openApiMetadata);
 057            FinalizeRouteOptions(func, sb, openApiMetadata, routeOptions, parsedVerb);
 058        }
 059        catch (Exception ex)
 60        {
 061            Host.Logger.Error("Error loading OpenAPI annotated function '{funcName}': {message}", func.Name, ex.Message)
 062        }
 063    }
 64
 65    /// <summary>
 66    /// Processes the OpenAPI-related attributes on the specified function.
 67    /// </summary>
 68    /// <param name="func">The function information.</param>
 69    /// <param name="help">The comment help information.</param>
 70    /// <param name="attrs">The collection of attributes applied to the function.</param>
 71    /// <param name="routeOptions">The route options to configure.</param>
 72    /// <param name="openApiMetadata">The OpenAPI metadata to populate.</param>
 73    /// <returns>The parsed HTTP verb for the function.</returns>
 74    private HttpVerb ProcessFunctionAttributes(
 75        FunctionInfo func,
 76        CommentHelpInfo help,
 77        IReadOnlyCollection<Attribute> attrs,
 78        MapRouteOptions routeOptions,
 79        OpenAPIMetadata openApiMetadata)
 80    {
 081        var parsedVerb = HttpVerb.Get;
 82
 083        foreach (var attr in attrs)
 84        {
 85            try
 86            {
 87                switch (attr)
 88                {
 89                    case OpenApiPath path:
 090                        parsedVerb = ApplyPathAttribute(func, help, routeOptions, openApiMetadata, parsedVerb, path);
 091                        break;
 92                    case OpenApiResponseRefAttribute responseRef:
 093                        ApplyResponseRefAttribute(openApiMetadata, responseRef);
 094                        break;
 95                    case OpenApiResponseAttribute responseAttr:
 096                        ApplyResponseAttribute(openApiMetadata, responseAttr);
 097                        break;
 98                    case OpenApiPropertyAttribute propertyAttr:
 099                        ApplyPropertyAttribute(openApiMetadata, propertyAttr);
 0100                        break;
 101                    case OpenApiAuthorizationAttribute authAttr:
 0102                        ApplyAuthorizationAttribute(routeOptions, openApiMetadata, authAttr);
 0103                        break;
 104                    case KestrunAnnotation ka:
 0105                        throw new InvalidOperationException($"Unhandled Kestrun annotation: {ka.GetType().Name}");
 106                }
 0107            }
 0108            catch (InvalidOperationException ex)
 109            {
 0110                Host.Logger.Error("Error processing OpenApiPath attribute on function '{funcName}': {message}", func.Nam
 0111            }
 0112            catch (Exception ex)
 113            {
 0114                Host.Logger.Error("Error processing OpenApiPath attribute on function '{funcName}': {message}", func.Nam
 0115            }
 116        }
 117
 0118        return parsedVerb;
 119    }
 120
 121    /// <summary>
 122    /// Applies the OpenApiPath attribute to the function's route options and metadata.
 123    /// </summary>
 124    /// <param name="func">The function information.</param>
 125    /// <param name="help">The comment help information.</param>
 126    /// <param name="routeOptions">The route options to configure.</param>
 127    /// <param name="metadata">The OpenAPI metadata to populate.</param>
 128    /// <param name="parsedVerb">The currently parsed HTTP verb.</param>
 129    /// <param name="oaPath">The OpenApiPath attribute instance.</param>
 130    /// <returns>The updated HTTP verb after processing the attribute.</returns>
 131    private static HttpVerb ApplyPathAttribute(
 132        FunctionInfo func,
 133        CommentHelpInfo help,
 134        MapRouteOptions routeOptions,
 135        OpenAPIMetadata metadata,
 136        HttpVerb parsedVerb,
 137        OpenApiPath oaPath)
 138    {
 0139        var httpVerb = oaPath.HttpVerb ?? string.Empty;
 0140        if (!string.IsNullOrWhiteSpace(httpVerb))
 141        {
 0142            parsedVerb = HttpVerbExtensions.FromMethodString(httpVerb);
 0143            routeOptions.HttpVerbs.Add(parsedVerb);
 144        }
 145
 0146        if (!string.IsNullOrWhiteSpace(oaPath.Pattern))
 147        {
 0148            routeOptions.Pattern = oaPath.Pattern;
 0149            metadata.Pattern = oaPath.Pattern;
 150        }
 151
 0152        metadata.Summary = ChooseFirstNonEmpty(oaPath.Summary, help.GetSynopsis());
 0153        metadata.Description = ChooseFirstNonEmpty(oaPath.Description, help.GetDescription());
 0154        metadata.Tags = [.. oaPath.Tags];
 0155        metadata.OperationId = oaPath.OperationId is null
 0156            ? func.Name
 0157            : string.IsNullOrWhiteSpace(oaPath.OperationId) ? metadata.OperationId : oaPath.OperationId;
 158        // Apply deprecated flag if specified
 0159        metadata.Deprecated |= oaPath.Deprecated;
 160        // Apply Cors policy name if specified
 0161        metadata.CorsPolicy = oaPath.CorsPolicy;
 0162        return parsedVerb;
 163    }
 164
 165    /// <summary>
 166    /// Chooses the first non-empty string from the provided values, normalizing newlines.
 167    /// </summary>
 168    /// <param name="values">An array of string values to evaluate.</param>
 169    /// <returns>The first non-empty string with normalized newlines, or null if all are null or whitespace.</returns>
 170    private static string? ChooseFirstNonEmpty(params string?[] values)
 171    {
 0172        foreach (var value in values)
 173        {
 0174            if (!string.IsNullOrWhiteSpace(value))
 175            {
 0176                return NormalizeNewlines(value);
 177            }
 178        }
 179
 0180        return null;
 181    }
 182
 183    /// <summary>
 184    /// Normalizes newlines in the given string to use '\n' only.
 185    /// </summary>
 186    /// <param name="value">The string to normalize.</param>
 187    /// <returns>The normalized string.</returns>
 0188    private static string? NormalizeNewlines(string? value) => value?.Replace("\r\n", "\n");
 189    private void ApplyResponseRefAttribute(OpenAPIMetadata metadata, OpenApiResponseRefAttribute attribute)
 190    {
 0191        metadata.Responses ??= [];
 0192        IOpenApiResponse response = attribute.Inline
 0193            ? GetResponse(attribute.ReferenceId).Clone()
 0194            : new OpenApiResponseReference(attribute.ReferenceId);
 195
 0196        if (attribute.Description is not null)
 197        {
 0198            response.Description = attribute.Description;
 199        }
 200
 0201        if (metadata.Responses.ContainsKey(attribute.StatusCode))
 202        {
 0203            throw new InvalidOperationException($"Response for status code '{attribute.StatusCode}' is already defined f
 204        }
 205
 0206        metadata.Responses.Add(attribute.StatusCode, response);
 0207    }
 208
 209    /// <summary>
 210    /// Applies the OpenApiResponse attribute to the function's OpenAPI metadata.
 211    /// </summary>
 212    /// <param name="metadata">The OpenAPI metadata to update.</param>
 213    /// <param name="attribute">The OpenApiResponse attribute containing response details.</param>
 214    private void ApplyResponseAttribute(OpenAPIMetadata metadata, OpenApiResponseAttribute attribute)
 215    {
 0216        metadata.Responses ??= [];
 0217        var response = new OpenApiResponse();
 0218        if (CreateResponseFromAttribute(attribute, response))
 219        {
 0220            metadata.Responses.Add(attribute.StatusCode, response);
 221        }
 0222    }
 223
 224    /// <summary>
 225    /// Applies the OpenApiProperty attribute to the function's OpenAPI metadata.
 226    /// </summary>
 227    /// <param name="metadata">The OpenAPI metadata to update.</param>
 228    /// <param name="attribute">The OpenApiProperty attribute containing property details.</param>
 229    /// <exception cref="InvalidOperationException"></exception>
 230    private static void ApplyPropertyAttribute(OpenAPIMetadata metadata, OpenApiPropertyAttribute attribute)
 231    {
 0232        if (attribute.StatusCode is null)
 233        {
 0234            return;
 235        }
 236
 0237        if (metadata.Responses is null || !metadata.Responses.TryGetValue(attribute.StatusCode, out var res))
 238        {
 0239            throw new InvalidOperationException($"Response for status code '{attribute.StatusCode}' is not defined for t
 240        }
 241
 0242        if (res is OpenApiResponseReference)
 243        {
 0244            throw new InvalidOperationException($"Cannot apply OpenApiPropertyAttribute to response '{attribute.StatusCo
 245        }
 246
 0247        if (res is OpenApiResponse response)
 248        {
 0249            if (response.Content is null || response.Content.Count == 0)
 250            {
 0251                throw new InvalidOperationException($"Cannot apply OpenApiPropertyAttribute to response '{attribute.Stat
 252            }
 253
 0254            foreach (var content in response.Content.Values)
 255            {
 0256                if (content.Schema is null)
 257                {
 0258                    throw new InvalidOperationException($"Cannot apply OpenApiPropertyAttribute to response '{attribute.
 259                }
 260
 0261                ApplySchemaAttr(attribute, content.Schema);
 262            }
 263        }
 0264    }
 265
 266    private void ApplyAuthorizationAttribute(MapRouteOptions routeOptions, OpenAPIMetadata metadata, OpenApiAuthorizatio
 267    {
 0268        metadata.SecuritySchemes ??= [];
 0269        var policyList = BuildPolicyList(attribute.Policies);
 0270        var securitySchemeList = Host.AddSecurityRequirementObject(attribute.Scheme, policyList, metadata.SecurityScheme
 0271        routeOptions.AddSecurityRequirementObject(schemes: securitySchemeList, policies: policyList);
 0272    }
 273
 274    private static List<string> BuildPolicyList(string? policies)
 275    {
 0276        return [.. (string.IsNullOrWhiteSpace(policies) ? new List<string>() : [.. policies.Split(',')])
 0277            .Where(p => !string.IsNullOrWhiteSpace(p))
 0278            .Select(p => p.Trim())];
 279    }
 280
 281    /// <summary>
 282    /// Processes the parameters of the specified function, applying OpenAPI annotations as needed.
 283    /// </summary>
 284    /// <param name="func">The function information.</param>
 285    /// <param name="help">The comment help information.</param>
 286    /// <param name="routeOptions">The route options to update.</param>
 287    /// <param name="openApiMetadata">The OpenAPI metadata to update.</param>
 288    /// <exception cref="InvalidOperationException">Thrown when an invalid operation occurs during parameter processing.
 289    private void ProcessParameters(
 290        FunctionInfo func,
 291        CommentHelpInfo help,
 292        MapRouteOptions routeOptions,
 293        OpenAPIMetadata openApiMetadata)
 294    {
 0295        foreach (var paramInfo in func.Parameters.Values)
 296        {
 0297            foreach (var attribute in paramInfo.Attributes)
 298            {
 299                switch (attribute)
 300                {
 301                    case OpenApiParameterAttribute paramAttr:
 0302                        ApplyParameterAttribute(func, help, routeOptions, openApiMetadata, paramInfo, paramAttr);
 0303                        break;
 304                    case OpenApiParameterRefAttribute paramRefAttr:
 0305                        ApplyParameterRefAttribute(help, routeOptions, openApiMetadata, paramInfo, paramRefAttr);
 0306                        break;
 307                    case OpenApiRequestBodyRefAttribute requestBodyRefAttr:
 0308                        ApplyRequestBodyRefAttribute(help, routeOptions, openApiMetadata, paramInfo, requestBodyRefAttr)
 0309                        break;
 310                    case OpenApiRequestBodyAttribute requestBodyAttr:
 0311                        ApplyRequestBodyAttribute(help, routeOptions, openApiMetadata, paramInfo, requestBodyAttr);
 0312                        break;
 313                    case KestrunAnnotation ka:
 0314                        throw new InvalidOperationException($"Unhandled Kestrun annotation: {ka.GetType().Name}");
 315                }
 316            }
 317        }
 0318    }
 319
 320    /// <summary>
 321    /// Applies the OpenApiParameter attribute to the function's OpenAPI metadata.
 322    /// </summary>
 323    /// <param name="func">The function information.</param>
 324    /// <param name="help">The comment help information.</param>
 325    /// <param name="routeOptions">The route options to update.</param>
 326    /// <param name="metadata">The OpenAPI metadata to update.</param>
 327    /// <param name="paramInfo">The parameter information.</param>
 328    /// <param name="attribute">The OpenApiParameter attribute containing parameter details.</param>
 329    private void ApplyParameterAttribute(
 330        FunctionInfo func,
 331        CommentHelpInfo help,
 332        MapRouteOptions routeOptions,
 333        OpenAPIMetadata metadata,
 334        ParameterMetadata paramInfo,
 335        OpenApiParameterAttribute attribute)
 336    {
 0337        metadata.Parameters ??= [];
 0338        var parameter = new OpenApiParameter();
 0339        if (!CreateParameterFromAttribute(attribute, parameter))
 340        {
 0341            Host.Logger.Error("Error processing OpenApiParameter attribute on parameter '{paramName}' of function '{func
 0342            return;
 343        }
 344
 0345        if (!string.IsNullOrEmpty(parameter.Name) && parameter.Name != paramInfo.Name)
 346        {
 0347            throw new InvalidOperationException($"Parameter name {parameter.Name} is different from variable name: '{par
 348        }
 349
 0350        parameter.Name = paramInfo.Name;
 0351        parameter.Schema = InferPrimitiveSchema(paramInfo.ParameterType);
 352
 0353        if (parameter.Schema is OpenApiSchema schema)
 354        {
 0355            var defaultValue = func.GetDefaultParameterValue(paramInfo.Name);
 0356            if (defaultValue is not null)
 357            {
 0358                schema.Default = ToNode(defaultValue);
 359            }
 360        }
 361
 0362        parameter.Description ??= help.GetParameterDescription(paramInfo.Name);
 363
 0364        foreach (var attr in paramInfo.Attributes.OfType<CmdletMetadataAttribute>())
 365        {
 0366            PowerShellAttributes.ApplyPowerShellAttribute(attr, (OpenApiSchema)parameter.Schema);
 367        }
 368
 0369        metadata.Parameters.Add(parameter);
 0370        routeOptions.ScriptCode.Parameters.Add(new ParameterForInjectionInfo(paramInfo, parameter));
 0371    }
 372
 373    /// <summary>
 374    /// Applies the OpenApiParameterRef attribute to the function's OpenAPI metadata.
 375    /// </summary>
 376    /// <param name="help">The comment help information.</param>
 377    /// <param name="routeOptions">The route options to update.</param>
 378    /// <param name="metadata">The OpenAPI metadata to update.</param>
 379    /// <param name="paramInfo">The parameter information.</param>
 380    /// <param name="attribute">The OpenApiParameterRef attribute containing parameter reference details.</param>
 381    /// <exception cref="InvalidOperationException">If the parameter name does not match the reference name when inlinin
 382    private void ApplyParameterRefAttribute(
 383        CommentHelpInfo help,
 384        MapRouteOptions routeOptions,
 385        OpenAPIMetadata metadata,
 386        ParameterMetadata paramInfo,
 387        OpenApiParameterRefAttribute attribute)
 388    {
 0389        metadata.Parameters ??= [];
 0390        routeOptions.ScriptCode.Parameters ??= [];
 391
 0392        var componentParameter = GetParameter(attribute.ReferenceId);
 393        IOpenApiParameter parameter;
 394
 0395        if (attribute.Inline)
 396        {
 0397            parameter = componentParameter.Clone();
 0398            if (componentParameter.Name != paramInfo.Name)
 399            {
 0400                throw new InvalidOperationException($"Parameter name {componentParameter.Name} is different from variabl
 401            }
 402
 0403            parameter.Description ??= help.GetParameterDescription(paramInfo.Name);
 404        }
 405        else
 406        {
 0407            parameter = new OpenApiParameterReference(attribute.ReferenceId);
 408        }
 409
 0410        routeOptions.ScriptCode.Parameters.Add(new ParameterForInjectionInfo(paramInfo, componentParameter));
 0411        metadata.Parameters.Add(parameter);
 0412    }
 413
 414    /// <summary>
 415    /// Applies the OpenApiRequestBodyRef attribute to the function's OpenAPI metadata.
 416    /// </summary>
 417    /// <param name="help">The comment help information.</param>
 418    /// <param name="routeOptions">The route options to update.</param>
 419    /// <param name="metadata">The OpenAPI metadata to update.</param>
 420    /// <param name="paramInfo">The parameter information.</param>
 421    /// <param name="attribute">The OpenApiRequestBodyRef attribute containing request body reference details.</param>
 422    private void ApplyRequestBodyRefAttribute(
 423        CommentHelpInfo help,
 424        MapRouteOptions routeOptions,
 425        OpenAPIMetadata metadata,
 426        ParameterMetadata paramInfo,
 427        OpenApiRequestBodyRefAttribute attribute)
 428    {
 0429        var referenceId = ResolveRequestBodyReferenceId(attribute, paramInfo);
 0430        var componentRequestBody = GetRequestBody(referenceId);
 431
 0432        metadata.RequestBody = attribute.Inline ? componentRequestBody.Clone() : new OpenApiRequestBodyReference(referen
 0433        metadata.RequestBody.Description = attribute.Description ?? help.GetParameterDescription(paramInfo.Name);
 434
 0435        routeOptions.ScriptCode.Parameters.Add(new ParameterForInjectionInfo(paramInfo, componentRequestBody));
 0436    }
 437
 438    /// <summary>
 439    /// Resolves the reference ID for the OpenApiRequestBodyRef attribute.
 440    /// </summary>
 441    /// <param name="attribute">The OpenApiRequestBodyRef attribute.</param>
 442    /// <param name="paramInfo">The parameter metadata.</param>
 443    /// <returns>The resolved reference ID.</returns>
 444    /// <exception cref="InvalidOperationException">
 445    /// Thrown when the ReferenceId is not specified and the parameter type is 'object',
 446    /// or when the ReferenceId does not match the parameter type name.
 447    /// </exception>
 448    private static string ResolveRequestBodyReferenceId(OpenApiRequestBodyRefAttribute attribute, ParameterMetadata para
 449    {
 0450        if (string.IsNullOrWhiteSpace(attribute.ReferenceId))
 451        {
 0452            if (paramInfo.ParameterType.Name is "Object" or null)
 453            {
 0454                throw new InvalidOperationException("OpenApiRequestBodyRefAttribute must have a ReferenceId specified wh
 455            }
 456
 0457            attribute.ReferenceId = paramInfo.ParameterType.Name;
 458        }
 0459        else if (paramInfo.ParameterType.Name != "Object" && attribute.ReferenceId != paramInfo.ParameterType.Name)
 460        {
 0461            throw new InvalidOperationException($"ReferenceId '{attribute.ReferenceId}' is different from parameter type
 462        }
 463
 0464        return attribute.ReferenceId;
 465    }
 466
 467    /// <summary>
 468    /// Applies the OpenApiRequestBody attribute to the function's OpenAPI metadata.
 469    /// </summary>
 470    /// <param name="help">The comment help information.</param>
 471    /// <param name="routeOptions">The route options to update.</param>
 472    /// <param name="metadata">The OpenAPI metadata to update.</param>
 473    /// <param name="paramInfo">The parameter information.</param>
 474    /// <param name="attribute">The OpenApiRequestBody attribute containing request body details.</param>
 475    private void ApplyRequestBodyAttribute(
 476        CommentHelpInfo help,
 477        MapRouteOptions routeOptions,
 478        OpenAPIMetadata metadata,
 479        ParameterMetadata paramInfo,
 480        OpenApiRequestBodyAttribute attribute)
 481    {
 0482        var requestBodyPreferred = ComponentRequestBodiesExists(paramInfo.ParameterType.Name);
 483
 0484        if (requestBodyPreferred)
 485        {
 0486            ApplyPreferredRequestBody(help, routeOptions, metadata, paramInfo, attribute);
 0487            return;
 488        }
 489
 0490        var requestBody = new OpenApiRequestBody();
 0491        var schema = InferPrimitiveSchema(type: paramInfo.ParameterType, inline: attribute.Inline);
 492
 0493        if (!CreateRequestBodyFromAttribute(attribute, requestBody, schema))
 494        {
 0495            return;
 496        }
 497
 0498        metadata.RequestBody = requestBody;
 0499        metadata.RequestBody.Description ??= help.GetParameterDescription(paramInfo.Name);
 0500        routeOptions.ScriptCode.Parameters.Add(new ParameterForInjectionInfo(paramInfo, requestBody));
 0501    }
 502
 503    /// <summary>
 504    /// Applies the preferred request body from components to the function's OpenAPI metadata.
 505    /// </summary>
 506    /// <param name="help">The comment help information.</param>
 507    /// <param name="routeOptions">The route options to update.</param>
 508    /// <param name="metadata">The OpenAPI metadata to update.</param>
 509    /// <param name="paramInfo">The parameter information.</param>
 510    /// <param name="attribute">The OpenApiRequestBody attribute containing request body details.</param>
 511    private void ApplyPreferredRequestBody(
 512        CommentHelpInfo help,
 513        MapRouteOptions routeOptions,
 514        OpenAPIMetadata metadata,
 515        ParameterMetadata paramInfo,
 516        OpenApiRequestBodyAttribute attribute)
 517    {
 0518        var componentRequestBody = GetRequestBody(paramInfo.ParameterType.Name);
 519
 0520        metadata.RequestBody = attribute.Inline
 0521            ? componentRequestBody.Clone()
 0522            : new OpenApiRequestBodyReference(paramInfo.ParameterType.Name);
 523
 0524        metadata.RequestBody.Description ??= help.GetParameterDescription(paramInfo.Name);
 0525        routeOptions.ScriptCode.Parameters.Add(new ParameterForInjectionInfo(paramInfo, componentRequestBody));
 0526    }
 527
 528    /// <summary>
 529    /// Ensures that the OpenAPIMetadata has default responses defined.
 530    /// </summary>
 531    /// <param name="metadata">The OpenAPI metadata to update.</param>
 532    private static void EnsureDefaultResponses(OpenAPIMetadata metadata)
 533    {
 0534        metadata.Responses ??= [];
 0535        if (metadata.Responses.Count > 0)
 536        {
 0537            return;
 538        }
 539
 0540        metadata.Responses.Add("200", new OpenApiResponse { Description = "Ok" });
 0541        metadata.Responses.Add("default", new OpenApiResponse { Description = "Unexpected error" });
 0542    }
 543
 544    /// <summary>
 545    /// Finalizes the route options by adding OpenAPI metadata and configuring defaults.
 546    /// </summary>
 547    /// <param name="func">The function information.</param>
 548    /// <param name="sb">The script block.</param>
 549    /// <param name="metadata">The OpenAPI metadata.</param>
 550    /// <param name="routeOptions">The route options to update.</param>
 551    /// <param name="parsedVerb">The HTTP verb parsed from the function.</param>
 552    private void FinalizeRouteOptions(
 553        FunctionInfo func,
 554        ScriptBlock sb,
 555        OpenAPIMetadata metadata,
 556        MapRouteOptions routeOptions,
 557        HttpVerb parsedVerb)
 558    {
 0559        routeOptions.OpenAPI.Add(parsedVerb, metadata);
 560
 0561        if (string.IsNullOrWhiteSpace(routeOptions.Pattern))
 562        {
 0563            routeOptions.Pattern = "/" + func.Name;
 564        }
 565
 0566        if (!string.IsNullOrWhiteSpace(metadata.CorsPolicy))
 567        {
 0568            routeOptions.CorsPolicy = metadata.CorsPolicy;
 569        }
 570
 0571        routeOptions.ScriptCode.ScriptBlock = sb;
 0572        routeOptions.DefaultResponseContentType = "application/json";
 0573        _ = Host.AddMapRoute(routeOptions);
 0574    }
 575
 576    /// <summary>
 577    /// Creates a request body from the given attribute.
 578    /// </summary>
 579    /// <param name="attribute">The attribute containing request body information.</param>
 580    /// <param name="requestBody">The OpenApiRequestBody object to populate.</param>
 581    /// <param name="schema">The schema to associate with the request body.</param>
 582    /// <returns>True if the request body was created successfully; otherwise, false.</returns>
 583    private static bool CreateRequestBodyFromAttribute(KestrunAnnotation attribute, OpenApiRequestBody requestBody, IOpe
 584    {
 585        switch (attribute)
 586        {
 587            case OpenApiRequestBodyAttribute request:
 0588                requestBody.Description = request.Description;
 0589                requestBody.Required = request.Required;
 590                // Content
 0591                requestBody.Content ??= new Dictionary<string, OpenApiMediaType>(StringComparer.Ordinal);
 0592                var mediaType = new OpenApiMediaType();
 593                // Example
 0594                if (request.Example is not null)
 595                {
 0596                    mediaType.Example = ToNode(request.Example);
 597                }
 598                // Schema
 0599                mediaType.Schema = schema;
 0600                foreach (var contentType in request.ContentType)
 601                {
 0602                    requestBody.Content[contentType] = mediaType;
 603                }
 0604                return true;
 605            default:
 0606                return false;
 607        }
 608    }
 609}

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

#LineLine coverage
 1using Microsoft.OpenApi;
 2using Kestrun.Hosting.Options;
 3using Kestrun.Utilities;
 4
 5namespace Kestrun.OpenApi;
 6
 7/// <summary>
 8/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 9/// </summary>
 10public partial class OpenApiDocDescriptor
 11{
 12    /// <summary>
 13    /// Populates Document.Paths from the registered routes using OpenAPI metadata on each route.
 14    /// </summary>
 15    /// <param name="routes">The registered routes with OpenAPI metadata.</param>
 16    private void BuildPathsFromRegisteredRoutes(Dictionary<(string Pattern, HttpVerb Method), MapRouteOptions> routes)
 17    {
 018        if (routes is null || routes.Count == 0)
 19        {
 020            return;
 21        }
 022        Document.Paths = [];
 23
 024        var groups = routes
 025            .GroupBy(kvp => kvp.Key.Pattern, StringComparer.Ordinal)
 026            .Where(g => !string.IsNullOrWhiteSpace(g.Key))
 027            .Where(g => g.Any(kvp => kvp.Value?.OpenAPI?.Count > 0));
 28
 029        foreach (var grp in groups)
 30        {
 031            ProcessRouteGroup(grp);
 32        }
 033    }
 34
 35    /// <summary>
 36    /// Processes a group of routes sharing the same pattern to build the corresponding OpenAPI path item.
 37    /// </summary>
 38    /// <param name="grp">The group of routes sharing the same pattern. </param>
 39    private void ProcessRouteGroup(IGrouping<string, KeyValuePair<(string Pattern, HttpVerb Method), MapRouteOptions>> g
 40    {
 041        var pattern = grp.Key;
 042        var pathItem = GetOrCreatePathItem(pattern);
 043        OpenAPICommonMetadata? pathMeta = null;
 44
 045        foreach (var kvp in grp)
 46        {
 047            pathMeta = ProcessRouteOperation(kvp, pathItem, pathMeta);
 48        }
 49
 050        if (pathMeta is not null)
 51        {
 052            ApplyPathLevelMetadata(pathItem, pathMeta, pattern);
 53        }
 054    }
 55    /// <summary>
 56    /// Retrieves or creates an OpenApiPathItem for the specified pattern.
 57    /// </summary>
 58    /// <param name="pattern">The route pattern.</param>
 59    /// <returns>The corresponding OpenApiPathItem.</returns>
 60    private OpenApiPathItem GetOrCreatePathItem(string pattern)
 61    {
 062        Document.Paths ??= [];
 063        if (!Document.Paths.TryGetValue(pattern, out var pathInterface) || pathInterface is null)
 64        {
 065            pathInterface = new OpenApiPathItem();
 066            Document.Paths[pattern] = pathInterface;
 67        }
 068        return (OpenApiPathItem)pathInterface;
 69    }
 70
 71    /// <summary>
 72    /// Processes a single route operation and adds it to the OpenApiPathItem.
 73    /// </summary>
 74    /// <param name="kvp">The key-value pair representing the route pattern, HTTP method, and route options.</param>
 75    /// <param name="pathItem">The OpenApiPathItem to which the operation will be added.</param>
 76    /// <param name="currentPathMeta">The current path-level OpenAPI metadata.</param>
 77    /// <returns>The updated path-level OpenAPI metadata.</returns>
 78    private OpenAPICommonMetadata? ProcessRouteOperation(KeyValuePair<(string Pattern, HttpVerb Method), MapRouteOptions
 79    {
 080        var method = kvp.Key.Method;
 081        var map = kvp.Value;
 82
 083        if (map is null || map.OpenAPI.Count == 0)
 84        {
 085            return currentPathMeta;
 86        }
 87
 088        if ((map.PathLevelOpenAPIMetadata is not null) && (currentPathMeta is null))
 89        {
 090            currentPathMeta = map.PathLevelOpenAPIMetadata;
 91        }
 92
 093        if (map.OpenAPI.TryGetValue(method, out var meta) && meta.Enabled)
 94        {
 095            var op = BuildOperationFromMetadata(meta);
 096            pathItem.AddOperation(HttpMethod.Parse(method.ToMethodString()), op);
 97        }
 98
 099        return currentPathMeta;
 100    }
 101
 102    /// <summary>
 103    /// Applies path-level OpenAPI metadata to the given OpenApiPathItem.
 104    /// </summary>
 105    /// <param name="pathItem">The OpenApiPathItem to which the metadata will be applied.</param>
 106    /// <param name="pathMeta">The path-level OpenAPI metadata.</param>
 107    /// <param name="pattern">The route pattern associated with the path item.</param>
 108    private void ApplyPathLevelMetadata(OpenApiPathItem pathItem, OpenAPICommonMetadata pathMeta, string pattern)
 109    {
 0110        pathItem.Description = pathMeta.Description;
 0111        pathItem.Summary = pathMeta.Summary;
 112        try
 113        {
 0114            ApplyPathLevelServers(pathItem, pathMeta);
 0115            ApplyPathLevelParameters(pathItem, pathMeta);
 0116        }
 0117        catch (Exception ex)
 118        {
 0119            if (Host.Logger.IsEnabled(Serilog.Events.LogEventLevel.Debug))
 120            {
 0121                Host.Logger.Debug(ex, "Tolerated exception in path-level OpenAPI metadata assignment for pattern {Patter
 122            }
 0123        }
 0124    }
 125
 126    /// <summary>
 127    /// Applies server information from path-level metadata to the OpenApiPathItem.
 128    /// </summary>
 129    /// <param name="pathItem">The OpenApiPathItem to modify.</param>
 130    /// <param name="pathMeta">The path-level OpenAPI metadata containing server information.</param>
 131    private static void ApplyPathLevelServers(OpenApiPathItem pathItem, OpenAPICommonMetadata pathMeta)
 132    {
 0133        if (pathMeta.Servers is { Count: > 0 })
 134        {
 0135            dynamic dPath = pathItem;
 0136            if (dPath.Servers == null) { dPath.Servers = new List<OpenApiServer>(); }
 0137            foreach (var s in pathMeta.Servers)
 138            {
 0139                dPath.Servers.Add(s);
 140            }
 141        }
 0142    }
 143
 144    /// <summary>
 145    /// Applies parameter information from path-level metadata to the OpenApiPathItem.
 146    /// </summary>
 147    /// <param name="pathItem">The OpenApiPathItem to modify.</param>
 148    /// <param name="pathMeta">The path-level OpenAPI metadata containing parameter information.</param>
 149    private static void ApplyPathLevelParameters(OpenApiPathItem pathItem, OpenAPICommonMetadata pathMeta)
 150    {
 0151        if (pathMeta.Parameters is { Count: > 0 })
 152        {
 0153            dynamic dPath = pathItem;
 0154            if (dPath.Parameters == null) { dPath.Parameters = new List<IOpenApiParameter>(); }
 0155            foreach (var p in pathMeta.Parameters)
 156            {
 0157                dPath.Parameters.Add(p);
 158            }
 159        }
 0160    }
 161
 162    /// <summary>
 163    /// Builds an OpenApiOperation from OpenAPIMetadata.
 164    /// </summary>
 165    /// <param name="meta">The OpenAPIMetadata to build from.</param>
 166    /// <returns>The constructed OpenApiOperation.</returns>
 167    private OpenApiOperation BuildOperationFromMetadata(OpenAPIMetadata meta)
 168    {
 0169        var op = new OpenApiOperation
 0170        {
 0171            OperationId = string.IsNullOrWhiteSpace(meta.OperationId) ? null : meta.OperationId,
 0172            Summary = string.IsNullOrWhiteSpace(meta.Summary) ? null : meta.Summary,
 0173            Description = string.IsNullOrWhiteSpace(meta.Description) ? null : meta.Description,
 0174            Deprecated = meta.Deprecated,
 0175            ExternalDocs = meta.ExternalDocs,
 0176            RequestBody = meta.RequestBody,
 0177            Responses = meta.Responses ?? new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Success"
 0178        };
 179
 0180        ApplyTags(op, meta);
 0181        ApplyServers(op, meta);
 0182        ApplyParameters(op, meta);
 0183        ApplyCallbacks(op, meta);
 0184        ApplySecurity(op, meta);
 185
 0186        return op;
 187    }
 188
 189    /// <summary>
 190    /// Applies tags from metadata to the OpenApiOperation.
 191    /// </summary>
 192    /// <param name="op">The OpenApiOperation to modify.</param>
 193    /// <param name="meta">The OpenAPIMetadata containing tags.</param>
 194    private static void ApplyTags(OpenApiOperation op, OpenAPIMetadata meta)
 195    {
 0196        if (meta.Tags.Count > 0)
 197        {
 0198            op.Tags = new HashSet<OpenApiTagReference>();
 0199            foreach (var t in meta.Tags ?? [])
 200            {
 0201                _ = op.Tags.Add(new OpenApiTagReference(t));
 202            }
 203        }
 0204    }
 205
 206    /// <summary>
 207    /// Applies server information from metadata to the OpenApiOperation.
 208    /// </summary>
 209    /// <param name="op">The OpenApiOperation to modify.</param>
 210    /// <param name="meta">The OpenAPIMetadata containing server information.</param>
 211    private void ApplyServers(OpenApiOperation op, OpenAPIMetadata meta)
 212    {
 213        try
 214        {
 0215            if (meta.Servers is { Count: > 0 })
 216            {
 0217                dynamic d = op;
 0218                if (d.Servers == null) { d.Servers = new List<OpenApiServer>(); }
 0219                foreach (var s in meta.Servers) { d.Servers.Add(s); }
 220            }
 0221        }
 0222        catch (Exception ex)
 223        {
 0224            Host.Logger.Warning(ex, "Failed to set operation-level servers for OpenAPI operation {OperationId}", op.Oper
 0225        }
 0226    }
 227
 228    /// <summary>
 229    /// Applies parameter information from metadata to the OpenApiOperation.
 230    /// </summary>
 231    /// <param name="op">The OpenApiOperation to modify.</param>
 232    /// <param name="meta">The OpenAPIMetadata containing parameter information.</param>
 233    private void ApplyParameters(OpenApiOperation op, OpenAPIMetadata meta)
 234    {
 235        try
 236        {
 0237            if (meta.Parameters is { Count: > 0 })
 238            {
 0239                dynamic d = op;
 0240                if (d.Parameters == null) { d.Parameters = new List<IOpenApiParameter>(); }
 0241                foreach (var p in meta.Parameters) { d.Parameters.Add(p); }
 242            }
 0243        }
 0244        catch { Host.Logger?.Warning("Failed to set operation-level parameters for OpenAPI operation {OperationId}", op.
 0245    }
 246
 247    /// <summary>
 248    /// Applies callback information from metadata to the OpenApiOperation.
 249    /// </summary>
 250    /// <param name="op">The OpenApiOperation to modify.</param>
 251    /// <param name="meta">The OpenAPIMetadata containing callback information.</param>
 252    private static void ApplyCallbacks(OpenApiOperation op, OpenAPIMetadata meta)
 253    {
 0254        if (meta.Callbacks is not null && meta.Callbacks.Count > 0)
 255        {
 0256            op.Callbacks = new Dictionary<string, IOpenApiCallback>(meta.Callbacks);
 257        }
 0258    }
 259
 260    /// <summary>
 261    /// Applies security requirement information from metadata to the OpenApiOperation.
 262    /// </summary>
 263    /// <param name="op">The OpenApiOperation to modify.</param>
 264    /// <param name="meta">The OpenAPIMetadata containing security requirement information.</param>
 265    private void ApplySecurity(OpenApiOperation op, OpenAPIMetadata meta)
 266    {
 0267        if (meta.SecuritySchemes is not null && meta.SecuritySchemes.Count != 0)
 268        {
 0269            op.Security ??= [];
 270
 0271            var seen = new HashSet<string>(StringComparer.Ordinal);
 272
 0273            foreach (var schemeName in meta.SecuritySchemes
 0274                         .SelectMany(d => d.Keys)
 0275                         .Distinct())
 276            {
 0277                if (!seen.Add(schemeName))
 278                {
 279                    continue;
 280                }
 281                // Gather scopes for this scheme
 0282                var scopesForScheme = meta.SecuritySchemes
 0283                    .SelectMany(dict => dict)
 0284                    .Where(kv => kv.Key == schemeName)
 0285                    .SelectMany(kv => kv.Value)
 0286                    .Distinct()
 0287                    .ToList();
 288                // Build requirement
 0289                var requirement = new OpenApiSecurityRequirement
 0290                {
 0291                    [new OpenApiSecuritySchemeReference(schemeName, Document)] = scopesForScheme
 0292                };
 293
 0294                op.Security.Add(requirement);
 295            }
 296        }
 0297        else if (meta.SecuritySchemes is not null && meta.SecuritySchemes.Count == 0)
 298        {
 299            // Explicitly anonymous for this operation (overrides Document.Security)
 0300            op.Security = [];
 301        }
 0302    }
 303}

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

#LineLine coverage
 1using System.Reflection;
 2using System.Text.Json.Nodes;
 3using Microsoft.OpenApi;
 4
 5namespace Kestrun.OpenApi;
 6
 7public partial class OpenApiDocDescriptor
 8{
 9    /// <summary>
 10    /// Builds and adds the schema for a given type to the document components.
 11    /// </summary>
 12    /// <param name="t">The type to build the schema for.</param>
 13    /// <param name="built">The set of already built types to avoid recursion.</param>
 14    private void BuildSchema(Type t, HashSet<Type>? built = null)
 15    {
 416        if (Document.Components is not null && Document.Components.Schemas is not null)
 17        {
 418            if (!ComponentSchemasExists(t.Name))
 19            {
 420                Document.Components.Schemas[t.Name] = BuildSchemaForType(t, built);
 21            }
 22        }
 423    }
 24
 25    /// <summary>
 26    /// Builds the schema for a property, handling nullable types and complex types.
 27    /// </summary>
 28    /// <param name="p">The property info.</param>
 29    /// <param name="built">The set of already built types to avoid recursion.</param>
 30    /// <returns>The constructed OpenAPI schema for the property.</returns>
 31    private IOpenApiSchema BuildPropertySchema(PropertyInfo p, HashSet<Type> built)
 32    {
 2333        var pt = p.PropertyType;
 2334        var allowNull = false;
 2335        var underlying = Nullable.GetUnderlyingType(pt);
 2336        if (underlying != null)
 37        {
 038            allowNull = true;
 039            pt = underlying;
 40        }
 41
 42        // Determine schema type and build accordingly
 2343        var schema = (!IsPrimitiveLike(pt) && !pt.IsEnum && !pt.IsArray)
 2344            ? BuildComplexTypeSchema(pt, p, built)
 2345            : pt.IsEnum
 2346                ? BuildEnumSchema(pt, p)
 2347                : pt.IsArray
 2348                    ? BuildArraySchema(pt, p, built)
 2349                    : BuildPrimitiveSchema(pt, p);
 50
 51        // Apply nullable flag if needed
 2352        if (allowNull && schema is OpenApiSchema s)
 53        {
 054            s.Type |= JsonSchemaType.Null;
 55        }
 56
 2357        return schema;
 58    }
 59
 60    /// <summary>
 61    /// Builds the schema for a complex type property.
 62    /// </summary>
 63    /// <param name="pt">The property type.</param>
 64    /// <param name="p">The property info.</param>
 65    /// <param name="built">The set of already built types to avoid recursion.</param>
 66    /// <returns>The constructed OpenAPI schema for the complex type property.</returns>
 67    private OpenApiSchemaReference BuildComplexTypeSchema(Type pt, PropertyInfo p, HashSet<Type> built)
 68    {
 069        BuildSchema(pt, built); // ensure component exists
 070        var refSchema = new OpenApiSchemaReference(pt.Name);
 071        ApplySchemaAttr(p.GetCustomAttribute<OpenApiProperties>(), refSchema);
 072        return refSchema;
 73    }
 74
 75    /// <summary>
 76    /// Builds the schema for an enum property.
 77    /// </summary>
 78    /// <param name="pt">The property type.</param>
 79    /// <param name="p">The property info.</param>
 80    /// <returns>The constructed OpenAPI schema for the enum property.</returns>
 81    private static OpenApiSchema BuildEnumSchema(Type pt, PropertyInfo p)
 82    {
 083        var s = new OpenApiSchema
 084        {
 085            Type = JsonSchemaType.String,
 086            Enum = [.. pt.GetEnumNames().Select(n => (JsonNode)n)]
 087        };
 088        var attrs = p.GetCustomAttributes<OpenApiPropertyAttribute>(inherit: false).ToArray();
 089        var a = MergeSchemaAttributes(attrs);
 090        ApplySchemaAttr(a, s);
 091        PowerShellAttributes.ApplyPowerShellAttributes(p, s);
 092        return s;
 93    }
 94
 95    /// <summary>
 96    /// Builds the schema for an array property.
 97    /// </summary>
 98    /// <param name="pt">The property type.</param>
 99    /// <param name="p">The property info.</param>
 100    /// <param name="built">The set of already built types to avoid recursion.</param>
 101    /// <returns>The constructed OpenAPI schema for the array property.</returns>
 102    private OpenApiSchema BuildArraySchema(Type pt, PropertyInfo p, HashSet<Type> built)
 103    {
 0104        var item = pt.GetElementType()!;
 105        IOpenApiSchema itemSchema;
 106
 0107        if (!IsPrimitiveLike(item) && !item.IsEnum)
 108        {
 0109            BuildSchema(item, built); // ensure component exists
 0110            itemSchema = new OpenApiSchemaReference(item.Name);
 111        }
 112        else
 113        {
 0114            itemSchema = InferPrimitiveSchema(item);
 115        }
 116
 0117        var s = new OpenApiSchema
 0118        {
 0119            Type = JsonSchemaType.Array,
 0120            Items = itemSchema
 0121        };
 0122        ApplySchemaAttr(p.GetCustomAttribute<OpenApiProperties>(), s);
 0123        PowerShellAttributes.ApplyPowerShellAttributes(p, s);
 0124        return s;
 125    }
 126
 127    /// <summary>
 128    /// Builds the schema for a primitive type property.
 129    /// </summary>
 130    /// <param name="pt">The property type.</param>
 131    /// <param name="p">The property info.</param>
 132    /// <returns>The constructed OpenAPI schema for the primitive type property.</returns>
 133    private IOpenApiSchema BuildPrimitiveSchema(Type pt, PropertyInfo p)
 134    {
 23135        var prim = InferPrimitiveSchema(pt);
 23136        ApplySchemaAttr(p.GetCustomAttribute<OpenApiProperties>(), prim);
 23137        PowerShellAttributes.ApplyPowerShellAttributes(p, prim);
 23138        return prim;
 139    }
 140}

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

#LineLine coverage
 1using Microsoft.OpenApi;
 2
 3namespace Kestrun.OpenApi;
 4
 5/// <summary>
 6/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 7/// </summary>
 8public partial class OpenApiDocDescriptor
 9{
 10    /// <summary>
 11    /// Builds example components from the specified type.
 12    /// </summary>
 13    /// <param name="t">The type to build examples from.</param>
 14    private void BuildExamples(Type t)
 15    {
 16        // Ensure Examples dictionary exists
 417        Document.Components!.Examples ??= new Dictionary<string, IOpenApiExample>(StringComparer.Ordinal);
 18
 19        // class-level
 420        var classAttrs = t.GetCustomAttributes(inherit: false)
 821                          .Where(a => a.GetType().Name == nameof(OpenApiExampleComponent))
 422                          .ToArray();
 1623        foreach (var a in classAttrs)
 24        {
 425            string? customName = null;
 426            if (a is OpenApiExampleComponent oaEa)
 27            {
 428                if (!string.IsNullOrWhiteSpace(oaEa.Key))
 29                {
 430                    customName = oaEa.Key;
 31                }
 32            }
 433            var name = customName ?? t.Name;
 434            if (!Document.Components!.Examples!.ContainsKey(name))
 35            {
 436                var ex = CreateExampleFromAttribute(a);
 37
 438                var inst = Activator.CreateInstance(t);
 439                ex.Value ??= ToNode(inst);
 440                Document.Components!.Examples![name] = ex;
 41            }
 42        }
 443    }
 44
 45    /// <summary>
 46    /// Creates an OpenApiExample from the specified attribute.
 47    /// </summary>
 48    /// <param name="attr">The attribute object.</param>
 49    /// <returns>The created OpenApiExample.</returns>
 50    private static OpenApiExample CreateExampleFromAttribute(object attr)
 51    {
 452        var t = attr.GetType();
 453        var summary = t.GetProperty("Summary")?.GetValue(attr) as string;
 454        var description = t.GetProperty("Description")?.GetValue(attr) as string;
 455        var value = t.GetProperty("Value")?.GetValue(attr);
 456        var external = t.GetProperty("ExternalValue")?.GetValue(attr) as string;
 57
 458        var ex = new OpenApiExample
 459        {
 460            Summary = summary,
 461            Description = description
 462        };
 63
 464        if (value is not null)
 65        {
 066            ex.Value = ToNode(value);
 67        }
 68
 469        if (!string.IsNullOrWhiteSpace(external))
 70        {
 071            ex.ExternalValue = external;
 72        }
 73
 474        return ex;
 75    }
 76
 77    /// <summary>
 78    /// Applies an OpenApiExampleRefAttribute to the specified OpenApiHeader.
 79    /// </summary>
 80    /// <param name="exRef">The OpenApiExampleRefAttribute to apply.</param>
 81    /// <param name="header">The OpenApiHeader to which the example reference will be applied.</param>
 82    /// <returns>True if the example reference was successfully applied; otherwise, false.</returns>
 83    /// <exception cref="InvalidOperationException"></exception>
 84    private static bool ApplyExampleRefAttribute(OpenApiExampleRefAttribute exRef, OpenApiHeader header)
 85    {
 186        header.Examples ??= new Dictionary<string, IOpenApiExample>(StringComparer.Ordinal);
 187        if (header.Examples.ContainsKey(exRef.Key))
 88        {
 089            throw new InvalidOperationException($"Header already contains an example with the key '{exRef.Key}'.");
 90        }
 191        header.Examples[exRef.Key] = new OpenApiExampleReference(exRef.ReferenceId);
 192        return true;
 93    }
 94
 95    /// <summary>
 96    /// Applies an OpenApiExampleAttribute to the specified OpenApiHeader.
 97    /// </summary>
 98    /// <param name="ex">The OpenApiExampleAttribute to apply.</param>
 99    /// <param name="header">The OpenApiHeader to which the example will be applied.</param>
 100    /// <returns>True if the example was successfully applied; otherwise, false.</returns>
 101    /// <exception cref="InvalidOperationException">Thrown if the example key is null or already exists in the header.</
 102    private static bool ApplyInlineExampleAttribute(OpenApiExampleAttribute ex, OpenApiHeader header)
 103    {
 1104        if (ex.Key is null)
 105        {
 0106            throw new InvalidOperationException("OpenApiExampleAttribute requires a non-null Name property.");
 107        }
 1108        header.Examples ??= new Dictionary<string, IOpenApiExample>(StringComparer.Ordinal);
 1109        if (header.Examples.ContainsKey(ex.Key))
 110        {
 0111            throw new InvalidOperationException($"Header already contains an example with the key '{ex.Key}'.");
 112        }
 1113        header.Examples[ex.Key] = new OpenApiExample
 1114        {
 1115            Summary = ex.Summary,
 1116            Description = ex.Description,
 1117            Value = ToNode(ex.Value),
 1118            ExternalValue = ex.ExternalValue
 1119        };
 1120        return true;
 121    }
 122}

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

#LineLine coverage
 1using System.Reflection;
 2using Microsoft.OpenApi;
 3
 4namespace Kestrun.OpenApi;
 5
 6/// <summary>
 7/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 8/// </summary>
 9public partial class OpenApiDocDescriptor
 10{
 11    /// <summary>
 12    /// Builds header components from the specified type.
 13    /// </summary>
 14    /// <param name="t">The type to build headers from.</param>
 15    /// <exception cref="InvalidOperationException">Thrown when the type has multiple [OpenApiHeaderComponent] attribute
 16    private void BuildHeaders(Type t)
 17    {
 118        var (defaultDescription, joinClassName) = GetHeaderComponentDefaults(t);
 119        Document.Components!.Headers ??= new Dictionary<string, IOpenApiHeader>(StringComparer.Ordinal);
 20        const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
 21
 422        foreach (var property in t.GetProperties(flags))
 23        {
 124            var attrs = GetHeaderAttributes(property);
 125            if (attrs.Length == 0)
 26            {
 27                continue;
 28            }
 29
 130            var (header, customName) = BuildHeader(attrs);
 131            ApplyDefaultDescription(header, defaultDescription);
 32
 133            var name = string.IsNullOrWhiteSpace(customName) ? property.Name : customName!;
 134            var key = BuildHeaderKey(joinClassName, name);
 135            Document.Components!.Headers![key] = header;
 36        }
 137    }
 38
 39    /// <summary>
 40    /// Gets the default description and join class name from the OpenApiHeaderComponent attribute on the specified type
 41    /// </summary>
 42    /// <param name="t">The type to inspect for the OpenApiHeaderComponent attribute.</param>
 43    /// <returns>A tuple containing the default description and join class name.</returns>
 44    private static (string? defaultDescription, string? joinClassName) GetHeaderComponentDefaults(Type t)
 45    {
 146        var componentAttr = t.GetCustomAttributes(inherit: false)
 147            .OfType<OpenApiHeaderComponent>()
 148            .FirstOrDefault();
 49
 150        var defaultDescription = componentAttr?.Description;
 151        var joinClassName = componentAttr?.JoinClassName is { Length: > 0 } joinSuffix
 152            ? t.FullName + joinSuffix
 153            : null;
 54
 155        return (defaultDescription, joinClassName);
 56    }
 57
 58    /// <summary>
 59    /// Gets the header-related attributes from the specified property.
 60    /// </summary>
 61    /// <param name="property">The property to inspect for header-related attributes.</param>
 62    /// <returns>An array of header-related attributes found on the property.</returns>
 63    private static object[] GetHeaderAttributes(PropertyInfo property)
 64    {
 165        return
 166        [
 167            .. property
 168                .GetCustomAttributes(inherit: false)
 369                .Where(a => a is OpenApiHeaderAttribute or OpenApiExampleRefAttribute or OpenApiExampleAttribute)
 170        ];
 71    }
 72    /// <summary>
 73    /// Builds an OpenApiHeader from the specified attributes.
 74    /// </summary>
 75    /// <param name="attributes">An array of attributes to build the header from.</param>
 76    /// <returns></returns>
 77    private static (OpenApiHeader header, string? customName) BuildHeader(object[] attributes)
 78    {
 179        var header = new OpenApiHeader();
 180        string? customName = null;
 81
 882        foreach (var attribute in attributes)
 83        {
 384            if (attribute is OpenApiHeaderAttribute headerAttr && !string.IsNullOrWhiteSpace(headerAttr.Key))
 85            {
 186                customName = headerAttr.Key;
 87            }
 88
 389            _ = CreateHeaderFromAttribute(attribute, header);
 90        }
 91
 192        return (header, customName);
 93    }
 94
 95    /// <summary>
 96    /// Applies the default description to the OpenApiHeader if it does not already have a description.
 97    /// </summary>
 98    /// <param name="header">The OpenApiHeader to apply the default description to.</param>
 99    /// <param name="defaultDescription">The default description to apply if the header's description is null.</param>
 100    private static void ApplyDefaultDescription(OpenApiHeader header, string? defaultDescription)
 101    {
 1102        if (header.Description is null && defaultDescription is not null)
 103        {
 0104            header.Description = defaultDescription;
 105        }
 1106    }
 107
 108    /// <summary>
 109    /// Builds the header key using the join class name and the header name.
 110    /// </summary>
 111    /// <param name="joinClassName">The join class name to prepend to the header name.</param>
 112    /// <param name="name">The header name.</param>
 113    /// <returns>The combined header key.</returns>
 114    private static string BuildHeaderKey(string? joinClassName, string name) =>
 1115        joinClassName is not null ? $"{joinClassName}{name}" : name;
 116
 117    /// <summary>
 118    /// Creates an OpenApiHeader from the specified supported attribute types.
 119    /// </summary>
 120    /// <param name="attr">Attribute instance.</param>
 121    /// <param name="header">Target header to populate.</param>
 122    /// <returns>True when the attribute type was recognized and applied; otherwise false.</returns>
 123    private static bool CreateHeaderFromAttribute(object attr, OpenApiHeader header)
 124    {
 3125        return attr switch
 3126        {
 1127            OpenApiHeaderAttribute h => ApplyHeaderAttribute(h, header),
 1128            OpenApiExampleRefAttribute exRef => ApplyExampleRefAttribute(exRef, header),
 1129            OpenApiExampleAttribute ex => ApplyInlineExampleAttribute(ex, header),
 3130
 0131            _ => false
 3132        };
 133    }
 134
 135    private static bool ApplyHeaderAttribute(OpenApiHeaderAttribute attribute, OpenApiHeader header)
 136    {
 1137        header.Description = attribute.Description;
 1138        header.Required = attribute.Required;
 1139        header.Deprecated = attribute.Deprecated;
 1140        header.AllowEmptyValue = attribute.AllowEmptyValue;
 1141        header.Schema = string.IsNullOrWhiteSpace(attribute.SchemaRef)
 1142            ? new OpenApiSchema { Type = JsonSchemaType.String }
 1143            : new OpenApiSchemaReference(attribute.SchemaRef);
 1144        header.Style = attribute.Style.ToOpenApi();
 1145        header.AllowReserved = attribute.AllowReserved;
 1146        header.Explode = attribute.Explode;
 1147        if (attribute.Example is not null)
 148        {
 1149            header.Example = ToNode(attribute.Example);
 150        }
 1151        return true;
 152    }
 153}

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

#LineLine coverage
 1using Microsoft.OpenApi;
 2
 3namespace Kestrun.OpenApi;
 4
 5/// <summary>
 6/// Helper methods for accessing OpenAPI document components.
 7/// </summary>
 8public partial class OpenApiDocDescriptor
 9{
 10    private OpenApiSchema GetSchema(string id)
 11    {
 012        ArgumentException.ThrowIfNullOrWhiteSpace(id);
 13        // Look up schema in components
 014        return Document.Components?.Schemas is { } schemas
 015               && schemas.TryGetValue(id, out var p)
 016               && p is OpenApiSchema op
 017            ? op
 018            : throw new InvalidOperationException($"Schema '{id}' not found.");
 19    }
 20
 21    private OpenApiParameter GetParameter(string id)
 22    {
 023        ArgumentException.ThrowIfNullOrWhiteSpace(id);
 24        // Look up parameter in components
 025        return Document.Components?.Parameters is { } parameters
 026               && parameters.TryGetValue(id, out var p)
 027               && p is OpenApiParameter op
 028            ? op
 029            : throw new InvalidOperationException($"Parameter '{id}' not found.");
 30    }
 31
 32    private OpenApiRequestBody GetRequestBody(string id)
 33    {
 034        ArgumentException.ThrowIfNullOrWhiteSpace(id);
 35        // Look up request body in components
 036        return Document.Components?.RequestBodies is { } requestBodies
 037               && requestBodies.TryGetValue(id, out var p)
 038               && p is OpenApiRequestBody op
 039            ? op
 040            : throw new InvalidOperationException($"RequestBody '{id}' not found.");
 41    }
 42
 43    private OpenApiHeader GetHeader(string id)
 44    {
 045        ArgumentException.ThrowIfNullOrWhiteSpace(id);
 46        // Look up header in components
 047        return Document.Components?.Headers is { } headers
 048               && headers.TryGetValue(id, out var p)
 049               && p is OpenApiHeader op
 050            ? op
 051            : throw new InvalidOperationException($"Header '{id}' not found.");
 52    }
 53
 54    private OpenApiResponse GetResponse(string id)
 55    {
 056        ArgumentException.ThrowIfNullOrWhiteSpace(id);
 57        // Look up response in components
 058        return Document.Components?.Responses is { } responses
 059               && responses.TryGetValue(id, out var p)
 060               && p is OpenApiResponse op
 061            ? op
 062            : throw new InvalidOperationException($"Response '{id}' not found.");
 63    }
 64
 65    private bool ComponentSchemasExists(string id) =>
 466        Document.Components?.Schemas?.ContainsKey(id) == true;
 67
 68    private bool ComponentRequestBodiesExists(string id) =>
 069        Document.Components?.RequestBodies?.ContainsKey(id) == true;
 70
 71    private bool ComponentResponsesExists(string id) =>
 072        Document.Components?.Responses?.ContainsKey(id) == true;
 73
 74    private bool ComponentParametersExists(string id) =>
 075        Document.Components?.Parameters?.ContainsKey(id) == true;
 76
 77    private bool ComponentExamplesExists(string id) =>
 078        Document.Components?.Examples?.ContainsKey(id) == true;
 79
 80    private bool ComponentHeadersExists(string id) =>
 081        Document.Components?.Headers?.ContainsKey(id) == true;
 82    private bool ComponentCallbacksExists(string id) =>
 083        Document.Components?.Callbacks?.ContainsKey(id) == true;
 84
 85    private bool ComponentLinksExists(string id) =>
 086        Document.Components?.Links?.ContainsKey(id) == true;
 87    private bool ComponentPathItemsExists(string id) =>
 088        Document.Components?.PathItems?.ContainsKey(id) == true;
 89}

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

#LineLine coverage
 1using System.Reflection;
 2using System.Text.Json.Nodes;
 3using Microsoft.OpenApi;
 4
 5namespace Kestrun.OpenApi;
 6
 7/// <summary>
 8/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 9/// </summary>
 10public partial class OpenApiDocDescriptor
 11{
 12    /// <summary>
 13    /// Builds OpenAPI parameters from a given type's properties.
 14    /// </summary>
 15    /// <param name="t">The type to build parameters from.</param>
 16    /// <exception cref="InvalidOperationException">Thrown when the type has multiple [OpenApiResponseComponent] attribu
 17    private void BuildParameters(Type t)
 18    {
 019        Document.Components!.Parameters ??= new Dictionary<string, IOpenApiParameter>(StringComparer.Ordinal);
 20
 021        var (defaultDescription, joinClassName) = GetClassLevelMetadata(t);
 22        const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
 23
 024        foreach (var p in t.GetProperties(flags))
 25        {
 026            ProcessPropertyForParameter(p, t, defaultDescription, joinClassName);
 27        }
 028    }
 29
 30    /// <summary>
 31    /// Retrieves class-level OpenAPI metadata from the given type.
 32    /// </summary>
 33    /// <param name="t">The type to retrieve metadata from.</param>
 34    /// <returns>A tuple containing the description and join class name, if any.</returns>
 35    private static (string? Description, string? JoinClassName) GetClassLevelMetadata(Type t)
 36    {
 037        string? description = null;
 038        string? joinClassName = null;
 39
 040        var classAttrs = t.GetCustomAttributes(inherit: false)
 041            .Where(a => a.GetType().Name == nameof(OpenApiParameterComponent))
 042            .Cast<object>()
 043            .ToArray();
 44
 045        if (classAttrs.Length > 1)
 46        {
 047            throw new InvalidOperationException($"Type '{t.FullName}' has multiple [OpenApiParameterComponent] attribute
 48        }
 49
 050        if (classAttrs.Length == 1 && classAttrs[0] is OpenApiParameterComponent attr)
 51        {
 052            if (!string.IsNullOrEmpty(attr.Description))
 53            {
 054                description = attr.Description;
 55            }
 056            if (!string.IsNullOrEmpty(attr.JoinClassName))
 57            {
 058                joinClassName = t.FullName + attr.JoinClassName;
 59            }
 60        }
 61
 062        return (description, joinClassName);
 63    }
 64
 65    /// <summary>
 66    /// Processes a property to create and register an OpenAPI parameter if applicable.
 67    /// </summary>
 68    /// <param name="p">The PropertyInfo representing the property.</param>
 69    /// <param name="t">The type that declares the property.</param>
 70    /// <param name="defaultDescription">A default description to apply if the parameter's description is not set.</para
 71    /// <param name="joinClassName">An optional string to join with the class name for unique key generation.</param>
 72    private void ProcessPropertyForParameter(PropertyInfo p, Type t, string? defaultDescription, string? joinClassName)
 73    {
 074        var parameter = new OpenApiParameter();
 075        var attrs = GetParameterAttributes(p);
 76
 077        if (attrs.Length == 0)
 78        {
 079            return;
 80        }
 81
 082        var (hasResponseDef, customName) = ApplyParameterAttributes(parameter, attrs);
 83
 084        if (hasResponseDef)
 85        {
 086            FinalizeAndRegisterParameter(parameter, p, t, customName, defaultDescription, joinClassName);
 87        }
 088    }
 89
 90    /// <summary>
 91    /// Retrieves parameter-related attributes from a property.
 92    /// </summary>
 93    /// <param name="p">The PropertyInfo representing the property.</param>
 94    /// <returns>An array of KestrunAnnotation attributes related to parameters.</returns>
 95    private static KestrunAnnotation[] GetParameterAttributes(PropertyInfo p)
 96    {
 097        return
 098        [
 099            .. p.GetCustomAttributes(inherit: false)
 0100             .Where(a => a.GetType().Name is
 0101                 nameof(OpenApiParameterAttribute) or
 0102                 nameof(OpenApiPropertyAttribute) or
 0103                 nameof(OpenApiExampleRefAttribute)
 0104             )
 0105             .Cast<KestrunAnnotation>()
 0106        ];
 107    }
 108
 109    /// <summary>
 110    /// Applies parameter-related attributes to the given OpenApiParameter.
 111    /// </summary>
 112    /// <param name="parameter">The OpenApiParameter to apply attributes to.</param>
 113    /// <param name="attrs">An array of KestrunAnnotation attributes to apply.</param>
 114    /// <returns>A tuple indicating if a response definition was found and a custom name, if any.</returns>
 115    private (bool HasResponseDef, string CustomName) ApplyParameterAttributes(OpenApiParameter parameter, KestrunAnnotat
 116    {
 0117        var hasResponseDef = false;
 0118        var customName = string.Empty;
 119
 0120        foreach (var a in attrs)
 121        {
 0122            if (a is OpenApiParameterAttribute oaRa && !string.IsNullOrWhiteSpace(oaRa.Key))
 123            {
 0124                customName = oaRa.Key;
 125            }
 126
 0127            if (CreateParameterFromAttribute(a, parameter))
 128            {
 0129                hasResponseDef = true;
 130            }
 131        }
 0132        return (hasResponseDef, customName);
 133    }
 134
 135    /// <summary>
 136    /// Finalizes and registers the OpenAPI parameter in the document components.
 137    /// </summary>
 138    /// <param name="parameter">The OpenApiParameter to finalize and register.</param>
 139    /// <param name="p">The PropertyInfo representing the property.</param>
 140    /// <param name="t">The type that declares the property.</param>
 141    /// <param name="customName">A custom name for the parameter, if specified.</param>
 142    /// <param name="defaultDescription">A default description to apply if the parameter's description is not set.</para
 143    /// <param name="joinClassName">An optional string to join with the class name for unique key generation.</param>
 144    private void FinalizeAndRegisterParameter(OpenApiParameter parameter, PropertyInfo p, Type t, string customName, str
 145    {
 0146        var tname = string.IsNullOrWhiteSpace(customName) ? p.Name : customName;
 0147        var key = joinClassName is not null ? $"{joinClassName}{tname}" : tname;
 148
 0149        if (string.IsNullOrWhiteSpace(parameter.Name))
 150        {
 0151            parameter.Name = tname;
 152        }
 0153        if (parameter.Description is null && defaultDescription is not null)
 154        {
 0155            parameter.Description = defaultDescription;
 156        }
 157
 0158        Document.Components!.Parameters![key] = parameter;
 159
 0160        var schemaAttr = (OpenApiPropertyAttribute?)p.GetCustomAttributes(inherit: false)
 0161                          .LastOrDefault(a => a.GetType().Name == nameof(OpenApiPropertyAttribute));
 162
 0163        parameter.Schema = CreatePropertySchema(schemaAttr, t, p);
 0164    }
 165
 166    /// <summary>
 167    /// Creates an OpenAPI schema for a property based on its type and any associated OpenApiPropertyAttribute.
 168    /// </summary>
 169    /// <param name="schemaAttr">The OpenApiPropertyAttribute associated with the property, if any.</param>
 170    /// <param name="t">The type that declares the property.</param>
 171    /// <param name="p">The PropertyInfo representing the property.</param>
 172    /// <returns>An IOpenApiSchema representing the property's schema.</returns>
 173    private IOpenApiSchema CreatePropertySchema(OpenApiPropertyAttribute? schemaAttr, Type t, PropertyInfo p)
 174    {
 0175        var pt = p.PropertyType;
 0176        var allowNull = false;
 0177        var underlying = Nullable.GetUnderlyingType(pt);
 0178        if (underlying != null)
 179        {
 0180            allowNull = true;
 0181            pt = underlying;
 182        }
 183        // enums first
 0184        if (pt.IsEnum)
 185        {
 0186            return CreateEnumSchema(pt, schemaAttr, allowNull);
 187        }
 188        // check for array after enum to handle enum arrays
 0189        if (pt.IsArray)
 190        {
 0191            return CreateArraySchema(pt, p, schemaAttr, allowNull);
 192        }
 193        // complex types
 0194        if (!IsPrimitiveLike(pt))
 195        {
 0196            return CreateComplexSchema(pt, schemaAttr);
 197        }
 198        // primitive types
 0199        return CreatePrimitiveSchema(pt, t, p, schemaAttr, allowNull);
 200    }
 201
 202    /// <summary>
 203    /// Creates an OpenAPI schema for an enum property.
 204    /// </summary>
 205    /// <param name="pt">The enum type of the property</param>
 206    /// <param name="schemaAttr">Optional OpenApiPropertyAttribute for the property</param>
 207    /// <param name="allowNull">Indicates if the property allows null values</param>
 208    /// <returns>An IOpenApiSchema representing the enum property</returns>
 209    private static IOpenApiSchema CreateEnumSchema(Type pt, OpenApiPropertyAttribute? schemaAttr, bool allowNull)
 210    {
 0211        var s = new OpenApiSchema
 0212        {
 0213            Type = JsonSchemaType.String,
 0214            Enum = [.. pt.GetEnumNames().Select(n => (JsonNode)n)]
 0215        };
 0216        ApplySchemaAttr(schemaAttr, s);
 0217        if (allowNull)
 218        {
 0219            s.Type |= JsonSchemaType.Null;
 220        }
 0221        return s;
 222    }
 223
 224    /// <summary>
 225    /// Creates an OpenAPI schema for an array property.
 226    /// </summary>
 227    /// <param name="pt">The array type of the property</param>
 228    /// <param name="p">The PropertyInfo representing the property</param>
 229    /// <param name="schemaAttr">Optional OpenApiPropertyAttribute for the property</param>
 230    /// <param name="allowNull">Indicates if the property allows null values</param>
 231    /// <returns>An IOpenApiSchema representing the array property</returns>
 232    private IOpenApiSchema CreateArraySchema(Type pt, PropertyInfo p, OpenApiPropertyAttribute? schemaAttr, bool allowNu
 233    {
 0234        var elem = pt.GetElementType()!;
 235        IOpenApiSchema itemSchema;
 0236        if (!IsPrimitiveLike(elem) && !elem.IsEnum)
 237        {
 238            // ensure a component schema exists for the complex element and $ref it
 0239            EnsureSchemaComponent(elem);
 0240            itemSchema = new OpenApiSchemaReference(elem.Name);
 241        }
 242        else
 243        {
 0244            itemSchema = elem.IsEnum
 0245                ? new OpenApiSchema
 0246                {
 0247                    Type = JsonSchemaType.String,
 0248                    Enum = [.. elem.GetEnumNames().Select(n => (JsonNode)n)]
 0249                }
 0250                : InferPrimitiveSchema(elem);
 251        }
 252
 0253        var s = new OpenApiSchema { Type = JsonSchemaType.Array, Items = itemSchema };
 0254        ApplySchemaAttr(schemaAttr, s);
 0255        PowerShellAttributes.ApplyPowerShellAttributes(p, s);
 0256        if (allowNull)
 257        {
 0258            s.Type |= JsonSchemaType.Null;
 259        }
 0260        return s;
 261    }
 262
 263    /// <summary>
 264    /// Creates an OpenAPI schema for a complex property.
 265    /// </summary>
 266    /// <param name="pt">The complex type of the property</param>
 267    /// <param name="schemaAttr">Optional OpenApiPropertyAttribute for the property</param>
 268    /// <returns>An IOpenApiSchema representing the complex property</returns>
 269    private IOpenApiSchema CreateComplexSchema(Type pt, OpenApiPropertyAttribute? schemaAttr)
 270    {
 0271        EnsureSchemaComponent(pt);
 0272        var r = new OpenApiSchemaReference(pt.Name);
 0273        ApplySchemaAttr(schemaAttr, r);
 0274        return r;
 275    }
 276
 277    /// <summary>
 278    /// Creates an OpenAPI schema for a primitive property.
 279    /// </summary>
 280    /// <param name="pt">The primitive type of the property</param>
 281    /// <param name="t">The containing type</param>
 282    /// <param name="p">The PropertyInfo representing the property</param>
 283    /// <param name="schemaAttr">Optional OpenApiPropertyAttribute for the property</param>
 284    /// <param name="allowNull">Indicates if the property allows null values</param>
 285    /// <returns>An IOpenApiSchema representing the primitive property</returns>
 286    private IOpenApiSchema CreatePrimitiveSchema(Type pt, Type t, PropertyInfo p, OpenApiPropertyAttribute? schemaAttr, 
 287    {
 0288        var s = InferPrimitiveSchema(pt);
 0289        ApplySchemaAttr(schemaAttr, s);
 0290        PowerShellAttributes.ApplyPowerShellAttributes(p, s);
 291        // If no explicit default provided via schema attribute, try to pull default from property value
 0292        if (s is OpenApiSchema sc && sc.Default is null)
 293        {
 0294            TryApplyDefaultValue(t, p, sc);
 295
 0296            if (allowNull)
 297            {
 0298                sc.Type |= JsonSchemaType.Null;
 299            }
 300        }
 0301        return s;
 302    }
 303
 304    /// <summary>
 305    /// Tries to apply the default value of a property to the given OpenApiSchema.
 306    /// </summary>
 307    /// <param name="t">Type containing the property</param>
 308    /// <param name="p">PropertyInfo of the property</param>
 309    /// <param name="sc">OpenApiSchema to apply the default value to</param>
 310    private static void TryApplyDefaultValue(Type t, PropertyInfo p, OpenApiSchema sc)
 311    {
 312        try
 313        {
 0314            var inst = Activator.CreateInstance(t);
 0315            if (inst != null)
 316            {
 0317                var val = p.GetValue(inst);
 0318                if (!IsIntrinsicDefault(val, p.PropertyType))
 319                {
 0320                    sc.Default = ToNode(val);
 321                }
 322            }
 0323        }
 0324        catch { }
 0325    }
 326
 327    /// <summary>
 328    /// Creates an OpenAPI parameter from a given attribute.
 329    /// </summary>
 330    /// <param name="attr">The attribute to create the parameter from</param>
 331    /// <param name="parameter">The OpenApiParameter object to populate</param>
 332    /// <returns>True if the parameter was created successfully, otherwise false</returns>
 333    /// <exception cref="InvalidOperationException">Thrown when an example reference cannot be embedded due to missing o
 334    private bool CreateParameterFromAttribute(KestrunAnnotation attr, OpenApiParameter parameter)
 335    {
 336        switch (attr)
 337        {
 338            case OpenApiParameterAttribute param:
 0339                ApplyParameterAttribute(param, parameter);
 0340                break;
 341
 342            case OpenApiExampleRefAttribute exRef:
 0343                ApplyExampleRefAttribute(exRef, parameter);
 0344                break;
 345
 346            default:
 0347                return false; // unrecognized attribute type
 348        }
 0349        return true;
 350    }
 351
 352    /// <summary>
 353    /// Applies an OpenApiParameterAttribute to an OpenApiParameter.
 354    /// </summary>
 355    /// <param name="param">The OpenApiParameterAttribute to apply</param>
 356    /// <param name="parameter">The OpenApiParameter to modify</param>
 357    private static void ApplyParameterAttribute(OpenApiParameterAttribute param, OpenApiParameter parameter)
 358    {
 0359        parameter.Description = param.Description;
 0360        parameter.Name = string.IsNullOrEmpty(param.Name) ? param.Key : param.Name;
 0361        parameter.Required = param.Required;
 0362        parameter.Deprecated = param.Deprecated;
 0363        parameter.AllowEmptyValue = param.AllowEmptyValue;
 0364        if (param.Explode)
 365        {
 0366            parameter.Explode = param.Explode;
 367        }
 0368        parameter.AllowReserved = param.AllowReserved;
 0369        if (!string.IsNullOrEmpty(param.In))
 370        {
 0371            parameter.In = param.In.ToOpenApiParameterLocation();
 0372            if (parameter.In == ParameterLocation.Path)
 373            {
 0374                parameter.Required = true; // path parameters must be required
 375            }
 376        }
 377
 0378        if (param.Style is not null)
 379        {
 0380            parameter.Style = param.Style.ToParameterStyle();
 381        }
 0382        if (param.Example is not null)
 383        {
 0384            parameter.Example = ToNode(param.Example);
 385        }
 0386    }
 387
 388    /// <summary>
 389    /// Applies an example reference attribute to an OpenAPI parameter.
 390    /// </summary>
 391    /// <param name="exRef">The OpenApiExampleRefAttribute to apply</param>
 392    /// <param name="parameter">The OpenApiParameter to modify</param>
 393    private void ApplyExampleRefAttribute(OpenApiExampleRefAttribute exRef, OpenApiParameter parameter)
 394    {
 0395        parameter.Examples ??= new Dictionary<string, IOpenApiExample>(StringComparer.Ordinal);
 0396        if (exRef.Inline)
 397        {
 0398            if (Document.Components?.Examples == null || !Document.Components.Examples.TryGetValue(exRef.ReferenceId, ou
 399            {
 0400                throw new InvalidOperationException($"Example reference '{exRef.ReferenceId}' cannot be embedded because
 401            }
 0402            if (value is not OpenApiExample example)
 403            {
 0404                throw new InvalidOperationException($"Example reference '{exRef.ReferenceId}' cannot be embedded because
 405            }
 0406            parameter.Examples[exRef.Key] = example.Clone();
 407        }
 408        else
 409        {
 0410            parameter.Examples[exRef.Key] = new OpenApiExampleReference(exRef.ReferenceId);
 411        }
 0412    }
 413}

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

#LineLine coverage
 1using System.Reflection;
 2using Microsoft.OpenApi;
 3
 4namespace Kestrun.OpenApi;
 5
 6/// <summary>
 7/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 8/// </summary>
 9public partial class OpenApiDocDescriptor
 10{
 11    /// <summary>
 12    /// Builds request body components from the specified type.
 13    /// </summary>
 14    /// <param name="t">The type to build request bodies for.</param>
 15    private void BuildRequestBodies(Type t)
 16    {
 217        Document.Components!.RequestBodies ??= new Dictionary<string, IOpenApiRequestBody>(StringComparer.Ordinal);
 218        var componentSchema = BuildSchemaForType(t);
 219        var requestBody = new OpenApiRequestBody();
 20        // Apply request body component attribute if present
 221        var name = ApplyRequestBodyComponent(t.GetCustomAttribute<OpenApiRequestBodyComponent>(), requestBody, component
 22
 23        // Apply example references if any (handle multiple attributes)
 224        var exampleRefs = t.GetCustomAttributes<OpenApiExampleRefAttribute>();
 825        foreach (var exRef in exampleRefs)
 26        {
 227            ApplyRequestBodyExampleRef(exRef, requestBody);
 28        }
 29
 230        if (string.IsNullOrWhiteSpace(name))
 31        {
 032            name = t.Name; // fallback to type name if no explicit key was provided
 33        }
 34        // Register the request body component
 235        _ = Document.Components!.RequestBodies.TryAdd(name, requestBody);
 236    }
 37
 38    /// <summary>
 39    /// Applies the request body component attribute to the request body.
 40    /// </summary>
 41    /// <param name="bodyAttribute"> The request body component attribute to apply.</param>
 42    /// <param name="requestBody"> The request body to apply the attribute to.</param>
 43    /// <param name="schema"> The schema to associate with the request body.</param>
 44    /// <returns>The name of the request body component if explicitly specified; otherwise, null.</returns>
 45    private static string? ApplyRequestBodyComponent(OpenApiRequestBodyComponent? bodyAttribute, OpenApiRequestBody requ
 46    {
 247        if (bodyAttribute is null)
 48        {
 049            return null;
 50        }
 251        var name = string.Empty;
 252        var explicitKey = GetKeyOverride(bodyAttribute);
 253        if (!string.IsNullOrWhiteSpace(explicitKey))
 54        {
 255            name = explicitKey;
 56        }
 57
 258        if (bodyAttribute.Description is not null)
 59        {
 260            requestBody.Description = bodyAttribute.Description;
 61        }
 262        requestBody.Required |= bodyAttribute.IsRequired;
 263        requestBody.Content ??= new Dictionary<string, OpenApiMediaType>(StringComparer.Ordinal);
 64
 265        var mediaType = new OpenApiMediaType { Schema = schema };
 266        if (bodyAttribute.Example is not null)
 67        {
 168            mediaType.Example = ToNode(bodyAttribute.Example);
 69        }
 70
 871        foreach (var ct in bodyAttribute.ContentType)
 72        {
 273            requestBody.Content[ct] = mediaType;
 74        }
 75
 276        return name;
 77    }
 78
 79    /// <summary>
 80    /// Applies example references to the request body.
 81    /// </summary>
 82    /// <param name="exRef">The example reference attribute to apply.</param>
 83    /// <param name="requestBody">The request body to apply the example references to.</param>
 84    private void ApplyRequestBodyExampleRef(OpenApiExampleRefAttribute? exRef, OpenApiRequestBody requestBody)
 85    {
 286        if (exRef is null)
 87        {
 088            return;
 89        }
 290        requestBody.Content ??= new Dictionary<string, OpenApiMediaType>(StringComparer.Ordinal);
 291        var targets = ResolveExampleContentTypes(exRef, requestBody);
 892        foreach (var ct in targets)
 93        {
 294            var mediaType = requestBody.Content.TryGetValue(ct, out var existing)
 295                ? existing
 296                : (requestBody.Content[ct] = new OpenApiMediaType());
 97
 298            mediaType.Examples ??= new Dictionary<string, IOpenApiExample>(StringComparer.Ordinal);
 299            mediaType.Examples[exRef.Key] = exRef.Inline
 2100                ? CloneExampleOrThrow(exRef.ReferenceId)
 2101                : new OpenApiExampleReference(exRef.ReferenceId);
 102        }
 2103    }
 104    /// <summary>
 105    /// Resolves the content types to apply example references to.
 106    /// </summary>
 107    /// <param name="exRef">The example reference attribute.</param>
 108    /// <param name="requestBody">The request body.</param>
 109    /// <returns>The list of content types to apply the example references to.</returns>
 110    private static IEnumerable<string> ResolveExampleContentTypes(OpenApiExampleRefAttribute exRef, OpenApiRequestBody r
 111    {
 2112        var keys = exRef.ContentType is null ? (requestBody.Content?.Keys ?? Array.Empty<string>()) : [exRef.ContentType
 2113        return keys.Count == 0 ? ["application/json"] : (IEnumerable<string>)keys;
 114    }
 115
 116    /// <summary>
 117    /// Clones an example from components or throws if not found.
 118    /// </summary>
 119    /// <param name="referenceId"> The reference ID of the example to clone.</param>
 120    /// <returns>The cloned example.</returns>
 121    /// <exception cref="InvalidOperationException">Thrown if the example reference cannot be found or is not an OpenApi
 122#pragma warning disable CA1859 // Use concrete types when possible for improved performance
 123    private IOpenApiExample CloneExampleOrThrow(string referenceId)
 124#pragma warning restore CA1859 // Use concrete types when possible for improved performance
 125    {
 1126        return Document.Components?.Examples == null || !Document.Components.Examples.TryGetValue(referenceId, out var v
 1127            ? throw new InvalidOperationException($"Example reference '{referenceId}' cannot be embedded because it was 
 1128            : value is not OpenApiExample example
 1129            ? throw new InvalidOperationException($"Example reference '{referenceId}' cannot be embedded because it is n
 1130            : (IOpenApiExample)example.Clone();
 131    }
 132}

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

#LineLine coverage
 1using System.Reflection;
 2using System.Text.Json.Nodes;
 3using Microsoft.OpenApi;
 4
 5namespace Kestrun.OpenApi;
 6
 7/// <summary>
 8/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 9/// </summary>
 10public partial class OpenApiDocDescriptor
 11{
 12    /// <summary>
 13    /// Builds response components from the specified type.
 14    /// </summary>
 15    /// <param name="t">The type to build responses for.</param>
 16    private void BuildResponses(Type t)
 17    {
 18        // Ensure Responses dictionary exists
 119        Document.Components!.Responses ??= new Dictionary<string, IOpenApiResponse>(StringComparer.Ordinal);
 20
 121        var (defaultDescription, joinClassName) = GetClassLevelResponseMetadata(t);
 22        const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
 23
 624        foreach (var p in t.GetProperties(flags))
 25        {
 226            ProcessPropertyForResponse(p, defaultDescription, joinClassName);
 27        }
 128    }
 29
 30    private static (string? Description, string? JoinClassName) GetClassLevelResponseMetadata(Type t)
 31    {
 132        string? description = null;
 133        string? joinClassName = null;
 34
 135        var classAttrs = t.GetCustomAttributes(inherit: false)
 236            .Where(a => a.GetType().Name == nameof(OpenApiResponseComponent))
 137            .Cast<object>()
 138            .ToArray();
 39
 140        if (classAttrs.Length > 1)
 41        {
 042            throw new InvalidOperationException($"Type '{t.FullName}' has multiple [OpenApiResponseComponent] attributes
 43        }
 44
 145        if (classAttrs.Length == 1 && classAttrs[0] is OpenApiResponseComponent attr)
 46        {
 147            if (!string.IsNullOrEmpty(attr.Description))
 48            {
 149                description = attr.Description;
 50            }
 151            if (!string.IsNullOrEmpty(attr.JoinClassName))
 52            {
 053                joinClassName = t.FullName + attr.JoinClassName;
 54            }
 55        }
 56
 157        return (description, joinClassName);
 58    }
 59
 60    private void ProcessPropertyForResponse(PropertyInfo p, string? defaultDescription, string? joinClassName)
 61    {
 262        var attrs = GetPropertyResponseAttributes(p);
 263        if (attrs.Length == 0)
 64        {
 065            return;
 66        }
 67
 268        var response = new OpenApiResponse();
 269        var (hasResponseDef, customName) = ApplyPropertyAttributesToResponse(p, attrs, response);
 70
 271        if (hasResponseDef)
 72        {
 273            RegisterResponse(response, p, customName, defaultDescription, joinClassName);
 74        }
 275    }
 76
 77    private static object[] GetPropertyResponseAttributes(PropertyInfo p)
 78    {
 279        return [.. p.GetCustomAttributes(inherit: false)
 480             .Where(a => a.GetType().Name is
 481                 nameof(OpenApiResponseAttribute) or
 482                 nameof(OpenApiLinkRefAttribute) or
 483                 nameof(OpenApiHeaderRefAttribute) or
 484                 nameof(OpenApiExampleRefAttribute)
 285             )
 286             .Cast<object>()];
 87    }
 88
 89    private (bool HasResponseDef, string CustomName) ApplyPropertyAttributesToResponse(PropertyInfo p, object[] attrs, O
 90    {
 291        var hasResponseDef = false;
 292        var customName = string.Empty;
 93
 1294        foreach (var a in attrs)
 95        {
 496            if (a is OpenApiResponseAttribute oaRa && !string.IsNullOrWhiteSpace(oaRa.Key))
 97            {
 098                customName = oaRa.Key;
 99            }
 100
 4101            var schema = GetAttributeValue(p);
 4102            if (CreateResponseFromAttribute(a, response, schema))
 103            {
 4104                hasResponseDef = true;
 105            }
 106        }
 2107        return (hasResponseDef, customName);
 108    }
 109
 110    private void RegisterResponse(OpenApiResponse response, PropertyInfo p, string customName, string? defaultDescriptio
 111    {
 2112        var tname = string.IsNullOrWhiteSpace(customName) ? p.Name : customName;
 2113        var key = joinClassName is not null ? $"{joinClassName}{tname}" : tname;
 114
 2115        if (response.Description is null && defaultDescription is not null)
 116        {
 0117            response.Description = defaultDescription;
 118        }
 119
 2120        Document.Components!.Responses![key] = response;
 2121    }
 122
 123    /// <summary>
 124    /// Gets the OpenAPI schema for a property based on its attributes and type.
 125    /// </summary>
 126    /// <param name="p"> The property info to get the schema for.</param>
 127    /// <returns> The OpenAPI schema for the property.</returns>
 128    private IOpenApiSchema GetAttributeValue(PropertyInfo p)
 129    {
 4130        var pt = p.PropertyType;
 4131        var allowNull = false;
 4132        var underlying = Nullable.GetUnderlyingType(pt);
 4133        if (underlying != null)
 134        {
 0135            allowNull = true;
 0136            pt = underlying;
 137        }
 138        // enum type
 4139        if (pt.IsEnum)
 140        {
 0141            return GetEnumSchema(p, pt, allowNull);
 142        }
 143        // array type
 4144        if (pt.IsArray)
 145        {
 0146            return GetArraySchema(p, pt, allowNull);
 147        }
 148        // complex type
 4149        if (!IsPrimitiveLike(pt))
 150        {
 0151            return GetComplexSchema(pt);
 152        }
 153        // primitive type
 4154        return GetPrimitiveSchema(p, pt, allowNull);
 155    }
 156
 157    /// <summary>
 158    /// Creates an OpenAPI schema for an enum property.
 159    /// </summary>
 160    /// <param name="p"> The property info.</param>
 161    /// <param name="pt"> The property type.</param>
 162    /// <param name="allowNull"> Indicates if null is allowed.</param>
 163    /// <returns> The OpenAPI schema.</returns>
 164    private static IOpenApiSchema GetEnumSchema(PropertyInfo p, Type pt, bool allowNull)
 165    {
 0166        var schema = new OpenApiSchema
 0167        {
 0168            Type = JsonSchemaType.String,
 0169            Enum = [.. pt.GetEnumNames().Select(n => (JsonNode)n)]
 0170        };
 0171        var propAttrs = p.GetCustomAttributes<OpenApiPropertyAttribute>(inherit: false).ToArray();
 0172        var a = MergeSchemaAttributes(propAttrs);
 0173        ApplySchemaAttr(a, schema);
 0174        PowerShellAttributes.ApplyPowerShellAttributes(p, schema);
 0175        if (allowNull)
 176        {
 0177            schema.Type |= JsonSchemaType.Null;
 178        }
 0179        return schema;
 180    }
 181
 182    private IOpenApiSchema GetArraySchema(PropertyInfo p, Type pt, bool allowNull)
 183    {
 0184        var item = pt.GetElementType()!;
 185        IOpenApiSchema itemSchema;
 186
 0187        if (!IsPrimitiveLike(item) && !item.IsEnum)
 188        {
 189            // then reference it
 0190            itemSchema = new OpenApiSchemaReference(item.Name);
 191        }
 192        else
 193        {
 0194            itemSchema = InferPrimitiveSchema(item);
 195        }
 0196        var schema = new OpenApiSchema
 0197        {
 0198            // then build the array schema
 0199            Type = JsonSchemaType.Array,
 0200            Items = itemSchema
 0201        };
 0202        ApplySchemaAttr(p.GetCustomAttribute<OpenApiPropertyAttribute>(), schema);
 0203        PowerShellAttributes.ApplyPowerShellAttributes(p, schema);
 0204        if (allowNull)
 205        {
 0206            schema.Type |= JsonSchemaType.Null;
 207        }
 0208        return schema;
 209    }
 210
 211    private IOpenApiSchema GetComplexSchema(Type pt)
 212    {
 0213        EnsureSchemaComponent(pt);
 0214        return new OpenApiSchemaReference(pt.Name);
 215    }
 216
 217    private IOpenApiSchema GetPrimitiveSchema(PropertyInfo p, Type pt, bool allowNull)
 218    {
 4219        var sc = InferPrimitiveSchema(pt);
 4220        if (sc is OpenApiSchema schema)
 221        {
 4222            ApplySchemaAttr(p.GetCustomAttribute<OpenApiPropertyAttribute>(), schema);
 4223            PowerShellAttributes.ApplyPowerShellAttributes(p, schema);
 4224            if (allowNull)
 225            {
 0226                schema.Type |= JsonSchemaType.Null;
 227            }
 4228            return schema;
 229        }
 0230        return sc;
 231    }
 232
 233    /// <summary>
 234    /// Gets the name override from an attribute, if present.
 235    /// </summary>
 236    /// <param name="attr">The attribute to inspect.</param>
 237    /// <returns>The name override, if present; otherwise, null.</returns>
 238    private static string? GetKeyOverride(object attr)
 239    {
 2240        var t = attr.GetType();
 2241        return t.GetProperty("Key")?.GetValue(attr) as string;
 242    }
 243
 244    /// <summary>
 245    /// Creates or modifies an OpenApiResponse based on the provided attribute.
 246    /// </summary>
 247    /// <param name="attr"> The attribute to apply.</param>
 248    /// <param name="response"> The OpenApiResponse to modify.</param>
 249    /// <param name="iSchema"> An optional schema to apply.</param>
 250    /// <returns>True if the response was modified; otherwise, false.</returns>
 251    private bool CreateResponseFromAttribute(object attr, OpenApiResponse response, IOpenApiSchema? iSchema = null)
 252    {
 4253        ArgumentNullException.ThrowIfNull(attr);
 4254        ArgumentNullException.ThrowIfNull(response);
 255
 4256        return attr switch
 4257        {
 2258            OpenApiResponseAttribute resp => ApplyResponseAttribute(resp, response, iSchema),
 0259            OpenApiHeaderRefAttribute href => ApplyHeaderRefAttribute(href, response),
 0260            OpenApiLinkRefAttribute lref => ApplyLinkRefAttribute(lref, response),
 2261            OpenApiExampleRefAttribute exRef => ApplyExampleRefAttribute(exRef, response),
 0262            _ => false
 4263        };
 264    }
 265    // --- local helpers -------------------------------------------------------
 266
 267    /// <summary>
 268    /// Applies an OpenApiResponseAttribute to an OpenApiResponse.
 269    /// </summary>
 270    /// <param name="resp">The OpenApiResponseAttribute to apply.</param>
 271    /// <param name="response">The OpenApiResponse to modify.</param>
 272    /// <param name="schema">An optional schema to apply.</param>
 273    /// <returns>True if the response was modified; otherwise, false.</returns>
 274    private bool ApplyResponseAttribute(OpenApiResponseAttribute resp, OpenApiResponse response, IOpenApiSchema? schema)
 275    {
 2276        ApplyDescription(resp, response);
 2277        schema = ResolveResponseSchema(resp, schema);
 2278        ApplySchemaToContentTypes(resp, response, schema);
 2279        return true;
 280    }
 281
 282    private static void ApplyDescription(OpenApiResponseAttribute resp, OpenApiResponse response)
 283    {
 2284        if (!string.IsNullOrEmpty(resp.Description))
 285        {
 2286            response.Description = resp.Description;
 287        }
 2288    }
 289
 290    private IOpenApiSchema? ResolveResponseSchema(OpenApiResponseAttribute resp, IOpenApiSchema? propertySchema)
 291    {
 292        // 1) Type-based schema
 2293        if (resp.Schema is not null)
 294        {
 0295            return InferPrimitiveSchema(resp.Schema, inline: resp.Inline);
 296        }
 297
 298        // 2) Explicit Component reference
 2299        if (resp.SchemaRef is not null)
 300        {
 2301            return ResolveSchemaRef(resp.SchemaRef, resp.Inline);
 302        }
 303
 304        // 3) Fallback to property schema reference if available
 0305        if (propertySchema is OpenApiSchemaReference refSchema && refSchema.Reference.Id is not null)
 306        {
 0307            return ResolveSchemaRef(refSchema.Reference.Id, resp.Inline);
 308        }
 309
 310        // 4) Fallback to existing property schema (primitive/concrete)
 0311        return propertySchema;
 312    }
 313
 314    private IOpenApiSchema ResolveSchemaRef(string refId, bool inline)
 315    {
 2316        return inline
 2317            ? CloneSchemaOrThrow(refId)
 2318            : new OpenApiSchemaReference(refId);
 319    }
 320
 321    private void ApplySchemaToContentTypes(OpenApiResponseAttribute resp, OpenApiResponse response, IOpenApiSchema? sche
 322    {
 2323        if (schema is not null && resp.ContentType is { Length: > 0 })
 324        {
 8325            foreach (var ct in resp.ContentType)
 326            {
 2327                var media = GetOrAddMediaType(response, ct);
 2328                media.Schema = schema;
 329            }
 330        }
 2331    }
 332
 333    /// <summary>
 334    /// Applies a header reference attribute to an OpenAPI response.
 335    /// </summary>
 336    /// <param name="href">The header reference attribute.</param>
 337    /// <param name="response">The OpenAPI response to modify.</param>
 338    /// <returns>True if the header reference was applied; otherwise, false.</returns>
 339    private static bool ApplyHeaderRefAttribute(OpenApiHeaderRefAttribute href, OpenApiResponse response)
 340    {
 0341        (response.Headers ??= new Dictionary<string, IOpenApiHeader>(StringComparer.Ordinal))[href.Key] = new OpenApiHea
 0342        return true;
 343    }
 344
 345    private static bool ApplyLinkRefAttribute(OpenApiLinkRefAttribute lref, OpenApiResponse response)
 346    {
 0347        (response.Links ??= new Dictionary<string, IOpenApiLink>(StringComparer.Ordinal))[lref.Key] = new OpenApiLinkRef
 0348        return true;
 349    }
 350
 351    private bool ApplyExampleRefAttribute(OpenApiExampleRefAttribute exRef, OpenApiResponse response)
 352    {
 2353        var targets = exRef.ContentType is null
 2354            ? (IEnumerable<string>)(response.Content?.Keys ?? Array.Empty<string>())
 2355            : [exRef.ContentType];
 356
 2357        if (!targets.Any())
 358        {
 0359            targets = ["application/json"];
 360        }
 361
 8362        foreach (var ct in targets)
 363        {
 2364            var media = GetOrAddMediaType(response, ct);
 2365            media.Examples ??= new Dictionary<string, IOpenApiExample>(StringComparer.Ordinal);
 2366            if (exRef.Inline)
 367            {
 1368                if (Document.Components?.Examples == null || !Document.Components.Examples.TryGetValue(exRef.ReferenceId
 369                {
 0370                    throw new InvalidOperationException($"Example reference '{exRef.ReferenceId}' cannot be embedded bec
 371                }
 1372                if (value is not OpenApiExample example)
 373                {
 0374                    throw new InvalidOperationException($"Example reference '{exRef.ReferenceId}' cannot be embedded bec
 375                }
 1376                media.Examples[exRef.Key] = example.Clone();
 377            }
 378            else
 379            {
 1380                media.Examples[exRef.Key] = new OpenApiExampleReference(exRef.ReferenceId);
 381            }
 382        }
 2383        return true;
 384    }
 385
 386    private OpenApiMediaType GetOrAddMediaType(OpenApiResponse resp, string contentType)
 387    {
 4388        resp.Content ??= new Dictionary<string, OpenApiMediaType>(StringComparer.Ordinal);
 4389        if (!resp.Content.TryGetValue(contentType, out var media))
 390        {
 2391            media = resp.Content[contentType] = new OpenApiMediaType();
 392        }
 393
 4394        return media;
 395    }
 396
 397    private OpenApiSchema CloneSchemaOrThrow(string refId)
 398    {
 1399        if (Document.Components?.Schemas is { } schemas &&
 1400            schemas.TryGetValue(refId, out var schema))
 401        {
 402            // your existing clone semantics
 1403            return (OpenApiSchema)schema.Clone();
 404        }
 405
 0406        throw new InvalidOperationException(
 0407            $"Schema reference '{refId}' cannot be embedded because it was not found in components.");
 408    }
 409}

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

#LineLine coverage
 1using System.Reflection;
 2using System.Text.Json.Nodes;
 3using Microsoft.OpenApi;
 4using Kestrun.Runtime;
 5
 6namespace Kestrun.OpenApi;
 7
 8/// <summary>
 9/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 10/// </summary>
 11public partial class OpenApiDocDescriptor
 12{
 13    #region Schemas
 14    private static OpenApiPropertyAttribute? GetSchemaIdentity(Type t)
 15    {
 16        // inherit:true already climbs the chain until it finds the first one
 017        var attrs = (OpenApiPropertyAttribute[])t.GetCustomAttributes(typeof(OpenApiPropertyAttribute), inherit: true);
 018        return attrs.Length > 0 ? attrs[0] : null;
 19    }
 20
 21    /// <summary>
 22    /// Builds and returns the schema for a given type.
 23    /// </summary>
 24    /// <param name="t">Type to build schema for</param>
 25    /// <param name="built">Set of types already built to avoid recursion</param>
 26    /// <returns>OpenApiSchema representing the type</returns>
 27    private IOpenApiSchema BuildSchemaForType(Type t, HashSet<Type>? built = null)
 28    {
 2129        built ??= [];
 30
 31        // Handle custom base type derivations first
 2132        if (t.BaseType is not null && t.BaseType != typeof(object))
 33        {
 634            var baseTypeSchema = BuildBaseTypeSchema(t);
 635            if (baseTypeSchema is not null)
 36            {
 637                return baseTypeSchema;
 38            }
 39        }
 40
 1541        var schema = new OpenApiSchema
 1542        {
 1543            Type = JsonSchemaType.Object,
 1544            Properties = new Dictionary<string, IOpenApiSchema>(StringComparer.Ordinal)
 1545        };
 46
 47        // Prevent infinite recursion
 1548        if (built.Contains(t))
 49        {
 150            return schema;
 51        }
 52
 1453        _ = built.Add(t);
 54
 55        // Handle enum types
 1456        if (t.IsEnum)
 57        {
 058            RegisterEnumSchema(t);
 059            return schema;
 60        }
 61
 62        // Early return for primitive types
 1463        if (IsPrimitiveLike(t))
 64        {
 265            return schema;
 66        }
 67
 68        // Apply type-level attributes
 1269        ApplyTypeAttributes(t, schema);
 70
 71        // Process properties with default value capture
 1272        ProcessTypeProperties(t, schema, built);
 73
 1274        return schema;
 75    }
 76
 77    /// <summary>
 78    /// Builds schema for custom base type derivations.
 79    /// </summary>
 80    ///  <param name="t">Type to build schema for</param>
 81    /// <returns>OpenApiSchema representing the base type derivation, or null if not applicable</returns>
 82    private IOpenApiSchema? BuildBaseTypeSchema(Type t)
 83    {
 84        // Determine if the base type is a known OpenAPI primitive type
 685        OaSchemaType? baseTypeName = t.BaseType switch
 686        {
 687            Type bt when bt == typeof(OaString) => OaSchemaType.String,
 688            Type bt when bt == typeof(OaInteger) => OaSchemaType.Integer,
 689            Type bt when bt == typeof(OaNumber) => OaSchemaType.Number,
 690            Type bt when bt == typeof(OaBoolean) => OaSchemaType.Boolean,
 691            _ => null
 692        };
 93
 694        if (typeof(IOpenApiType).IsAssignableFrom(t))
 95        {
 096            return BuildOpenApiTypeSchema(t);
 97        }
 98        // Fallback to custom base type schema building
 699        return BuildCustomBaseTypeSchema(t, baseTypeName);
 100    }
 101
 102    /// <summary>
 103    /// Builds schema for types implementing IOpenApiType.
 104    /// </summary>
 105    private static OpenApiSchema? BuildOpenApiTypeSchema(Type t)
 106    {
 0107        var attr = GetSchemaIdentity(t);
 0108        return attr is not null
 0109            ? new OpenApiSchema
 0110            {
 0111                Type = attr.Type.ToJsonSchemaType(),
 0112                Format = attr.Format
 0113            }
 0114            : null;
 115    }
 116
 117    /// <summary>
 118    /// Builds schema for types with custom base types.
 119    /// </summary>
 120    private IOpenApiSchema BuildCustomBaseTypeSchema(Type t, OaSchemaType? baseTypeName)
 121    {
 6122        IOpenApiSchema baseSchema = baseTypeName is not null
 6123            ? new OpenApiSchema { Type = baseTypeName?.ToJsonSchemaType() }
 6124            : new OpenApiSchemaReference(t.BaseType!.Name);
 125
 6126        var schemaComps = t.GetCustomAttributes<OpenApiProperties>()
 0127            .Where(schemaComp => schemaComp is not null)
 6128            .Cast<OpenApiProperties>();
 129
 12130        foreach (var prop in schemaComps)
 131        {
 0132            return BuildPropertyFromAttribute(prop, baseSchema);
 133        }
 134
 6135        return baseSchema;
 0136    }
 137
 138    /// <summary>
 139    /// Builds a property schema from an OpenApiProperties attribute.
 140    /// </summary>
 141    private static IOpenApiSchema BuildPropertyFromAttribute(OpenApiProperties prop, IOpenApiSchema baseSchema)
 142    {
 0143        var schema = prop.Array
 0144            ? new OpenApiSchema { Type = JsonSchemaType.Array, Items = baseSchema }
 0145            : baseSchema;
 146
 0147        ApplySchemaAttr(prop, schema);
 0148        return schema;
 149    }
 150
 151    /// <summary>
 152    /// Registers an enum type schema in the document components.
 153    /// </summary>
 154    private void RegisterEnumSchema(Type enumType)
 155    {
 0156        if (Document.Components?.Schemas is not null)
 157        {
 0158            Document.Components.Schemas[enumType.Name] = new OpenApiSchema
 0159            {
 0160                Type = JsonSchemaType.String,
 0161                Enum = [.. enumType.GetEnumNames().Select(n => (JsonNode)n)]
 0162            };
 163        }
 0164    }
 165
 166    /// <summary>
 167    /// Applies type-level attributes to a schema.
 168    /// </summary>
 169    private static void ApplyTypeAttributes(Type t, OpenApiSchema schema)
 170    {
 32171        foreach (var attr in t.GetCustomAttributes(true)
 38172          .Where(a => a is OpenApiPropertyAttribute or OpenApiSchemaComponent))
 173        {
 4174            ApplySchemaAttr(attr as OpenApiProperties, schema);
 175
 4176            if (attr is OpenApiSchemaComponent schemaAttribute && schemaAttribute.Examples is not null)
 177            {
 0178                schema.Examples ??= [];
 0179                var node = ToNode(schemaAttribute.Examples);
 0180                if (node is not null)
 181                {
 0182                    schema.Examples.Add(node);
 183                }
 184            }
 185        }
 12186    }
 187
 188    /// <summary>
 189    /// Processes all properties of a type and builds their schemas.
 190    /// </summary>
 191    private void ProcessTypeProperties(Type t, OpenApiSchema schema, HashSet<Type> built)
 192    {
 12193        var instance = TryCreateTypeInstance(t);
 194
 70195        foreach (var prop in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
 196        {
 23197            var propSchema = BuildPropertySchema(prop, built);
 23198            CapturePropertyDefault(instance, prop, propSchema);
 199
 23200            if (prop.GetCustomAttribute<OpenApiAdditionalPropertiesAttribute>() is not null)
 201            {
 1202                schema.AdditionalPropertiesAllowed = true;
 1203                schema.AdditionalProperties = propSchema;
 204            }
 205            else
 206            {
 22207                schema.Properties?.Add(prop.Name, propSchema);
 208            }
 209        }
 12210    }
 211
 212    /// <summary>
 213    /// Attempts to create an instance of a type to capture default values.
 214    /// </summary>
 215    private static object? TryCreateTypeInstance(Type t)
 216    {
 217        try
 218        {
 12219            return Activator.CreateInstance(t);
 220        }
 1221        catch
 222        {
 1223            return null;
 224        }
 12225    }
 226
 227    /// <summary>
 228    /// Captures the default value of a property if not already set.
 229    /// </summary>
 230    private static void CapturePropertyDefault(object? instance, PropertyInfo prop, IOpenApiSchema propSchema)
 231    {
 23232        if (instance is null || propSchema is not OpenApiSchema concrete || concrete.Default is not null)
 233        {
 1234            return;
 235        }
 236
 237        try
 238        {
 22239            var value = prop.GetValue(instance);
 22240            if (!IsIntrinsicDefault(value, prop.PropertyType))
 241            {
 20242                concrete.Default = ToNode(value);
 243            }
 22244        }
 0245        catch
 246        {
 247            // Ignore failures when capturing defaults
 0248        }
 22249    }
 250
 251    /// <summary>
 252    /// Determines if a value is the intrinsic default for its declared type.
 253    /// </summary>
 254    /// <param name="value">The value to check.</param>
 255    /// <param name="declaredType">The declared type of the value.</param>
 256    /// <returns>True if the value is the intrinsic default for its declared type; otherwise, false.</returns>
 257    private static bool IsIntrinsicDefault(object? value, Type declaredType)
 258    {
 22259        if (value is null)
 260        {
 1261            return true;
 262        }
 263
 264        // Unwrap Nullable<T>
 21265        var t = Nullable.GetUnderlyingType(declaredType) ?? declaredType;
 266
 267        // Reference types: null is the only intrinsic default
 21268        if (!t.IsValueType)
 269        {
 12270            return false;
 271        }
 272
 273        // Special-cases for common structs
 9274        if (t == typeof(Guid))
 275        {
 0276            return value.Equals(Guid.Empty);
 277        }
 278
 9279        if (t == typeof(TimeSpan))
 280        {
 0281            return value.Equals(TimeSpan.Zero);
 282        }
 283
 9284        if (t == typeof(DateTime))
 285        {
 1286            return value.Equals(default(DateTime));
 287        }
 288
 8289        if (t == typeof(DateTimeOffset))
 290        {
 0291            return value.Equals(default(DateTimeOffset));
 292        }
 293
 294        // Enums: 0 is intrinsic default
 8295        if (t.IsEnum)
 296        {
 0297            return Convert.ToInt64(value) == 0;
 298        }
 299
 300        // Primitive/value types: compare to default(T)
 8301        var def = Activator.CreateInstance(t);
 8302        return value.Equals(def);
 303    }
 304
 305    /// <summary>
 306    /// Merges multiple OpenApiPropertyAttribute instances into one.
 307    /// </summary>
 308    /// <param name="attrs">An array of OpenApiPropertyAttribute instances to merge.</param>
 309    /// <returns>A single OpenApiPropertyAttribute instance representing the merged attributes.</returns>
 310    private static OpenApiPropertyAttribute? MergeSchemaAttributes(OpenApiPropertyAttribute[] attrs)
 311    {
 0312        if (attrs == null || attrs.Length == 0)
 313        {
 0314            return null;
 315        }
 316
 0317        if (attrs.Length == 1)
 318        {
 0319            return attrs[0];
 320        }
 321
 0322        var m = new OpenApiPropertyAttribute();
 323
 0324        foreach (var a in attrs)
 325        {
 0326            MergeStringProperties(m, a);
 0327            MergeEnumAndCollections(m, a);
 0328            MergeNumericProperties(m, a);
 0329            MergeBooleanProperties(m, a);
 0330            MergeTypeAndRequired(m, a);
 0331            MergeCustomFields(m, a);
 332        }
 333
 0334        return m;
 335    }
 336
 337    /// <summary>
 338    /// Merges string properties where the last non-empty value wins.
 339    /// </summary>
 340    /// <param name="merged">The merged OpenApiPropertyAttribute to update.</param>
 341    /// <param name="attr">The OpenApiPropertyAttribute to merge from.</param>
 342    private static void MergeStringProperties(OpenApiPropertyAttribute merged, OpenApiPropertyAttribute attr)
 343    {
 0344        if (!string.IsNullOrWhiteSpace(attr.Title))
 345        {
 0346            merged.Title = attr.Title;
 347        }
 348
 0349        if (!string.IsNullOrWhiteSpace(attr.Description))
 350        {
 0351            merged.Description = attr.Description;
 352        }
 353
 0354        if (!string.IsNullOrWhiteSpace(attr.Format))
 355        {
 0356            merged.Format = attr.Format;
 357        }
 358
 0359        if (!string.IsNullOrWhiteSpace(attr.Pattern))
 360        {
 0361            merged.Pattern = attr.Pattern;
 362        }
 363
 0364        if (!string.IsNullOrWhiteSpace(attr.Maximum))
 365        {
 0366            merged.Maximum = attr.Maximum;
 367        }
 368
 0369        if (!string.IsNullOrWhiteSpace(attr.Minimum))
 370        {
 0371            merged.Minimum = attr.Minimum;
 372        }
 0373    }
 374
 375    /// <summary>
 376    /// Merges enum and collection properties.
 377    /// </summary>
 378    /// <param name="merged">The merged OpenApiPropertyAttribute to update.</param>
 379    /// <param name="attr">The OpenApiPropertyAttribute to merge from.</param>
 380    private static void MergeEnumAndCollections(OpenApiPropertyAttribute merged, OpenApiPropertyAttribute attr)
 381    {
 0382        if (attr.Enum is { Length: > 0 })
 383        {
 0384            merged.Enum = [.. merged.Enum ?? [], .. attr.Enum];
 385        }
 386
 0387        if (attr.Default is not null)
 388        {
 0389            merged.Default = attr.Default;
 390        }
 391
 0392        if (attr.Example is not null)
 393        {
 0394            merged.Example = attr.Example;
 395        }
 0396    }
 397
 398    /// <summary>
 399    /// Merges numeric properties where values >= 0 are considered explicitly set.
 400    /// </summary>
 401    /// <param name="merged">The merged OpenApiPropertyAttribute to update.</param>
 402    /// <param name="attr">The OpenApiPropertyAttribute to merge from.</param>
 403    private static void MergeNumericProperties(OpenApiPropertyAttribute merged, OpenApiPropertyAttribute attr)
 404    {
 0405        if (attr.MaxLength >= 0)
 406        {
 0407            merged.MaxLength = attr.MaxLength;
 408        }
 409
 0410        if (attr.MinLength >= 0)
 411        {
 0412            merged.MinLength = attr.MinLength;
 413        }
 414
 0415        if (attr.MaxItems >= 0)
 416        {
 0417            merged.MaxItems = attr.MaxItems;
 418        }
 419
 0420        if (attr.MinItems >= 0)
 421        {
 0422            merged.MinItems = attr.MinItems;
 423        }
 424
 0425        if (attr.MultipleOf is not null)
 426        {
 0427            merged.MultipleOf = attr.MultipleOf;
 428        }
 0429    }
 430
 431    /// <summary>
 432    /// Merges boolean properties using OR logic.
 433    /// </summary>
 434    /// <param name="merged">The merged OpenApiPropertyAttribute to update.</param>
 435    /// <param name="attr">The OpenApiPropertyAttribute to merge from.</param>
 436    private static void MergeBooleanProperties(OpenApiPropertyAttribute merged, OpenApiPropertyAttribute attr)
 437    {
 0438        merged.Nullable |= attr.Nullable;
 0439        merged.ReadOnly |= attr.ReadOnly;
 0440        merged.WriteOnly |= attr.WriteOnly;
 0441        merged.Deprecated |= attr.Deprecated;
 0442        merged.UniqueItems |= attr.UniqueItems;
 0443        merged.ExclusiveMaximum |= attr.ExclusiveMaximum;
 0444        merged.ExclusiveMinimum |= attr.ExclusiveMinimum;
 0445    }
 446
 447    /// <summary>
 448    /// Merges type and required properties.
 449    /// </summary>
 450    /// <param name="merged">The merged OpenApiPropertyAttribute to update.</param>
 451    /// <param name="attr">The OpenApiPropertyAttribute to merge from.</param>
 452    private static void MergeTypeAndRequired(OpenApiPropertyAttribute merged, OpenApiPropertyAttribute attr)
 453    {
 0454        if (attr.Type != OaSchemaType.None)
 455        {
 0456            merged.Type = attr.Type;
 457        }
 458
 0459        if (attr.Required is { Length: > 0 })
 460        {
 0461            merged.Required = [.. (merged.Required ?? []).Concat(attr.Required).Distinct()];
 462        }
 0463    }
 464
 465    /// <summary>
 466    /// Merges custom fields like XmlName.
 467    /// </summary>
 468    /// <param name="merged">The merged OpenApiPropertyAttribute to update.</param>
 469    /// <param name="attr">The OpenApiPropertyAttribute to merge from.</param>
 470    private static void MergeCustomFields(OpenApiPropertyAttribute merged, OpenApiPropertyAttribute attr)
 471    {
 0472        if (!string.IsNullOrWhiteSpace(attr.XmlName))
 473        {
 0474            merged.XmlName = attr.XmlName;
 475        }
 0476    }
 477
 478    /// <summary>
 479    /// Infers a primitive OpenApiSchema from a .NET type.
 480    /// </summary>
 481    /// <param name="type">The .NET type to infer from.</param>
 482    /// <param name="inline">Indicates if the schema should be inlined.</param>
 483    /// <returns>The inferred OpenApiSchema.</returns>
 484    private IOpenApiSchema InferPrimitiveSchema(Type type, bool inline = false)
 485    {
 486        // Direct type mappings
 27487        if (PrimitiveSchemaMap.TryGetValue(type, out var schemaFactory))
 488        {
 27489            return schemaFactory();
 490        }
 491
 492        // Array type handling
 0493        if (type.Name.EndsWith("[]"))
 494        {
 0495            return InferArraySchema(type, inline);
 496        }
 497
 498        // Special handling for PowerShell OpenAPI classes
 0499        if (PowerShellOpenApiClassExporter.ValidClassNames.Contains(type.Name))
 500        {
 0501            return InferPowerShellClassSchema(type, inline);
 502        }
 503
 504        // Fallback
 0505        return new OpenApiSchema { Type = JsonSchemaType.String };
 506    }
 507
 508    /// <summary>
 509    /// Infers an array OpenApiSchema from a .NET array type.
 510    /// </summary>
 511    /// <param name="type">The .NET array type to infer from.</param>
 512    /// <param name="inline">Indicates if the schema should be inlined.</param>
 513    /// <returns>The inferred OpenApiSchema.</returns>
 514    private OpenApiSchema InferArraySchema(Type type, bool inline)
 515    {
 0516        var typeName = type.Name[..^2];
 0517        if (ComponentSchemasExists(typeName))
 518        {
 0519            IOpenApiSchema? items = inline ? GetSchema(typeName).Clone() : new OpenApiSchemaReference(typeName);
 0520            return new OpenApiSchema { Type = JsonSchemaType.Array, Items = items };
 521        }
 522
 0523        return new OpenApiSchema { Type = JsonSchemaType.Array, Items = InferPrimitiveSchema(type.GetElementType() ?? ty
 524    }
 525
 526    /// <summary>
 527    /// Infers a PowerShell OpenAPI class schema.
 528    /// </summary>
 529    /// <param name="type">The .NET type representing the PowerShell OpenAPI class.</param>
 530    /// <param name="inline">Indicates if the schema should be inlined.</param>
 531    /// <returns>The inferred OpenApiSchema.</returns>
 532    private IOpenApiSchema InferPowerShellClassSchema(Type type, bool inline)
 533    {
 0534        var schema = GetSchema(type.Name);
 535
 0536        if (inline)
 537        {
 0538            if (schema is OpenApiSchema concreteSchema)
 539            {
 0540                return concreteSchema.Clone();
 541            }
 542        }
 543        else
 544        {
 0545            if (schema is not null)
 546            {
 0547                return new OpenApiSchemaReference(type.Name);
 548            }
 549        }
 550
 0551        Host.Logger.Warning("Schema for PowerShell OpenAPI class '{typeName}' not found. Defaulting to string schema.", 
 0552        return new OpenApiSchema { Type = JsonSchemaType.String };
 553    }
 554
 555    /// <summary>
 556    /// Mapping of .NET primitive types to OpenAPI schema definitions.
 557    /// </summary>
 558    /// <remarks>
 559    /// This dictionary maps common .NET primitive types to their corresponding OpenAPI schema representations.
 560    /// Each entry consists of a .NET type as the key and a function that returns an OpenApiSchema as the value.
 561    /// </remarks>
 1562    private static readonly Dictionary<Type, Func<OpenApiSchema>> PrimitiveSchemaMap = new()
 1563    {
 14564        [typeof(string)] = () => new OpenApiSchema { Type = JsonSchemaType.String },
 1565        [typeof(bool)] = () => new OpenApiSchema { Type = JsonSchemaType.Boolean },
 0566        [typeof(long)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64" },
 1567        [typeof(DateTime)] = () => new OpenApiSchema { Type = JsonSchemaType.String, Format = "date-time" },
 0568        [typeof(DateTimeOffset)] = () => new OpenApiSchema { Type = JsonSchemaType.String, Format = "date-time" },
 0569        [typeof(TimeSpan)] = () => new OpenApiSchema { Type = JsonSchemaType.String, Format = "duration" },
 0570        [typeof(byte[])] = () => new OpenApiSchema { Type = JsonSchemaType.String, Format = "binary" },
 0571        [typeof(Uri)] = () => new OpenApiSchema { Type = JsonSchemaType.String, Format = "uri" },
 0572        [typeof(Guid)] = () => new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid" },
 4573        [typeof(object)] = () => new OpenApiSchema { Type = JsonSchemaType.Object },
 0574        [typeof(void)] = () => new OpenApiSchema { Type = JsonSchemaType.Null },
 0575        [typeof(char)] = () => new OpenApiSchema { Type = JsonSchemaType.String, MaxLength = 1, MinLength = 1 },
 0576        [typeof(sbyte)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
 0577        [typeof(byte)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
 0578        [typeof(short)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
 0579        [typeof(ushort)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
 7580        [typeof(int)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
 0581        [typeof(uint)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
 0582        [typeof(long)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64" },
 0583        [typeof(ulong)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int64" },
 0584        [typeof(float)] = () => new OpenApiSchema { Type = JsonSchemaType.Number, Format = "float" },
 0585        [typeof(double)] = () => new OpenApiSchema { Type = JsonSchemaType.Number, Format = "double" },
 0586        [typeof(decimal)] = () => new OpenApiSchema { Type = JsonSchemaType.Number, Format = "decimal" },
 0587        [typeof(OaString)] = () => new OpenApiSchema { Type = JsonSchemaType.String },
 0588        [typeof(OaInteger)] = () => new OpenApiSchema { Type = JsonSchemaType.Integer },
 0589        [typeof(OaNumber)] = () => new OpenApiSchema { Type = JsonSchemaType.Number },
 0590        [typeof(OaBoolean)] = () => new OpenApiSchema { Type = JsonSchemaType.Boolean }
 1591    };
 592
 593    /// <summary>
 594    /// Applies schema attributes to an OpenAPI schema.
 595    /// </summary>
 596    /// <param name="oaProperties">The OpenApiProperties containing attributes to apply.</param>
 597    /// <param name="ioaSchema">The OpenAPI schema to apply attributes to.</param>
 598    private static void ApplySchemaAttr(OpenApiProperties? oaProperties, IOpenApiSchema ioaSchema)
 599    {
 31600        if (oaProperties is null)
 601        {
 25602            return;
 603        }
 604
 605        // Most models implement OpenApiSchema (concrete) OR OpenApiSchemaReference.
 606        // We set common metadata when possible (Description/Title apply only to concrete schema).
 6607        if (ioaSchema is OpenApiSchema concreteSchema)
 608        {
 6609            ApplyConcreteSchemaAttributes(oaProperties, concreteSchema);
 6610            return;
 611        }
 612
 0613        if (ioaSchema is OpenApiSchemaReference refSchema)
 614        {
 0615            ApplyReferenceSchemaAttributes(oaProperties, refSchema);
 616        }
 0617    }
 618
 619    /// <summary>
 620    /// Applies concrete schema attributes to an OpenApiSchema.
 621    /// </summary>
 622    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 623    /// <param name="schema">The OpenApiSchema to apply attributes to.</param>
 624    private static void ApplyConcreteSchemaAttributes(OpenApiProperties properties, OpenApiSchema schema)
 625    {
 6626        ApplyTitleAndDescription(properties, schema);
 6627        ApplySchemaType(properties, schema);
 6628        ApplyFormatAndNumericBounds(properties, schema);
 6629        ApplyLengthAndPattern(properties, schema);
 6630        ApplyCollectionConstraints(properties, schema);
 6631        ApplyFlags(properties, schema);
 6632        ApplyExamplesAndDefaults(properties, schema);
 6633    }
 634
 635    /// <summary>
 636    /// Applies title and description to an OpenApiSchema.
 637    /// </summary>
 638    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 639    /// <param name="schema">The OpenApiSchema to apply attributes to.</param>
 640    private static void ApplyTitleAndDescription(OpenApiProperties properties, OpenApiSchema schema)
 641    {
 6642        if (properties.Title is not null)
 643        {
 4644            schema.Title = properties.Title;
 645        }
 646
 6647        if (properties.Description is not null)
 648        {
 5649            schema.Description = properties.Description;
 650        }
 6651    }
 652
 653    /// <summary>
 654    /// Applies schema type and nullability to an OpenApiSchema.
 655    /// </summary>
 656    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 657    /// <param name="schema">The OpenApiSchema to apply attributes to.</param>
 658    private static void ApplySchemaType(OpenApiProperties properties, OpenApiSchema schema)
 659    {
 6660        if (properties.Type != OaSchemaType.None)
 661        {
 0662            schema.Type = properties.Type switch
 0663            {
 0664                OaSchemaType.String => JsonSchemaType.String,
 0665                OaSchemaType.Number => JsonSchemaType.Number,
 0666                OaSchemaType.Integer => JsonSchemaType.Integer,
 0667                OaSchemaType.Boolean => JsonSchemaType.Boolean,
 0668                OaSchemaType.Array => JsonSchemaType.Array,
 0669                OaSchemaType.Object => JsonSchemaType.Object,
 0670                OaSchemaType.Null => JsonSchemaType.Null,
 0671                _ => schema.Type
 0672            };
 673        }
 674
 6675        if (properties.Nullable)
 676        {
 0677            schema.Type |= JsonSchemaType.Null;
 678        }
 6679    }
 680
 681    /// <summary>
 682    /// Applies format and numeric bounds to an OpenApiSchema.
 683    /// </summary>
 684    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 685    /// <param name="schema"></param>
 686    private static void ApplyFormatAndNumericBounds(OpenApiProperties properties, OpenApiSchema schema)
 687    {
 6688        if (!string.IsNullOrWhiteSpace(properties.Format))
 689        {
 0690            schema.Format = properties.Format;
 691        }
 692
 6693        if (properties.MultipleOf.HasValue)
 694        {
 0695            schema.MultipleOf = properties.MultipleOf;
 696        }
 697
 6698        if (!string.IsNullOrWhiteSpace(properties.Maximum))
 699        {
 0700            schema.Maximum = properties.Maximum;
 0701            if (properties.ExclusiveMaximum)
 702            {
 0703                schema.ExclusiveMaximum = properties.Maximum;
 704            }
 705        }
 706
 6707        if (!string.IsNullOrWhiteSpace(properties.Minimum))
 708        {
 0709            schema.Minimum = properties.Minimum;
 0710            if (properties.ExclusiveMinimum)
 711            {
 0712                schema.ExclusiveMinimum = properties.Minimum;
 713            }
 714        }
 6715    }
 716
 717    /// <summary>
 718    /// Applies length and pattern constraints to an OpenApiSchema.
 719    /// </summary>
 720    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 721    /// <param name="schema"></param>
 722    private static void ApplyLengthAndPattern(OpenApiProperties properties, OpenApiSchema schema)
 723    {
 6724        if (properties.MaxLength >= 0)
 725        {
 0726            schema.MaxLength = properties.MaxLength;
 727        }
 728
 6729        if (properties.MinLength >= 0)
 730        {
 0731            schema.MinLength = properties.MinLength;
 732        }
 733
 6734        if (!string.IsNullOrWhiteSpace(properties.Pattern))
 735        {
 0736            schema.Pattern = properties.Pattern;
 737        }
 6738    }
 739
 740    /// <summary>
 741    /// Applies collection constraints to an OpenApiSchema.
 742    /// </summary>
 743    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 744    /// <param name="schema">The OpenApiSchema to apply attributes to.</param>
 745    private static void ApplyCollectionConstraints(OpenApiProperties properties, OpenApiSchema schema)
 746    {
 6747        if (properties.MaxItems >= 0)
 748        {
 0749            schema.MaxItems = properties.MaxItems;
 750        }
 751
 6752        if (properties.MinItems >= 0)
 753        {
 0754            schema.MinItems = properties.MinItems;
 755        }
 756
 6757        if (properties.UniqueItems)
 758        {
 0759            schema.UniqueItems = true;
 760        }
 761
 6762        if (properties.MaxProperties >= 0)
 763        {
 0764            schema.MaxProperties = properties.MaxProperties;
 765        }
 766
 6767        if (properties.MinProperties >= 0)
 768        {
 0769            schema.MinProperties = properties.MinProperties;
 770        }
 6771    }
 772
 773    private static void ApplyFlags(OpenApiProperties properties, OpenApiSchema schema)
 774    {
 6775        schema.ReadOnly = properties.ReadOnly;
 6776        schema.WriteOnly = properties.WriteOnly;
 6777        schema.Deprecated = properties.Deprecated;
 6778        schema.AdditionalPropertiesAllowed = properties.AdditionalPropertiesAllowed;
 6779        schema.UnevaluatedProperties = properties.UnevaluatedProperties;
 6780    }
 781
 782    private static void ApplyExamplesAndDefaults(OpenApiProperties properties, OpenApiSchema schema)
 783    {
 6784        if (properties.Default is not null)
 785        {
 0786            schema.Default = ToNode(properties.Default);
 787        }
 788
 6789        if (properties.Example is not null)
 790        {
 0791            schema.Example = ToNode(properties.Example);
 792        }
 793
 6794        if (properties.Enum is { Length: > 0 })
 795        {
 0796            schema.Enum = [.. properties.Enum.Select(ToNode).OfType<JsonNode>()];
 797        }
 798
 6799        if (properties.Required is { Length: > 0 })
 800        {
 0801            schema.Required ??= new HashSet<string>(StringComparer.Ordinal);
 0802            foreach (var r in properties.Required)
 803            {
 0804                _ = schema.Required.Add(r);
 805            }
 806        }
 6807    }
 808
 809    /// <summary>
 810    /// Applies reference schema attributes to an OpenApiSchemaReference.
 811    /// </summary>
 812    /// <param name="properties">The OpenApiProperties containing attributes to apply.</param>
 813    /// <param name="reference">The OpenApiSchemaReference to apply attributes to.</param>
 814    private static void ApplyReferenceSchemaAttributes(OpenApiProperties properties, OpenApiSchemaReference reference)
 815    {
 816        // Description/Title can live on a reference proxy in v2 (and serialize alongside $ref)
 0817        if (!string.IsNullOrWhiteSpace(properties.Description))
 818        {
 0819            reference.Description = properties.Description;
 820        }
 821
 0822        if (!string.IsNullOrWhiteSpace(properties.Title))
 823        {
 0824            reference.Title = properties.Title;
 825        }
 826
 827        // Example/Default/Enum aren’t typically set on the ref node itself;
 828        // attach such metadata to the component target instead if you need it.
 0829    }
 830
 831    /// <summary>
 832    /// Determines if a type is considered primitive-like for schema generation.
 833    /// </summary>
 834    /// <param name="t">The type to check.</param>
 835    /// <returns>True if the type is considered primitive-like; otherwise, false.</returns>
 836    private static bool IsPrimitiveLike(Type t)
 41837        => t.IsPrimitive || t == typeof(string) || t == typeof(decimal) || t == typeof(DateTime) ||
 41838        t == typeof(Guid) || t == typeof(object) || t == typeof(OaString) || t == typeof(OaInteger) ||
 41839         t == typeof(OaNumber) || t == typeof(OaBoolean);
 840
 841    #endregion
 842
 843    /// <summary>
 844    /// Converts a .NET object to a JsonNode representation.
 845    /// </summary>
 846    /// <param name="value">The .NET object to convert.</param>
 847    /// <returns>A JsonNode representing the object, or null if the object is null.</returns>
 848    internal static JsonNode? ToNode(object? value)
 849    {
 31850        if (value is null)
 851        {
 0852            return null;
 853        }
 854
 855        // handle common types
 31856        return value switch
 31857        {
 1858            bool b => JsonValue.Create(b),
 19859            string s => JsonValue.Create(s),
 6860            sbyte or byte or short or ushort or int or uint or long or ulong => JsonValue.Create(Convert.ToInt64(value))
 0861            float or double or decimal => JsonValue.Create(Convert.ToDouble(value)),
 1862            DateTime dt => JsonValue.Create(dt.ToString("o")),
 0863            Guid g => JsonValue.Create(g.ToString()),
 31864            // Hashtable/IDictionary -> JsonObject
 0865            System.Collections.IDictionary dict => ToJsonObject(dict),
 31866            // Generic enumerable -> JsonArray
 0867            IEnumerable<object?> seq => new JsonArray([.. seq.Select(ToNode)]),
 31868            // Non-generic enumerable -> JsonArray
 0869            System.Collections.IEnumerable en when value is not string => ToJsonArray(en),
 4870            _ => ToNodeFromPocoOrString(value)
 31871        };
 872    }
 873
 874    private static JsonObject ToJsonObject(System.Collections.IDictionary dict)
 875    {
 0876        var obj = new JsonObject();
 0877        foreach (System.Collections.DictionaryEntry de in dict)
 878        {
 0879            if (de.Key is null) { continue; }
 0880            var k = de.Key.ToString() ?? string.Empty;
 0881            obj[k] = ToNode(de.Value);
 882        }
 0883        return obj;
 884    }
 885
 886    private static JsonArray ToJsonArray(System.Collections.IEnumerable en)
 887    {
 0888        var arr = new JsonArray();
 0889        foreach (var item in en)
 890        {
 0891            arr.Add(ToNode(item));
 892        }
 0893        return arr;
 894    }
 895
 896    private static JsonNode ToNodeFromPocoOrString(object value)
 897    {
 898        // Try POCO reflection
 4899        var t = value.GetType();
 900        // Ignore types that are clearly not POCOs
 4901        if (!t.IsPrimitive && t != typeof(string) && !typeof(System.Collections.IEnumerable).IsAssignableFrom(t))
 902        {
 4903            var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
 4904            if (props.Length > 0)
 905            {
 4906                var obj = new JsonObject();
 16907                foreach (var p in props)
 908                {
 4909                    if (!p.CanRead) { continue; }
 4910                    var v = p.GetValue(value);
 4911                    if (v is null) { continue; }
 4912                    obj[p.Name] = ToNode(v);
 913                }
 4914                return obj;
 915            }
 916        }
 917        // Fallback
 0918        return JsonValue.Create(value?.ToString() ?? string.Empty);
 919    }
 920
 921    /// <summary>
 922    /// Ensures that a schema component exists for a complex .NET type.
 923    /// </summary>
 924    /// <param name="complexType">The complex .NET type.</param>
 925    private void EnsureSchemaComponent(Type complexType)
 926    {
 0927        if (Document.Components?.Schemas != null && Document.Components.Schemas.ContainsKey(complexType.Name))
 928        {
 0929            return;
 930        }
 931
 0932        var temp = new HashSet<Type>();
 0933        BuildSchema(complexType, temp);
 0934    }
 935}

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

#LineLine coverage
 1using Microsoft.OpenApi;
 2using Kestrun.Authentication;
 3
 4namespace Kestrun.OpenApi;
 5/// <summary>
 6/// Methods for applying security schemes to the OpenAPI document.
 7/// </summary>
 8public partial class OpenApiDocDescriptor
 9{
 10    /// <summary>
 11    /// Applies a security scheme to the OpenAPI document based on the provided authentication options.
 12    /// </summary>
 13    /// <param name="scheme">The name of the security scheme.</param>
 14    /// <param name="options">The authentication options.</param>
 15    /// <exception cref="NotSupportedException">Thrown when the authentication options type is not supported.</exception
 16    public void ApplySecurityScheme(string scheme, IOpenApiAuthenticationOptions options)
 17    {
 2018        var securityScheme = options switch
 2019        {
 620            ApiKeyAuthenticationOptions apiKeyOptions => GetSecurityScheme(apiKeyOptions),
 821            BasicAuthenticationOptions basicOptions => GetSecurityScheme(basicOptions),
 222            CookieAuthOptions cookieOptions => GetSecurityScheme(cookieOptions),
 323            JwtAuthOptions jwtOptions => GetSecurityScheme(jwtOptions),
 024            OAuth2Options oauth2Options => GetSecurityScheme(oauth2Options),
 025            OidcOptions oidcOptions => GetSecurityScheme(oidcOptions),
 126            WindowsAuthOptions windowsOptions => GetSecurityScheme(windowsOptions),
 027            _ => throw new NotSupportedException($"Unsupported authentication options type: {options.GetType().FullName}
 2028        };
 2029        AddSecurityComponent(scheme: scheme, globalScheme: options.GlobalScheme, securityScheme: securityScheme);
 2030    }
 31
 32    /// <summary>
 33    /// Gets the OpenAPI security scheme for Windows authentication.
 34    /// </summary>
 35    /// <param name="options">The Windows authentication options.</param>
 36    /// <returns>The OpenAPI security scheme for Windows authentication.</returns>
 37    private static OpenApiSecurityScheme GetSecurityScheme(WindowsAuthOptions options)
 38    {
 139        return new OpenApiSecurityScheme()
 140        {
 141            Type = SecuritySchemeType.Http,
 142            Scheme = options.Protocol == WindowsAuthProtocol.Ntlm ? "ntlm" : "negotiate",
 143            Description = options.Description
 144        };
 45    }
 46
 47    /// <summary>
 48    /// Gets the OpenAPI security scheme for OIDC authentication.
 49    /// </summary>
 50    /// <param name="options">The OIDC authentication options.</param>
 51    /// <returns></returns>
 52    /// <exception cref="InvalidOperationException">Thrown when neither Authority nor MetadataAddress is set.</exception
 53    private static OpenApiSecurityScheme GetSecurityScheme(OidcOptions options)
 54    {
 55        // Prefer explicit MetadataAddress if set
 056        var discoveryUrl = options.MetadataAddress
 057                           ?? (options.Authority is null
 058                               ? throw new InvalidOperationException(
 059                                   "Either Authority or MetadataAddress must be set to build OIDC OpenAPI scheme.")
 060                               : $"{options.Authority.TrimEnd('/')}/.well-known/openid-configuration");
 61
 062        return new OpenApiSecurityScheme
 063        {
 064            Type = SecuritySchemeType.OpenIdConnect,
 065            OpenIdConnectUrl = new Uri(discoveryUrl, UriKind.Absolute),
 066            // Description comes from AuthenticationSchemeOptions base class
 067            Description = options.Description
 068        };
 69    }
 70
 71    /// <summary>
 72    /// Gets the OpenAPI security scheme for OAuth2 authentication.
 73    /// </summary>
 74    /// <param name="options">The OAuth2 authentication options.</param>
 75    /// <returns></returns>
 76    private static OpenApiSecurityScheme GetSecurityScheme(OAuth2Options options)
 77    {
 78        // Build OAuth flows
 079        var flows = new OpenApiOAuthFlows
 080        {
 081            // Client Credentials flow
 082            AuthorizationCode = new OpenApiOAuthFlow
 083            {
 084                AuthorizationUrl = new Uri(options.AuthorizationEndpoint, UriKind.Absolute),
 085            }
 086        };
 87        // Scopes
 088        if (options.ClaimPolicy is not null && options.ClaimPolicy.Policies is not null && options.ClaimPolicy.Policies.
 89        {
 090            var scopes = new Dictionary<string, string>();
 091            var policies = options.ClaimPolicy.Policies;
 092            foreach (var item in policies)
 93            {
 094                scopes.Add(item.Key, item.Value.Description ?? string.Empty);
 95            }
 096            flows.AuthorizationCode.Scopes = scopes;
 97        }
 98        // Token endpoint
 099        if (options.TokenEndpoint is not null)
 100        {
 0101            flows.AuthorizationCode.TokenUrl = new Uri(options.TokenEndpoint, UriKind.Absolute);
 102        }
 103
 0104        return new OpenApiSecurityScheme()
 0105        {
 0106            Type = SecuritySchemeType.OAuth2,
 0107            Flows = flows,
 0108            Description = options.Description
 0109        };
 110    }
 111    /// <summary>
 112    /// Gets the OpenAPI security scheme for API key authentication.
 113    /// </summary>
 114    /// <param name="options">The API key authentication options.</param>
 115    private static OpenApiSecurityScheme GetSecurityScheme(ApiKeyAuthenticationOptions options)
 116    {
 6117        return new OpenApiSecurityScheme()
 6118        {
 6119            Type = SecuritySchemeType.ApiKey,
 6120            Name = options.ApiKeyName,
 6121            In = options.In,
 6122            Description = options.Description
 6123        };
 124    }
 125
 126    /// <summary>
 127    /// Gets the OpenAPI security scheme for cookie authentication.
 128    /// </summary>
 129    /// <param name="options">The cookie authentication options.</param>
 130    /// <returns></returns>
 131    private static OpenApiSecurityScheme GetSecurityScheme(CookieAuthOptions options)
 132    {
 2133        return new OpenApiSecurityScheme()
 2134        {
 2135            Type = SecuritySchemeType.ApiKey,
 2136            Name = options.Cookie.Name,
 2137            In = ParameterLocation.Cookie,
 2138            Description = options.Description
 2139        };
 140    }
 141
 142    /// <summary>
 143    /// Gets the OpenAPI security scheme for JWT authentication.
 144    /// </summary>
 145    /// <param name="options">The JWT authentication options.</param>
 146    /// <returns></returns>
 147    private static OpenApiSecurityScheme GetSecurityScheme(JwtAuthOptions options)
 148    {
 3149        return new OpenApiSecurityScheme()
 3150        {
 3151            Type = SecuritySchemeType.Http,
 3152            Scheme = "bearer",
 3153            BearerFormat = "JWT",
 3154            Description = options.Description
 3155        };
 156    }
 157
 158    /// <summary>
 159    ///  Gets the OpenAPI security scheme for basic authentication.
 160    /// </summary>
 161    /// <param name="options">The basic authentication options.</param>
 162    private static OpenApiSecurityScheme GetSecurityScheme(BasicAuthenticationOptions options)
 163    {
 8164        return new OpenApiSecurityScheme()
 8165        {
 8166            Type = SecuritySchemeType.Http,
 8167            Scheme = "basic",
 8168            Description = options.Description
 8169        };
 170    }
 171
 172    /// <summary>
 173    /// Adds a security component to the OpenAPI document.
 174    /// </summary>
 175    /// <param name="scheme">The name of the security component.</param>
 176    /// <param name="globalScheme">Indicates whether the security scheme should be applied globally.</param>
 177    /// <param name="securityScheme">The security scheme to add.</param>
 178    private void AddSecurityComponent(string scheme, bool globalScheme, OpenApiSecurityScheme securityScheme)
 179    {
 20180        _ = Document.AddComponent(scheme, securityScheme);
 181
 182        // Reference it by NAME in the requirement (no .Reference in v2)
 20183        var requirement = new OpenApiSecurityRequirement
 20184        {
 20185            {
 20186                new OpenApiSecuritySchemeReference(scheme,Document), new List<string>()
 20187            }
 20188        };
 20189        SecurityRequirement.Add(scheme, requirement);
 190
 191        // Apply globally if specified
 20192        if (globalScheme)
 193        {
 194            // Apply globally
 0195            Document.Security ??= [];
 0196            Document.Security.Add(requirement);
 197        }
 20198    }
 199}

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

#LineLine coverage
 1using Microsoft.OpenApi;
 2using Kestrun.Hosting;
 3using Microsoft.OpenApi.Reader;
 4using System.Text;
 5
 6namespace Kestrun.OpenApi;
 7
 8/// <summary>
 9/// Generates OpenAPI v2 (Swagger) documents from C# types decorated with OpenApiSchema attributes.
 10/// </summary>
 11public partial class OpenApiDocDescriptor
 12{
 13    /// <summary>
 14    /// The Kestrun host providing registered routes.
 15    /// </summary>
 3916    public KestrunHost Host { get; init; }
 17
 18    /// <summary>
 19    /// The ID of the OpenAPI document being generated.
 20    /// </summary>
 3921    public string DocumentId { get; init; }
 22
 23    /// <summary>
 24    /// The OpenAPI document being generated.
 25    /// </summary>
 17726    public OpenApiDocument Document { get; private set; } = new OpenApiDocument { Components = new OpenApiComponents() }
 27
 28    /// <summary>
 29    /// Security requirements for the OpenAPI document.
 30    /// </summary>
 5931    public IDictionary<string, OpenApiSecurityRequirement> SecurityRequirement { get; private set; } = new Dictionary<st
 32
 33    /// <summary>
 34    /// Initializes a new instance of the OpenApiDocDescriptor.
 35    /// </summary>
 36    /// <param name="host">The Kestrun host.</param>
 37    /// <param name="docId">The ID of the OpenAPI document being generated.</param>
 38    /// <exception cref="ArgumentNullException">Thrown if host or docId is null.</exception>
 3939    public OpenApiDocDescriptor(KestrunHost host, string docId)
 40    {
 3941        ArgumentNullException.ThrowIfNull(host);
 3942        ArgumentNullException.ThrowIfNull(docId);
 3943        Host = host;
 3944        DocumentId = docId;
 3945        HasBeenGenerated = false;
 3946    }
 47
 48    /// <summary>
 49    /// Indicates whether the OpenAPI document has been generated at least once.
 50    /// </summary>
 3951    public bool HasBeenGenerated { get; private set; }
 52    /// <summary>
 53    /// Generates an OpenAPI document from the provided schema types.
 54    /// </summary>
 55    /// <param name="components">The set of discovered OpenAPI component types.</param>
 56    /// <returns>The generated OpenAPI document.</returns>
 57    internal void GenerateComponents(OpenApiComponentSet components)
 58    {
 459        Document.Components ??= new OpenApiComponents();
 60
 861        ProcessComponentTypes(components.ExampleTypes, () => Document.Components.Examples ??= new Dictionary<string, IOp
 1262        ProcessComponentTypes(components.SchemaTypes, () => Document.Components.Schemas ??= new Dictionary<string, IOpen
 563        ProcessComponentTypes(components.HeaderTypes, () => Document.Components.Headers ??= new Dictionary<string, IOpen
 464        ProcessComponentTypes(components.ParameterTypes, () => Document.Components.Parameters ??= new Dictionary<string,
 65#if EXTENDED_OPENAPI
 66        ProcessComponentTypes(components.LinkTypes, () => Document.Components.Links ??= new Dictionary<string, IOpenApiL
 67        ProcessComponentTypes(components.CallbackTypes, () => Document.Components.Callbacks ??= new Dictionary<string, I
 68#endif
 569        ProcessComponentTypes(components.ResponseTypes, () => Document.Components.Responses ??= new Dictionary<string, I
 670        ProcessComponentTypes(components.RequestBodyTypes, () => Document.Components.RequestBodies ??= new Dictionary<st
 471    }
 72
 73    /// <summary>
 74    /// Processes a list of component types and builds them into the OpenAPI document.
 75    /// </summary>
 76    /// <param name="types">The list of component types to process.</param>
 77    /// <param name="ensureDictionary">An action to ensure the corresponding dictionary is initialized.</param>
 78    /// <param name="buildAction">An action to build each component type.</param>
 79    private static void ProcessComponentTypes(
 80        IReadOnlyList<Type>? types,
 81        Action ensureDictionary,
 82        Action<Type> buildAction)
 83    {
 2484        if (types is null || types.Count == 0)
 85        {
 1286            return;
 87        }
 88
 1289        ensureDictionary();
 4890        foreach (var type in types)
 91        {
 1292            buildAction(type);
 93        }
 1294    }
 95
 96    /// <summary>
 97    /// Generates the OpenAPI document by auto-discovering component types.
 98    /// </summary>
 99    public void GenerateComponents()
 100    {
 0101        var components = OpenApiSchemaDiscovery.GetOpenApiTypesAuto();
 0102        GenerateComponents(components);
 0103    }
 104    /// <summary>
 105    /// Generates the OpenAPI document by processing components and registered routes.
 106    /// </summary>
 107    public void GenerateDoc()
 108    {
 109        // First, generate components
 0110        GenerateComponents();
 111        // Finally, build paths from registered routes
 0112        BuildPathsFromRegisteredRoutes(Host.RegisteredRoutes);
 0113        HasBeenGenerated = true;
 0114    }
 115
 116    /// <summary>
 117    /// Reads and diagnoses the OpenAPI document by serializing and re-parsing it.
 118    /// </summary>
 119    /// <param name="version">The OpenAPI specification version to read as.</param>
 120    /// <returns>A tuple containing the OpenAPI document and any diagnostics.</returns>
 121    public ReadResult ReadAndDiagnose(OpenApiSpecVersion version)
 122    {
 0123        using var sw = new StringWriter();
 0124        var w = new OpenApiJsonWriter(sw);
 0125        Document.SerializeAs(version, w);
 0126        using var ms = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
 127        // format must be "json" or "yaml"
 0128        return OpenApiDocument.Load(ms);
 0129    }
 130
 131    /// <summary>
 132    /// Serializes the OpenAPI document to a JSON string.
 133    /// </summary>
 134    /// <param name="version">The OpenAPI specification version to serialize as.</param>
 135    /// <returns>The serialized JSON string.</returns>
 136    public string ToJson(OpenApiSpecVersion version)
 137    {
 0138        using var sw = new StringWriter();
 0139        var w = new OpenApiJsonWriter(sw);
 0140        Document.SerializeAs(version, w);
 0141        return sw.ToString();
 0142    }
 143
 144    /// <summary>
 145    /// Serializes the OpenAPI document to a YAML string.
 146    /// </summary>
 147    /// <param name="version">The OpenAPI specification version to serialize as.</param>
 148    /// <returns>The serialized YAML string.</returns>
 149    public string ToYaml(OpenApiSpecVersion version)
 150    {
 0151        using var sw = new StringWriter();
 0152        var w = new OpenApiYamlWriter(sw);
 0153        Document.SerializeAs(version, w);
 0154        return sw.ToString();
 0155    }
 156}

Methods/Properties

LoadAnnotatedFunctions(System.Collections.Generic.List`1<System.Management.Automation.FunctionInfo>)
ProcessFunction(System.Management.Automation.FunctionInfo)
ProcessFunctionAttributes(System.Management.Automation.FunctionInfo,System.Management.Automation.Language.CommentHelpInfo,System.Collections.Generic.IReadOnlyCollection`1<System.Attribute>,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata)
ApplyPathAttribute(System.Management.Automation.FunctionInfo,System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,Kestrun.Utilities.HttpVerb,OpenApiPath)
ChooseFirstNonEmpty(System.String[])
NormalizeNewlines(System.String)
ApplyResponseRefAttribute(Kestrun.Hosting.Options.OpenAPIMetadata,OpenApiResponseRefAttribute)
ApplyResponseAttribute(Kestrun.Hosting.Options.OpenAPIMetadata,OpenApiResponseAttribute)
ApplyPropertyAttribute(Kestrun.Hosting.Options.OpenAPIMetadata,OpenApiPropertyAttribute)
ApplyAuthorizationAttribute(Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,OpenApiAuthorizationAttribute)
BuildPolicyList(System.String)
ProcessParameters(System.Management.Automation.FunctionInfo,System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata)
ApplyParameterAttribute(System.Management.Automation.FunctionInfo,System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,System.Management.Automation.ParameterMetadata,OpenApiParameterAttribute)
ApplyParameterRefAttribute(System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,System.Management.Automation.ParameterMetadata,OpenApiParameterRefAttribute)
ApplyRequestBodyRefAttribute(System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,System.Management.Automation.ParameterMetadata,OpenApiRequestBodyRefAttribute)
ResolveRequestBodyReferenceId(OpenApiRequestBodyRefAttribute,System.Management.Automation.ParameterMetadata)
ApplyRequestBodyAttribute(System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,System.Management.Automation.ParameterMetadata,OpenApiRequestBodyAttribute)
ApplyPreferredRequestBody(System.Management.Automation.Language.CommentHelpInfo,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.OpenAPIMetadata,System.Management.Automation.ParameterMetadata,OpenApiRequestBodyAttribute)
EnsureDefaultResponses(Kestrun.Hosting.Options.OpenAPIMetadata)
FinalizeRouteOptions(System.Management.Automation.FunctionInfo,System.Management.Automation.ScriptBlock,Kestrun.Hosting.Options.OpenAPIMetadata,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Utilities.HttpVerb)
CreateRequestBodyFromAttribute(KestrunAnnotation,Microsoft.OpenApi.OpenApiRequestBody,Microsoft.OpenApi.IOpenApiSchema)
BuildPathsFromRegisteredRoutes(System.Collections.Generic.Dictionary`2<System.ValueTuple`2<System.String,Kestrun.Utilities.HttpVerb>,Kestrun.Hosting.Options.MapRouteOptions>)
ProcessRouteGroup(System.Linq.IGrouping`2<System.String,System.Collections.Generic.KeyValuePair`2<System.ValueTuple`2<System.String,Kestrun.Utilities.HttpVerb>,Kestrun.Hosting.Options.MapRouteOptions>>)
GetOrCreatePathItem(System.String)
ProcessRouteOperation(System.Collections.Generic.KeyValuePair`2<System.ValueTuple`2<System.String,Kestrun.Utilities.HttpVerb>,Kestrun.Hosting.Options.MapRouteOptions>,Microsoft.OpenApi.OpenApiPathItem,Kestrun.Hosting.Options.OpenAPICommonMetadata)
ApplyPathLevelMetadata(Microsoft.OpenApi.OpenApiPathItem,Kestrun.Hosting.Options.OpenAPICommonMetadata,System.String)
ApplyPathLevelServers(Microsoft.OpenApi.OpenApiPathItem,Kestrun.Hosting.Options.OpenAPICommonMetadata)
ApplyPathLevelParameters(Microsoft.OpenApi.OpenApiPathItem,Kestrun.Hosting.Options.OpenAPICommonMetadata)
BuildOperationFromMetadata(Kestrun.Hosting.Options.OpenAPIMetadata)
ApplyTags(Microsoft.OpenApi.OpenApiOperation,Kestrun.Hosting.Options.OpenAPIMetadata)
ApplyServers(Microsoft.OpenApi.OpenApiOperation,Kestrun.Hosting.Options.OpenAPIMetadata)
ApplyParameters(Microsoft.OpenApi.OpenApiOperation,Kestrun.Hosting.Options.OpenAPIMetadata)
ApplyCallbacks(Microsoft.OpenApi.OpenApiOperation,Kestrun.Hosting.Options.OpenAPIMetadata)
ApplySecurity(Microsoft.OpenApi.OpenApiOperation,Kestrun.Hosting.Options.OpenAPIMetadata)
BuildSchema(System.Type,System.Collections.Generic.HashSet`1<System.Type>)
BuildPropertySchema(System.Reflection.PropertyInfo,System.Collections.Generic.HashSet`1<System.Type>)
BuildComplexTypeSchema(System.Type,System.Reflection.PropertyInfo,System.Collections.Generic.HashSet`1<System.Type>)
BuildEnumSchema(System.Type,System.Reflection.PropertyInfo)
BuildArraySchema(System.Type,System.Reflection.PropertyInfo,System.Collections.Generic.HashSet`1<System.Type>)
BuildPrimitiveSchema(System.Type,System.Reflection.PropertyInfo)
BuildExamples(System.Type)
CreateExampleFromAttribute(System.Object)
ApplyExampleRefAttribute(OpenApiExampleRefAttribute,Microsoft.OpenApi.OpenApiHeader)
ApplyInlineExampleAttribute(OpenApiExampleAttribute,Microsoft.OpenApi.OpenApiHeader)
BuildHeaders(System.Type)
GetHeaderComponentDefaults(System.Type)
GetHeaderAttributes(System.Reflection.PropertyInfo)
BuildHeader(System.Object[])
ApplyDefaultDescription(Microsoft.OpenApi.OpenApiHeader,System.String)
BuildHeaderKey(System.String,System.String)
CreateHeaderFromAttribute(System.Object,Microsoft.OpenApi.OpenApiHeader)
ApplyHeaderAttribute(OpenApiHeaderAttribute,Microsoft.OpenApi.OpenApiHeader)
GetSchema(System.String)
GetParameter(System.String)
GetRequestBody(System.String)
GetHeader(System.String)
GetResponse(System.String)
ComponentSchemasExists(System.String)
ComponentRequestBodiesExists(System.String)
ComponentResponsesExists(System.String)
ComponentParametersExists(System.String)
ComponentExamplesExists(System.String)
ComponentHeadersExists(System.String)
ComponentCallbacksExists(System.String)
ComponentLinksExists(System.String)
ComponentPathItemsExists(System.String)
BuildParameters(System.Type)
GetClassLevelMetadata(System.Type)
ProcessPropertyForParameter(System.Reflection.PropertyInfo,System.Type,System.String,System.String)
GetParameterAttributes(System.Reflection.PropertyInfo)
ApplyParameterAttributes(Microsoft.OpenApi.OpenApiParameter,KestrunAnnotation[])
FinalizeAndRegisterParameter(Microsoft.OpenApi.OpenApiParameter,System.Reflection.PropertyInfo,System.Type,System.String,System.String,System.String)
CreatePropertySchema(OpenApiPropertyAttribute,System.Type,System.Reflection.PropertyInfo)
CreateEnumSchema(System.Type,OpenApiPropertyAttribute,System.Boolean)
CreateArraySchema(System.Type,System.Reflection.PropertyInfo,OpenApiPropertyAttribute,System.Boolean)
CreateComplexSchema(System.Type,OpenApiPropertyAttribute)
CreatePrimitiveSchema(System.Type,System.Type,System.Reflection.PropertyInfo,OpenApiPropertyAttribute,System.Boolean)
TryApplyDefaultValue(System.Type,System.Reflection.PropertyInfo,Microsoft.OpenApi.OpenApiSchema)
CreateParameterFromAttribute(KestrunAnnotation,Microsoft.OpenApi.OpenApiParameter)
ApplyParameterAttribute(OpenApiParameterAttribute,Microsoft.OpenApi.OpenApiParameter)
ApplyExampleRefAttribute(OpenApiExampleRefAttribute,Microsoft.OpenApi.OpenApiParameter)
BuildRequestBodies(System.Type)
ApplyRequestBodyComponent(OpenApiRequestBodyComponent,Microsoft.OpenApi.OpenApiRequestBody,Microsoft.OpenApi.IOpenApiSchema)
ApplyRequestBodyExampleRef(OpenApiExampleRefAttribute,Microsoft.OpenApi.OpenApiRequestBody)
ResolveExampleContentTypes(OpenApiExampleRefAttribute,Microsoft.OpenApi.OpenApiRequestBody)
CloneExampleOrThrow(System.String)
BuildResponses(System.Type)
GetClassLevelResponseMetadata(System.Type)
ProcessPropertyForResponse(System.Reflection.PropertyInfo,System.String,System.String)
GetPropertyResponseAttributes(System.Reflection.PropertyInfo)
ApplyPropertyAttributesToResponse(System.Reflection.PropertyInfo,System.Object[],Microsoft.OpenApi.OpenApiResponse)
RegisterResponse(Microsoft.OpenApi.OpenApiResponse,System.Reflection.PropertyInfo,System.String,System.String,System.String)
GetAttributeValue(System.Reflection.PropertyInfo)
GetEnumSchema(System.Reflection.PropertyInfo,System.Type,System.Boolean)
GetArraySchema(System.Reflection.PropertyInfo,System.Type,System.Boolean)
GetComplexSchema(System.Type)
GetPrimitiveSchema(System.Reflection.PropertyInfo,System.Type,System.Boolean)
GetKeyOverride(System.Object)
CreateResponseFromAttribute(System.Object,Microsoft.OpenApi.OpenApiResponse,Microsoft.OpenApi.IOpenApiSchema)
ApplyResponseAttribute(OpenApiResponseAttribute,Microsoft.OpenApi.OpenApiResponse,Microsoft.OpenApi.IOpenApiSchema)
ApplyDescription(OpenApiResponseAttribute,Microsoft.OpenApi.OpenApiResponse)
ResolveResponseSchema(OpenApiResponseAttribute,Microsoft.OpenApi.IOpenApiSchema)
ResolveSchemaRef(System.String,System.Boolean)
ApplySchemaToContentTypes(OpenApiResponseAttribute,Microsoft.OpenApi.OpenApiResponse,Microsoft.OpenApi.IOpenApiSchema)
ApplyHeaderRefAttribute(OpenApiHeaderRefAttribute,Microsoft.OpenApi.OpenApiResponse)
ApplyLinkRefAttribute(OpenApiLinkRefAttribute,Microsoft.OpenApi.OpenApiResponse)
ApplyExampleRefAttribute(OpenApiExampleRefAttribute,Microsoft.OpenApi.OpenApiResponse)
GetOrAddMediaType(Microsoft.OpenApi.OpenApiResponse,System.String)
CloneSchemaOrThrow(System.String)
GetSchemaIdentity(System.Type)
BuildSchemaForType(System.Type,System.Collections.Generic.HashSet`1<System.Type>)
BuildBaseTypeSchema(System.Type)
BuildOpenApiTypeSchema(System.Type)
BuildCustomBaseTypeSchema(System.Type,System.Nullable`1<OaSchemaType>)
BuildPropertyFromAttribute(OpenApiProperties,Microsoft.OpenApi.IOpenApiSchema)
RegisterEnumSchema(System.Type)
ApplyTypeAttributes(System.Type,Microsoft.OpenApi.OpenApiSchema)
ProcessTypeProperties(System.Type,Microsoft.OpenApi.OpenApiSchema,System.Collections.Generic.HashSet`1<System.Type>)
TryCreateTypeInstance(System.Type)
CapturePropertyDefault(System.Object,System.Reflection.PropertyInfo,Microsoft.OpenApi.IOpenApiSchema)
IsIntrinsicDefault(System.Object,System.Type)
MergeSchemaAttributes(OpenApiPropertyAttribute[])
MergeStringProperties(OpenApiPropertyAttribute,OpenApiPropertyAttribute)
MergeEnumAndCollections(OpenApiPropertyAttribute,OpenApiPropertyAttribute)
MergeNumericProperties(OpenApiPropertyAttribute,OpenApiPropertyAttribute)
MergeBooleanProperties(OpenApiPropertyAttribute,OpenApiPropertyAttribute)
MergeTypeAndRequired(OpenApiPropertyAttribute,OpenApiPropertyAttribute)
MergeCustomFields(OpenApiPropertyAttribute,OpenApiPropertyAttribute)
InferPrimitiveSchema(System.Type,System.Boolean)
InferArraySchema(System.Type,System.Boolean)
InferPowerShellClassSchema(System.Type,System.Boolean)
.cctor()
ApplySchemaAttr(OpenApiProperties,Microsoft.OpenApi.IOpenApiSchema)
ApplyConcreteSchemaAttributes(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyTitleAndDescription(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplySchemaType(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyFormatAndNumericBounds(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyLengthAndPattern(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyCollectionConstraints(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyFlags(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyExamplesAndDefaults(OpenApiProperties,Microsoft.OpenApi.OpenApiSchema)
ApplyReferenceSchemaAttributes(OpenApiProperties,Microsoft.OpenApi.OpenApiSchemaReference)
IsPrimitiveLike(System.Type)
ToNode(System.Object)
ToJsonObject(System.Collections.IDictionary)
ToJsonArray(System.Collections.IEnumerable)
ToNodeFromPocoOrString(System.Object)
EnsureSchemaComponent(System.Type)
ApplySecurityScheme(System.String,Kestrun.Authentication.IOpenApiAuthenticationOptions)
GetSecurityScheme(Kestrun.Authentication.WindowsAuthOptions)
GetSecurityScheme(Kestrun.Authentication.OidcOptions)
GetSecurityScheme(Kestrun.Authentication.OAuth2Options)
GetSecurityScheme(Kestrun.Authentication.ApiKeyAuthenticationOptions)
GetSecurityScheme(Kestrun.Authentication.CookieAuthOptions)
GetSecurityScheme(Kestrun.Authentication.JwtAuthOptions)
GetSecurityScheme(Kestrun.Authentication.BasicAuthenticationOptions)
AddSecurityComponent(System.String,System.Boolean,Microsoft.OpenApi.OpenApiSecurityScheme)
get_Host()
get_DocumentId()
get_Document()
get_SecurityRequirement()
.ctor(Kestrun.Hosting.KestrunHost,System.String)
get_HasBeenGenerated()
GenerateComponents(Kestrun.OpenApi.OpenApiComponentSet)
ProcessComponentTypes(System.Collections.Generic.IReadOnlyList`1<System.Type>,System.Action,System.Action`1<System.Type>)
GenerateComponents()
GenerateDoc()
ReadAndDiagnose(Microsoft.OpenApi.OpenApiSpecVersion)
ToJson(Microsoft.OpenApi.OpenApiSpecVersion)
ToYaml(Microsoft.OpenApi.OpenApiSpecVersion)