From 69cefb74a673bbf7e292ff18237cafcf23a8ad89 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Mon, 2 Jun 2025 14:31:19 +0200 Subject: [PATCH 01/14] Update OpenAPI range formatting to format using the target culture and Update tests to write in the InvariantCulture --- .../SchemaTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs index 290100cdbfc3..61194e27fbbc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http; From f3244885357ff06e8fe773d6bee1d894bace97ce Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Mon, 2 Jun 2025 17:00:02 +0200 Subject: [PATCH 02/14] Updated schema reference XML Comment handling. Update handling of nested schemas and referenced schemas --- .../Extensions/OpenApiDocumentExtensions.cs | 5 +- .../SchemaTests.cs | 75 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs index c53465f8226c..fc96ad196397 100644 --- a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs +++ b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs @@ -21,6 +21,9 @@ public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument do document.Workspace ??= new(); var location = document.BaseUri + "/components/schemas/" + schemaId; document.Workspace.RegisterComponentForDocument(document, schema, location); - return new OpenApiSchemaReference(schemaId, document); + return new OpenApiSchemaReference(schemaId, document) + { + Description = schema.Description, + }; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs index 61194e27fbbc..1f8065c1a756 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs @@ -19,7 +19,10 @@ public async Task SupportsXmlCommentsOnSchemas() var builder = WebApplication.CreateBuilder(); -builder.Services.AddOpenApi(); +builder.Services.AddOpenApi(options => { + var prevCreateSchemaReferenceId = options.CreateSchemaReferenceId; + options.CreateSchemaReferenceId = (x) => x.Type == typeof(AddressNested) ? null : prevCreateSchemaReferenceId(x); +}); var app = builder.Build(); @@ -31,6 +34,7 @@ public async Task SupportsXmlCommentsOnSchemas() app.MapPost("/todo-with-description", (TodoWithDescription todo) => { }); app.MapPost("/type-with-examples", (TypeWithExamples typeWithExamples) => { }); app.MapPost("/user", (User user) => { }); +app.MapPost("/company", (Company company) => { }); app.Run(); @@ -175,6 +179,60 @@ internal class User : IUser /// public string Name { get; set; } } + +/// +/// An address. +/// +public class AddressWithSummary +{ + public string Street { get; set; } +} + +public class AddressWithoutSummary +{ + public string Street { get; set; } +} + +/// +/// An address. +/// +public class AddressNested +{ + public string Street { get; set; } +} + +public class Company +{ + /// + /// Billing address. + /// + public AddressWithSummary BillingAddressClassWithSummary { get; set; } + + /// + /// Billing address. + /// + public AddressWithoutSummary BillingAddressClassWithoutSummary { get; set; } + + /// + /// Billing address. + /// + public AddressNested BillingAddressNested { get; set; } + + /// + /// Visiting address. + /// + public AddressWithSummary VisitingAddressClassWithSummary { get; set; } + + /// + /// Visiting address. + /// + public AddressWithoutSummary VisitingAddressClassWithoutSummary { get; set; } + + /// + /// Visiting address. + /// + public AddressNested VisitingAddressNested { get; set; } +} """; var generator = new XmlCommentGenerator(); await SnapshotTestHelper.Verify(source, generator, out var compilation); @@ -258,6 +316,21 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => var user = path.RequestBody.Content["application/json"].Schema; Assert.Equal("The unique identifier for the user.", user.Properties["id"].Description); Assert.Equal("The user's display name.", user.Properties["name"].Description); + + path = document.Paths["/company"].Operations[HttpMethod.Post]; + var company = path.RequestBody.Content["application/json"].Schema; + Assert.Equal("Billing address.", company.Properties["billingAddressClassWithSummary"].Description); + Assert.Equal("Billing address.", company.Properties["billingAddressClassWithoutSummary"].Description); + Assert.Equal("Billing address.", company.Properties["billingAddressNested"].Description); + Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithSummary"].Description); + Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithoutSummary"].Description); + Assert.Equal("Visiting address.", company.Properties["visitingAddressNested"].Description); + + var addressWithSummary = document.Components.Schemas["AddressWithSummary"]; + Assert.Equal("An address.", addressWithSummary.Description); + + var addressWithoutSummary = document.Components.Schemas["AddressWithoutSummary"]; + Assert.Null(addressWithSummary.Description); }); } } From 98f8c59765312bbac55cb6166f349a9153527f5f Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Thu, 31 Jul 2025 16:37:59 +0200 Subject: [PATCH 03/14] Only set a schema property description when it's not a schema reference Update XmlCommentGenerator.Emitter.cs to skip applying property descriptions when it's a schema reference. Schema transformers get the full schema and not the schema reference. So when the description is updated it would update it for all locations. --- .../gen/XmlCommentGenerator.Emitter.cs | 25 +- .../Extensions/OpenApiDocumentExtensions.cs | 5 +- .../CompletenessTests.cs | 6 +- .../SchemaTests.cs | 107 +++- ...ApiXmlCommentSupport.generated.verified.cs | 24 +- ...ApiXmlCommentSupport.generated.verified.cs | 24 +- ...ApiXmlCommentSupport.generated.verified.cs | 24 +- ...ApiXmlCommentSupport.generated.verified.cs | 24 +- ...ApiXmlCommentSupport.generated.verified.cs | 24 +- ...ApiXmlCommentSupport.generated.verified.cs | 35 +- ...ApiXmlCommentSupport.generated.verified.cs | 516 ++++++++++++++++++ ...ApiXmlCommentSupport.generated.verified.cs | 26 +- 12 files changed, 754 insertions(+), 86 deletions(-) create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index cf8bb5e875eb..268dce20983c 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -449,8 +449,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -460,14 +474,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } @@ -507,6 +513,7 @@ file static class GeneratedServiceCollectionExtensions {{GenerateAddOpenApiInterceptions(groupedAddOpenApiInvocations)}} } } + """; internal static string GetAddOpenApiInterceptor(AddOpenApiOverloadVariant overloadVariant) => overloadVariant switch diff --git a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs index fc96ad196397..c53465f8226c 100644 --- a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs +++ b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs @@ -21,9 +21,6 @@ public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument do document.Workspace ??= new(); var location = document.BaseUri + "/components/schemas/" + schemaId; document.Workspace.RegisterComponentForDocument(document, schema, location); - return new OpenApiSchemaReference(schemaId, document) - { - Description = schema.Description, - }; + return new OpenApiSchemaReference(schemaId, document); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs index 82ae70006c10..020b6ec61382 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs @@ -516,9 +516,9 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.Equal("This class validates the behavior for mapping\ngeneric types to open generics for use in\ntypeof expressions.", genericParent.Description, ignoreLineEndingDifferences: true); Assert.Equal("This property is a nullable value type.", genericParent.Properties["id"].Description); Assert.Equal("This property is a nullable reference type.", genericParent.Properties["name"].Description); - Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description); - Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description); - Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description); + //Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description); + //Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description); + //Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description); path = document.Paths["/params-and-param-refs"].Operations[HttpMethod.Post]; var paramsAndParamRefs = path.RequestBody.Content["application/json"].Schema; diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs index 1f8065c1a756..d99c0e7f938f 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs @@ -319,18 +319,115 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => path = document.Paths["/company"].Operations[HttpMethod.Post]; var company = path.RequestBody.Content["application/json"].Schema; - Assert.Equal("Billing address.", company.Properties["billingAddressClassWithSummary"].Description); - Assert.Equal("Billing address.", company.Properties["billingAddressClassWithoutSummary"].Description); Assert.Equal("Billing address.", company.Properties["billingAddressNested"].Description); - Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithSummary"].Description); - Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithoutSummary"].Description); Assert.Equal("Visiting address.", company.Properties["visitingAddressNested"].Description); var addressWithSummary = document.Components.Schemas["AddressWithSummary"]; Assert.Equal("An address.", addressWithSummary.Description); var addressWithoutSummary = document.Components.Schemas["AddressWithoutSummary"]; - Assert.Null(addressWithSummary.Description); + Assert.Null(addressWithoutSummary.Description); + }); + } + + [Fact] + public async Task XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas() + { + var source = """ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(); + +builder.Services.AddOpenApi(options => { + var prevCreateSchemaReferenceId = options.CreateSchemaReferenceId; + options.CreateSchemaReferenceId = (x) => x.Type == typeof(ModelInline) ? null : prevCreateSchemaReferenceId(x); +}); + +var app = builder.Build(); + +app.MapPost("/example", (RootModel model) => { }); + +app.Run(); + +/// +/// Comment on class ModelWithSummary. +/// +public class ModelWithSummary +{ + public string Street { get; set; } +} + +public class ModelWithoutSummary +{ + public string Street { get; set; } +} + +/// +/// Comment on class ModelInline. +/// +public class ModelInline +{ + public string Street { get; set; } +} + +/// +/// Comment on class RootModel. +/// +public class RootModel +{ + /// + /// Comment on property FirstModelWithSummary. + /// + public ModelWithSummary FirstModelWithSummary { get; set; } + + /// + /// Comment on property SecondModelWithSummary. + /// + public ModelWithSummary SecondModelWithSummary { get; set; } + + /// + /// Comment on property FirstModelWithoutSummary. + /// + public ModelWithoutSummary FirstModelWithoutSummary { get; set; } + + /// + /// Comment on property SecondModelWithoutSummary. + /// + public ModelWithoutSummary SecondModelWithoutSummary { get; set; } + + /// + /// Comment on property FirstModelInline. + /// + public ModelInline FirstModelInline { get; set; } + + /// + /// Comment on property SecondModelInline. + /// + public ModelInline SecondModelInline { get; set; } +} +"""; + var generator = new XmlCommentGenerator(); + await SnapshotTestHelper.Verify(source, generator, out var compilation); + await SnapshotTestHelper.VerifyOpenApi(compilation, document => + { + var path = document.Paths["/example"].Operations[HttpMethod.Post]; + var exampleOperationBodySchema = path.RequestBody.Content["application/json"].Schema; + Assert.Equal("Comment on class RootModel.", exampleOperationBodySchema.Description); + + var rootModelSchema = document.Components.Schemas["RootModel"]; + Assert.Equal("Comment on class RootModel.", rootModelSchema.Description); + + var modelWithSummary = document.Components.Schemas["ModelWithSummary"]; + Assert.Equal("Comment on class ModelWithSummary.", modelWithSummary.Description); + + var modelWithoutSummary = document.Components.Schemas["ModelWithoutSummary"]; + Assert.Null(modelWithoutSummary.Description); + + //Assert.DoesNotContain("ModelInline", document.Components.Schemas.Keys); + Assert.Equal("Comment on property FirstModelInline.", rootModelSchema.Properties["firstModelInline"].Description); + Assert.Equal("Comment on property SecondModelInline.", rootModelSchema.Properties["secondModelInline"].Description); }); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index dea5968bdf63..71ca6f5d3fc5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -431,8 +431,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -442,14 +456,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index fe18f3d0de2c..37157431f9dc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -460,8 +460,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -471,14 +485,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 69e1bbcab4b4..050c5a8d9b91 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -552,8 +552,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -563,14 +577,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 4baa534fd17c..2e7a60909b32 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -435,8 +435,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -446,14 +460,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index fc1fbec8ab34..e448edfb42e6 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -453,8 +453,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -464,14 +478,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 33e3e561e958..11c278ff80c0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -76,6 +76,8 @@ private static Dictionary GenerateCacheEntries() cache.Add(@"T:ProjectBoard.ProtectedInternalElement", new XmlComment(@"Can find this XML comment.", null, null, null, null, false, null, null, null)); cache.Add(@"T:ProjectRecord", new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, [new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false)], null)); cache.Add(@"T:User", new XmlComment(null, null, null, null, null, false, null, null, null)); + cache.Add(@"T:AddressWithSummary", new XmlComment(@"An address.", null, null, null, null, false, null, null, null)); + cache.Add(@"T:AddressNested", new XmlComment(@"An address.", null, null, null, null, false, null, null, null)); cache.Add(@"P:ProjectBoard.ProtectedInternalElement.Name", new XmlComment(@"The unique identifier for the element.", null, null, null, null, false, null, null, null)); cache.Add(@"P:ProjectRecord.Name", new XmlComment(@"The name of the project.", null, null, null, null, false, null, null, null)); cache.Add(@"P:ProjectRecord.Description", new XmlComment(@"The description of the project.", null, null, null, null, false, null, null, null)); @@ -100,6 +102,12 @@ private static Dictionary GenerateCacheEntries() cache.Add(@"P:IUser.Name", new XmlComment(@"The user's display name.", null, null, null, null, false, null, null, null)); cache.Add(@"P:User.Id", new XmlComment(@"The unique identifier for the user.", null, null, null, null, false, null, null, null)); cache.Add(@"P:User.Name", new XmlComment(@"The user's display name.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:Company.BillingAddressClassWithSummary", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:Company.BillingAddressClassWithoutSummary", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:Company.BillingAddressNested", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:Company.VisitingAddressClassWithSummary", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:Company.VisitingAddressClassWithoutSummary", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:Company.VisitingAddressNested", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null)); return cache; } @@ -461,8 +469,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -472,14 +494,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } @@ -517,12 +531,13 @@ file static class JsonNodeExtensions file static class GeneratedServiceCollectionExtensions { [InterceptsLocation] - public static IServiceCollection AddOpenApi(this IServiceCollection services) + public static IServiceCollection AddOpenApi(this IServiceCollection services, Action configureOptions) { return services.AddOpenApi("v1", options => { options.AddSchemaTransformer(new XmlCommentSchemaTransformer()); options.AddOperationTransformer(new XmlCommentOperationTransformer()); + configureOptions(options); }); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs new file mode 100644 index 000000000000..694d548d3cc0 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -0,0 +1,516 @@ +//HintName: OpenApiXmlCommentSupport.generated.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +// Suppress warnings about obsolete types and members +// in generated code +#pragma warning disable CS0612, CS0618 + +namespace System.Runtime.CompilerServices +{ + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.AspNetCore.OpenApi.Generated +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Text.Json; + using System.Text.Json.Nodes; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.OpenApi; + using Microsoft.AspNetCore.Mvc.Controllers; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.OpenApi; + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlComment( + string? Summary, + string? Description, + string? Remarks, + string? Returns, + string? Value, + bool Deprecated, + List? Examples, + List? Parameters, + List? Responses); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlParameterComment(string? Name, string? Description, string? Example, bool Deprecated); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file record XmlResponseComment(string Code, string? Description, string? Example); + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class XmlCommentCache + { + private static Dictionary? _cache; + public static Dictionary Cache => _cache ??= GenerateCacheEntries(); + + private static Dictionary GenerateCacheEntries() + { + var cache = new Dictionary(); + + cache.Add(@"T:ModelWithSummary", new XmlComment(@"Comment on class ModelWithSummary.", null, null, null, null, false, null, null, null)); + cache.Add(@"T:ModelInline", new XmlComment(@"Comment on class ModelInline.", null, null, null, null, false, null, null, null)); + cache.Add(@"T:RootModel", new XmlComment(@"Comment on class RootModel.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:RootModel.FirstModelWithSummary", new XmlComment(@"Comment on property FirstModelWithSummary.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:RootModel.SecondModelWithSummary", new XmlComment(@"Comment on property SecondModelWithSummary.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:RootModel.FirstModelWithoutSummary", new XmlComment(@"Comment on property FirstModelWithoutSummary.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:RootModel.SecondModelWithoutSummary", new XmlComment(@"Comment on property SecondModelWithoutSummary.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:RootModel.FirstModelInline", new XmlComment(@"Comment on property FirstModelInline.", null, null, null, null, false, null, null, null)); + cache.Add(@"P:RootModel.SecondModelInline", new XmlComment(@"Comment on property SecondModelInline.", null, null, null, null, false, null, null, null)); + + return cache; + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class DocumentationCommentIdHelper + { + /// + /// Generates a documentation comment ID for a type. + /// Example: T:Namespace.Outer+Inner`1 becomes T:Namespace.Outer.Inner`1 + /// + public static string CreateDocumentationId(this Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return "T:" + GetTypeDocId(type, includeGenericArguments: false, omitGenericArity: false); + } + + /// + /// Generates a documentation comment ID for a property. + /// Example: P:Namespace.ContainingType.PropertyName or for an indexer P:Namespace.ContainingType.Item(System.Int32) + /// + public static string CreateDocumentationId(this PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + var sb = new StringBuilder(); + sb.Append("P:"); + + if (property.DeclaringType != null) + { + sb.Append(GetTypeDocId(property.DeclaringType, includeGenericArguments: false, omitGenericArity: false)); + } + + sb.Append('.'); + sb.Append(property.Name); + + // For indexers, include the parameter list. + var indexParams = property.GetIndexParameters(); + if (indexParams.Length > 0) + { + sb.Append('('); + for (int i = 0; i < indexParams.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append(GetTypeDocId(indexParams[i].ParameterType, includeGenericArguments: true, omitGenericArity: false)); + } + sb.Append(')'); + } + + return sb.ToString(); + } + + /// + /// Generates a documentation comment ID for a method (or constructor). + /// For example: + /// M:Namespace.ContainingType.MethodName(ParamType1,ParamType2)~ReturnType + /// M:Namespace.ContainingType.#ctor(ParamType) + /// + public static string CreateDocumentationId(this MethodInfo method) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + var sb = new StringBuilder(); + sb.Append("M:"); + + // Append the fully qualified name of the declaring type. + if (method.DeclaringType != null) + { + sb.Append(GetTypeDocId(method.DeclaringType, includeGenericArguments: false, omitGenericArity: false)); + } + + sb.Append('.'); + + // Append the method name, handling constructors specially. + if (method.IsConstructor) + { + sb.Append(method.IsStatic ? "#cctor" : "#ctor"); + } + else + { + sb.Append(method.Name); + if (method.IsGenericMethod) + { + sb.Append("``"); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", method.GetGenericArguments().Length); + } + } + + // Append the parameter list, if any. + var parameters = method.GetParameters(); + if (parameters.Length > 0) + { + sb.Append('('); + for (int i = 0; i < parameters.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + // Omit the generic arity for the parameter type. + sb.Append(GetTypeDocId(parameters[i].ParameterType, includeGenericArguments: true, omitGenericArity: true)); + } + sb.Append(')'); + } + + // Append the return type after a '~' (if the method returns a value). + if (method.ReturnType != typeof(void)) + { + sb.Append('~'); + // Omit the generic arity for the return type. + sb.Append(GetTypeDocId(method.ReturnType, includeGenericArguments: true, omitGenericArity: true)); + } + + return sb.ToString(); + } + + /// + /// Generates a documentation ID string for a type. + /// This method handles nested types (replacing '+' with '.'), + /// generic types, arrays, pointers, by-ref types, and generic parameters. + /// The flag controls whether + /// constructed generic type arguments are emitted, while + /// controls whether the generic arity marker (e.g. "`1") is appended. + /// + private static string GetTypeDocId(Type type, bool includeGenericArguments, bool omitGenericArity) + { + if (type.IsGenericParameter) + { + // Use `` for method-level generic parameters and ` for type-level. + if (type.DeclaringMethod != null) + { + return "``" + type.GenericParameterPosition; + } + else if (type.DeclaringType != null) + { + return "`" + type.GenericParameterPosition; + } + else + { + return type.Name; + } + } + + if (type.IsGenericType) + { + Type genericDef = type.GetGenericTypeDefinition(); + string fullName = genericDef.FullName ?? genericDef.Name; + + var sb = new StringBuilder(fullName.Length); + + // Replace '+' with '.' for nested types + for (var i = 0; i < fullName.Length; i++) + { + char c = fullName[i]; + if (c == '+') + { + sb.Append('.'); + } + else if (c == '`') + { + break; + } + else + { + sb.Append(c); + } + } + + if (!omitGenericArity) + { + int arity = genericDef.GetGenericArguments().Length; + sb.Append('`'); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", arity); + } + + if (includeGenericArguments && !type.IsGenericTypeDefinition) + { + var typeArgs = type.GetGenericArguments(); + sb.Append('{'); + + for (int i = 0; i < typeArgs.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + sb.Append(GetTypeDocId(typeArgs[i], includeGenericArguments, omitGenericArity)); + } + + sb.Append('}'); + } + + return sb.ToString(); + } + + // For non-generic types, use FullName (if available) and replace nested type separators. + return (type.FullName ?? type.Name).Replace('+', '.'); + } + + /// + /// Normalizes a documentation comment ID to match the compiler-style format. + /// Strips the return type suffix for ordinary methods but retains it for conversion operators. + /// + /// The documentation comment ID to normalize. + /// The normalized documentation comment ID. + public static string NormalizeDocId(string docId) + { + // Find the tilde character that indicates the return type suffix + var tildeIndex = docId.IndexOf('~'); + if (tildeIndex == -1) + { + // No return type suffix, return as-is + return docId; + } + + // Check if this is a conversion operator (op_Implicit or op_Explicit) + // For these operators, we need to keep the return type suffix + if (docId.Contains("op_Implicit") || docId.Contains("op_Explicit")) + { + return docId; + } + + // For ordinary methods, strip the return type suffix + return docId.Substring(0, tildeIndex); + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class XmlCommentOperationTransformer : IOpenApiOperationTransformer + { + public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) + { + var methodInfo = context.Description.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor + ? controllerActionDescriptor.MethodInfo + : context.Description.ActionDescriptor.EndpointMetadata.OfType().SingleOrDefault(); + + if (methodInfo is null) + { + return Task.CompletedTask; + } + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(methodInfo.CreateDocumentationId()), out var methodComment)) + { + if (methodComment.Summary is { } summary) + { + operation.Summary = summary; + } + if (methodComment.Description is { } description) + { + operation.Description = description; + } + if (methodComment.Remarks is { } remarks) + { + operation.Description = remarks; + } + if (methodComment.Parameters is { Count: > 0}) + { + foreach (var parameterComment in methodComment.Parameters) + { + var parameterInfo = methodInfo.GetParameters().SingleOrDefault(info => info.Name == parameterComment.Name); + var operationParameter = operation.Parameters?.SingleOrDefault(parameter => parameter.Name == parameterComment.Name); + if (operationParameter is not null) + { + var targetOperationParameter = UnwrapOpenApiParameter(operationParameter); + targetOperationParameter.Description = parameterComment.Description; + if (parameterComment.Example is { } jsonString) + { + targetOperationParameter.Example = jsonString.Parse(); + } + targetOperationParameter.Deprecated = parameterComment.Deprecated; + } + else + { + var requestBody = operation.RequestBody; + if (requestBody is not null) + { + requestBody.Description = parameterComment.Description; + if (parameterComment.Example is { } jsonString) + { + var content = requestBody?.Content?.Values; + if (content is null) + { + continue; + } + foreach (var mediaType in content) + { + mediaType.Example = jsonString.Parse(); + } + } + } + } + } + } + // Applies `` on XML comments for operation with single response value. + if (methodComment.Returns is { } returns && operation.Responses is { Count: 1 }) + { + var response = operation.Responses.First(); + response.Value.Description = returns; + } + // Applies `` on XML comments for operation with multiple response values. + if (methodComment.Responses is { Count: > 0} && operation.Responses is { Count: > 0 }) + { + foreach (var response in operation.Responses) + { + var responseComment = methodComment.Responses.SingleOrDefault(xmlResponse => xmlResponse.Code == response.Key); + if (responseComment is not null) + { + response.Value.Description = responseComment.Description; + } + } + } + } + + return Task.CompletedTask; + } + + private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceParameter) + { + if (sourceParameter is OpenApiParameterReference parameterReference) + { + if (parameterReference.Target is OpenApiParameter target) + { + return target; + } + else + { + throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}."); + } + } + else if (sourceParameter is OpenApiParameter directParameter) + { + return directParameter; + } + else + { + throw new InvalidOperationException($"The input schema must be an {nameof(OpenApiParameter)} or {nameof(OpenApiParameterReference)}."); + } + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer + { + public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) + { + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + { + // Apply comments from the property + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) + { + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + } + return Task.CompletedTask; + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class JsonNodeExtensions + { + public static JsonNode? Parse(this string? json) + { + if (json is null) + { + return null; + } + + try + { + return JsonNode.Parse(json); + } + catch (JsonException) + { + try + { + // If parsing fails, try wrapping in quotes to make it a valid JSON string + return JsonNode.Parse($"\"{json.Replace("\"", "\\\"")}\""); + } + catch (JsonException) + { + return null; + } + } + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static IServiceCollection AddOpenApi(this IServiceCollection services, Action configureOptions) + { + return services.AddOpenApi("v1", options => + { + options.AddSchemaTransformer(new XmlCommentSchemaTransformer()); + options.AddOperationTransformer(new XmlCommentOperationTransformer()); + configureOptions(options); + }); + } + + } +} diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index fb09682caf38..e2ccda84dcc0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -432,8 +432,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP { public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { - if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + // Apply comments from the type + if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) + { + schema.Description = typeComment.Summary; + if (typeComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + + var isPropertyInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { + // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; @@ -443,14 +457,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } } - if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment)) - { - schema.Description = typeComment.Summary; - if (typeComment.Examples?.FirstOrDefault() is { } jsonString) - { - schema.Example = jsonString.Parse(); - } - } return Task.CompletedTask; } } @@ -498,4 +504,4 @@ public static IServiceCollection AddOpenApi(this IServiceCollection services) } } -} \ No newline at end of file +} From 027a2e2af368db833d83aa9f0b35d1002cdabbd5 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Thu, 31 Jul 2025 16:38:35 +0200 Subject: [PATCH 04/14] Snapshot the generated openapi as part of the OpenApi source generator tests --- .../SnapshotTestHelper.cs | 8 + ...nAdditionalTexts_openapi.json.verified.txt | 387 +++++++++++ ...XmlTagsOnSchemas_openapi.json.verified.txt | 517 +++++++++++++++ ...sFromControllers_openapi.json.verified.txt | 210 ++++++ ...sFromMinimalApis_openapi.json.verified.txt | 604 ++++++++++++++++++ ...ommentsOnSchemas_openapi.json.verified.txt | 532 +++++++++++++++ ...eferencedSchemas_openapi.json.verified.txt | 116 ++++ ...ntationIdFormats_openapi.json.verified.txt | 43 ++ 8 files changed, 2417 insertions(+) create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt create mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs index 4693176aedc3..956313ed214c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs @@ -200,6 +200,14 @@ void OnEntryPointExit(Exception exception) using var writer = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture) { AutoFlush = true }; var targetMethod = serviceType.GetMethod("GenerateAsync", [typeof(string), typeof(TextWriter)]) ?? throw new InvalidOperationException("Could not resolve GenerateAsync method."); targetMethod.Invoke(service, ["v1", writer]); + + var openApiString = Encoding.UTF8.GetString(stream.ToArray()); + await Verifier.Verify(openApiString) + .UseTextForParameters("openapi.json") + .UseDirectory(SkipOnHelixAttribute.OnHelix() + ? Path.Combine(Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT"), "snapshots") + : "snapshots"); + stream.Position = 0; var (document, _) = await OpenApiDocument.LoadAsync(stream, "json"); verifyFunc(document); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt new file mode 100644 index 000000000000..a7f44be82a50 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt @@ -0,0 +1,387 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/todo": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Todo" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/project": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/board": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoardItem" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/project-record": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRecord" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/todo-with-description": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TodoWithDescription" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/type-with-examples": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TypeWithExamples" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/external-method": { + "post": { + "tags": [ + "Endpoints" + ], + "summary": "An external method.", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the tester. Defaults to \"Tester\".", + "schema": { + "type": "string", + "default": "Tester" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "BoardItem": { + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ], + "description": "The identifier of the board item. Defaults to \"name\"." + } + }, + "description": "An item on the board." + }, + "Project": { + "required": [ + "name", + "description" + ], + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + } + }, + "description": "The project that contains Todo items." + }, + "ProjectRecord": { + "required": [ + "name", + "description" + ], + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ], + "description": "The name of the project." + }, + "description": { + "type": [ + "null", + "string" + ], + "description": "The description of the project." + } + }, + "description": "The project that contains Todo items." + }, + "Todo": { + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + } + }, + "description": "This is a todo item." + }, + "TodoWithDescription": { + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "description": "The identifier of the todo.", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "The name of the todo." + }, + "description": { + "type": [ + "null", + "string" + ], + "description": "Another description of the todo." + } + } + }, + "TypeWithExamples": { + "type": "object", + "properties": { + "booleanType": { + "type": "boolean", + "example": true + }, + "integerType": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32", + "example": 42 + }, + "longType": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int64", + "example": 1234567890123456789 + }, + "doubleType": { + "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", + "type": [ + "number", + "string" + ], + "format": "double", + "example": 3.14 + }, + "floatType": { + "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", + "type": [ + "number", + "string" + ], + "format": "float", + "example": 3.14 + }, + "dateTimeType": { + "type": "string", + "format": "date-time", + "example": "2022-01-01T00:00:00Z" + }, + "dateOnlyType": { + "type": "string", + "format": "date", + "example": "2022-01-01" + }, + "stringType": { + "type": [ + "null", + "string" + ], + "example": "Hello, World!" + }, + "guidType": { + "type": "string", + "format": "uuid", + "example": "2d8f1eac-b5c6-4e29-8c62-4d9d75ef3d3d" + }, + "timeOnlyType": { + "type": "string", + "format": "time", + "example": "12:30:45" + }, + "timeSpanType": { + "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$", + "type": "string", + "example": "P3DT4H5M" + }, + "byteType": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "uint8", + "example": 255 + }, + "decimalType": { + "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$", + "type": [ + "number", + "string" + ], + "format": "double", + "example": 3.14159265359 + }, + "uriType": { + "type": [ + "null", + "string" + ], + "format": "uri", + "example": "https://example.com" + } + } + } + } + }, + "tags": [ + { + "name": "testhost" + }, + { + "name": "Endpoints" + } + ] +} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt new file mode 100644 index 000000000000..cf9ff5deda2f --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt @@ -0,0 +1,517 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/example-class": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExampleClass" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/person": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Person" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/derived-class": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DerivedClass" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/main-class": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MainClass" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/test-interface": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITestInterface" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/implementing-class": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImplementingClass" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/inherit-only-returns": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InheritOnlyReturns" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/inherit-all-but-remarks": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InheritAllButRemarks" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/generic-class": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericClassOfstring" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/generic-parent": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericParent" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/params-and-param-refs": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParamsAndParamRefs" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "AggregateException": { + "type": [ + "null", + "object" + ], + "properties": { + "innerExceptions": { + "type": [ + "null", + "array" + ], + "items": { + "$ref": "#/components/schemas/Exception" + } + }, + "message": { + "type": [ + "null", + "string" + ] + }, + "targetSite": { + "$ref": "#/components/schemas/MethodBase" + }, + "data": { + "type": [ + "null", + "object" + ] + }, + "innerException": { + "$ref": "#/components/schemas/Exception" + }, + "helpLink": { + "type": [ + "null", + "string" + ] + }, + "source": { + "type": [ + "null", + "string" + ] + }, + "hResult": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "stackTrace": { + "type": [ + "null", + "string" + ] + } + } + }, + "DerivedClass": { + "type": "object", + "description": "A summary about this class." + }, + "ExampleClass": { + "type": "object", + "properties": { + "label": { + "type": [ + "null", + "string" + ], + "description": "The `Label` property represents a label\r\nfor this instance." + } + }, + "description": "Every class and member should have a one sentence\r\nsummary describing its purpose." + }, + "Exception": { + "type": [ + "null", + "object" + ], + "properties": { + "targetSite": { + "$ref": "#/components/schemas/MethodBase" + }, + "message": { + "type": [ + "null", + "string" + ] + }, + "data": { + "$ref": "#/components/schemas/GenericParent/properties/taskOfTupleProp/properties/exception/properties/innerExceptions/items/properties/data" + }, + "innerException": { + "$ref": "#/components/schemas/Exception" + }, + "helpLink": { + "type": [ + "null", + "string" + ] + }, + "source": { + "type": [ + "null", + "string" + ] + }, + "hResult": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "stackTrace": { + "type": [ + "null", + "string" + ] + } + } + }, + "GenericClassOfstring": { + "type": "object", + "description": "This is a generic class." + }, + "GenericParent": { + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "null", + "integer", + "string" + ], + "description": "This property is a nullable value type.", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "This property is a nullable reference type." + }, + "taskOfTupleProp": { + "$ref": "#/components/schemas/TaskOfValueTupleOfintAndstring" + }, + "tupleWithGenericProp": { + "$ref": "#/components/schemas/ValueTupleOfintAndDictionaryOfintAndstring" + }, + "tupleWithNestedGenericProp": { + "$ref": "#/components/schemas/ValueTupleOfintAndDictionaryOfintAndDictionaryOfstringAndint" + } + }, + "description": "This class validates the behavior for mapping\r\ngeneric types to open generics for use in\r\ntypeof expressions." + }, + "ImplementingClass": { + "type": "object", + "description": "This interface would describe all the methods in\r\nits contract." + }, + "InheritAllButRemarks": { + "type": "object", + "description": "This class shows an example of sharing comments across methods." + }, + "InheritOnlyReturns": { + "type": "object", + "description": "This class shows hows you can \"inherit\" the doc\r\ncomments from one method in another method." + }, + "ITestInterface": { + "type": "object", + "description": "This interface would describe all the methods in\r\nits contract." + }, + "MainClass": { + "type": "object", + "description": "A summary about this class." + }, + "MethodBase": { }, + "ParamsAndParamRefs": { + "type": "object", + "description": "This shows examples of typeparamref and typeparam tags" + }, + "Person": { + "required": [ + "firstName", + "lastName" + ], + "type": "object", + "properties": { + "firstName": { + "type": [ + "null", + "string" + ], + "description": "This tag will apply to the primary constructor parameter." + }, + "lastName": { + "type": [ + "null", + "string" + ], + "description": "This tag will apply to the primary constructor parameter." + } + }, + "description": "This is an example of a positional record." + }, + "TaskCreationOptions": { + "type": "integer" + }, + "TaskOfValueTupleOfintAndstring": { + "type": [ + "null", + "object" + ], + "properties": { + "result": { + "$ref": "#/components/schemas/ValueTupleOfintAndstring" + }, + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "exception": { + "$ref": "#/components/schemas/AggregateException" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "isCanceled": { + "type": "boolean" + }, + "isCompleted": { + "type": "boolean" + }, + "isCompletedSuccessfully": { + "type": "boolean" + }, + "creationOptions": { + "$ref": "#/components/schemas/TaskCreationOptions" + }, + "asyncState": { }, + "isFaulted": { + "type": "boolean" + } + } + }, + "TaskStatus": { + "type": "integer" + }, + "ValueTupleOfintAndDictionaryOfintAndDictionaryOfstringAndint": { + "type": "object" + }, + "ValueTupleOfintAndDictionaryOfintAndstring": { + "type": "object" + }, + "ValueTupleOfintAndstring": { + "type": "object" + } + } + }, + "tags": [ + { + "name": "testhost" + } + ] +} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt new file mode 100644 index 000000000000..593d74fa4008 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt @@ -0,0 +1,210 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/Test": { + "get": { + "tags": [ + "Test" + ], + "summary": "A summary of the action.", + "description": "A description of the action.", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/Test2": { + "get": { + "tags": [ + "Test2" + ], + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the person.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns the greeting.", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "tags": [ + "Test2" + ], + "requestBody": { + "description": "The todo to insert into the database.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Todo" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Todo" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Todo" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/Test2/HelloByInt": { + "get": { + "tags": [ + "Test2" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "description": "The id associated with the request.", + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Todo": { + "required": [ + "id", + "title", + "completed" + ], + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "title": { + "type": [ + "null", + "string" + ] + }, + "completed": { + "type": "boolean" + } + } + } + } + }, + "tags": [ + { + "name": "Test" + }, + { + "name": "Test2" + } + ] +} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt new file mode 100644 index 000000000000..1cb9a70236ed --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt @@ -0,0 +1,604 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/1": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of the action.", + "description": "A description of the action.", + "responses": { + "200": { + "description": "Returns the greeting.", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/2": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the person.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns the greeting.", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/3": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the person.", + "schema": { + "type": "string" + }, + "example": "Testy McTester" + } + ], + "responses": { + "200": { + "description": "Returns the greeting.", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/4": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "responses": { + "404": { + "description": "Indicates that the value was not found.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/5": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "responses": { + "404": { + "description": "Indicates that the value was not found.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "200": { + "description": "Indicates that the value is even.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "201": { + "description": "Indicates that the value is less than 50." + } + } + } + }, + "/6": { + "post": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "Creates a new user.", + "description": "Sample request:\r\n POST /6\r\n {\r\n \"username\": \"johndoe\",\r\n \"email\": \"john@example.com\"\r\n }", + "requestBody": { + "description": "The user information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + }, + "example": { + "username": "johndoe", + "email": "john@example.com" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/7": { + "put": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "Updates an existing record.", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Legacy ID parameter - use uuid instead.", + "deprecated": true, + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + }, + { + "name": "uuid", + "in": "query", + "description": "Unique identifier for the record.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/8": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get8.", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/9": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get9.", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/10": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get10.", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/11": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get11.", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/12": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get12.", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/13": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get13.", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/14": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get14.", + "responses": { + "200": { + "description": "Returns the greeting.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HolderOfstring" + } + } + } + } + } + } + }, + "/15": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get15.", + "responses": { + "200": { + "description": "Returns the greeting.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HolderOfstring" + } + } + } + } + } + } + }, + "/16": { + "post": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Post16.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/17": { + "get": { + "tags": [ + "RouteHandlerExtensionMethods" + ], + "summary": "A summary of Get17.", + "parameters": [ + { + "name": "args", + "in": "query", + "schema": { + "type": "array", + "items": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AggregateException": { + "type": [ + "null", + "object" + ], + "properties": { + "innerExceptions": { + "type": [ + "null", + "array" + ], + "items": { + "$ref": "#/components/schemas/Exception" + } + }, + "message": { + "type": [ + "null", + "string" + ] + }, + "targetSite": { + "$ref": "#/components/schemas/MethodBase" + }, + "data": { + "type": [ + "null", + "object" + ] + }, + "innerException": { + "$ref": "#/components/schemas/Exception" + }, + "helpLink": { + "type": [ + "null", + "string" + ] + }, + "source": { + "type": [ + "null", + "string" + ] + }, + "hResult": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "stackTrace": { + "type": [ + "null", + "string" + ] + } + } + }, + "Example": { + "type": "object", + "properties": { + "result": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "exception": { + "$ref": "#/components/schemas/AggregateException" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "isCanceled": { + "type": "boolean" + }, + "isCompleted": { + "type": "boolean" + }, + "isCompletedSuccessfully": { + "type": "boolean" + }, + "creationOptions": { + "$ref": "#/components/schemas/TaskCreationOptions" + }, + "asyncState": { }, + "isFaulted": { + "type": "boolean" + } + } + }, + "Exception": { + "type": [ + "null", + "object" + ], + "properties": { + "targetSite": { + "$ref": "#/components/schemas/MethodBase" + }, + "message": { + "type": [ + "null", + "string" + ] + }, + "data": { + "$ref": "#/components/schemas/Example/properties/exception/properties/innerExceptions/items/properties/data" + }, + "innerException": { + "$ref": "#/components/schemas/Exception" + }, + "helpLink": { + "type": [ + "null", + "string" + ] + }, + "source": { + "type": [ + "null", + "string" + ] + }, + "hResult": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "stackTrace": { + "type": [ + "null", + "string" + ] + } + } + }, + "HolderOfstring": { + "type": "object", + "properties": { + "value": { + "type": [ + "null", + "string" + ] + } + } + }, + "MethodBase": { }, + "TaskCreationOptions": { + "type": "integer" + }, + "TaskStatus": { + "type": "integer" + }, + "User": { + "type": "object", + "properties": { + "username": { + "type": [ + "null", + "string" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + } + } + } + } + }, + "tags": [ + { + "name": "RouteHandlerExtensionMethods" + } + ] +} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt new file mode 100644 index 000000000000..3c7877f96ec0 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt @@ -0,0 +1,532 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/todo": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Todo" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/project": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/board": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoardItem" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/protected-internal-element": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProtectedInternalElement" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/project-record": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectRecord" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/todo-with-description": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TodoWithDescription" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/type-with-examples": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TypeWithExamples" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/company": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Company" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "AddressWithoutSummary": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + } + }, + "AddressWithSummary": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + }, + "description": "An address." + }, + "BoardItem": { + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ] + } + }, + "description": "An item on the board." + }, + "Company": { + "type": "object", + "properties": { + "billingAddressClassWithSummary": { + "$ref": "#/components/schemas/AddressWithSummary" + }, + "billingAddressClassWithoutSummary": { + "$ref": "#/components/schemas/AddressWithoutSummary" + }, + "billingAddressNested": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + }, + "description": "Billing address." + }, + "visitingAddressClassWithSummary": { + "$ref": "#/components/schemas/AddressWithSummary" + }, + "visitingAddressClassWithoutSummary": { + "$ref": "#/components/schemas/AddressWithoutSummary" + }, + "visitingAddressNested": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + }, + "description": "Visiting address." + } + } + }, + "Project": { + "required": [ + "name", + "description" + ], + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + } + }, + "description": "The project that contains Todo items." + }, + "ProjectRecord": { + "required": [ + "name", + "description" + ], + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ], + "description": "The name of the project." + }, + "description": { + "type": [ + "null", + "string" + ], + "description": "The description of the project." + } + }, + "description": "The project that contains Todo items." + }, + "ProtectedInternalElement": { + "type": "object", + "properties": { + "name": { + "type": [ + "null", + "string" + ], + "description": "The unique identifier for the element." + } + }, + "description": "Can find this XML comment." + }, + "Todo": { + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + } + }, + "description": "This is a todo item." + }, + "TodoWithDescription": { + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "description": "The identifier of the todo.", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "The name of the todo." + }, + "description": { + "type": [ + "null", + "string" + ], + "description": "Another description of the todo." + } + } + }, + "TypeWithExamples": { + "type": "object", + "properties": { + "booleanType": { + "type": "boolean", + "example": true + }, + "integerType": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32", + "example": 42 + }, + "longType": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int64", + "example": 1234567890123456789 + }, + "doubleType": { + "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", + "type": [ + "number", + "string" + ], + "format": "double", + "example": 3.14 + }, + "floatType": { + "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", + "type": [ + "number", + "string" + ], + "format": "float", + "example": 3.14 + }, + "dateTimeType": { + "type": "string", + "format": "date-time", + "example": "2022-01-01T00:00:00Z" + }, + "dateOnlyType": { + "type": "string", + "format": "date", + "example": "2022-01-01" + }, + "stringType": { + "type": [ + "null", + "string" + ], + "example": "Hello, World!" + }, + "guidType": { + "type": "string", + "format": "uuid", + "example": "2d8f1eac-b5c6-4e29-8c62-4d9d75ef3d3d" + }, + "timeOnlyType": { + "type": "string", + "format": "time", + "example": "12:30:45" + }, + "timeSpanType": { + "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$", + "type": "string", + "example": "P3DT4H5M" + }, + "byteType": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "uint8", + "example": 255 + }, + "decimalType": { + "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$", + "type": [ + "number", + "string" + ], + "format": "double", + "example": 3.14159265359 + }, + "uriType": { + "type": [ + "null", + "string" + ], + "format": "uri", + "example": "https://example.com" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "description": "The unique identifier for the user.", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "The user's display name." + } + } + } + } + }, + "tags": [ + { + "name": "testhost" + } + ] +} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt new file mode 100644 index 000000000000..9f640c47e8e9 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt @@ -0,0 +1,116 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/example": { + "post": { + "tags": [ + "testhost" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RootModel" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "ModelWithoutSummary": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + } + }, + "ModelWithSummary": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + }, + "description": "Comment on class ModelWithSummary." + }, + "RootModel": { + "type": "object", + "properties": { + "firstModelWithSummary": { + "$ref": "#/components/schemas/ModelWithSummary" + }, + "secondModelWithSummary": { + "$ref": "#/components/schemas/ModelWithSummary" + }, + "firstModelWithoutSummary": { + "$ref": "#/components/schemas/ModelWithoutSummary" + }, + "secondModelWithoutSummary": { + "$ref": "#/components/schemas/ModelWithoutSummary" + }, + "firstModelInline": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + }, + "description": "Comment on property FirstModelInline." + }, + "secondModelInline": { + "type": [ + "null", + "object" + ], + "properties": { + "street": { + "type": [ + "null", + "string" + ] + } + }, + "description": "Comment on property SecondModelInline." + } + }, + "description": "Comment on class RootModel." + } + } + }, + "tags": [ + { + "name": "testhost" + } + ] +} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt new file mode 100644 index 000000000000..b252dafd093f --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt @@ -0,0 +1,43 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "testhost | v1", + "version": "1.0.0" + }, + "paths": { + "/test-method": { + "post": { + "tags": [ + "TestApi" + ], + "summary": "This method should have its XML comment merged properly.", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "The identifier for the test.", + "required": true, + "schema": { + "pattern": "^-?(?:0|[1-9]\\d*)$", + "type": [ + "integer", + "string" + ], + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "A task representing the asynchronous operation." + } + } + } + } + }, + "tags": [ + { + "name": "TestApi" + } + ] +} \ No newline at end of file From 98ae2a2810d71009672dec2af5f07019f64de792 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Thu, 31 Jul 2025 17:02:11 +0200 Subject: [PATCH 05/14] Uncomment and remove commented code --- .../CompletenessTests.cs | 3 --- .../SchemaTests.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs index 020b6ec61382..ead167aef011 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs @@ -516,9 +516,6 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.Equal("This class validates the behavior for mapping\ngeneric types to open generics for use in\ntypeof expressions.", genericParent.Description, ignoreLineEndingDifferences: true); Assert.Equal("This property is a nullable value type.", genericParent.Properties["id"].Description); Assert.Equal("This property is a nullable reference type.", genericParent.Properties["name"].Description); - //Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description); - //Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description); - //Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description); path = document.Paths["/params-and-param-refs"].Operations[HttpMethod.Post]; var paramsAndParamRefs = path.RequestBody.Content["application/json"].Schema; diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs index d99c0e7f938f..0f03c8ec9dba 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs @@ -425,7 +425,7 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => var modelWithoutSummary = document.Components.Schemas["ModelWithoutSummary"]; Assert.Null(modelWithoutSummary.Description); - //Assert.DoesNotContain("ModelInline", document.Components.Schemas.Keys); + Assert.DoesNotContain("ModelInline", document.Components.Schemas.Keys); Assert.Equal("Comment on property FirstModelInline.", rootModelSchema.Properties["firstModelInline"].Description); Assert.Equal("Comment on property SecondModelInline.", rootModelSchema.Properties["secondModelInline"].Description); }); From 81df677f4048cb7bc1c678c24f58d159cae21cf0 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Thu, 31 Jul 2025 17:16:51 +0200 Subject: [PATCH 06/14] Rename emitted variable to isInlinedSchema --- src/OpenApi/gen/XmlCommentGenerator.Emitter.cs | 4 ++-- ...tAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...tionalTexts#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...gsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...Controllers#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...MinimalApis#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...tsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...ncedSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- ...onIdFormats#OpenApiXmlCommentSupport.generated.verified.cs | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index 268dce20983c..5b9bc6b48893 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -459,10 +459,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 71ca6f5d3fc5..03a02f3ae801 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -441,10 +441,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 37157431f9dc..238841727ee5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -470,10 +470,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 050c5a8d9b91..7140007d7e51 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -562,10 +562,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 2e7a60909b32..3d4e20a30388 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -445,10 +445,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index e448edfb42e6..36d7783f8dd2 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -463,10 +463,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 11c278ff80c0..08204c622668 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -479,10 +479,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 694d548d3cc0..2e6423a035df 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -450,10 +450,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index e2ccda84dcc0..105a54d461e8 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -442,10 +442,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isPropertyInlinedSchema = schema.Metadata is null + var isInlinedSchema = schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string); - if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) From 12a0766d3ad6c08aecb6dad00c19866ab739663e Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Tue, 5 Aug 2025 16:36:51 +0200 Subject: [PATCH 07/14] Add handling of XML Comments on properties with a schema reference with Metadata properties --- .../gen/XmlCommentGenerator.Emitter.cs | 25 +++++++++++++------ .../Extensions/OpenApiDocumentExtensions.cs | 17 ++++++++++++- src/OpenApi/src/Services/OpenApiConstants.cs | 2 ++ .../CompletenessTests.cs | 3 +++ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...XmlTagsOnSchemas_openapi.json.verified.txt | 3 +++ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...ommentsOnSchemas_openapi.json.verified.txt | 4 +++ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ ...eferencedSchemas_openapi.json.verified.txt | 4 +++ ...ApiXmlCommentSupport.generated.verified.cs | 25 +++++++++++++------ 15 files changed, 194 insertions(+), 64 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index 5b9bc6b48893..24e3c75b38e8 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -459,18 +459,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs index c53465f8226c..c09bd50dc67b 100644 --- a/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs +++ b/src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Nodes; + namespace Microsoft.AspNetCore.OpenApi; internal static class OpenApiDocumentExtensions @@ -21,6 +23,19 @@ public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument do document.Workspace ??= new(); var location = document.BaseUri + "/components/schemas/" + schemaId; document.Workspace.RegisterComponentForDocument(document, schema, location); - return new OpenApiSchemaReference(schemaId, document); + + object? description = null; + object? example = null; + if (schema is OpenApiSchema actualSchema) + { + actualSchema.Metadata?.TryGetValue(OpenApiConstants.RefDescriptionAnnotation, out description); + actualSchema.Metadata?.TryGetValue(OpenApiConstants.RefExampleAnnotation, out example); + } + + return new OpenApiSchemaReference(schemaId, document) + { + Description = description as string, + Examples = example is JsonNode exampleJson ? [exampleJson] : null, + }; } } diff --git a/src/OpenApi/src/Services/OpenApiConstants.cs b/src/OpenApi/src/Services/OpenApiConstants.cs index 8c34704cf2b6..b32e5bb6e936 100644 --- a/src/OpenApi/src/Services/OpenApiConstants.cs +++ b/src/OpenApi/src/Services/OpenApiConstants.cs @@ -13,6 +13,8 @@ internal static class OpenApiConstants internal const string DescriptionId = "x-aspnetcore-id"; internal const string SchemaId = "x-schema-id"; internal const string RefId = "x-ref-id"; + internal const string RefDescriptionAnnotation = "x-ref-description"; + internal const string RefExampleAnnotation = "x-ref-example"; internal const string DefaultOpenApiResponseKey = "default"; // Since there's a finite set of HTTP methods that can be included in a given // OpenApiPaths, we can pre-allocate an array of these methods and use a direct diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs index ead167aef011..82ae70006c10 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs @@ -516,6 +516,9 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.Equal("This class validates the behavior for mapping\ngeneric types to open generics for use in\ntypeof expressions.", genericParent.Description, ignoreLineEndingDifferences: true); Assert.Equal("This property is a nullable value type.", genericParent.Properties["id"].Description); Assert.Equal("This property is a nullable reference type.", genericParent.Properties["name"].Description); + Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description); + Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description); + Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description); path = document.Paths["/params-and-param-refs"].Operations[HttpMethod.Post]; var paramsAndParamRefs = path.RequestBody.Content["application/json"].Schema; diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 03a02f3ae801..57ee588a685b 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -441,18 +441,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 238841727ee5..42fbee8d368d 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -470,18 +470,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 7140007d7e51..0388ffa1446d 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -562,18 +562,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt index cf9ff5deda2f..a3399ca7e3c0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt @@ -391,12 +391,15 @@ "description": "This property is a nullable reference type." }, "taskOfTupleProp": { + "description": "This property is a generic type containing a tuple.", "$ref": "#/components/schemas/TaskOfValueTupleOfintAndstring" }, "tupleWithGenericProp": { + "description": "This property is a tuple with a generic type inside.", "$ref": "#/components/schemas/ValueTupleOfintAndDictionaryOfintAndstring" }, "tupleWithNestedGenericProp": { + "description": "This property is a tuple with a nested generic type inside.", "$ref": "#/components/schemas/ValueTupleOfintAndDictionaryOfintAndDictionaryOfstringAndint" } }, diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 3d4e20a30388..4f0f0c6306de 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -445,18 +445,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 36d7783f8dd2..87d8011a2939 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -463,18 +463,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 08204c622668..d035077b7d48 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -479,18 +479,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt index 3c7877f96ec0..59c01738f270 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt @@ -242,9 +242,11 @@ "type": "object", "properties": { "billingAddressClassWithSummary": { + "description": "Billing address.", "$ref": "#/components/schemas/AddressWithSummary" }, "billingAddressClassWithoutSummary": { + "description": "Billing address.", "$ref": "#/components/schemas/AddressWithoutSummary" }, "billingAddressNested": { @@ -263,9 +265,11 @@ "description": "Billing address." }, "visitingAddressClassWithSummary": { + "description": "Visiting address.", "$ref": "#/components/schemas/AddressWithSummary" }, "visitingAddressClassWithoutSummary": { + "description": "Visiting address.", "$ref": "#/components/schemas/AddressWithoutSummary" }, "visitingAddressNested": { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 2e6423a035df..50f490a82aa4 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -450,18 +450,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt index 9f640c47e8e9..6a784a54cf0c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt @@ -62,15 +62,19 @@ "type": "object", "properties": { "firstModelWithSummary": { + "description": "Comment on property FirstModelWithSummary.", "$ref": "#/components/schemas/ModelWithSummary" }, "secondModelWithSummary": { + "description": "Comment on property SecondModelWithSummary.", "$ref": "#/components/schemas/ModelWithSummary" }, "firstModelWithoutSummary": { + "description": "Comment on property FirstModelWithoutSummary.", "$ref": "#/components/schemas/ModelWithoutSummary" }, "secondModelWithoutSummary": { + "description": "Comment on property SecondModelWithoutSummary.", "$ref": "#/components/schemas/ModelWithoutSummary" }, "firstModelInline": { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index 105a54d461e8..6597f34eaf10 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -442,18 +442,29 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } } - var isInlinedSchema = schema.Metadata is null - || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if (isInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) + if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo }) { // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; - if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + var isInlinedSchema = schema.Metadata is null + || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) + || string.IsNullOrEmpty(schemaId as string); + if(isInlinedSchema) { - schema.Example = jsonString.Parse(); + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Example = jsonString.Parse(); + } + } + else + { + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) + { + schema.Metadata["x-ref-example"] = jsonString.Parse(); + } } } } From c9805b9ba0da3b35d0a41a31f0da1e8396e8c348 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Tue, 5 Aug 2025 16:55:56 +0200 Subject: [PATCH 08/14] Improve XmlComments SchemaReference tests and revert modified unrelated tests --- .../SchemaTests.cs | 95 +++++----------- ...ApiXmlCommentSupport.generated.verified.cs | 11 +- ...ommentsOnSchemas_openapi.json.verified.txt | 101 ------------------ ...piXmlCommentSupport.generated.verified.cs} | 0 ...chemaReferences_openapi.json.verified.txt} | 3 + 5 files changed, 29 insertions(+), 181 deletions(-) rename src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/{SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs => SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs} (100%) rename src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/{SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt => SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt} (96%) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs index 0f03c8ec9dba..f6dc3c9e4f2f 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs @@ -19,10 +19,7 @@ public async Task SupportsXmlCommentsOnSchemas() var builder = WebApplication.CreateBuilder(); -builder.Services.AddOpenApi(options => { - var prevCreateSchemaReferenceId = options.CreateSchemaReferenceId; - options.CreateSchemaReferenceId = (x) => x.Type == typeof(AddressNested) ? null : prevCreateSchemaReferenceId(x); -}); +builder.Services.AddOpenApi(); var app = builder.Build(); @@ -34,7 +31,6 @@ public async Task SupportsXmlCommentsOnSchemas() app.MapPost("/todo-with-description", (TodoWithDescription todo) => { }); app.MapPost("/type-with-examples", (TypeWithExamples typeWithExamples) => { }); app.MapPost("/user", (User user) => { }); -app.MapPost("/company", (Company company) => { }); app.Run(); @@ -180,59 +176,6 @@ internal class User : IUser public string Name { get; set; } } -/// -/// An address. -/// -public class AddressWithSummary -{ - public string Street { get; set; } -} - -public class AddressWithoutSummary -{ - public string Street { get; set; } -} - -/// -/// An address. -/// -public class AddressNested -{ - public string Street { get; set; } -} - -public class Company -{ - /// - /// Billing address. - /// - public AddressWithSummary BillingAddressClassWithSummary { get; set; } - - /// - /// Billing address. - /// - public AddressWithoutSummary BillingAddressClassWithoutSummary { get; set; } - - /// - /// Billing address. - /// - public AddressNested BillingAddressNested { get; set; } - - /// - /// Visiting address. - /// - public AddressWithSummary VisitingAddressClassWithSummary { get; set; } - - /// - /// Visiting address. - /// - public AddressWithoutSummary VisitingAddressClassWithoutSummary { get; set; } - - /// - /// Visiting address. - /// - public AddressNested VisitingAddressNested { get; set; } -} """; var generator = new XmlCommentGenerator(); await SnapshotTestHelper.Verify(source, generator, out var compilation); @@ -316,22 +259,11 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => var user = path.RequestBody.Content["application/json"].Schema; Assert.Equal("The unique identifier for the user.", user.Properties["id"].Description); Assert.Equal("The user's display name.", user.Properties["name"].Description); - - path = document.Paths["/company"].Operations[HttpMethod.Post]; - var company = path.RequestBody.Content["application/json"].Schema; - Assert.Equal("Billing address.", company.Properties["billingAddressNested"].Description); - Assert.Equal("Visiting address.", company.Properties["visitingAddressNested"].Description); - - var addressWithSummary = document.Components.Schemas["AddressWithSummary"]; - Assert.Equal("An address.", addressWithSummary.Description); - - var addressWithoutSummary = document.Components.Schemas["AddressWithoutSummary"]; - Assert.Null(addressWithoutSummary.Description); }); } [Fact] - public async Task XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas() + public async Task XmlCommentsOnPropertiesShouldApplyToSchemaReferences() { var source = """ using System; @@ -377,6 +309,8 @@ public class ModelInline /// public class RootModel { + public ModelWithSummary NoPropertyComment { get; set; } + /// /// Comment on property FirstModelWithSummary. /// @@ -426,7 +360,28 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.Null(modelWithoutSummary.Description); Assert.DoesNotContain("ModelInline", document.Components.Schemas.Keys); + + // Check RootModel properties + var noPropertyCommentReference = Assert.IsType(rootModelSchema.Properties["noPropertyComment"]); + Assert.Null(noPropertyCommentReference.Reference.Description); + + Assert.IsType(rootModelSchema.Properties["firstModelWithSummary"]); + Assert.Equal("Comment on property FirstModelWithSummary.", rootModelSchema.Properties["firstModelWithSummary"].Description); + + Assert.IsType(rootModelSchema.Properties["firstModelWithoutSummary"]); + Assert.Equal("Comment on property FirstModelWithoutSummary.", rootModelSchema.Properties["firstModelWithoutSummary"].Description); + + Assert.IsType(rootModelSchema.Properties["firstModelInline"]); Assert.Equal("Comment on property FirstModelInline.", rootModelSchema.Properties["firstModelInline"].Description); + + // Verify that comments on the same type override each other + Assert.IsType(rootModelSchema.Properties["secondModelWithSummary"]); + Assert.Equal("Comment on property FirstModelWithSummary.", rootModelSchema.Properties["firstModelWithSummary"].Description); + + Assert.IsType(rootModelSchema.Properties["secondModelWithoutSummary"]); + Assert.Equal("Comment on property FirstModelWithoutSummary.", rootModelSchema.Properties["firstModelWithoutSummary"].Description); + + Assert.IsType(rootModelSchema.Properties["secondModelInline"]); Assert.Equal("Comment on property SecondModelInline.", rootModelSchema.Properties["secondModelInline"].Description); }); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index d035077b7d48..b1ede504b35a 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -76,8 +76,6 @@ private static Dictionary GenerateCacheEntries() cache.Add(@"T:ProjectBoard.ProtectedInternalElement", new XmlComment(@"Can find this XML comment.", null, null, null, null, false, null, null, null)); cache.Add(@"T:ProjectRecord", new XmlComment(@"The project that contains Todo items.", null, null, null, null, false, null, [new XmlParameterComment(@"Name", @"The name of the project.", null, false), new XmlParameterComment(@"Description", @"The description of the project.", null, false)], null)); cache.Add(@"T:User", new XmlComment(null, null, null, null, null, false, null, null, null)); - cache.Add(@"T:AddressWithSummary", new XmlComment(@"An address.", null, null, null, null, false, null, null, null)); - cache.Add(@"T:AddressNested", new XmlComment(@"An address.", null, null, null, null, false, null, null, null)); cache.Add(@"P:ProjectBoard.ProtectedInternalElement.Name", new XmlComment(@"The unique identifier for the element.", null, null, null, null, false, null, null, null)); cache.Add(@"P:ProjectRecord.Name", new XmlComment(@"The name of the project.", null, null, null, null, false, null, null, null)); cache.Add(@"P:ProjectRecord.Description", new XmlComment(@"The description of the project.", null, null, null, null, false, null, null, null)); @@ -102,12 +100,6 @@ private static Dictionary GenerateCacheEntries() cache.Add(@"P:IUser.Name", new XmlComment(@"The user's display name.", null, null, null, null, false, null, null, null)); cache.Add(@"P:User.Id", new XmlComment(@"The unique identifier for the user.", null, null, null, null, false, null, null, null)); cache.Add(@"P:User.Name", new XmlComment(@"The user's display name.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:Company.BillingAddressClassWithSummary", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:Company.BillingAddressClassWithoutSummary", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:Company.BillingAddressNested", new XmlComment(@"Billing address.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:Company.VisitingAddressClassWithSummary", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:Company.VisitingAddressClassWithoutSummary", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:Company.VisitingAddressNested", new XmlComment(@"Visiting address.", null, null, null, null, false, null, null, null)); return cache; } @@ -542,13 +534,12 @@ file static class JsonNodeExtensions file static class GeneratedServiceCollectionExtensions { [InterceptsLocation] - public static IServiceCollection AddOpenApi(this IServiceCollection services, Action configureOptions) + public static IServiceCollection AddOpenApi(this IServiceCollection services) { return services.AddOpenApi("v1", options => { options.AddSchemaTransformer(new XmlCommentSchemaTransformer()); options.AddOperationTransformer(new XmlCommentOperationTransformer()); - configureOptions(options); }); } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt index 59c01738f270..44c56fcf04b7 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt @@ -172,60 +172,10 @@ } } } - }, - "/company": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Company" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } } }, "components": { "schemas": { - "AddressWithoutSummary": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - } - }, - "AddressWithSummary": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - }, - "description": "An address." - }, "BoardItem": { "type": "object", "properties": { @@ -238,57 +188,6 @@ }, "description": "An item on the board." }, - "Company": { - "type": "object", - "properties": { - "billingAddressClassWithSummary": { - "description": "Billing address.", - "$ref": "#/components/schemas/AddressWithSummary" - }, - "billingAddressClassWithoutSummary": { - "description": "Billing address.", - "$ref": "#/components/schemas/AddressWithoutSummary" - }, - "billingAddressNested": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - }, - "description": "Billing address." - }, - "visitingAddressClassWithSummary": { - "description": "Visiting address.", - "$ref": "#/components/schemas/AddressWithSummary" - }, - "visitingAddressClassWithoutSummary": { - "description": "Visiting address.", - "$ref": "#/components/schemas/AddressWithoutSummary" - }, - "visitingAddressNested": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - }, - "description": "Visiting address." - } - } - }, "Project": { "required": [ "name", diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs similarity index 100% rename from src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas#OpenApiXmlCommentSupport.generated.verified.cs rename to src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt similarity index 96% rename from src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt rename to src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt index 6a784a54cf0c..34480702bb5e 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas_openapi.json.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt @@ -61,6 +61,9 @@ "RootModel": { "type": "object", "properties": { + "noPropertyComment": { + "$ref": "#/components/schemas/ModelWithSummary" + }, "firstModelWithSummary": { "description": "Comment on property FirstModelWithSummary.", "$ref": "#/components/schemas/ModelWithSummary" From c51cec12d0dbd43cee9e9c41e0b01a8c770faeea Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Tue, 5 Aug 2025 17:00:15 +0200 Subject: [PATCH 09/14] Revert outputting OpenApi.json snapshots in the XmlSourceGenerator Tests --- .../SnapshotTestHelper.cs | 8 - ...nAdditionalTexts_openapi.json.verified.txt | 387 ----------- ...XmlTagsOnSchemas_openapi.json.verified.txt | 520 --------------- ...sFromControllers_openapi.json.verified.txt | 210 ------ ...sFromMinimalApis_openapi.json.verified.txt | 604 ------------------ ...ommentsOnSchemas_openapi.json.verified.txt | 435 ------------- ...SchemaReferences_openapi.json.verified.txt | 123 ---- ...ntationIdFormats_openapi.json.verified.txt | 43 -- 8 files changed, 2330 deletions(-) delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt delete mode 100644 src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs index 956313ed214c..4693176aedc3 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SnapshotTestHelper.cs @@ -200,14 +200,6 @@ void OnEntryPointExit(Exception exception) using var writer = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture) { AutoFlush = true }; var targetMethod = serviceType.GetMethod("GenerateAsync", [typeof(string), typeof(TextWriter)]) ?? throw new InvalidOperationException("Could not resolve GenerateAsync method."); targetMethod.Invoke(service, ["v1", writer]); - - var openApiString = Encoding.UTF8.GetString(stream.ToArray()); - await Verifier.Verify(openApiString) - .UseTextForParameters("openapi.json") - .UseDirectory(SkipOnHelixAttribute.OnHelix() - ? Path.Combine(Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT"), "snapshots") - : "snapshots"); - stream.Position = 0; var (document, _) = await OpenApiDocument.LoadAsync(stream, "json"); verifyFunc(document); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt deleted file mode 100644 index a7f44be82a50..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts_openapi.json.verified.txt +++ /dev/null @@ -1,387 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/todo": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Todo" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/project": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/board": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BoardItem" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/project-record": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectRecord" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/todo-with-description": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TodoWithDescription" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/type-with-examples": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TypeWithExamples" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/external-method": { - "post": { - "tags": [ - "Endpoints" - ], - "summary": "An external method.", - "parameters": [ - { - "name": "name", - "in": "query", - "description": "The name of the tester. Defaults to \"Tester\".", - "schema": { - "type": "string", - "default": "Tester" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - } - }, - "components": { - "schemas": { - "BoardItem": { - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ], - "description": "The identifier of the board item. Defaults to \"name\"." - } - }, - "description": "An item on the board." - }, - "Project": { - "required": [ - "name", - "description" - ], - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ] - }, - "description": { - "type": [ - "null", - "string" - ] - } - }, - "description": "The project that contains Todo items." - }, - "ProjectRecord": { - "required": [ - "name", - "description" - ], - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ], - "description": "The name of the project." - }, - "description": { - "type": [ - "null", - "string" - ], - "description": "The description of the project." - } - }, - "description": "The project that contains Todo items." - }, - "Todo": { - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "name": { - "type": [ - "null", - "string" - ] - }, - "description": { - "type": [ - "null", - "string" - ] - } - }, - "description": "This is a todo item." - }, - "TodoWithDescription": { - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "description": "The identifier of the todo.", - "format": "int32" - }, - "name": { - "type": [ - "null", - "string" - ], - "description": "The name of the todo." - }, - "description": { - "type": [ - "null", - "string" - ], - "description": "Another description of the todo." - } - } - }, - "TypeWithExamples": { - "type": "object", - "properties": { - "booleanType": { - "type": "boolean", - "example": true - }, - "integerType": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32", - "example": 42 - }, - "longType": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int64", - "example": 1234567890123456789 - }, - "doubleType": { - "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", - "type": [ - "number", - "string" - ], - "format": "double", - "example": 3.14 - }, - "floatType": { - "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", - "type": [ - "number", - "string" - ], - "format": "float", - "example": 3.14 - }, - "dateTimeType": { - "type": "string", - "format": "date-time", - "example": "2022-01-01T00:00:00Z" - }, - "dateOnlyType": { - "type": "string", - "format": "date", - "example": "2022-01-01" - }, - "stringType": { - "type": [ - "null", - "string" - ], - "example": "Hello, World!" - }, - "guidType": { - "type": "string", - "format": "uuid", - "example": "2d8f1eac-b5c6-4e29-8c62-4d9d75ef3d3d" - }, - "timeOnlyType": { - "type": "string", - "format": "time", - "example": "12:30:45" - }, - "timeSpanType": { - "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$", - "type": "string", - "example": "P3DT4H5M" - }, - "byteType": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "uint8", - "example": 255 - }, - "decimalType": { - "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$", - "type": [ - "number", - "string" - ], - "format": "double", - "example": 3.14159265359 - }, - "uriType": { - "type": [ - "null", - "string" - ], - "format": "uri", - "example": "https://example.com" - } - } - } - } - }, - "tags": [ - { - "name": "testhost" - }, - { - "name": "Endpoints" - } - ] -} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt deleted file mode 100644 index a3399ca7e3c0..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas_openapi.json.verified.txt +++ /dev/null @@ -1,520 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/example-class": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ExampleClass" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/person": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Person" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/derived-class": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DerivedClass" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/main-class": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MainClass" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/test-interface": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ITestInterface" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/implementing-class": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ImplementingClass" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/inherit-only-returns": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InheritOnlyReturns" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/inherit-all-but-remarks": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InheritAllButRemarks" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/generic-class": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenericClassOfstring" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/generic-parent": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenericParent" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/params-and-param-refs": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ParamsAndParamRefs" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - } - }, - "components": { - "schemas": { - "AggregateException": { - "type": [ - "null", - "object" - ], - "properties": { - "innerExceptions": { - "type": [ - "null", - "array" - ], - "items": { - "$ref": "#/components/schemas/Exception" - } - }, - "message": { - "type": [ - "null", - "string" - ] - }, - "targetSite": { - "$ref": "#/components/schemas/MethodBase" - }, - "data": { - "type": [ - "null", - "object" - ] - }, - "innerException": { - "$ref": "#/components/schemas/Exception" - }, - "helpLink": { - "type": [ - "null", - "string" - ] - }, - "source": { - "type": [ - "null", - "string" - ] - }, - "hResult": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "stackTrace": { - "type": [ - "null", - "string" - ] - } - } - }, - "DerivedClass": { - "type": "object", - "description": "A summary about this class." - }, - "ExampleClass": { - "type": "object", - "properties": { - "label": { - "type": [ - "null", - "string" - ], - "description": "The `Label` property represents a label\r\nfor this instance." - } - }, - "description": "Every class and member should have a one sentence\r\nsummary describing its purpose." - }, - "Exception": { - "type": [ - "null", - "object" - ], - "properties": { - "targetSite": { - "$ref": "#/components/schemas/MethodBase" - }, - "message": { - "type": [ - "null", - "string" - ] - }, - "data": { - "$ref": "#/components/schemas/GenericParent/properties/taskOfTupleProp/properties/exception/properties/innerExceptions/items/properties/data" - }, - "innerException": { - "$ref": "#/components/schemas/Exception" - }, - "helpLink": { - "type": [ - "null", - "string" - ] - }, - "source": { - "type": [ - "null", - "string" - ] - }, - "hResult": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "stackTrace": { - "type": [ - "null", - "string" - ] - } - } - }, - "GenericClassOfstring": { - "type": "object", - "description": "This is a generic class." - }, - "GenericParent": { - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "null", - "integer", - "string" - ], - "description": "This property is a nullable value type.", - "format": "int32" - }, - "name": { - "type": [ - "null", - "string" - ], - "description": "This property is a nullable reference type." - }, - "taskOfTupleProp": { - "description": "This property is a generic type containing a tuple.", - "$ref": "#/components/schemas/TaskOfValueTupleOfintAndstring" - }, - "tupleWithGenericProp": { - "description": "This property is a tuple with a generic type inside.", - "$ref": "#/components/schemas/ValueTupleOfintAndDictionaryOfintAndstring" - }, - "tupleWithNestedGenericProp": { - "description": "This property is a tuple with a nested generic type inside.", - "$ref": "#/components/schemas/ValueTupleOfintAndDictionaryOfintAndDictionaryOfstringAndint" - } - }, - "description": "This class validates the behavior for mapping\r\ngeneric types to open generics for use in\r\ntypeof expressions." - }, - "ImplementingClass": { - "type": "object", - "description": "This interface would describe all the methods in\r\nits contract." - }, - "InheritAllButRemarks": { - "type": "object", - "description": "This class shows an example of sharing comments across methods." - }, - "InheritOnlyReturns": { - "type": "object", - "description": "This class shows hows you can \"inherit\" the doc\r\ncomments from one method in another method." - }, - "ITestInterface": { - "type": "object", - "description": "This interface would describe all the methods in\r\nits contract." - }, - "MainClass": { - "type": "object", - "description": "A summary about this class." - }, - "MethodBase": { }, - "ParamsAndParamRefs": { - "type": "object", - "description": "This shows examples of typeparamref and typeparam tags" - }, - "Person": { - "required": [ - "firstName", - "lastName" - ], - "type": "object", - "properties": { - "firstName": { - "type": [ - "null", - "string" - ], - "description": "This tag will apply to the primary constructor parameter." - }, - "lastName": { - "type": [ - "null", - "string" - ], - "description": "This tag will apply to the primary constructor parameter." - } - }, - "description": "This is an example of a positional record." - }, - "TaskCreationOptions": { - "type": "integer" - }, - "TaskOfValueTupleOfintAndstring": { - "type": [ - "null", - "object" - ], - "properties": { - "result": { - "$ref": "#/components/schemas/ValueTupleOfintAndstring" - }, - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "exception": { - "$ref": "#/components/schemas/AggregateException" - }, - "status": { - "$ref": "#/components/schemas/TaskStatus" - }, - "isCanceled": { - "type": "boolean" - }, - "isCompleted": { - "type": "boolean" - }, - "isCompletedSuccessfully": { - "type": "boolean" - }, - "creationOptions": { - "$ref": "#/components/schemas/TaskCreationOptions" - }, - "asyncState": { }, - "isFaulted": { - "type": "boolean" - } - } - }, - "TaskStatus": { - "type": "integer" - }, - "ValueTupleOfintAndDictionaryOfintAndDictionaryOfstringAndint": { - "type": "object" - }, - "ValueTupleOfintAndDictionaryOfintAndstring": { - "type": "object" - }, - "ValueTupleOfintAndstring": { - "type": "object" - } - } - }, - "tags": [ - { - "name": "testhost" - } - ] -} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt deleted file mode 100644 index 593d74fa4008..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers_openapi.json.verified.txt +++ /dev/null @@ -1,210 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/Test": { - "get": { - "tags": [ - "Test" - ], - "summary": "A summary of the action.", - "description": "A description of the action.", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - }, - "application/json": { - "schema": { - "type": "string" - } - }, - "text/json": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/Test2": { - "get": { - "tags": [ - "Test2" - ], - "parameters": [ - { - "name": "name", - "in": "query", - "description": "The name of the person.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Returns the greeting.", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - }, - "application/json": { - "schema": { - "type": "string" - } - }, - "text/json": { - "schema": { - "type": "string" - } - } - } - } - } - }, - "post": { - "tags": [ - "Test2" - ], - "requestBody": { - "description": "The todo to insert into the database.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Todo" - } - }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/Todo" - } - }, - "application/*+json": { - "schema": { - "$ref": "#/components/schemas/Todo" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - }, - "application/json": { - "schema": { - "type": "string" - } - }, - "text/json": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/Test2/HelloByInt": { - "get": { - "tags": [ - "Test2" - ], - "parameters": [ - { - "name": "id", - "in": "query", - "description": "The id associated with the request.", - "schema": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - }, - "application/json": { - "schema": { - "type": "string" - } - }, - "text/json": { - "schema": { - "type": "string" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Todo": { - "required": [ - "id", - "title", - "completed" - ], - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "title": { - "type": [ - "null", - "string" - ] - }, - "completed": { - "type": "boolean" - } - } - } - } - }, - "tags": [ - { - "name": "Test" - }, - { - "name": "Test2" - } - ] -} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt deleted file mode 100644 index 1cb9a70236ed..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis_openapi.json.verified.txt +++ /dev/null @@ -1,604 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/1": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of the action.", - "description": "A description of the action.", - "responses": { - "200": { - "description": "Returns the greeting.", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/2": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "parameters": [ - { - "name": "name", - "in": "query", - "description": "The name of the person.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Returns the greeting.", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/3": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "parameters": [ - { - "name": "name", - "in": "query", - "description": "The name of the person.", - "schema": { - "type": "string" - }, - "example": "Testy McTester" - } - ], - "responses": { - "200": { - "description": "Returns the greeting.", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/4": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "responses": { - "404": { - "description": "Indicates that the value was not found.", - "content": { - "application/json": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/5": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "responses": { - "404": { - "description": "Indicates that the value was not found.", - "content": { - "application/json": { - "schema": { - "type": "string" - } - } - } - }, - "200": { - "description": "Indicates that the value is even.", - "content": { - "application/json": { - "schema": { - "type": "string" - } - } - } - }, - "201": { - "description": "Indicates that the value is less than 50." - } - } - } - }, - "/6": { - "post": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "Creates a new user.", - "description": "Sample request:\r\n POST /6\r\n {\r\n \"username\": \"johndoe\",\r\n \"email\": \"john@example.com\"\r\n }", - "requestBody": { - "description": "The user information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/User" - }, - "example": { - "username": "johndoe", - "email": "john@example.com" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/7": { - "put": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "Updates an existing record.", - "parameters": [ - { - "name": "id", - "in": "query", - "description": "Legacy ID parameter - use uuid instead.", - "deprecated": true, - "schema": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - } - }, - { - "name": "uuid", - "in": "query", - "description": "Unique identifier for the record.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/8": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get8.", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/9": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get9.", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/10": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get10.", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/11": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get11.", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/12": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get12.", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/13": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get13.", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/14": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get14.", - "responses": { - "200": { - "description": "Returns the greeting.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HolderOfstring" - } - } - } - } - } - } - }, - "/15": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get15.", - "responses": { - "200": { - "description": "Returns the greeting.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HolderOfstring" - } - } - } - } - } - } - }, - "/16": { - "post": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Post16.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Example" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/17": { - "get": { - "tags": [ - "RouteHandlerExtensionMethods" - ], - "summary": "A summary of Get17.", - "parameters": [ - { - "name": "args", - "in": "query", - "schema": { - "type": "array", - "items": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - } - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "array", - "items": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - } - } - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "AggregateException": { - "type": [ - "null", - "object" - ], - "properties": { - "innerExceptions": { - "type": [ - "null", - "array" - ], - "items": { - "$ref": "#/components/schemas/Exception" - } - }, - "message": { - "type": [ - "null", - "string" - ] - }, - "targetSite": { - "$ref": "#/components/schemas/MethodBase" - }, - "data": { - "type": [ - "null", - "object" - ] - }, - "innerException": { - "$ref": "#/components/schemas/Exception" - }, - "helpLink": { - "type": [ - "null", - "string" - ] - }, - "source": { - "type": [ - "null", - "string" - ] - }, - "hResult": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "stackTrace": { - "type": [ - "null", - "string" - ] - } - } - }, - "Example": { - "type": "object", - "properties": { - "result": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "exception": { - "$ref": "#/components/schemas/AggregateException" - }, - "status": { - "$ref": "#/components/schemas/TaskStatus" - }, - "isCanceled": { - "type": "boolean" - }, - "isCompleted": { - "type": "boolean" - }, - "isCompletedSuccessfully": { - "type": "boolean" - }, - "creationOptions": { - "$ref": "#/components/schemas/TaskCreationOptions" - }, - "asyncState": { }, - "isFaulted": { - "type": "boolean" - } - } - }, - "Exception": { - "type": [ - "null", - "object" - ], - "properties": { - "targetSite": { - "$ref": "#/components/schemas/MethodBase" - }, - "message": { - "type": [ - "null", - "string" - ] - }, - "data": { - "$ref": "#/components/schemas/Example/properties/exception/properties/innerExceptions/items/properties/data" - }, - "innerException": { - "$ref": "#/components/schemas/Exception" - }, - "helpLink": { - "type": [ - "null", - "string" - ] - }, - "source": { - "type": [ - "null", - "string" - ] - }, - "hResult": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "stackTrace": { - "type": [ - "null", - "string" - ] - } - } - }, - "HolderOfstring": { - "type": "object", - "properties": { - "value": { - "type": [ - "null", - "string" - ] - } - } - }, - "MethodBase": { }, - "TaskCreationOptions": { - "type": "integer" - }, - "TaskStatus": { - "type": "integer" - }, - "User": { - "type": "object", - "properties": { - "username": { - "type": [ - "null", - "string" - ] - }, - "email": { - "type": [ - "null", - "string" - ] - } - } - } - } - }, - "tags": [ - { - "name": "RouteHandlerExtensionMethods" - } - ] -} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt deleted file mode 100644 index 44c56fcf04b7..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas_openapi.json.verified.txt +++ /dev/null @@ -1,435 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/todo": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Todo" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/project": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/board": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BoardItem" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/protected-internal-element": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProtectedInternalElement" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/project-record": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectRecord" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/todo-with-description": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TodoWithDescription" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/type-with-examples": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TypeWithExamples" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/user": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/User" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - } - }, - "components": { - "schemas": { - "BoardItem": { - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ] - } - }, - "description": "An item on the board." - }, - "Project": { - "required": [ - "name", - "description" - ], - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ] - }, - "description": { - "type": [ - "null", - "string" - ] - } - }, - "description": "The project that contains Todo items." - }, - "ProjectRecord": { - "required": [ - "name", - "description" - ], - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ], - "description": "The name of the project." - }, - "description": { - "type": [ - "null", - "string" - ], - "description": "The description of the project." - } - }, - "description": "The project that contains Todo items." - }, - "ProtectedInternalElement": { - "type": "object", - "properties": { - "name": { - "type": [ - "null", - "string" - ], - "description": "The unique identifier for the element." - } - }, - "description": "Can find this XML comment." - }, - "Todo": { - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - }, - "name": { - "type": [ - "null", - "string" - ] - }, - "description": { - "type": [ - "null", - "string" - ] - } - }, - "description": "This is a todo item." - }, - "TodoWithDescription": { - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "description": "The identifier of the todo.", - "format": "int32" - }, - "name": { - "type": [ - "null", - "string" - ], - "description": "The name of the todo." - }, - "description": { - "type": [ - "null", - "string" - ], - "description": "Another description of the todo." - } - } - }, - "TypeWithExamples": { - "type": "object", - "properties": { - "booleanType": { - "type": "boolean", - "example": true - }, - "integerType": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32", - "example": 42 - }, - "longType": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int64", - "example": 1234567890123456789 - }, - "doubleType": { - "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", - "type": [ - "number", - "string" - ], - "format": "double", - "example": 3.14 - }, - "floatType": { - "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$", - "type": [ - "number", - "string" - ], - "format": "float", - "example": 3.14 - }, - "dateTimeType": { - "type": "string", - "format": "date-time", - "example": "2022-01-01T00:00:00Z" - }, - "dateOnlyType": { - "type": "string", - "format": "date", - "example": "2022-01-01" - }, - "stringType": { - "type": [ - "null", - "string" - ], - "example": "Hello, World!" - }, - "guidType": { - "type": "string", - "format": "uuid", - "example": "2d8f1eac-b5c6-4e29-8c62-4d9d75ef3d3d" - }, - "timeOnlyType": { - "type": "string", - "format": "time", - "example": "12:30:45" - }, - "timeSpanType": { - "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$", - "type": "string", - "example": "P3DT4H5M" - }, - "byteType": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "uint8", - "example": 255 - }, - "decimalType": { - "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$", - "type": [ - "number", - "string" - ], - "format": "double", - "example": 3.14159265359 - }, - "uriType": { - "type": [ - "null", - "string" - ], - "format": "uri", - "example": "https://example.com" - } - } - }, - "User": { - "type": "object", - "properties": { - "id": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "description": "The unique identifier for the user.", - "format": "int32" - }, - "name": { - "type": [ - "null", - "string" - ], - "description": "The user's display name." - } - } - } - } - }, - "tags": [ - { - "name": "testhost" - } - ] -} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt deleted file mode 100644 index 34480702bb5e..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences_openapi.json.verified.txt +++ /dev/null @@ -1,123 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/example": { - "post": { - "tags": [ - "testhost" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RootModel" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - } - } - } - } - }, - "components": { - "schemas": { - "ModelWithoutSummary": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - } - }, - "ModelWithSummary": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - }, - "description": "Comment on class ModelWithSummary." - }, - "RootModel": { - "type": "object", - "properties": { - "noPropertyComment": { - "$ref": "#/components/schemas/ModelWithSummary" - }, - "firstModelWithSummary": { - "description": "Comment on property FirstModelWithSummary.", - "$ref": "#/components/schemas/ModelWithSummary" - }, - "secondModelWithSummary": { - "description": "Comment on property SecondModelWithSummary.", - "$ref": "#/components/schemas/ModelWithSummary" - }, - "firstModelWithoutSummary": { - "description": "Comment on property FirstModelWithoutSummary.", - "$ref": "#/components/schemas/ModelWithoutSummary" - }, - "secondModelWithoutSummary": { - "description": "Comment on property SecondModelWithoutSummary.", - "$ref": "#/components/schemas/ModelWithoutSummary" - }, - "firstModelInline": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - }, - "description": "Comment on property FirstModelInline." - }, - "secondModelInline": { - "type": [ - "null", - "object" - ], - "properties": { - "street": { - "type": [ - "null", - "string" - ] - } - }, - "description": "Comment on property SecondModelInline." - } - }, - "description": "Comment on class RootModel." - } - } - }, - "tags": [ - { - "name": "testhost" - } - ] -} \ No newline at end of file diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt deleted file mode 100644 index b252dafd093f..000000000000 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats_openapi.json.verified.txt +++ /dev/null @@ -1,43 +0,0 @@ -{ - "openapi": "3.1.1", - "info": { - "title": "testhost | v1", - "version": "1.0.0" - }, - "paths": { - "/test-method": { - "post": { - "tags": [ - "TestApi" - ], - "summary": "This method should have its XML comment merged properly.", - "parameters": [ - { - "name": "id", - "in": "query", - "description": "The identifier for the test.", - "required": true, - "schema": { - "pattern": "^-?(?:0|[1-9]\\d*)$", - "type": [ - "integer", - "string" - ], - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "A task representing the asynchronous operation." - } - } - } - } - }, - "tags": [ - { - "name": "TestApi" - } - ] -} \ No newline at end of file From 86b5753aa0a3febfae707adcd06a23e7ae99337b Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Tue, 5 Aug 2025 17:32:52 +0200 Subject: [PATCH 10/14] Fix dereference null warning because the compiler cannot analyze it from a boolean value --- src/OpenApi/gen/XmlCommentGenerator.Emitter.cs | 7 ++++--- ...penApi#OpenApiXmlCommentSupport.generated.verified.cs | 9 +++++---- ...lTexts#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- ...chemas#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- ...ollers#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- ...alApis#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- ...chemas#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- ...rences#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- ...ormats#OpenApiXmlCommentSupport.generated.verified.cs | 7 ++++--- 9 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index 24e3c75b38e8..d6e39caeb8a4 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -464,11 +464,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -477,6 +477,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 57ee588a685b..63f361c13299 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -1,4 +1,4 @@ -//HintName: OpenApiXmlCommentSupport.generated.cs +//HintName: OpenApiXmlCommentSupport.generated.cs //------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -446,11 +446,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -459,6 +459,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 42fbee8d368d..b9a3352445cc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -475,11 +475,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -488,6 +488,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 0388ffa1446d..35f757e49647 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -567,11 +567,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -580,6 +580,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 4f0f0c6306de..13079b29b206 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -450,11 +450,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -463,6 +463,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 87d8011a2939..8391c75101a1 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -468,11 +468,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -481,6 +481,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index b1ede504b35a..ba40d5314523 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -476,11 +476,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -489,6 +489,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 50f490a82aa4..f299cc4d89bc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -455,11 +455,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -468,6 +468,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index 6597f34eaf10..b12aa91bb48c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -447,11 +447,11 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - var isInlinedSchema = schema.Metadata is null + if(schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) - || string.IsNullOrEmpty(schemaId as string); - if(isInlinedSchema) + || string.IsNullOrEmpty(schemaId as string)) { + // Inlined schema schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { @@ -460,6 +460,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext } else { + // Schema Reference schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { From c6f4f23d47d0646180717b4d0a17178c10401b6c Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Tue, 5 Aug 2025 18:36:13 +0200 Subject: [PATCH 11/14] Fix null assignment warning --- src/OpenApi/gen/XmlCommentGenerator.Emitter.cs | 6 +++--- src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs | 3 ++- ...ddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...onalTexts#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...OnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...ntrollers#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...nimalApis#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...OnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...eferences#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- ...IdFormats#OpenApiXmlCommentSupport.generated.verified.cs | 6 +++--- 10 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index d6e39caeb8a4..d7336cfcd93d 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -469,7 +469,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -478,10 +478,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 2971ab2e9b6c..396a3b76c4be 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -101,7 +101,8 @@ internal sealed class OpenApiSchemaService( { schema.ApplyNullabilityContextInfo(jsonPropertyInfo); } - if (context.PropertyInfo is { AttributeProvider: { } attributeProvider }) + var isInlinedSchema = schema["x-schema-id"] is null; + if (isInlinedSchema && context.PropertyInfo is { AttributeProvider: { } attributeProvider }) { if (attributeProvider.GetCustomAttributes(inherit: false).OfType() is { } validationAttributes) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 63f361c13299..214e980d5018 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -451,7 +451,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -460,10 +460,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index b9a3352445cc..4be8664b28b0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -480,7 +480,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -489,10 +489,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 35f757e49647..8745b4d97c47 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -572,7 +572,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -581,10 +581,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index 13079b29b206..d833e1aeb6b9 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -455,7 +455,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -464,10 +464,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 8391c75101a1..45acad5d7559 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -473,7 +473,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -482,10 +482,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index ba40d5314523..a7bd799c37d8 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -481,7 +481,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -490,10 +490,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index f299cc4d89bc..6f711e0aaef5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -460,7 +460,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -469,10 +469,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index b12aa91bb48c..e53a7f6108ae 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -452,7 +452,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext || string.IsNullOrEmpty(schemaId as string)) { // Inlined schema - schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { schema.Example = jsonString.Parse(); @@ -461,10 +461,10 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext else { // Schema Reference - schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary; + schema.Metadata["x-ref-description"] = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary!; if (propertyComment.Examples?.FirstOrDefault() is { } jsonString) { - schema.Metadata["x-ref-example"] = jsonString.Parse(); + schema.Metadata["x-ref-example"] = jsonString.Parse()!; } } } From bf609205daeb372cbe07a60070c1b1140596b429 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Tue, 5 Aug 2025 19:44:36 +0200 Subject: [PATCH 12/14] Add tests around comments --- .../SchemaTests.cs | 87 +++++++++++++------ ...ApiXmlCommentSupport.generated.verified.cs | 18 ++-- 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs index f6dc3c9e4f2f..629b1322c376 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs @@ -286,6 +286,9 @@ public async Task XmlCommentsOnPropertiesShouldApplyToSchemaReferences() /// /// Comment on class ModelWithSummary. /// +/// +/// { "street": "ModelWithSummaryClass" } +/// public class ModelWithSummary { public string Street { get; set; } @@ -299,6 +302,9 @@ public class ModelWithoutSummary /// /// Comment on class ModelInline. /// +/// +/// { "street": "ModelInlineClass" } +/// public class ModelInline { public string Street { get; set; } @@ -307,39 +313,60 @@ public class ModelInline /// /// Comment on class RootModel. /// +/// +/// { } +/// public class RootModel { public ModelWithSummary NoPropertyComment { get; set; } /// - /// Comment on property FirstModelWithSummary. + /// Comment on property ModelWithSummary1. /// - public ModelWithSummary FirstModelWithSummary { get; set; } + /// + /// { "street": "ModelWithSummary1Prop" } + /// + public ModelWithSummary ModelWithSummary1 { get; set; } /// - /// Comment on property SecondModelWithSummary. + /// Comment on property ModelWithSummary2. /// - public ModelWithSummary SecondModelWithSummary { get; set; } + /// + /// { "street": "ModelWithSummary2Prop" } + /// + public ModelWithSummary ModelWithSummary2 { get; set; } /// - /// Comment on property FirstModelWithoutSummary. + /// Comment on property ModelWithoutSummary1. /// - public ModelWithoutSummary FirstModelWithoutSummary { get; set; } + /// + /// { "street": "ModelWithoutSummary1Prop" } + /// + public ModelWithoutSummary ModelWithoutSummary1 { get; set; } /// - /// Comment on property SecondModelWithoutSummary. + /// Comment on property ModelWithoutSummary2. /// - public ModelWithoutSummary SecondModelWithoutSummary { get; set; } + /// + /// { "street": "ModelWithoutSummary2Prop" } + /// + public ModelWithoutSummary ModelWithoutSummary2 { get; set; } /// - /// Comment on property FirstModelInline. + /// Comment on property ModelInline1. /// - public ModelInline FirstModelInline { get; set; } + /// + /// { "street": "ModelInline1Prop" } + /// + public ModelInline ModelInline1 { get; set; } /// - /// Comment on property SecondModelInline. + /// Comment on property ModelInline2. /// - public ModelInline SecondModelInline { get; set; } + /// + /// { "street": "ModelInline2Prop" } + /// + public ModelInline ModelInline2 { get; set; } } """; var generator = new XmlCommentGenerator(); @@ -355,6 +382,7 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => var modelWithSummary = document.Components.Schemas["ModelWithSummary"]; Assert.Equal("Comment on class ModelWithSummary.", modelWithSummary.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelWithSummaryClass" }"""), modelWithSummary.Example)); var modelWithoutSummary = document.Components.Schemas["ModelWithoutSummary"]; Assert.Null(modelWithoutSummary.Description); @@ -362,27 +390,32 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document => Assert.DoesNotContain("ModelInline", document.Components.Schemas.Keys); // Check RootModel properties - var noPropertyCommentReference = Assert.IsType(rootModelSchema.Properties["noPropertyComment"]); - Assert.Null(noPropertyCommentReference.Reference.Description); + var noPropertyCommentProp = Assert.IsType(rootModelSchema.Properties["noPropertyComment"]); + Assert.Null(noPropertyCommentProp.Reference.Description); - Assert.IsType(rootModelSchema.Properties["firstModelWithSummary"]); - Assert.Equal("Comment on property FirstModelWithSummary.", rootModelSchema.Properties["firstModelWithSummary"].Description); + var modelWithSummary1Prop = Assert.IsType(rootModelSchema.Properties["modelWithSummary1"]); + Assert.Equal("Comment on property ModelWithSummary1.", modelWithSummary1Prop.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelWithSummary1Prop" }"""), modelWithSummary1Prop.Examples[0])); - Assert.IsType(rootModelSchema.Properties["firstModelWithoutSummary"]); - Assert.Equal("Comment on property FirstModelWithoutSummary.", rootModelSchema.Properties["firstModelWithoutSummary"].Description); + var modelWithSummary2Prop = Assert.IsType(rootModelSchema.Properties["modelWithSummary2"]); + Assert.Equal("Comment on property ModelWithSummary2.", modelWithSummary2Prop.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelWithSummary2Prop" }"""), modelWithSummary2Prop.Examples[0])); - Assert.IsType(rootModelSchema.Properties["firstModelInline"]); - Assert.Equal("Comment on property FirstModelInline.", rootModelSchema.Properties["firstModelInline"].Description); + var modelWithoutSummary1Prop = Assert.IsType(rootModelSchema.Properties["modelWithoutSummary1"]); + Assert.Equal("Comment on property ModelWithoutSummary1.", modelWithoutSummary1Prop.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelWithoutSummary1Prop" }"""), modelWithoutSummary1Prop.Examples[0])); - // Verify that comments on the same type override each other - Assert.IsType(rootModelSchema.Properties["secondModelWithSummary"]); - Assert.Equal("Comment on property FirstModelWithSummary.", rootModelSchema.Properties["firstModelWithSummary"].Description); + var modelWithoutSummary2Prop = Assert.IsType(rootModelSchema.Properties["modelWithoutSummary2"]); + Assert.Equal("Comment on property ModelWithoutSummary2.", modelWithoutSummary2Prop.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelWithoutSummary2Prop" }"""), modelWithoutSummary2Prop.Examples[0])); - Assert.IsType(rootModelSchema.Properties["secondModelWithoutSummary"]); - Assert.Equal("Comment on property FirstModelWithoutSummary.", rootModelSchema.Properties["firstModelWithoutSummary"].Description); + var modelInline1Prop = Assert.IsType(rootModelSchema.Properties["modelInline1"]); + Assert.Equal("Comment on property ModelInline1.", modelInline1Prop.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelInline1Prop" }"""), modelInline1Prop.Example)); - Assert.IsType(rootModelSchema.Properties["secondModelInline"]); - Assert.Equal("Comment on property SecondModelInline.", rootModelSchema.Properties["secondModelInline"].Description); + var modelInline2Prop = Assert.IsType(rootModelSchema.Properties["modelInline2"]); + Assert.Equal("Comment on property ModelInline2.", modelInline2Prop.Description); + Assert.True(JsonNode.DeepEquals(JsonNode.Parse("""{ "street": "ModelInline2Prop" }"""), modelInline2Prop.Example)); }); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 6f711e0aaef5..621c313f28c0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -70,15 +70,15 @@ private static Dictionary GenerateCacheEntries() { var cache = new Dictionary(); - cache.Add(@"T:ModelWithSummary", new XmlComment(@"Comment on class ModelWithSummary.", null, null, null, null, false, null, null, null)); - cache.Add(@"T:ModelInline", new XmlComment(@"Comment on class ModelInline.", null, null, null, null, false, null, null, null)); - cache.Add(@"T:RootModel", new XmlComment(@"Comment on class RootModel.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:RootModel.FirstModelWithSummary", new XmlComment(@"Comment on property FirstModelWithSummary.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:RootModel.SecondModelWithSummary", new XmlComment(@"Comment on property SecondModelWithSummary.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:RootModel.FirstModelWithoutSummary", new XmlComment(@"Comment on property FirstModelWithoutSummary.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:RootModel.SecondModelWithoutSummary", new XmlComment(@"Comment on property SecondModelWithoutSummary.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:RootModel.FirstModelInline", new XmlComment(@"Comment on property FirstModelInline.", null, null, null, null, false, null, null, null)); - cache.Add(@"P:RootModel.SecondModelInline", new XmlComment(@"Comment on property SecondModelInline.", null, null, null, null, false, null, null, null)); + cache.Add(@"T:ModelWithSummary", new XmlComment(@"Comment on class ModelWithSummary.", null, null, null, null, false, [@"{ ""street"": ""ModelWithSummaryClass"" }"], null, null)); + cache.Add(@"T:ModelInline", new XmlComment(@"Comment on class ModelInline.", null, null, null, null, false, [@"{ ""street"": ""ModelInlineClass"" }"], null, null)); + cache.Add(@"T:RootModel", new XmlComment(@"Comment on class RootModel.", null, null, null, null, false, [@"{ }"], null, null)); + cache.Add(@"P:RootModel.ModelWithSummary1", new XmlComment(@"Comment on property ModelWithSummary1.", null, null, null, null, false, [@"{ ""street"": ""ModelWithSummary1Prop"" }"], null, null)); + cache.Add(@"P:RootModel.ModelWithSummary2", new XmlComment(@"Comment on property ModelWithSummary2.", null, null, null, null, false, [@"{ ""street"": ""ModelWithSummary2Prop"" }"], null, null)); + cache.Add(@"P:RootModel.ModelWithoutSummary1", new XmlComment(@"Comment on property ModelWithoutSummary1.", null, null, null, null, false, [@"{ ""street"": ""ModelWithoutSummary1Prop"" }"], null, null)); + cache.Add(@"P:RootModel.ModelWithoutSummary2", new XmlComment(@"Comment on property ModelWithoutSummary2.", null, null, null, null, false, [@"{ ""street"": ""ModelWithoutSummary2Prop"" }"], null, null)); + cache.Add(@"P:RootModel.ModelInline1", new XmlComment(@"Comment on property ModelInline1.", null, null, null, null, false, [@"{ ""street"": ""ModelInline1Prop"" }"], null, null)); + cache.Add(@"P:RootModel.ModelInline2", new XmlComment(@"Comment on property ModelInline2.", null, null, null, null, false, [@"{ ""street"": ""ModelInline2Prop"" }"], null, null)); return cache; } From c8f987f0dafab97afbdcc8d11437360e87464228 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Wed, 6 Aug 2025 10:07:28 +0200 Subject: [PATCH 13/14] Fix formatting of emitted code --- src/OpenApi/gen/XmlCommentGenerator.Emitter.cs | 2 +- ...eptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...ditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...TagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...omControllers#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...omMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...entsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...emaReferences#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- ...tionIdFormats#OpenApiXmlCommentSupport.generated.verified.cs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs index d7336cfcd93d..ecd59881e3f2 100644 --- a/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs +++ b/src/OpenApi/gen/XmlCommentGenerator.Emitter.cs @@ -464,7 +464,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs index 214e980d5018..4f143854b812 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs @@ -446,7 +446,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs index 4be8664b28b0..7b4426da5bee 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs @@ -475,7 +475,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index 8745b4d97c47..979f2251418d 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -567,7 +567,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs index d833e1aeb6b9..e1505e95c046 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs @@ -450,7 +450,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs index 45acad5d7559..b0c6faedc4ee 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs @@ -468,7 +468,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs index a7bd799c37d8..bf889e0c50be 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.SupportsXmlCommentsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs @@ -476,7 +476,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs index 621c313f28c0..e60c10ce68f7 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/SchemaTests.XmlCommentsOnPropertiesShouldApplyToSchemaReferences#OpenApiXmlCommentSupport.generated.verified.cs @@ -455,7 +455,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs index e53a7f6108ae..a2a2af1855b3 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/XmlCommentDocumentationIdTests.CanMergeXmlCommentsWithDifferentDocumentationIdFormats#OpenApiXmlCommentSupport.generated.verified.cs @@ -447,7 +447,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext // Apply comments from the property if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment)) { - if(schema.Metadata is null + if (schema.Metadata is null || !schema.Metadata.TryGetValue("x-schema-id", out var schemaId) || string.IsNullOrEmpty(schemaId as string)) { From 01cf3b063a45706a40427115275fde888dbf3999 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Meer Date: Thu, 7 Aug 2025 09:41:52 +0200 Subject: [PATCH 14/14] Revert change to OpenApiSchemaService --- src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 396a3b76c4be..2971ab2e9b6c 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -101,8 +101,7 @@ internal sealed class OpenApiSchemaService( { schema.ApplyNullabilityContextInfo(jsonPropertyInfo); } - var isInlinedSchema = schema["x-schema-id"] is null; - if (isInlinedSchema && context.PropertyInfo is { AttributeProvider: { } attributeProvider }) + if (context.PropertyInfo is { AttributeProvider: { } attributeProvider }) { if (attributeProvider.GetCustomAttributes(inherit: false).OfType() is { } validationAttributes) {