Skip to content

Custom CreateSchemaReferenceId not always used in OpenAPI schema $ref #58341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
davidkarlsson opened this issue Oct 10, 2024 · 3 comments
Open
1 task done
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi

Comments

@davidkarlsson
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

If you set a custom OpenApiOptions.CreateSchemaReferenceId delegate it won't use the custom id from it when setting the $ref for a property that has the same type as its declaring type. The reason for this seems to be this code in OpenApiSchemaService where it uses the default JsonTypeInfo.GetSchemaReferenceId extension method instead of the one from settings:

if (jsonPropertyInfo.PropertyType == jsonPropertyInfo.DeclaringType)
{
    return new JsonObject { [OpenApiSchemaKeywords.RefKeyword] = context.TypeInfo.GetSchemaReferenceId() };
}

Expected Behavior

I expected the custom CreateSchemaReferenceId to be used even for nested properties of the same type as its parent which doesn't seem to be the case as far as I can tell.

Steps To Reproduce

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi(options =>
{
    options.CreateSchemaReferenceId = _ => "Overridden";
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

app.UseRouting();

app.UseAuthorization();

app.MapOpenApi();

app.MapControllers();

app.Run();

[ApiController]
[Route("Api/[controller]")]
public class TestController : ControllerBase
{
    [HttpGet("")]
    [ProducesResponseType<Parent>(StatusCodes.Status200OK)]
    public Ok<Parent> Index()
    {
        return TypedResults.Ok(new Parent { RequiredChild = new() });
    }
}

public class Parent
{
    public Child? NullableChild { get; init; }
    public required Child RequiredChild { get; init; }

    public Parent? NestedParent { get; set; }
}

public class Child;

/openapi/v1.json:

{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenApiTest | v1",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://localhost:5001"
    }
  ],
  "paths": {
    "/Api/Test": {
      "get": {
        "tags": [
          "Test"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/Overridden"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Overridden"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/Overridden"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Overridden": {
        "required": [
          "requiredChild"
        ],
        "type": "object",
        "properties": {
          "nullableChild": {
            "$ref": "#/components/schemas/Overridden2"
          },
          "requiredChild": {
            "$ref": "#/components/schemas/Overridden3"
          },
          "nestedParent": {
            "$ref": "#/components/schemas/Parent"
          }
        }
      },
      "Overridden2": {
        "type": "object",
        "nullable": true
      },
      "Overridden3": {
        "type": "object"
      }
    }
  },
  "tags": [
    {
      "name": "Test"
    }
  ]
}

Here I expected the nestedParent $ref to be #/components/schemas/Overridden and not #/components/schemas/Parent since that's what it will reference if you don't override OpenApiOptions.CreateSchemaReferenceId.

Exceptions (if any)

No response

.NET Version

9.0.100-rc.1.24452.12

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Oct 10, 2024
@gfoidl gfoidl added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Oct 10, 2024
@marthijn
Copy link

Same problem here I think. When I use types as follows:

public class ModelA { .. }
public class ModelB
{
  public ModelA? MyModelA { get; set; }
}

[ProducesResponseType(typeof(ModelA), StatusCodes.Status200OK)]
public IActionResult Function1(){ .. }

[ProducesResponseType(typeof(ModelB), StatusCodes.Status200OK)]
public IActionResult Function2(){ .. }

The OpenAPI spec will become something like this

"ModelA": {
  ...
  "nullable": true
},
"ModelA2": {
},

The difference is the "nullable": true property (as used in class ModelB). In my opinion this should not result in a duplicate.

Also, when ModelA is used in a list:

public class ModelC
{
  public List<ModelA> Models { get; set; }
}

a version 3 is created:

"ModelA3": {
},

"List`1": {
  "type": "array",
  "items": {
    "$ref": "#/components/schemas/ModelA3"
  }
},

@davidkarlsson
Copy link
Author

@marthijn That's not what this issue is about though if I understand you correctly. This issue is about that the custom delegate you set with OpenApiOptions.CreateSchemaReferenceId isn't being used to generate schema reference ids in a certain case.

I admit that my repro isn't very clear about this though because I was experimenting with the nullability stuff you are referencing as well when I was testing this.

I'm not sure but your issue sounds more like it's about a shortcoming with the OpenAPI 3.0 spec regarding nullability which #58619 would fix I think?

@marthijn
Copy link

@davidkarlsson thanks for the clarification, I indeed misunderstood your issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi
Projects
None yet
Development

No branches or pull requests

3 participants