Skip to content

Commit 8eb2713

Browse files
committed
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.
1 parent 7595507 commit 8eb2713

12 files changed

+754
-86
lines changed

src/OpenApi/gen/XmlCommentGenerator.Emitter.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
449449
{
450450
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
451451
{
452-
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
452+
// Apply comments from the type
453+
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
454+
{
455+
schema.Description = typeComment.Summary;
456+
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
457+
{
458+
schema.Example = jsonString.Parse();
459+
}
460+
}
461+
462+
var isPropertyInlinedSchema = schema.Metadata is null
463+
|| !schema.Metadata.TryGetValue("x-schema-id", out var schemaId)
464+
|| string.IsNullOrEmpty(schemaId as string);
465+
if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
453466
{
467+
// Apply comments from the property
454468
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment))
455469
{
456470
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
@@ -460,14 +474,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
460474
}
461475
}
462476
}
463-
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
464-
{
465-
schema.Description = typeComment.Summary;
466-
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
467-
{
468-
schema.Example = jsonString.Parse();
469-
}
470-
}
471477
return Task.CompletedTask;
472478
}
473479
}
@@ -507,6 +513,7 @@ file static class GeneratedServiceCollectionExtensions
507513
{{GenerateAddOpenApiInterceptions(groupedAddOpenApiInvocations)}}
508514
}
509515
}
516+
510517
""";
511518

512519
internal static string GetAddOpenApiInterceptor(AddOpenApiOverloadVariant overloadVariant) => overloadVariant switch

src/OpenApi/src/Extensions/OpenApiDocumentExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ public static IOpenApiSchema AddOpenApiSchemaByReference(this OpenApiDocument do
2121
document.Workspace ??= new();
2222
var location = document.BaseUri + "/components/schemas/" + schemaId;
2323
document.Workspace.RegisterComponentForDocument(document, schema, location);
24-
return new OpenApiSchemaReference(schemaId, document)
25-
{
26-
Description = schema.Description,
27-
};
24+
return new OpenApiSchemaReference(schemaId, document);
2825
}
2926
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/CompletenessTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -516,9 +516,9 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
516516
Assert.Equal("This class validates the behavior for mapping\ngeneric types to open generics for use in\ntypeof expressions.", genericParent.Description, ignoreLineEndingDifferences: true);
517517
Assert.Equal("This property is a nullable value type.", genericParent.Properties["id"].Description);
518518
Assert.Equal("This property is a nullable reference type.", genericParent.Properties["name"].Description);
519-
Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description);
520-
Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description);
521-
Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description);
519+
//Assert.Equal("This property is a generic type containing a tuple.", genericParent.Properties["taskOfTupleProp"].Description);
520+
//Assert.Equal("This property is a tuple with a generic type inside.", genericParent.Properties["tupleWithGenericProp"].Description);
521+
//Assert.Equal("This property is a tuple with a nested generic type inside.", genericParent.Properties["tupleWithNestedGenericProp"].Description);
522522

523523
path = document.Paths["/params-and-param-refs"].Operations[HttpMethod.Post];
524524
var paramsAndParamRefs = path.RequestBody.Content["application/json"].Schema;

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/SchemaTests.cs

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -319,18 +319,115 @@ await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
319319

320320
path = document.Paths["/company"].Operations[HttpMethod.Post];
321321
var company = path.RequestBody.Content["application/json"].Schema;
322-
Assert.Equal("Billing address.", company.Properties["billingAddressClassWithSummary"].Description);
323-
Assert.Equal("Billing address.", company.Properties["billingAddressClassWithoutSummary"].Description);
324322
Assert.Equal("Billing address.", company.Properties["billingAddressNested"].Description);
325-
Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithSummary"].Description);
326-
Assert.Equal("Visiting address.", company.Properties["visitingAddressClassWithoutSummary"].Description);
327323
Assert.Equal("Visiting address.", company.Properties["visitingAddressNested"].Description);
328324

329325
var addressWithSummary = document.Components.Schemas["AddressWithSummary"];
330326
Assert.Equal("An address.", addressWithSummary.Description);
331327

332328
var addressWithoutSummary = document.Components.Schemas["AddressWithoutSummary"];
333-
Assert.Null(addressWithSummary.Description);
329+
Assert.Null(addressWithoutSummary.Description);
330+
});
331+
}
332+
333+
[Fact]
334+
public async Task XmlCommentsOnPropertiesShouldNotApplyToReferencedSchemas()
335+
{
336+
var source = """
337+
using System;
338+
using Microsoft.AspNetCore.Builder;
339+
using Microsoft.Extensions.DependencyInjection;
340+
341+
var builder = WebApplication.CreateBuilder();
342+
343+
builder.Services.AddOpenApi(options => {
344+
var prevCreateSchemaReferenceId = options.CreateSchemaReferenceId;
345+
options.CreateSchemaReferenceId = (x) => x.Type == typeof(ModelInline) ? null : prevCreateSchemaReferenceId(x);
346+
});
347+
348+
var app = builder.Build();
349+
350+
app.MapPost("/example", (RootModel model) => { });
351+
352+
app.Run();
353+
354+
/// <summary>
355+
/// Comment on class ModelWithSummary.
356+
/// </summary>
357+
public class ModelWithSummary
358+
{
359+
public string Street { get; set; }
360+
}
361+
362+
public class ModelWithoutSummary
363+
{
364+
public string Street { get; set; }
365+
}
366+
367+
/// <summary>
368+
/// Comment on class ModelInline.
369+
/// </summary>
370+
public class ModelInline
371+
{
372+
public string Street { get; set; }
373+
}
374+
375+
/// <summary>
376+
/// Comment on class RootModel.
377+
/// </summary>
378+
public class RootModel
379+
{
380+
/// <summary>
381+
/// Comment on property FirstModelWithSummary.
382+
/// </summary>
383+
public ModelWithSummary FirstModelWithSummary { get; set; }
384+
385+
/// <summary>
386+
/// Comment on property SecondModelWithSummary.
387+
/// </summary>
388+
public ModelWithSummary SecondModelWithSummary { get; set; }
389+
390+
/// <summary>
391+
/// Comment on property FirstModelWithoutSummary.
392+
/// </summary>
393+
public ModelWithoutSummary FirstModelWithoutSummary { get; set; }
394+
395+
/// <summary>
396+
/// Comment on property SecondModelWithoutSummary.
397+
/// </summary>
398+
public ModelWithoutSummary SecondModelWithoutSummary { get; set; }
399+
400+
/// <summary>
401+
/// Comment on property FirstModelInline.
402+
/// </summary>
403+
public ModelInline FirstModelInline { get; set; }
404+
405+
/// <summary>
406+
/// Comment on property SecondModelInline.
407+
/// </summary>
408+
public ModelInline SecondModelInline { get; set; }
409+
}
410+
""";
411+
var generator = new XmlCommentGenerator();
412+
await SnapshotTestHelper.Verify(source, generator, out var compilation);
413+
await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
414+
{
415+
var path = document.Paths["/example"].Operations[HttpMethod.Post];
416+
var exampleOperationBodySchema = path.RequestBody.Content["application/json"].Schema;
417+
Assert.Equal("Comment on class RootModel.", exampleOperationBodySchema.Description);
418+
419+
var rootModelSchema = document.Components.Schemas["RootModel"];
420+
Assert.Equal("Comment on class RootModel.", rootModelSchema.Description);
421+
422+
var modelWithSummary = document.Components.Schemas["ModelWithSummary"];
423+
Assert.Equal("Comment on class ModelWithSummary.", modelWithSummary.Description);
424+
425+
var modelWithoutSummary = document.Components.Schemas["ModelWithoutSummary"];
426+
Assert.Null(modelWithoutSummary.Description);
427+
428+
//Assert.DoesNotContain("ModelInline", document.Components.Schemas.Keys);
429+
Assert.Equal("Comment on property FirstModelInline.", rootModelSchema.Properties["firstModelInline"].Description);
430+
Assert.Equal("Comment on property SecondModelInline.", rootModelSchema.Properties["secondModelInline"].Description);
334431
});
335432
}
336433
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AddOpenApiTests.CanInterceptAddOpenApi#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
431431
{
432432
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
433433
{
434-
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
434+
// Apply comments from the type
435+
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
436+
{
437+
schema.Description = typeComment.Summary;
438+
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
439+
{
440+
schema.Example = jsonString.Parse();
441+
}
442+
}
443+
444+
var isPropertyInlinedSchema = schema.Metadata is null
445+
|| !schema.Metadata.TryGetValue("x-schema-id", out var schemaId)
446+
|| string.IsNullOrEmpty(schemaId as string);
447+
if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
435448
{
449+
// Apply comments from the property
436450
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment))
437451
{
438452
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
@@ -442,14 +456,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
442456
}
443457
}
444458
}
445-
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
446-
{
447-
schema.Description = typeComment.Summary;
448-
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
449-
{
450-
schema.Example = jsonString.Parse();
451-
}
452-
}
453459
return Task.CompletedTask;
454460
}
455461
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/AdditionalTextsTests.CanHandleXmlForSchemasInAdditionalTexts#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -460,8 +460,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
460460
{
461461
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
462462
{
463-
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
463+
// Apply comments from the type
464+
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
465+
{
466+
schema.Description = typeComment.Summary;
467+
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
468+
{
469+
schema.Example = jsonString.Parse();
470+
}
471+
}
472+
473+
var isPropertyInlinedSchema = schema.Metadata is null
474+
|| !schema.Metadata.TryGetValue("x-schema-id", out var schemaId)
475+
|| string.IsNullOrEmpty(schemaId as string);
476+
if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
464477
{
478+
// Apply comments from the property
465479
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment))
466480
{
467481
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
@@ -471,14 +485,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
471485
}
472486
}
473487
}
474-
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
475-
{
476-
schema.Description = typeComment.Summary;
477-
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
478-
{
479-
schema.Example = jsonString.Parse();
480-
}
481-
}
482488
return Task.CompletedTask;
483489
}
484490
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/CompletenessTests.SupportsAllXmlTagsOnSchemas#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
552552
{
553553
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
554554
{
555-
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
555+
// Apply comments from the type
556+
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
557+
{
558+
schema.Description = typeComment.Summary;
559+
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
560+
{
561+
schema.Example = jsonString.Parse();
562+
}
563+
}
564+
565+
var isPropertyInlinedSchema = schema.Metadata is null
566+
|| !schema.Metadata.TryGetValue("x-schema-id", out var schemaId)
567+
|| string.IsNullOrEmpty(schemaId as string);
568+
if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
556569
{
570+
// Apply comments from the property
557571
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment))
558572
{
559573
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
@@ -563,14 +577,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
563577
}
564578
}
565579
}
566-
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
567-
{
568-
schema.Description = typeComment.Summary;
569-
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
570-
{
571-
schema.Example = jsonString.Parse();
572-
}
573-
}
574580
return Task.CompletedTask;
575581
}
576582
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromControllers#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
435435
{
436436
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
437437
{
438-
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
438+
// Apply comments from the type
439+
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
440+
{
441+
schema.Description = typeComment.Summary;
442+
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
443+
{
444+
schema.Example = jsonString.Parse();
445+
}
446+
}
447+
448+
var isPropertyInlinedSchema = schema.Metadata is null
449+
|| !schema.Metadata.TryGetValue("x-schema-id", out var schemaId)
450+
|| string.IsNullOrEmpty(schemaId as string);
451+
if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
439452
{
453+
// Apply comments from the property
440454
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment))
441455
{
442456
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
@@ -446,14 +460,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
446460
}
447461
}
448462
}
449-
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
450-
{
451-
schema.Description = typeComment.Summary;
452-
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
453-
{
454-
schema.Example = jsonString.Parse();
455-
}
456-
}
457463
return Task.CompletedTask;
458464
}
459465
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests/snapshots/OperationTests.SupportsXmlCommentsOnOperationsFromMinimalApis#OpenApiXmlCommentSupport.generated.verified.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,22 @@ private static OpenApiParameter UnwrapOpenApiParameter(IOpenApiParameter sourceP
453453
{
454454
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
455455
{
456-
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
456+
// Apply comments from the type
457+
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
458+
{
459+
schema.Description = typeComment.Summary;
460+
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
461+
{
462+
schema.Example = jsonString.Parse();
463+
}
464+
}
465+
466+
var isPropertyInlinedSchema = schema.Metadata is null
467+
|| !schema.Metadata.TryGetValue("x-schema-id", out var schemaId)
468+
|| string.IsNullOrEmpty(schemaId as string);
469+
if (isPropertyInlinedSchema && context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
457470
{
471+
// Apply comments from the property
458472
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(propertyInfo.CreateDocumentationId()), out var propertyComment))
459473
{
460474
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
@@ -464,14 +478,6 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
464478
}
465479
}
466480
}
467-
if (XmlCommentCache.Cache.TryGetValue(DocumentationCommentIdHelper.NormalizeDocId(context.JsonTypeInfo.Type.CreateDocumentationId()), out var typeComment))
468-
{
469-
schema.Description = typeComment.Summary;
470-
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
471-
{
472-
schema.Example = jsonString.Parse();
473-
}
474-
}
475481
return Task.CompletedTask;
476482
}
477483
}

0 commit comments

Comments
 (0)