Skip to content
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

Inheritance hierarchy not flattened by default #2514

Open
Yakimych opened this issue Oct 4, 2022 · 4 comments · May be fixed by #2683
Open

Inheritance hierarchy not flattened by default #2514

Yakimych opened this issue Oct 4, 2022 · 4 comments · May be fixed by #2683

Comments

@Yakimych
Copy link

Yakimych commented Oct 4, 2022

According to the docs:

By default, Swashbuckle flattens inheritance hierarchies. That is, for derived models, the inherited properties are combined and listed alongside the declared properties.

However, when using the Shape-Rectange-Circle hierarchy (as in the tests), the schema generated with Swashbuckle.AspNetCore.Swagger v6.4.0 still includes the base (parent) fields via allOf.

Expected: name and typeName to end up under Circle -> properties.
Actual: allOf that references Shape is under Circle, despite not calling c.UseAllOfForInheritance() in the code.

openapi: 3.0.1
info:
  title: 'WebApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
  version: '1.0'
paths:
  /Shapes:
    get:
      tags:
        - Shapes
      operationId: GetShape
      responses:
        '200':
          description: Success
          content:
            text/plain:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Rectangle'
                  - $ref: '#/components/schemas/Circle'
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Rectangle'
                  - $ref: '#/components/schemas/Circle'
            text/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Rectangle'
                  - $ref: '#/components/schemas/Circle'
components:
  schemas:
    Circle:
      type: object
      allOf:
        - $ref: '#/components/schemas/Shape'
      properties:
        radius:
          type: integer
          format: int32
      additionalProperties: false
    Rectangle:
      type: object
      allOf:
        - $ref: '#/components/schemas/Shape'
      properties:
        height:
          type: integer
          format: int32
        width:
          type: integer
          format: int32
      additionalProperties: false
    Shape:
      required:
        - typeName
      type: object
      properties:
        typeName:
          type: string
        name:
          type: string
          nullable: true
      additionalProperties: false
      discriminator:
        propertyName: typeName
        mapping:
          Rectangle: '#/components/schemas/Rectangle'
          Circle: '#/components/schemas/Circle'

Here is an (almost) minimal repro with .NET 6 (Program.cs):

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddSwaggerGen(c =>
{
    c.UseOneOfForPolymorphism();

    c.SelectDiscriminatorNameUsing((baseType) => "typeName");
    c.SelectDiscriminatorValueUsing((subType) => subType.Name);
});

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

[ApiController]
[Route("[controller]")]
public class ShapesController : ControllerBase
{
    [HttpGet(Name = "GetShape")]
    public Shape GetShape() => new Rectangle {Height = 10, Width = 5};


    public abstract class Shape
    {
        public string Name { get; set; }
    }

    public class Rectangle : Shape
    {
        public int Height { get; set; }

        public int Width { get; set; }
    }

    public class Circle : Shape
    {
        public int Radius { get; set; }
    }
}

And here is the WebApplication1.csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
        <PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.4.0" />
    </ItemGroup>
</Project>
@danielcrabtree
Copy link

I can confirm this bug occurs on v6.5.0 as well.

The issue lies in Swashbuckle.AspNetCore/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs.

Wherein if UseOneOfForPolymorphism OR UseAllOfForInheritance is set, then the other is treated as though it were enabled as well due to line 353: if (_generatorOptions.|| _generatorOptions.UseOneOfForPolymorphism).

In the current design, UseOneOfForPolymorphism relies on the fact that UseAllOfForInheritance is enabled, as it adds the discriminator properties into the BaseType and assumes these will be inherited via AllOf.

The fix is to use the current design for when both UseOneOfForPolymorphism AND UseAllOfForInheritance are enabled and to implement a new design for when only UseOneOfForPolymorphism is enabled.

This new design would need to add the discriminator properties to all the SubTypes rather than to just the BaseTypes. It may also need changes to ensure the Mapping goes to each SubType as well.

@danielcrabtree danielcrabtree linked a pull request Jul 15, 2023 that will close this issue
@danielcrabtree
Copy link

I have submitted a Pull Request #2683 that fixes this bug.

Copy link
Contributor

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

@github-actions github-actions bot added the stale Stale issues or pull requests label Apr 15, 2024
@martincostello
Copy link
Collaborator

#2683 (comment)

@martincostello martincostello removed the stale Stale issues or pull requests label Apr 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants