Companion module for "springdoc-openapi with Spring Boot 4.1: Generating, Customising and Versioning Your API Spec". Reuses the ApiVersionConfigurer setup from the versioning-mechanics companion project and tests what springdoc-openapi 3.1.1 actually generates for a path with multiple version-scoped handlers: a default oneOf-merged operation with an arbitrary operationId, a working GroupedOpenApi + OpenApiCustomizer fix that collapses it to one clean schema per version, and a check of the officially-versioning-supported functional-endpoint path (springdoc v3.0.2's "Add support for Spring Framework API Versioning with Functional Endpoints"), which turns out to document only one of two registered versions rather than either version separately. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M1uZDXWkY1MnTfQgt4HMeQ
75 lines
3.5 KiB
Markdown
75 lines
3.5 KiB
Markdown
# What springdoc actually generates for a versioned path
|
|
|
|
Full transcript: [docs/output/02-what-springdoc-actually-generates.txt](output/02-what-springdoc-actually-generates.txt),
|
|
produced by `OpenApiVersioningTest#b_defaultApiDocsMergesAllVersionsIntoOneOf`.
|
|
|
|
Hit the plain, ungrouped `/v3/api-docs` on this module — no custom configuration beyond adding
|
|
the starter — and here is the entire operation springdoc built for `GET /accounts/{id}`, which
|
|
has three handlers mapped to it (`version = "1"`, `"1.1+"`, `"2"`):
|
|
|
|
```json
|
|
{
|
|
"operationId": "getAccountV2",
|
|
"parameters": [
|
|
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
|
|
{
|
|
"name": "API-Version",
|
|
"in": "header",
|
|
"schema": {
|
|
"type": "string",
|
|
"default": "1.0.0",
|
|
"enum": ["2", "1", "1.0.0", "1.1"]
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"content": {
|
|
"*/*": {
|
|
"schema": {
|
|
"oneOf": [
|
|
{ "$ref": "#/components/schemas/AccountV1" },
|
|
{ "$ref": "#/components/schemas/AccountV2" },
|
|
{ "$ref": "#/components/schemas/AccountV1Point1" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Three things worth naming precisely, since none of them are documented behaviour as far as I
|
|
could find — this is what running it produced, not what any springdoc doc page promises:
|
|
|
|
**One operation, not three.** springdoc does not know that `version` is a routing discriminant
|
|
the way it knows `@PathVariable` or `@RequestParam` are. As far as its operation-building code is
|
|
concerned, three `HandlerMethod`s map to the same path and HTTP method, so it folds them into one
|
|
`Operation` — the same folding it would do for, say, an overloaded controller method disambiguated
|
|
by a `headers = "X-Foo"` condition.
|
|
|
|
**The response schema becomes `oneOf` all three shapes, with no discriminator.** This is
|
|
technically *not wrong* — a client that doesn't send `API-Version` really could get any of the
|
|
three shapes back, so `oneOf` is a defensible schema for "the operation's response, considered
|
|
without reference to versioning." But it is unusable as version-specific documentation: nothing in
|
|
this schema says which shape maps to which version.
|
|
|
|
**`operationId` is picked from whichever handler springdoc processes last, arbitrarily from the
|
|
reader's perspective.** It happened to be `getAccountV2` here. Generated API clients
|
|
(openapi-generator, openapi-typescript, etc.) use `operationId` as the method name they generate —
|
|
so a client generated from this spec gets one method, named after one version, that claims to
|
|
return `oneOf` three different shapes.
|
|
|
|
**The auto-documented `API-Version` header enum is also slightly wrong on its own terms**: it
|
|
lists `"1"` and `"1.0.0"` as if they were two different values, when `"1.0.0"` is just the
|
|
semantic-version-normalised form of the same default `"1"` configured in `WebConfig`
|
|
(`setDefaultVersion("1")`). Framework 7's version parser is doing exactly what it's supposed to —
|
|
`1` and `1.0.0` really do compare equal — but springdoc surfaces both the raw configured string and
|
|
the normalised form as if a caller could sensibly choose between them.
|
|
|
|
None of this is a crash — the document is valid OpenAPI 3.1, it just isn't the document most teams
|
|
actually want from a versioned API. [The next chapter](05-the-grouped-openapi-fix.md) is a working
|
|
fix for the first two problems; the header-enum oddity is cosmetic enough that I didn't chase it
|
|
further, and is called out in [06-known-issues.md](06-known-issues.md) instead.
|