-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Closed
Labels
area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-openapi
Milestone
Description
Is there an existing issue for this?
- I have searched the existing issuesTo pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.
Describe the bug
The generated schema transformer currently applies the comment on a property on a referenced schema instead of as part of the property. It also overwrites the description based on the last property transformed.
Expected Behavior
I would expect that the description of a referenced schema would be solely based on comments on that type. And for properties when it has a comment to generate something like:
"allOf": [
{ "$ref": "#/components/schemas/Address" }
],
"description": "Billing address"
But that removes some simplicity in the schemas, so it could also be that a description is not generated for "$ref" schemas.
Steps To Reproduce
Program.cs
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapPost("/operation", (Company c) =>
{
throw new NotImplementedException();
}).WithTags();
app.Run();
class Company
{
/// <summary>
/// Billing address
/// </summary>
public Address BillingAddress { get; set; }
/// <summary>
/// Visiting address
/// </summary>
public Address VisitingAddress { get; set; }
}
class Address
{
public string Street { get; set; }
}.csproj - all default, enable output on build and generate docs
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>
<OpenApiGenerateDocumentsOptions>--file-name openapi</OpenApiGenerateDocumentsOptions>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server"
Version="10.0.0-preview.4.25258.110">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>Output:
{
"openapi": "3.1.1",
"info": {
"title": "WebApplication5 | v1",
"version": "1.0.0"
},
"paths": {
"/operation": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Company"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"components": {
"schemas": {
"Address": {
"type": "object",
"properties": {
"street": {
"type": "string"
}
},
"description": "Visiting address"
},
"Company": {
"type": "object",
"properties": {
"billingAddress": {
"$ref": "#/components/schemas/Address"
},
"visitingAddress": {
"$ref": "#/components/schemas/Address"
}
}
}
}
}
}Exceptions (if any)
No response
.NET Version
10.0.100-preview.4.25258.110
Anything else?
Related xml generated code
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.OpenApi.SourceGenerators, Version=10.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "10.0.0.0")]
file class XmlCommentSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
if (context.JsonPropertyInfo is { AttributeProvider: PropertyInfo propertyInfo })
{
if (XmlCommentCache.Cache.TryGetValue(propertyInfo.CreateDocumentationId(), out var propertyComment))
{
schema.Description = propertyComment.Value ?? propertyComment.Returns ?? propertyComment.Summary;
if (propertyComment.Examples?.FirstOrDefault() is { } jsonString)
{
schema.Example = jsonString.Parse();
}
}
}
if (XmlCommentCache.Cache.TryGetValue(context.JsonTypeInfo.Type.CreateDocumentationId(), out var typeComment))
{
schema.Description = typeComment.Summary;
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
{
schema.Example = jsonString.Parse();
}
}
return Task.CompletedTask;
}
}Metadata
Metadata
Assignees
Labels
area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-openapi
Type
Projects
Relationships
Development
Select code repository
Activity
desjoerd commentedon May 16, 2025
So thinking how to solve this, it would probably need to know whether it is a schema with a reference id, if it is, it should handle that differently.
There is also another reverse problem. When a property is not something with a schema ref (reference id == null), the description will always be the comment of the
type.So if I set, don't use reference id's (emulating that address is a struct or value type for example)
I will get the following two results with comments:
Case: No comment on Address Type
This gives me an expected result, both have a different description.
Output:
{ "openapi": "3.1.1", "info": { "title": "WebApplication5 | v1", "version": "1.0.0" }, "paths": { "/operation": { "post": { "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "billingAddress": { "type": "object", "properties": { "street": { "type": "string" } }, "description": "Billing address" }, "visitingAddress": { "type": "object", "properties": { "street": { "type": "string" } }, "description": "Visiting address" } } } } }, "required": true }, "responses": { "200": { "description": "OK" } } } } } }Case: Comment on Address Type
This gives both address properties the same description.
Output:
{ "openapi": "3.1.1", "info": { "title": "WebApplication5 | v1", "version": "1.0.0" }, "paths": { "/operation": { "post": { "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "billingAddress": { "type": "object", "properties": { "street": { "type": "string" } }, "description": "Comment on Type Address" }, "visitingAddress": { "type": "object", "properties": { "street": { "type": "string" } }, "description": "Comment on Type Address" } } } } }, "required": true }, "responses": { "200": { "description": "OK" } } } } } }Maybe the logic should be something like:
The big question is whether rewriting the property schema to set the description on a property is something which is wanted/expected behavior. (Imho it's correct but it could surprise people).
{ "$ref": "#/components/schemas/Address" }To
captainsafia commentedon May 16, 2025
@desjoerd Thanks for filing this issue and doing the heavy lifting to get repros/ideas squared away here.
With OpenAPI 3.1, I believe we can take advantage of the face that
OpenApiSchemaReferenceobjects (ref) can contain description properties. That should allow us to emit a schemadescriptionproperties directly alongside the ref without having to use anallIOf. We'll need to update the schema transformer to distinguish betweenOpenApiSchemaReferenceandOpenApiSchematypes.With regard to the behavior you're seeing where type-level comments override property-level ones, I believe that's related to the application order we use in the emitted code. I think it's probably safe to switch this ordering. If you have a property, the type-level docs shouldn't override it.
Any interest in opening a PR to address this issue?
desjoerd commentedon May 17, 2025
After some digging, I think you are right, $ref merges instead of replaces in 2020-12 and doesn't touch annotations, which "description" is.
I am open to create a PR for it. Looking at the unit tests I think I have most of the bits and pieces to fix and test it.
For the schema transformer OpenApiSchemaReference vs OpenApiSchema, would just changing the argument to the interface
IOpenApiSchemawork, as both implement that? It would mean a breaking change, but I think as soon as someone used transformers in .NET 9 they already need to handle breaking changes.captainsafia commentedon May 19, 2025
Yep, we've been communicating the nature of the breaking changes between .NET 9 and .NET 10 since we are replatting onto the new version of the Microsoft.OpenApi library.
We could change the schema transformer API to take an IOpenApiSchema but the implementation currently assumes that all schema transforrmers run on the resolved schema (not the reference) to maintain behavioral compatibility with .NET 9. I'm a nit more cautious about introducing a behavioral change here. It would require that users check if an IOpenApiSchema is a reference or a direct entity frequently in their code.
Perhaps another way to do this is to use the operation transformer emitted by the source generator which operates on the IOpenApiOperation that has a reference to an IOpenApiSchema to support setting the description property on the reference instead of the resolved schema. Thoughts on this idea?
desjoerd commentedon May 19, 2025
If it is on the IOperationTransformer it would need to be recursive, following all references so I don't think that is an option.
I just got a maybe crazy idea, a reference transformer or a CreateSchemaReference option, or even a list of SchemaReferenceCreators. Currently it's hard to override the CreateSchemaReferenceId func from a library, which basically means, wrap the current func, if it's a certain type do the logic in the library else do the previous.
Example usage in my
undefinedwrapper library -> https://github.com/desjoerd/OptionalValues/blob/ccdab785c58e7660444feb71f512c83a8b222a91/examples/OptionalValues.Examples.OpenApi/Program.cs#L16This is going to be moved to a
IConfigureOptionsor an extension method. But it requires users to wrap CreateSchemaReferenceId as well.sorry for brevity, from my phone
captainsafia commentedon May 19, 2025
Pardon the dense question, but I don't quite see how a
CreateSchemaReferenceoption would help here? 😅I wonder if another way to address this is to make a change in the ResolveReferenceForSchema method that would check if any of the properties it is resolving references for have descriptions already set on them. If so, instead of returning the schema reference directly, it should hoist the description up to the reference then return it. Thoughts on this?
desjoerd commentedon May 20, 2025
I was thinking of an overload or more sophisticated
CreateSchemaReferencewhich creates theOpenApiSchemaReferenceitself, but I think that happens earlier in the process so it was an idea, but not a good one!I think your solution of merging the description on the schema reference should work also when looking at ordering.
I think it should hoist all annotations to the reference.
But I think it only should copy it to the reference when it's coming from a property comment. Otherwise you would get loads of duplications. I think that would require the Xml transformer to know whether the schema it's transforming is going to be a reference, but I am not sure if that is the case.
captainsafia commentedon May 20, 2025
Yep, I missed this in my original comment but it would only apply in the
ResolveReferenceForSchemacase where we are iterating through properties.I don't think it should, but we might be able to see what happens in practice.
desjoerd commentedon May 21, 2025
Doing it as part of
ResolveReferenceForSchemashould work indeed.On another note, I was trying to run the unit tests on the generation to do the first fix (order in the emitter) but as I've got the Dutch locale for numbers it generates the floating point numbers as
3,14. I don't know why yet because it looks to be using culture invariant or apis which I would assume to be invariant. I can create another issue for that for tracking.captainsafia commentedon May 21, 2025
@desjoerd I believe there may be an issue in the backlog already pertaining to this that I haven't gotten around to yet. :/ A quick glance at items labelled feature-openapi will help you see if it already exists
We really need to set up some infrastructure for testing culture-invariance on these things.
desjoerd commentedon Jun 3, 2025
I've done some fiddling in draft PR #62213 and I've added a comment with my findings #62213 (comment)
martincostello commentedon Jul 30, 2025
Does #62193 fully resolve this issue, or are there other bits that still need work?
It might just be that that PR just adds some infrastructure for localization stuff.
desjoerd commentedon Jul 30, 2025
What an awesome work on your PR @martincostello!
I will go through it all tomorrow. I will check with your changes and the changes done to the Xml comment source generator whether the issue is still there.
desjoerd commentedon Jul 31, 2025
@martincostello I verified the unit tests again which I now can easily run on my en-NL machine 🎉. But the issue around property comments still persists. It was only partly related to the fixes done in your PR.
@captainsafia I've done some changes to the XmlCommentGenerator in #62213 which I hope is still possible to have a look at and maybe merged. It unfortunately removes the possibility to set a description from a Property comment on a referenced schema, (it still allows it when it's getting inlined) but it when a schema would be referenced multiple times it would get the comment from the last property.
In a follow up PR on a later stage maybe we can make the bigger change, for example by ordering the transformers in a certain way and hoisting properties from the actual schema or exposing the actual SchemaReference. But that requires some design. I would like to help with that if possible. With PR #62213 we can make this change later as an enhancement.
captainsafia commentedon Aug 2, 2025
@desjoerd Thanks for taking a stab at this implementation and approaching it piece meal. I'll optimisitcally put this issue in 1.0-rc1 with the goal of getting it reviewed and merged before we snap for RC1.