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
3.3 KiB
The fix: one GroupedOpenApi per version
Full transcript: docs/output/05-grouped-openapi-fix.txt, from
OpenApiVersioningTest#c_groupedOpenApiCustomizerCollapsesToOneCleanSchemaPerVersion.
VersionedOpenApiConfig
registers three GroupedOpenApi beans — one per supported version — each scoped to
/accounts/** and each carrying an OpenApiCustomizer that collapses the merged oneOf response
from the previous chapter down to the one schema that version's clients actually receive, fixes
operationId and summary to match, and drops the now-meaningless API-Version header:
@Bean
public GroupedOpenApi accountsV1Group() {
return groupFor("accounts-v1", "1");
}
private GroupedOpenApi groupFor(String groupName, String version) {
VersionSpec spec = SPECS.get(version);
return GroupedOpenApi.builder()
.group(groupName)
.pathsToMatch("/accounts/**")
.addOpenApiCustomizer(collapseToSingleVersion(spec))
.build();
}
Each group is served at /v3/api-docs/{group} (springdoc's own convention — no extra wiring
needed). Result, all three fetched from the same running instance:
GET /v3/api-docs/accounts-v1 -> operationId=getAccountV1, schema=AccountV1, params=[id]
GET /v3/api-docs/accounts-v1.1 -> operationId=getAccountV1Point1Plus, schema=AccountV1Point1, params=[id]
GET /v3/api-docs/accounts-v2 -> operationId=getAccountV2, schema=AccountV2, params=[id]
Each is a clean, single-schema operation — feed any one of these three documents into
openapi-generator and you get one method whose declared response type is actually what that
version's clients receive, not a oneOf of three.
The default, ungrouped /v3/api-docs is untouched — confirmed by re-fetching it inside the
same test, immediately after fetching all three groups, and it still shows the exact oneOf merge
from the previous chapter. GroupedOpenApi's customizer runs against a document scoped to that
group's own build, not the shared default document, so adding version-specific groups is additive:
existing tooling pointed at the plain /v3/api-docs (or Swagger UI's default view) keeps working
exactly as it did before, while /v3/api-docs/accounts-v2 becomes a new, additional,
clean-per-version resource for anyone who wants one — a generated TypeScript client for your v2
mobile app, say, without also generating types for the v1 shape it will never receive.
See the previous chapter for why the version→schema mapping
this customizer applies is a small hand-written Map, not something derived automatically from
the running handler mappings — that's a real limitation of the public springdoc customizer API,
not a shortcut taken for the sake of the demo.
Swagger UI
With three additional groups registered, /swagger-ui.html grows a group selector (default,
accounts-v1, accounts-v1.1, accounts-v2) — switching between them re-fetches the
corresponding /v3/api-docs/{group} document, so the same collapsed, single-schema view is what a
human reading the UI sees too, not just API clients hitting the JSON directly.