From a8e7f40eb8c1d075115d1355366acc6dde6860c0 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Tue, 10 Feb 2026 21:34:12 +0100 Subject: [PATCH 01/12] add comments to the operation class --- .../JsonValidationExceptionGenerator.cs | 2 +- .../CodeGeneration/OperationGenerator.cs | 36 +++++++++++++++++-- .../Extensions/StringExtensions.cs | 22 ++++++++++++ tests/Example.OpenApi31/openapi.json | 2 ++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs index d175b31..9b28982 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs @@ -2,7 +2,7 @@ internal sealed class JsonValidationExceptionGenerator(string @namespace) { - private const string ClassName = "JsonValidationException"; + internal const string ClassName = "JsonValidationException"; internal string CreateThrowJsonValidationExceptionInvocation( string message, string validationResultVariableName) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs index 242ea59..21682d4 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs @@ -36,7 +36,11 @@ internal SourceCode Generate(string @namespace, using System.Threading; namespace {{@namespace}}; - +{{ new[] +{ + operation.Operation.Summary.AsComment("summary"), + operation.Operation.Description.AsComment("remarks", "para") +}.RemoveEmptyLines().AggregateToString()}} internal partial class Operation { internal const string PathTemplate = "{{pathTemplate}}"; @@ -79,8 +83,12 @@ internal partial class Operation private Func HandleForbidden { get; } = () => new Response.Forbidden(); """ : "")}} - internal sealed class BindRequestFilter(Operation operation) : IEndpointFilter + /// + /// Binds the request object + /// + internal sealed class BindRequestFilter : IEndpointFilter { + /// public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var httpContext = context.HttpContext; @@ -100,6 +108,11 @@ internal sealed class BindRequestFilter(Operation operation) : IEndpointFilter /// Handle a operation. /// /// + /// The current http context + /// The operation + /// Web api configuration + /// Cancellation + /// An awaitable task internal static async Task HandleAsync( HttpContext context, [FromServices] Operation operation, @@ -122,6 +135,12 @@ internal static async Task HandleAsync( .WriteTo(context.Response); } + /// + /// Validates a response + /// + /// Thrown when validation fails. + /// + /// internal Response Validate(Response response, WebApiConfiguration configuration) { if (!ValidateResponse) @@ -137,25 +156,36 @@ internal Response Validate(Response response, WebApiConfiguration configuration) }{{(requiresAuth ? """ +/// internal abstract partial class Response { + /// + /// Unauthorized response + /// internal sealed class Unauthorized : Response { + /// internal override void WriteTo(HttpResponse httpResponse) { httpResponse.StatusCode = 401; } - + + /// internal override ValidationContext Validate(ValidationLevel validationLevel) => ValidationContext.ValidContext; } + /// + /// Forbidden response + /// internal sealed class Forbidden : Response { + /// internal override void WriteTo(HttpResponse httpResponse) { httpResponse.StatusCode = 403; } + /// internal override ValidationContext Validate(ValidationLevel validationLevel) => ValidationContext.ValidContext; } } diff --git a/src/OpenAPI.WebApiGenerator/Extensions/StringExtensions.cs b/src/OpenAPI.WebApiGenerator/Extensions/StringExtensions.cs index 4fc13f6..20f8620 100644 --- a/src/OpenAPI.WebApiGenerator/Extensions/StringExtensions.cs +++ b/src/OpenAPI.WebApiGenerator/Extensions/StringExtensions.cs @@ -59,4 +59,26 @@ internal static string Indent(this string str, int spaces) .Split('\n') .Select(line => string.IsNullOrWhiteSpace(line) ? string.Empty : $"{indentation}{line}")); } + + internal static string AsComment(this string? str, params string[] commentTypes) + { + if (!commentTypes.Any()) + { + throw new InvalidOperationException("Must specify at least one comment type"); + } + + if (str is null || string.IsNullOrWhiteSpace(str)) + { + return string.Empty; + } + + Array.Reverse(commentTypes); + return string.Join("\n", + commentTypes.Aggregate(str + .Split('\n') + .Select(line => $"/// {line}"), (current, commentType) => + current + .Prepend($"/// <{commentType}>") + .Append($"/// "))); + } } \ No newline at end of file diff --git a/tests/Example.OpenApi31/openapi.json b/tests/Example.OpenApi31/openapi.json index f9f1100..8e02043 100644 --- a/tests/Example.OpenApi31/openapi.json +++ b/tests/Example.OpenApi31/openapi.json @@ -9,6 +9,8 @@ "/foo/{FooId}": { "put": { "operationId": "Update_Foo", + "summary": "Update a foo resource", + "description": "Updates an existing foo resource by its ID.\n\n## Request\n\nThe request body must contain the **foo properties** to update.\n\n## Notes\n\n- The `FooId` must reference an existing resource\n- Partial updates are *not* supported", "parameters": [ { "$ref": "#/components/parameters/Bar" From db01be8da5a2e053d392da3f5ab2919edc956ed6 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 18:24:46 +0100 Subject: [PATCH 02/12] doc: add comments for response objects --- src/OpenAPI.WebApiGenerator/ApiGenerator.cs | 7 +++-- .../ResponseBodyContentGenerator.cs | 7 +++++ .../ResponseContentGenerator.cs | 26 +++++++++++++++---- .../CodeGeneration/ResponseGenerator.cs | 21 +++++++++++++-- .../CodeGeneration/ResponseHeaderGenerator.cs | 3 ++- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/ApiGenerator.cs b/src/OpenAPI.WebApiGenerator/ApiGenerator.cs index a70fd1a..776ca45 100644 --- a/src/OpenAPI.WebApiGenerator/ApiGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/ApiGenerator.cs @@ -156,10 +156,9 @@ private static void GenerateCode(SourceProductionContext context, var responses = operation.Responses ?? throw new InvalidOperationException( $"No responses defined for operation at {openApiOperationVisitor.Pointer}"); - var responseBodyGenerators = responses.Select(pair => + var responseBodyGenerators = responses.Select(content => { - var response = pair.Value; - var responseStatusCodePattern = pair.Key.ToPascalCase(); + var response = content.Value; var openApiResponseVisitor = openApiOperationVisitor.Visit(response); var responseContent = @@ -186,7 +185,7 @@ private static void GenerateCode(SourceProductionContext context, }).ToList() ?? []; return new ResponseContentGenerator( - responseStatusCodePattern, + content, responseBodyGenerators, responseHeaderGenerators); }).ToList(); diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs index 315a468..a38bd74 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs @@ -11,6 +11,10 @@ internal sealed class ResponseBodyContentGenerator(string contentType, TypeDecla internal string SchemaLocation => typeDeclaration.RelativeSchemaLocation; public string GenerateConstructor(string className, string contentTypeFieldName) => $$""" +/// +/// Construct content for {{contentType}} +/// +/// Content public {{className}}({{typeDeclaration.FullyQualifiedDotnetTypeName()}} {{_contentVariableName}}) { {{ContentPropertyName}} = {{_contentVariableName}}; @@ -22,6 +26,9 @@ public string GenerateContentProperty() { return $$""" +/// +/// Content for {{contentType}} +/// internal {{typeDeclaration.FullyQualifiedDotnetTypeName()}}? {{ContentPropertyName}} { get; } """; } diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs index 050ea30..5f95eac 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using Microsoft.OpenApi; using OpenAPI.WebApiGenerator.Extensions; namespace OpenAPI.WebApiGenerator.CodeGeneration; @@ -12,10 +13,12 @@ internal sealed class ResponseContentGenerator private readonly List _headerGenerators = []; private readonly string _responseClassName; private readonly string _responseStatusCodePattern; + private readonly IOpenApiResponse _response; private ResponseContentGenerator( - string responseStatusCodePattern) + KeyValuePair response) { + var responseStatusCodePattern = response.Key.ToPascalCase(); var classNamePrefix = Enum.TryParse(responseStatusCodePattern, out var statusCode) ? statusCode.ToString() : responseStatusCodePattern.First() switch @@ -32,11 +35,12 @@ var chr when char.IsDigit(chr) => "X", _responseStatusCodePattern = responseStatusCodePattern; _responseClassName = responseClassName; + _response = response.Value; } public ResponseContentGenerator( - string responseStatusCodePattern, + KeyValuePair response, List contentGenerators, - List headerGenerators) : this(responseStatusCodePattern) + List headerGenerators) : this(response) { _contentGenerators = contentGenerators; _headerGenerators = headerGenerators; @@ -57,6 +61,7 @@ public string GenerateResponseContentClass() return $$$""" +{{{_response.Description.AsComment("summary", "para")}}} internal sealed class {{{_responseClassName}}} : Response { private string? {{{contentTypeFieldName}}} = null;{{{ @@ -67,7 +72,10 @@ internal sealed class {{{_responseClassName}}} : Response generator.GenerateContentProperty()).Indent(4) }}} - private int _statusCode{{{(hasExplicitStatusCode ? $" = {_responseStatusCodePattern}" : string.Empty)}}}; + private int _statusCode{{{(hasExplicitStatusCode ? $" = {_responseStatusCodePattern}" : string.Empty)}}}; + /// + /// Response status code + /// internal int StatusCode { get => _statusCode;{{{(hasExplicitStatusCode ? "" : @@ -78,8 +86,14 @@ internal int StatusCode {{{(anyHeaders ? $$""" + /// + /// Response Headers + /// internal {{headerRequiredDirective}}ResponseHeaders Headers { get; init; }{{defaultHeadersValueAssignment}} + /// + /// Response Headers + /// internal sealed class ResponseHeaders {{{ _headerGenerators.AggregateToString(generator => @@ -87,6 +101,7 @@ internal sealed class ResponseHeaders } """ : "")}}} + /// internal override void WriteTo(HttpResponse {{{responseVariableName}}}) {{{{(_contentGenerators.Any() ? $$""" @@ -111,6 +126,7 @@ internal override void WriteTo(HttpResponse {{{responseVariableName}}}) generator.GenerateWriteDirective(responseVariableName)).Indent(8)}}} } + /// internal override ValidationContext Validate(ValidationLevel validationLevel) { var validationContext = ValidationContext.ValidContext.UsingStack().UsingResults(); @@ -127,6 +143,6 @@ internal override ValidationContext Validate(ValidationLevel validationLevel) return validationContext; } } -"""; +""".Trim(); } } diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs index 63f9994..2919b11 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs @@ -19,17 +19,34 @@ public SourceCode GenerateResponseClass(string @namespace, string path) namespace {{@namespace}}; +/// +/// Contains the operation's response objects +/// internal abstract partial class Response {{{Enumerable.Range(1, 5).AggregateToString(i => $$""" + /// + /// Validate that status code is {{i}}xx + /// Thrown when the status code is not {{i}}xx + /// + /// Status code to validate + /// The validated status code protected int Validate{{i}}xxStatusCode(int code) => (code >= {{i}}00 && code <= {{i}}99) ? code : throw new InvalidOperationException($"Expected {{i}}xx status code, got {code}"); """)}} + /// + /// Write the response to a http response object + /// + /// Http response object to write the response to internal abstract void WriteTo(HttpResponse httpResponse); - internal abstract ValidationContext Validate(ValidationLevel validationLevel); - + /// + /// Validate the response + /// + /// Validation level + /// The validation result + internal abstract ValidationContext Validate(ValidationLevel validationLevel); {{ responseBodyGenerators.AggregateToString(generator => generator.GenerateResponseContentClass()).Indent(4) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseHeaderGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseHeaderGenerator.cs index 59365da..f194465 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseHeaderGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseHeaderGenerator.cs @@ -23,8 +23,9 @@ internal sealed class ResponseHeaderGenerator( internal string GenerateProperty() => $$""" + {{header.Description.AsComment("summary", "para")}} internal {{_requiredDirective}}{{FullyQualifiedTypeName}} {{_propertyName}} { get; init; }{{DefaultValueAssignment}} - """; + """.TrimStart(); internal string GenerateWriteDirective(string responseVariableName) { From e7949129dbcda303a348c8f53f1f7f71d3c58dd7 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 19:06:10 +0100 Subject: [PATCH 03/12] doc(auth): add scheme description as scheme class comment --- .../CodeGeneration/AuthGenerator.cs | 5 ++--- tests/Example.OpenApi31/openapi.json | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs index 9846f48..0e26cbf 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Reflection.Metadata; using Microsoft.OpenApi; using OpenAPI.WebApiGenerator.Extensions; @@ -47,10 +46,10 @@ internal static class SecuritySchemes return scheme.Type == null ? string.Empty : $$""" internal const string {{className}}Key = "{{pair.Key}}"; +{{scheme.Description.AsComment("summary", "para").Indent(4)}} internal static class {{className}} {{{new [] { - GenerateConst(nameof(scheme.Description), scheme.Description), GenerateConst(nameof(scheme.Type), scheme.Type?.GetDisplayName()), GenerateConst(nameof(scheme.Scheme), scheme.Scheme), GenerateConst(nameof(scheme.BearerFormat), scheme.BearerFormat), @@ -59,7 +58,7 @@ internal static class {{className}} $"internal const bool {nameof(scheme.Deprecated)} = {scheme.Deprecated.ToString().ToLowerInvariant()};", GenerateFlowsObject(nameof(scheme.Flows), scheme.Flows) }.RemoveEmptyLines().AggregateToString().Indent(8)}} - } + } """; })}} } diff --git a/tests/Example.OpenApi31/openapi.json b/tests/Example.OpenApi31/openapi.json index 8e02043..a1ebadd 100644 --- a/tests/Example.OpenApi31/openapi.json +++ b/tests/Example.OpenApi31/openapi.json @@ -155,6 +155,7 @@ "securitySchemes": { "petstore_auth": { "type": "oauth2", + "description": "OAuth 2.0 authorization using the implicit flow.\n\nGrants access to pet operations based on the requested scopes:\n- `write:pets` — Create and modify pets\n- `read:pets` — Read pet information", "flows": { "implicit": { "authorizationUrl": "https://localhost/api/oauth/dialog", @@ -167,18 +168,22 @@ }, "secret_key": { "type": "apiKey", + "description": "API key authentication passed via the `Bar` header.\n\nInclude your API key in the `Bar` HTTP header with each request.", "in": "header", "name": "Bar" }, "basicAuth": { "type": "http", + "description": "HTTP Basic authentication.\n\nProvide a **username** and **password** encoded as `Base64(username:password)` in the `Authorization` header.", "scheme": "basic" }, "mutualTLS": { - "type": "mutualTLS" + "type": "mutualTLS", + "description": "Mutual TLS (mTLS) client certificate authentication.\n\nThe client must present a valid X.509 certificate during the TLS handshake." }, "openIdConnect": { "type": "openIdConnect", + "description": "OpenID Connect authentication.\n\nDiscovery document is available at the `openIdConnectUrl` endpoint.", "openIdConnectUrl": "https://example.com/.well-known/openid-configuration" } } From 2d241f501e45819eb731236673c61d5dcba82040 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 19:51:39 +0100 Subject: [PATCH 04/12] doc(request): add comments for request objects --- .../CodeGeneration/AuthGenerator.cs | 3 +++ .../CodeGeneration/OperationGenerator.cs | 3 +++ .../CodeGeneration/ParameterGenerator.cs | 3 ++- .../RequestBodyContentGenerator.cs | 3 +++ .../CodeGeneration/RequestBodyGenerator.cs | 18 ++++++++++++++++++ .../CodeGeneration/RequestGenerator.cs | 17 +++++++++++++++++ tests/Example.OpenApi31/openapi.json | 3 +++ 7 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs index 0e26cbf..8be4ba1 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs @@ -37,6 +37,9 @@ public AuthGenerator(OpenApiDocument openApiDocument) namespace {{@namespace}}; +/// +/// Defines security schemes that can be used by the operations +/// internal static class SecuritySchemes {{{_securitySchemes.AggregateToString(pair => { diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs index 21682d4..b6ebc2d 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationGenerator.cs @@ -141,6 +141,9 @@ internal static async Task HandleAsync( /// Thrown when validation fails. /// /// + /// The response to validate + /// Web api configuration + /// The validated response internal Response Validate(Response response, WebApiConfiguration configuration) { if (!ValidateResponse) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ParameterGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ParameterGenerator.cs index c567fa9..55c756f 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ParameterGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ParameterGenerator.cs @@ -23,8 +23,9 @@ internal sealed class ParameterGenerator( internal string GenerateRequestProperty() => $$""" + {{parameter.Description.AsComment("summary", "para")}} internal {{(IsParameterRequired ? "required " : "")}}{{FullyQualifiedTypeName}} {{PropertyName}} { get; init; } - """; + """.TrimStart(); internal string AsRequired(string variableName) => $"{variableName}{(IsParameterRequired ? "" : $" ?? {FullyQualifiedTypeDeclarationIdentifier}.Undefined")}"; diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyContentGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyContentGenerator.cs index 9794196..2c1e830 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyContentGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyContentGenerator.cs @@ -31,6 +31,9 @@ internal string GenerateRequestBindingDirective() => public string GenerateRequestProperty() => $$""" + /// + /// Request content for {{contentType}} + /// internal {{FullyQualifiedTypeName}} {{PropertyName}} { get; private set; } """; } \ No newline at end of file diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyGenerator.cs index 40588a4..d5c2a2c 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestBodyGenerator.cs @@ -63,12 +63,24 @@ public string GenerateRequestProperty(string propertyName) return $$""" +/// +/// Request content +/// internal {{(Body.Required ? "required " : "")}}RequestContent{{(Body.Required ? "" : "?")}} {{propertyName}} { get; init; } +/// +/// Request content +/// internal sealed class RequestContent {{{ _contentGenerators.AggregateToString(content => content.GenerateRequestProperty()).Indent(4)}} + /// + /// Bind request content from http request + /// + /// Http request to bind from + /// Cancellation token + /// An awaitable task for the request content internal static async Task BindAsync( HttpRequest request, CancellationToken cancellationToken) @@ -94,6 +106,12 @@ internal sealed class RequestContent } } + /// + /// Validate the request content + /// + /// Current validation context + /// Validation level + /// The validation result internal ValidationContext Validate(ValidationContext validationContext, ValidationLevel validationLevel) { switch (true) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs index db72992..e3868e8 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs @@ -38,9 +38,18 @@ internal partial class Request { internal required HttpContext HttpContext { get; init; }{{_parameterGeneratorsGroupedByLocation.AggregateToString(group => $$""" + /// + /// {{group.Key}} parameters + /// internal required {{group.Key}}Parameters {{group.Key}} { get; init; } """)}} {{bodyGenerator.GenerateRequestProperty("Body").Indent(4)}} + /// + /// Bind request object from http request + /// + /// Http context to bind from + /// Cancellation token + /// An awaitable task for the request object public static {{(isAsync ? "async " : "")}}Task BindAsync(HttpContext context, CancellationToken cancellationToken) { var httpRequest = context.Request; @@ -59,6 +68,11 @@ internal partial class Request return {{(isAsync ? "request" : "Task.FromResult(request)")}}; } + /// + /// Validate the request + /// + /// Validation level + /// The validation result internal ValidationContext Validate(ValidationLevel validationLevel) { var validationContext = ValidationContext.ValidContext.UsingStack().UsingResults();{{ @@ -71,6 +85,9 @@ internal ValidationContext Validate(ValidationLevel validationLevel) return validationContext; }{{_parameterGeneratorsGroupedByLocation.AggregateToString(group => $$""" + /// + /// {{group.Key}} parameters + /// internal sealed class {{group.Key}}Parameters {{{group.AggregateToString(generator => generator.GenerateRequestProperty()).Indent(8)}} diff --git a/tests/Example.OpenApi31/openapi.json b/tests/Example.OpenApi31/openapi.json index a1ebadd..165d01a 100644 --- a/tests/Example.OpenApi31/openapi.json +++ b/tests/Example.OpenApi31/openapi.json @@ -18,6 +18,7 @@ { "name": "Fee", "in": "query", + "description": "Fee values to associate with the update.", "schema": { "type": "array", "items": { @@ -101,6 +102,7 @@ "Bar": { "name": "Bar", "in": "header", + "description": "Bar context for the request.", "schema": { "type": "string" }, @@ -109,6 +111,7 @@ "FooId": { "name": "FooId", "in": "path", + "description": "The unique identifier of the foo resource.", "schema": { "type": "integer" }, From db761537272eb4cf4a685f2777fa6172361a06d3 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 19:53:40 +0100 Subject: [PATCH 05/12] avoid using captured parameter --- src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs index 8be4ba1..2d7823e 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs @@ -223,6 +223,7 @@ namespace {{@namespace}}; internal abstract class BaseSecurityRequirementsFilter(WebApiConfiguration configuration) : IEndpointFilter { protected abstract SecurityRequirements Requirements { get; } + protected WebApiConfiguration Configuration { get; } = configuration; protected abstract void HandleForbidden(HttpResponse response); protected abstract void HandleUnauthorized(HttpResponse response); @@ -374,8 +375,8 @@ internal sealed class {{securityRequirementsFilterClassName}}(Operation operatio """)))}} }; - protected override void HandleUnauthorized(HttpResponse response) => operation.Validate(operation.HandleUnauthorized(), configuration).WriteTo(response); - protected override void HandleForbidden(HttpResponse response) => operation.Validate(operation.HandleForbidden(), configuration).WriteTo(response); + protected override void HandleUnauthorized(HttpResponse response) => operation.Validate(operation.HandleUnauthorized(), Configuration).WriteTo(response); + protected override void HandleForbidden(HttpResponse response) => operation.Validate(operation.HandleForbidden(), Configuration).WriteTo(response); } """; } From ffed1f1de59a9046ab680745bfdf420464d42f8d Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 20:10:07 +0100 Subject: [PATCH 06/12] doc(auth): add comments to security objects --- .../CodeGeneration/AuthGenerator.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs index 2d7823e..e5ac4e1 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/AuthGenerator.cs @@ -220,6 +220,9 @@ internal static class {{className}} namespace {{@namespace}}; +/// +/// Base class for handling security requirements for an operation +/// internal abstract class BaseSecurityRequirementsFilter(WebApiConfiguration configuration) : IEndpointFilter { protected abstract SecurityRequirements Requirements { get; } @@ -228,6 +231,7 @@ internal abstract class BaseSecurityRequirementsFilter(WebApiConfiguration confi protected abstract void HandleForbidden(HttpResponse response); protected abstract void HandleUnauthorized(HttpResponse response); + /// public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var httpContext = context.HttpContext; @@ -300,7 +304,15 @@ private static bool ClaimContainsScopes(ClaimsPrincipal? principal, SecuritySche return scopes.All(scope => foundScopes.Contains(scope)); } + /// + /// A declaration of which security mechanisms can be used for an operation. + /// The list of values includes alternative Security Requirement Objects that can be used. Only one of the Security Requirement Objects need to be satisfied to authorize a request. To make security optional, an empty security requirement can be included in the list. + /// internal class SecurityRequirements : List, IAuthorizationRequirement; + + /// + /// Lists the required security schemes to execute an operation. + /// internal class SecurityRequirement : Dictionary; } #nullable restore @@ -321,6 +333,9 @@ internal string GenerateAuthFilters(OpenApiOperation operation, ParameterGenerat _requestFilters.Add(operation, [securityRequirementsFilterClassName]); return $$""" +/// +/// Filter for handling security requirements +/// internal sealed class {{securityRequirementsFilterClassName}} : IEndpointFilter { public ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) @@ -341,6 +356,9 @@ internal sealed class {{securityRequirementsFilterClassName}} : IEndpointFilter : [securityRequirementsFilterClassName]); return (hasSecuritySchemeParameters ? $$""" +/// +/// Filter for extracting security scheme parameters +/// internal sealed class {{securitySchemeParameterFilterClassName}} : IEndpointFilter { public ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) @@ -361,6 +379,9 @@ internal sealed class {{securitySchemeParameterFilterClassName}} : IEndpointFilt """ : string.Empty) + $$""" +/// +/// Filter for handling security requirements +/// internal sealed class {{securityRequirementsFilterClassName}}(Operation operation, WebApiConfiguration configuration) : BaseSecurityRequirementsFilter(configuration) { protected override SecurityRequirements Requirements { get; } = new() @@ -392,12 +413,21 @@ internal sealed class {{securityRequirementsFilterClassName}}(Operation operatio #nullable enable namespace {{@namespace}}; +/// +/// Options for security schemes +/// internal sealed class SecuritySchemeOptions {{{_securitySchemes.AggregateToString(pair => $$""" + {{pair.Value.Description.AsComment("summary", "para")}} public SecuritySchemeOption {{pair.Key.ToPascalCase()}} { get; init; } = new(); """).Indent(4)}} + /// + /// Get scope options + /// + /// Name of security scheme + /// Scope options for the security scheme internal ScopeOptions GetScopeOptions(string scheme) => scheme switch {{{_securitySchemes.AggregateToString(pair => @@ -407,8 +437,14 @@ internal ScopeOptions GetScopeOptions(string scheme) => _ => throw new InvalidOperationException($"Scheme {scheme} is unknown") }; + /// + /// Security scheme option + /// internal sealed class SecuritySchemeOption { + /// + /// Scope options + /// public ScopeOptions Scope {get; init; } = new() { Claim = "scope", @@ -416,11 +452,24 @@ internal sealed class SecuritySchemeOption }; } + /// + /// Scope options + /// internal sealed class ScopeOptions { + /// + /// Name of the claim + /// public required string Claim { get; init; } + + /// + /// Claim format + /// public required ClaimFormat Format { get; init; } + /// + /// Claim formats + /// internal enum ClaimFormat { SpaceDelimited, From 123a9fa27fa5228e90d6b6cb13ef2d79dd57efd0 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 22:38:34 +0100 Subject: [PATCH 07/12] doc: add comments for openapi route methods --- .../CodeGeneration/ApiConfigurationGenerator.cs | 3 +++ .../CodeGeneration/OperationRouterGenerator.cs | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ApiConfigurationGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ApiConfigurationGenerator.cs index 9b240e8..946b325 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ApiConfigurationGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ApiConfigurationGenerator.cs @@ -23,6 +23,9 @@ public sealed class {{ClassName}} public Uri? OpenApiSpecificationUri { get; init; }{{(authGenerator.HasSecuritySchemes ? """ + /// + /// Security scheme options + /// internal SecuritySchemeOptions SecuritySchemeOptions { get; set; } = new(); """ : "")}} } diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationRouterGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationRouterGenerator.cs index bbdc860..e452fe9 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationRouterGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/OperationRouterGenerator.cs @@ -15,8 +15,16 @@ internal SourceCode ForMinimalApi(List<(string Namespace, KeyValuePair +/// Configure routes for OpenAPI operations +/// internal static class OperationRouter { + /// + /// Maps OpenAPI operations + /// + /// Web application to map the operations to + /// The web application internal static WebApplication MapOperations(this WebApplication app) {{{operations.AggregateToString(operation => $""" @@ -32,6 +40,12 @@ internal static WebApplication MapOperations(this WebApplication app) return app; } + /// + /// Adds OpenAPI operations to DI + /// + /// Web application builder to add the operations to + /// Web api configuration + /// The web application builder internal static WebApplicationBuilder AddOperations(this WebApplicationBuilder builder, WebApiConfiguration? configuration = null) {{{operations.AggregateToString(operation => $""" From 92e59f53d08a00bce8fbb91d6b029309b7becf8b Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 22:43:02 +0100 Subject: [PATCH 08/12] doc: add comments to validation extension methods --- .../ValidationExtensionsGenerator.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ValidationExtensionsGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ValidationExtensionsGenerator.cs index 3a6871a..8005a93 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ValidationExtensionsGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ValidationExtensionsGenerator.cs @@ -11,8 +11,17 @@ internal sealed class ValidationExtensionsGenerator(string @namespace) namespace {{@namespace}}; +/// +/// Extension methods for validation +/// internal static class {{ClassName}} { + /// + /// Add schema location to validation results + /// + /// Validation results to add schema location to + /// The schema location uri + /// The validation results internal static ImmutableList WithLocation( this ImmutableList validationResults, Uri? uri) { @@ -36,6 +45,15 @@ private static (JsonReference ValidationLocation, JsonReference SchemaLocation, return (location.Value.ValidationLocation, schemaLocation, location.Value.DocumentLocation); } + /// + /// Validate a json object + /// + /// json object to validate + /// The location of the schema describing the json object + /// Is the object required? + /// Current validation context + /// The validation level + /// The validation result internal static ValidationContext Validate(this T value, string schemaLocation, bool isRequired, From dfd5e18bdbe851eca712493922636893d3460af3 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 22:47:15 +0100 Subject: [PATCH 09/12] doc: add comments to json validation exception --- .../JsonValidationExceptionGenerator.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs index 9b28982..e066b7e 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/JsonValidationExceptionGenerator.cs @@ -21,14 +21,23 @@ internal SourceCode GenerateJsonValidationExceptionClass() => namespace {{@namespace}}; + /// + /// Exception thrown when validation of json objects fail + /// internal sealed class {{ClassName}} : Exception { + /// + /// Create json validation exception + /// internal {{ClassName}}(string message, ImmutableList validationResult) : base( - GetValidationMessage(message, validationResult)) + GetValidationMessage(message, validationResult)) { ValidationResult = validationResult; } + /// + /// The validation result + /// internal ImmutableList ValidationResult { get; } private static string GetValidationMessage(string message, ImmutableList validationResult) From 9fccf49172a3ececb1cd07213863fad11486216b Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 22:55:27 +0100 Subject: [PATCH 10/12] doc: http request and response extension methods --- .../HttpRequestExtensionsGenerator.cs | 15 ++++++++++++--- .../HttpResponseExtensionsGenerator.cs | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs index 9b14792..4feb448 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs @@ -47,6 +47,9 @@ internal SourceCode GenerateHttpRequestExtensionsClass() => namespace {{{@namespace}}}; + /// + /// Extension methods for http request objects + /// internal static class {{{HttpRequestExtensionsClassName}}} { private const string ParameterValueParserVersion = "{{{openApiVersion.GetParameterVersion()}}}"; @@ -64,11 +67,10 @@ private static IParameter GetParameter(string parameterSpecificationAsJson) => /// /// Binds an http parameter to a json type /// - /// + /// Request to bind from /// OpenAPI parameter specification formatted as json /// The type to bind /// The bound instance - /// internal static T Bind(this HttpRequest request, string parameterSpecificationAsJson) where T : struct, IJsonValue @@ -82,8 +84,15 @@ _ when TryParse(request, parameter, out var value) => value.Value, }; } + /// + /// Binds an http body to a json type + /// + /// Request to bind from + /// Cancellation token + /// The type to bind + /// An awaitable task to the bound instance internal static async Task BindBodyAsync(this HttpRequest request, - CancellationToken cancellationToken) + CancellationToken cancellationToken) where T : struct, IJsonValue { var document = await JsonDocument.ParseAsync(request.Body, diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpResponseExtensionsGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpResponseExtensionsGenerator.cs index 3405be0..da36c6c 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpResponseExtensionsGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpResponseExtensionsGenerator.cs @@ -31,12 +31,23 @@ internal SourceCode GenerateHttpResponseExtensionsClass() => using JsonObject = System.Text.Json.Nodes.JsonObject; namespace {{{@namespace}}}; - + + /// + /// Extension methods for http response objects + /// internal static class {{{HttpResponseExtensionsClassName}}} { private static readonly ConcurrentDictionary ParserCache = new(); private const string ParameterValueParserVersion = "{{{openApiSpecVersion.GetParameterVersion()}}}"; + /// + /// Write header to a response object + /// + /// The response object to write the header to + /// OpenAPI specification for the header + /// The header name + /// The header value + /// The type of the header internal static void WriteResponseHeader(this HttpResponse response, string headerSpecificationAsJson, string name, @@ -55,6 +66,12 @@ internal static void WriteResponseHeader(this HttpResponse response, response.Headers[name] = serializedValue; } + /// + /// Write body to a response object + /// + /// The response object to write the body to + /// The value of the body + /// The type of the body internal static void WriteResponseBody(this HttpResponse response, TValue value) where TValue : struct, IJsonValue { From 0372ba87f9e3e8a9aa75c9b072813ec715398d60 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Wed, 11 Feb 2026 22:58:18 +0100 Subject: [PATCH 11/12] doc: add comment for request class --- src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs index e3868e8..8322fe4 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/RequestGenerator.cs @@ -34,6 +34,9 @@ internal SourceCode GenerateRequestClass(string @namespace, string path) namespace {{@namespace}}; +/// +/// Contains the operation's request object +/// internal partial class Request { internal required HttpContext HttpContext { get; init; }{{_parameterGeneratorsGroupedByLocation.AggregateToString(group => From 9ccb680775bbba33f40d1ee9c1a0cde82545bf8e Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Thu, 12 Feb 2026 18:05:00 +0100 Subject: [PATCH 12/12] test: add description and summary where applicable --- .../OpenApiSpecs/openapi-v2.json | 21 +++++++++++- .../OpenApiSpecs/openapi-v3.1.json | 34 ++++++++++++++++--- .../OpenApiSpecs/openapi-v3.2.json | 34 ++++++++++++++++--- .../OpenApiSpecs/openapi-v3.json | 34 ++++++++++++++++--- 4 files changed, 108 insertions(+), 15 deletions(-) diff --git a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v2.json b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v2.json index 1c62a29..f2368c9 100644 --- a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v2.json +++ b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v2.json @@ -27,6 +27,7 @@ "get": { "operationId": "listPets", "summary": "List all pets", + "description": "Retrieves a paginated list of all pets.\n\nSupports filtering by **status** and **tags**, with configurable pagination via `limit` and `offset`.", "tags": ["pets"], "security": [], "parameters": [ @@ -116,6 +117,7 @@ "post": { "operationId": "createPet", "summary": "Create a pet", + "description": "Creates a new pet resource.\n\nRequires authentication via bearer token or API key.", "tags": ["pets"], "security": [{"bearerAuth": []}, {"apiKey": []}], "parameters": [ @@ -166,6 +168,7 @@ "get": { "operationId": "getPet", "summary": "Get a pet by ID", + "description": "Retrieves a single pet by its unique identifier.", "tags": ["pets"], "responses": { "200": { @@ -185,6 +188,7 @@ "put": { "operationId": "updatePet", "summary": "Update a pet", + "description": "Updates an existing pet resource.\n\nThe request body must contain the complete pet data.", "tags": ["pets"], "parameters": [ { @@ -215,6 +219,7 @@ "delete": { "operationId": "deletePet", "summary": "Delete a pet", + "description": "Permanently deletes a pet resource.\n\nRequires both bearer token and API key authentication.", "tags": ["pets"], "security": [{"bearerAuth": [], "apiKey": []}], "parameters": [ @@ -243,6 +248,7 @@ "post": { "operationId": "uploadPetImage", "summary": "Upload pet image", + "description": "Uploads an image file for a specific pet.\n\nAccepts `multipart/form-data` with an optional description.", "tags": ["pets"], "consumes": ["multipart/form-data"], "parameters": [ @@ -283,6 +289,7 @@ "post": { "operationId": "placeOrder", "summary": "Place an order", + "description": "Places a new order for a pet.\n\nRequires OAuth2 authorization with `write:pets` scope.", "tags": ["store"], "security": [{"oauth2": ["write:pets"]}], "parameters": [ @@ -316,6 +323,7 @@ "get": { "operationId": "getOrder", "summary": "Get order by ID", + "description": "Retrieves an order by its unique identifier.", "tags": ["store"], "parameters": [ { @@ -342,6 +350,7 @@ "delete": { "operationId": "cancelOrder", "summary": "Cancel an order", + "description": "Cancels an existing order.\n\nReturns 204 on success or 404 if the order does not exist.", "tags": ["store"], "parameters": [ { @@ -367,6 +376,7 @@ "get": { "operationId": "getInventory", "summary": "Get store inventory", + "description": "Returns inventory counts grouped by pet status.\n\nRequires OAuth2 or API key authentication.", "tags": ["store"], "security": [{"oauth2": ["read:pets"]}, {"apiKey": []}], "responses": { @@ -387,6 +397,7 @@ "post": { "operationId": "createUser", "summary": "Create user", + "description": "Creates a new user account.", "tags": ["users"], "parameters": [ { @@ -413,6 +424,7 @@ "get": { "operationId": "getUser", "summary": "Get user by username", + "description": "Retrieves a user by their username.", "tags": ["users"], "parameters": [ { @@ -441,6 +453,7 @@ "put": { "operationId": "updateUser", "summary": "Update user", + "description": "Updates an existing user's profile information.", "tags": ["users"], "parameters": [ { @@ -472,6 +485,7 @@ "delete": { "operationId": "deleteUser", "summary": "Delete user", + "description": "Permanently deletes a user account.", "tags": ["users"], "parameters": [ { @@ -496,6 +510,7 @@ "post": { "operationId": "loginUser", "summary": "User login", + "description": "Authenticates a user with username and password.\n\nReturns a session token on success.", "tags": ["users"], "security": [{"basicAuth": []}], "parameters": [ @@ -786,20 +801,24 @@ }, "securityDefinitions": { "basicAuth": { - "type": "basic" + "type": "basic", + "description": "HTTP Basic authentication.\n\nProvide a username and password encoded as Base64 in the Authorization header." }, "bearerAuth": { "type": "apiKey", + "description": "Bearer token authentication via the Authorization header.\n\nPass the token as the value of the X-Api-Key header.", "name": "Authorization", "in": "header" }, "apiKey": { "type": "apiKey", + "description": "API key authentication passed via the `X-Api-Key` header.", "in": "header", "name": "X-Api-Key" }, "oauth2": { "type": "oauth2", + "description": "OAuth 2.0 authorization code flow.\n\nGrants access based on the requested scopes.", "flow": "accessCode", "authorizationUrl": "https://example.com/oauth/authorize", "tokenUrl": "https://example.com/oauth/token", diff --git a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.1.json b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.1.json index 4c06ea7..c94b635 100644 --- a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.1.json +++ b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.1.json @@ -29,6 +29,7 @@ "get": { "operationId": "listPets", "summary": "List all pets", + "description": "Retrieves a paginated list of all pets.\n\nSupports filtering by **status** and **tags**, with configurable pagination via `limit` and `offset`.", "tags": ["pets"], "security": [], "parameters": [ @@ -98,6 +99,7 @@ "post": { "operationId": "createPet", "summary": "Create a pet", + "description": "Creates a new pet resource.\n\nRequires authentication via bearer token or API key.", "tags": ["pets"], "security": [{"bearerAuth": []}, {"apiKey": []}], "requestBody": {"$ref": "#/components/requestBodies/NewPetBody"}, @@ -132,6 +134,7 @@ "get": { "operationId": "getPet", "summary": "Get a pet by ID", + "description": "Retrieves a single pet by its unique identifier.", "tags": ["pets"], "responses": { "200": {"$ref": "#/components/responses/PetResponse"}, @@ -141,6 +144,7 @@ "put": { "operationId": "updatePet", "summary": "Update a pet", + "description": "Updates an existing pet resource.\n\nThe request body must contain the complete pet data.", "tags": ["pets"], "requestBody": { "description": "Pet data to update", @@ -170,6 +174,7 @@ "delete": { "operationId": "deletePet", "summary": "Delete a pet", + "description": "Permanently deletes a pet resource.\n\nRequires both bearer token and API key authentication.", "tags": ["pets"], "security": [{"bearerAuth": [], "apiKey": []}], "parameters": [ @@ -195,12 +200,14 @@ "post": { "operationId": "uploadPetImage", "summary": "Upload pet image", + "description": "Uploads an image file for a specific pet.\n\nAccepts `multipart/form-data` with an optional description.", "tags": ["pets"], "security": [{"mutualTLS": []}], "parameters": [ { "name": "petId", "in": "path", + "description": "Pet ID", "required": true, "schema": { "type": "integer" @@ -247,6 +254,7 @@ "post": { "operationId": "placeOrder", "summary": "Place an order", + "description": "Places a new order for a pet.\n\nRequires OAuth2 authorization with `write:pets` scope.", "tags": ["store"], "security": [{"oauth2": ["write:pets"]}], "requestBody": {"$ref": "#/components/requestBodies/OrderBody"}, @@ -269,6 +277,7 @@ "get": { "operationId": "getOrder", "summary": "Get order by ID", + "description": "Retrieves an order by its unique identifier.", "tags": ["store"], "parameters": [ {"$ref": "#/components/parameters/OrderIdPath"} @@ -283,11 +292,13 @@ "delete": { "operationId": "cancelOrder", "summary": "Cancel an order", + "description": "Cancels an existing order.\n\nReturns 204 on success or 404 if the order does not exist.", "tags": ["store"], "parameters": [ { "name": "orderId", "in": "path", + "description": "Order ID", "required": true, "schema": { "type": "string", @@ -309,6 +320,7 @@ "get": { "operationId": "getInventory", "summary": "Get store inventory", + "description": "Returns inventory counts grouped by pet status.\n\nRequires OAuth2 or API key authentication.", "tags": ["store"], "security": [{"oauth2": ["read:pets"]}, {"apiKey": []}], "responses": { @@ -332,6 +344,7 @@ "post": { "operationId": "createUser", "summary": "Create user", + "description": "Creates a new user account.", "tags": ["users"], "requestBody": { "required": true, @@ -355,6 +368,7 @@ "get": { "operationId": "getUser", "summary": "Get user by username", + "description": "Retrieves a user by their username.\n\nRequires OpenID Connect authentication.", "tags": ["users"], "security": [{"openIdConnect": ["profile", "email"]}], "responses": { @@ -365,6 +379,7 @@ "put": { "operationId": "updateUser", "summary": "Update user", + "description": "Updates an existing user's profile information.", "tags": ["users"], "requestBody": {"$ref": "#/components/requestBodies/UserBody"}, "responses": { @@ -377,11 +392,13 @@ "delete": { "operationId": "deleteUser", "summary": "Delete user", + "description": "Permanently deletes a user account.", "tags": ["users"], "parameters": [ { "name": "username", "in": "path", + "description": "The username of the user to delete", "required": true, "schema": { "type": "string" @@ -402,6 +419,7 @@ "post": { "operationId": "loginUser", "summary": "User login", + "description": "Authenticates a user with username and password.\n\nReturns a session token on success.", "tags": ["users"], "security": [{"basicAuth": []}], "requestBody": { @@ -855,27 +873,33 @@ "securitySchemes": { "basicAuth": { "type": "http", - "scheme": "basic" + "scheme": "basic", + "description": "HTTP Basic authentication.\n\nProvide a username and password encoded as Base64 in the Authorization header." }, "bearerAuth": { "type": "http", "scheme": "bearer", - "bearerFormat": "JWT" + "bearerFormat": "JWT", + "description": "Bearer token authentication.\n\nInclude a JWT in the `Authorization` header." }, "apiKey": { "type": "apiKey", "in": "header", - "name": "X-Api-Key" + "name": "X-Api-Key", + "description": "API key authentication passed via the `X-Api-Key` header." }, "mutualTLS": { - "type": "mutualTLS" + "type": "mutualTLS", + "description": "Mutual TLS (mTLS) client certificate authentication.\n\nThe client must present a valid X.509 certificate during the TLS handshake." }, "openIdConnect": { "type": "openIdConnect", - "openIdConnectUrl": "https://example.com/.well-known/openid-configuration" + "openIdConnectUrl": "https://example.com/.well-known/openid-configuration", + "description": "OpenID Connect authentication.\n\nDiscovery document is available at the openIdConnectUrl endpoint." }, "oauth2": { "type": "oauth2", + "description": "OAuth 2.0 authorization code flow.\n\nGrants access based on the requested scopes.", "flows": { "authorizationCode": { "authorizationUrl": "https://example.com/oauth/authorize", diff --git a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.2.json b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.2.json index a0f8b7c..4460085 100644 --- a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.2.json +++ b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.2.json @@ -29,6 +29,7 @@ "get": { "operationId": "listPets", "summary": "List all pets", + "description": "Retrieves a paginated list of all pets.\n\nSupports filtering by **status** and **tags**, with configurable pagination via `limit` and `offset`.", "tags": ["pets"], "security": [], "parameters": [ @@ -98,6 +99,7 @@ "post": { "operationId": "createPet", "summary": "Create a pet", + "description": "Creates a new pet resource.\n\nRequires authentication via bearer token or API key.", "tags": ["pets"], "security": [ {"bearerAuth": []}, @@ -135,6 +137,7 @@ "get": { "operationId": "getPet", "summary": "Get a pet by ID", + "description": "Retrieves a single pet by its unique identifier.", "tags": ["pets"], "responses": { "200": {"$ref": "#/components/responses/PetResponse"}, @@ -144,6 +147,7 @@ "put": { "operationId": "updatePet", "summary": "Update a pet", + "description": "Updates an existing pet resource.\n\nThe request body must contain the complete pet data.", "tags": ["pets"], "requestBody": { "description": "Pet data to update", @@ -173,6 +177,7 @@ "delete": { "operationId": "deletePet", "summary": "Delete a pet", + "description": "Permanently deletes a pet resource.\n\nRequires both bearer token and API key authentication.", "tags": ["pets"], "security": [ {"bearerAuth": [], "apiKey": []} @@ -200,12 +205,14 @@ "post": { "operationId": "uploadPetImage", "summary": "Upload pet image", + "description": "Uploads an image file for a specific pet.\n\nAccepts `multipart/form-data` with an optional description.", "tags": ["pets"], "security": [{"mutualTLS": []}], "parameters": [ { "name": "petId", "in": "path", + "description": "Pet ID", "required": true, "schema": { "type": "integer" @@ -252,6 +259,7 @@ "post": { "operationId": "placeOrder", "summary": "Place an order", + "description": "Places a new order for a pet.\n\nRequires OAuth2 authorization with `write:pets` scope.", "tags": ["store"], "security": [ {"oauth2": ["write:pets"]} @@ -276,6 +284,7 @@ "get": { "operationId": "getOrder", "summary": "Get order by ID", + "description": "Retrieves an order by its unique identifier.", "tags": ["store"], "parameters": [ {"$ref": "#/components/parameters/OrderIdPath"} @@ -290,11 +299,13 @@ "delete": { "operationId": "cancelOrder", "summary": "Cancel an order", + "description": "Cancels an existing order.\n\nReturns 204 on success or 404 if the order does not exist.", "tags": ["store"], "parameters": [ { "name": "orderId", "in": "path", + "description": "Order ID", "required": true, "schema": { "type": "string", @@ -316,6 +327,7 @@ "get": { "operationId": "getInventory", "summary": "Get store inventory", + "description": "Returns inventory counts grouped by pet status.\n\nRequires OAuth2 or API key authentication.", "tags": ["store"], "security": [ {"oauth2": ["read:pets"]}, @@ -342,6 +354,7 @@ "post": { "operationId": "createUser", "summary": "Create user", + "description": "Creates a new user account.", "tags": ["users"], "requestBody": { "required": true, @@ -365,6 +378,7 @@ "get": { "operationId": "getUser", "summary": "Get user by username", + "description": "Retrieves a user by their username.\n\nRequires OpenID Connect authentication.", "tags": ["users"], "security": [{"openIdConnect": ["profile", "email"]}], "responses": { @@ -375,6 +389,7 @@ "put": { "operationId": "updateUser", "summary": "Update user", + "description": "Updates an existing user's profile information.", "tags": ["users"], "requestBody": {"$ref": "#/components/requestBodies/UserBody"}, "responses": { @@ -387,11 +402,13 @@ "delete": { "operationId": "deleteUser", "summary": "Delete user", + "description": "Permanently deletes a user account.", "tags": ["users"], "parameters": [ { "name": "username", "in": "path", + "description": "The username of the user to delete", "required": true, "schema": { "type": "string" @@ -412,6 +429,7 @@ "post": { "operationId": "loginUser", "summary": "User login", + "description": "Authenticates a user with username and password.\n\nReturns a session token on success.", "tags": ["users"], "security": [{"basicAuth": []}], "requestBody": { @@ -865,27 +883,33 @@ "securitySchemes": { "basicAuth": { "type": "http", - "scheme": "basic" + "scheme": "basic", + "description": "HTTP Basic authentication.\n\nProvide a username and password encoded as Base64 in the Authorization header." }, "bearerAuth": { "type": "http", "scheme": "bearer", - "bearerFormat": "JWT" + "bearerFormat": "JWT", + "description": "Bearer token authentication.\n\nInclude a JWT in the `Authorization` header." }, "apiKey": { "type": "apiKey", "in": "header", - "name": "X-Api-Key" + "name": "X-Api-Key", + "description": "API key authentication passed via the `X-Api-Key` header." }, "mutualTLS": { - "type": "mutualTLS" + "type": "mutualTLS", + "description": "Mutual TLS (mTLS) client certificate authentication.\n\nThe client must present a valid X.509 certificate during the TLS handshake." }, "openIdConnect": { "type": "openIdConnect", - "openIdConnectUrl": "https://example.com/.well-known/openid-configuration" + "openIdConnectUrl": "https://example.com/.well-known/openid-configuration", + "description": "OpenID Connect authentication.\n\nDiscovery document is available at the openIdConnectUrl endpoint." }, "oauth2": { "type": "oauth2", + "description": "OAuth 2.0 authorization code flow.\n\nGrants access based on the requested scopes.", "flows": { "authorizationCode": { "authorizationUrl": "https://auth.example.com/authorize", diff --git a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.json b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.json index d266043..c3e4e96 100644 --- a/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.json +++ b/tests/OpenAPI.WebApiGenerator.Tests/OpenApiSpecs/openapi-v3.json @@ -27,6 +27,7 @@ "get": { "operationId": "listPets", "summary": "List all pets", + "description": "Retrieves a paginated list of all pets.\n\nSupports filtering by **status** and **tags**, with configurable pagination via `limit` and `offset`.", "tags": ["pets"], "security": [], "parameters": [ @@ -143,6 +144,7 @@ "post": { "operationId": "createPet", "summary": "Create a pet", + "description": "Creates a new pet resource.\n\nRequires authentication via bearer token or API key.", "tags": ["pets"], "security": [{"bearerAuth": []}, {"apiKey": []}], "requestBody": { @@ -205,6 +207,7 @@ "get": { "operationId": "getPet", "summary": "Get a pet by ID", + "description": "Retrieves a single pet by its unique identifier.", "tags": ["pets"], "responses": { "200": { @@ -232,6 +235,7 @@ "put": { "operationId": "updatePet", "summary": "Update a pet", + "description": "Updates an existing pet resource.\n\nThe request body must contain the complete pet data.", "tags": ["pets"], "requestBody": { "description": "Pet data to update", @@ -270,6 +274,7 @@ "delete": { "operationId": "deletePet", "summary": "Delete a pet", + "description": "Permanently deletes a pet resource.\n\nRequires both bearer token and API key authentication.", "tags": ["pets"], "security": [{"bearerAuth": [], "apiKey": []}], "parameters": [ @@ -304,11 +309,13 @@ "post": { "operationId": "uploadPetImage", "summary": "Upload pet image", + "description": "Uploads an image file for a specific pet.\n\nAccepts `multipart/form-data` with an optional description.", "tags": ["pets"], "parameters": [ { "name": "petId", "in": "path", + "description": "Pet ID", "required": true, "schema": { "type": "integer", @@ -355,6 +362,7 @@ "post": { "operationId": "placeOrder", "summary": "Place an order", + "description": "Places a new order for a pet.\n\nRequires OAuth2 authorization with `write:pets` scope.", "tags": ["store"], "security": [{"oauth2": ["write:pets"]}], "requestBody": { @@ -395,11 +403,13 @@ "get": { "operationId": "getOrder", "summary": "Get order by ID", + "description": "Retrieves an order by its unique identifier.", "tags": ["store"], "parameters": [ { "name": "orderId", "in": "path", + "description": "Order ID", "required": true, "schema": { "type": "string", @@ -426,11 +436,13 @@ "delete": { "operationId": "cancelOrder", "summary": "Cancel an order", + "description": "Cancels an existing order.\n\nReturns 204 on success or 404 if the order does not exist.", "tags": ["store"], "parameters": [ { "name": "orderId", "in": "path", + "description": "Order ID", "required": true, "schema": { "type": "string", @@ -452,6 +464,7 @@ "get": { "operationId": "getInventory", "summary": "Get store inventory", + "description": "Returns inventory counts grouped by pet status.\n\nRequires OAuth2 or API key authentication.", "tags": ["store"], "security": [{"oauth2": ["read:pets"]}, {"apiKey": []}], "responses": { @@ -476,6 +489,7 @@ "post": { "operationId": "createUser", "summary": "Create user", + "description": "Creates a new user account.", "tags": ["users"], "requestBody": { "required": true, @@ -505,12 +519,14 @@ "get": { "operationId": "getUser", "summary": "Get user by username", + "description": "Retrieves a user by their username.\n\nRequires OpenID Connect authentication.", "tags": ["users"], "security": [{"openIdConnect": ["profile", "email"]}], "parameters": [ { "name": "username", "in": "path", + "description": "The username of the user to retrieve", "required": true, "schema": { "type": "string", @@ -539,11 +555,13 @@ "put": { "operationId": "updateUser", "summary": "Update user", + "description": "Updates an existing user's profile information.", "tags": ["users"], "parameters": [ { "name": "username", "in": "path", + "description": "The username of the user to update", "required": true, "schema": { "type": "string" @@ -572,11 +590,13 @@ "delete": { "operationId": "deleteUser", "summary": "Delete user", + "description": "Permanently deletes a user account.", "tags": ["users"], "parameters": [ { "name": "username", "in": "path", + "description": "The username of the user to delete", "required": true, "schema": { "type": "string" @@ -597,6 +617,7 @@ "post": { "operationId": "loginUser", "summary": "User login", + "description": "Authenticates a user with username and password.\n\nReturns a session token on success.", "tags": ["users"], "security": [{"basicAuth": []}], "requestBody": { @@ -897,24 +918,29 @@ "securitySchemes": { "basicAuth": { "type": "http", - "scheme": "basic" + "scheme": "basic", + "description": "HTTP Basic authentication.\n\nProvide a username and password encoded as Base64 in the Authorization header." }, "bearerAuth": { "type": "http", "scheme": "bearer", - "bearerFormat": "JWT" + "bearerFormat": "JWT", + "description": "Bearer token authentication.\n\nInclude a JWT in the `Authorization` header." }, "apiKey": { "type": "apiKey", "in": "header", - "name": "X-Api-Key" + "name": "X-Api-Key", + "description": "API key authentication passed via the `X-Api-Key` header." }, "openIdConnect": { "type": "openIdConnect", - "openIdConnectUrl": "https://example.com/.well-known/openid-configuration" + "openIdConnectUrl": "https://example.com/.well-known/openid-configuration", + "description": "OpenID Connect authentication.\n\nDiscovery document is available at the openIdConnectUrl endpoint." }, "oauth2": { "type": "oauth2", + "description": "OAuth 2.0 authorization code flow.\n\nGrants access based on the requested scopes.", "flows": { "authorizationCode": { "authorizationUrl": "https://example.com/oauth/authorize",