Skip to content

Microsoft.AspNetCore.OpenApi should generate references to shared schemas rather then duplicating them  #56264

@michael-wolfenden

Description

@michael-wolfenden

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

This code using Microsoft.AspNetCore.OpenApi -Version 9.0.0-preview.5.24306.11

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();

var app = builder.Build();
app.MapOpenApi();

app
    .MapGet("/todos", () => new Todo[] { new ("delectus aut autem", false) })
    .ProducesProblem(StatusCodes.Status500InternalServerError);

app
    .MapGet("/todo/{id}", (string id) => new Todo("delectus aut autem", false))
    .ProducesProblem(StatusCodes.Status500InternalServerError);

app.Run();

public record Todo(string Title, bool Completed);

generates the following OpenAPI specification

{
  "openapi": "3.0.1",
  "info": {
    "title": "WebApplication v1",
    "version": "1.0.0"
  },
  "paths": {
    "/todos": {
      "get": {
        "tags": [
          "WebApplication"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "required": [
                      "title",
                      "completed"
                    ],
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string"
                      },
                      "completed": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "nullable": true
                    },
                    "title": {
                      "type": "string",
                      "nullable": true
                    },
                    "status": {
                      "type": "integer",
                      "format": "int32",
                      "nullable": true
                    },
                    "detail": {
                      "type": "string",
                      "nullable": true
                    },
                    "instance": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        },
        "x-aspnetcore-id": "7e0fed5d-29fc-41ad-8c18-4ad9bec4771a"
      }
    },
    "/todo/{id}": {
      "get": {
        "tags": [
          "WebApplication2"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "required": [
                    "title",
                    "completed"
                  ],
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "completed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "nullable": true
                    },
                    "title": {
                      "type": "string",
                      "nullable": true
                    },
                    "status": {
                      "type": "integer",
                      "format": "int32",
                      "nullable": true
                    },
                    "detail": {
                      "type": "string",
                      "nullable": true
                    },
                    "instance": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        },
        "x-aspnetcore-id": "3cec12b4-18e4-4d74-ba92-b96440f6f43e"
      }
    }
  },
  "tags": [
    {
      "name": "WebApplication"
    }
  ]
}

You can see that the ProblemDetails details schema as well as the Todo schema are duplicated for each operation.

This results in code generation tools like kiota generating unique models for each operation rather than creating shared ones which causes an explosion of types.

Describe the solution you'd like

This code using Swashbuckle.AspNetCore -Version 6.6.2

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger();

app
    .MapGet("/todos", () => new Todo[] { new ("delectus aut autem", false) })
    .ProducesProblem(StatusCodes.Status500InternalServerError);

app
    .MapGet("/todo/{id}", () => new Todo("delectus aut autem", false))
    .ProducesProblem(StatusCodes.Status500InternalServerError);

app.Run();

public record Todo(string Title, bool Completed);

generates the following OpenAPI specification

{
  "openapi": "3.0.1",
  "info": {
    "title": "WebApplication",
    "version": "1.0"
  },
  "paths": {
    "/todos": {
      "get": {
        "tags": [
          "WebApplication"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Todo"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/todo/{id}": {
      "get": {
        "tags": [
          "WebApplication"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Todo"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "Todo": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "completed": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      }
    }
  }
}

In this case there is only one definition of the ProblemDetails and Todo schema which is referenced by the operations.

Additional context

I wasn't sure whether this was a bug or by design so I've raised as a feature request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions