Skip to content

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

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

Closed
1 task done
michael-wolfenden opened this issue Jun 17, 2024 · 2 comments
Labels
feature-openapi old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Milestone

Comments

@michael-wolfenden
Copy link

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.

@ghost ghost added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Jun 17, 2024
@martincostello
Copy link
Member

I think #56175 will resolve this. It's been planned in the epic, but hadn't been gotten to yet.

@martincostello martincostello added feature-openapi old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Jun 17, 2024
@captainsafia captainsafia added this to the 9.0-preview6 milestone Jun 17, 2024
@captainsafia
Copy link
Member

The referenced PR does indeed solve this. Some follow on fixes to it were made that will (unfortunately) ship in preview.7 since we didn't make the cut-off for preview.6. I'll close this out as done for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-openapi old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels
Projects
None yet
Development

No branches or pull requests

3 participants