Skip to content

OAS: various fixes and improvements #160

Closed

Description

@MoonWorm

Summary

Correctness and cleanup pass over the OpenAPI plugin, made possible by the golden-file tests added in #159. Every defect below was found by reading the generated document against the JSON:API and OpenAPI specs, and each fix is pinned by a test.

The reference document shrank from 5407 to 4197 lines while gaining content: request bodies, typed linkage meta and nine new schemas.

before after
Write operations documenting a request body 0 / 9 9 / 9
Non-OpenAPI keys (exampleSetFlag) 612 0
Phantom empty property 34 0
Dangling $ref 3 classes of 0
Schemas nothing references 2 0
Unit tests in the plugin 2 32

Defects fixed

Security requirements referenced schemes that were never declared

JsonApiOperationsCustomizer hardcoded Authorization_Code_PKCE, while CommonOpenApiCustomizer declares the scheme under the configured name (Authorization_Code_With_PKCE in the samples). Every PKCE requirement was a dangling reference, so the Swagger UI Authorize button silently did nothing. Client_Credentials matched only by coincidence and would break on any rename.

Scheme names now come from OasProperties, the same source the declaration uses. A grant flow that is not configured is skipped rather than referenced.

No write operation documented a request body

POST /users, PATCH /users/{id} and all seven relationship writes had no requestBody at all — "Try it out" offered no body field, and generated clients took no argument. Two code paths were permanently dead: the plugin stores an OasOperationInfoModel, but both consumers tested instanceof OasOperationInfo, the annotation type. Always false, so payloadType had never worked.

Request bodies are now derived from what JSON:API prescribes per operation; @OasOperationInfo(payloadType = …) remains as an override. The parameter is typed, so the mismatch is no longer expressible.

Request and response schemas were the same schema

id was required on the resource object, so a spec-conformant POST without a client-generated id violated the published contract. Requests also advertised links and included, which a request never carries.

The request side now has its own schemas: <Type>CreateRequestDoc (id optional) and <Type>UpdateRequestDoc (id required), plus linkage-only relationship documents.

Every relationship was marked required

UsersRelationships.required = [citizenships, placeOfBirth, relatives] meant any response using sparse fieldsets — or simply omitting a relationship — violated its own schema.

Custom to-one linkage meta produced dangling references

The to-many branch registered the schemas it built; the to-one branch built them, $ref-ed them and dropped them. Invisible until now because no sample declared resourceLinkageMetaType — which was itself the bug: relatives carries meta.relationshipType in real requests and responses, and the document described it as an untyped object.

The sample now declares it, and RelativeLinkageMeta is the single definition of that shape — used by the relationship, by the operations that read and validate it, and by the plugin. Previously the same shape was spelled out three times as raw Map access on a string key.

The document contained keys OpenAPI does not define

OasServlet serialized with a plain Jackson mapper instead of swagger's, publishing internal bookkeeping — exampleSetFlag (612), types (268), jsonSchema (62), valueSetFlag — as if they were OpenAPI keywords.

Vendor extensions were affected too: they were nested under an extensions wrapper rather than emitted as top-level x- members, so tooling could not see x-operation-properties at all.

LinksObject was dead, and links published a phantom property

The hand-written LinksObject schema — the one describing a link as either a URL string or a link object — was registered and referenced by nothing. Instead 18 schemas reflected the Java class in place, publishing its isEmpty() accessor as an empty boolean that no response has ever returned.

Implementation artifacts leaked into the contract

ResourceObjectObjectObject — ResourceObject with its erased type arguments appended — was a public schema name referenced five times, with attributes and relationships typed as bare objects. It appeared wherever the includable types could not be determined. included is now published only when those types are known.

CurrenciesSingleResourceDoc was generated because the resource had some operation, though nothing returns a single currency. Documents are now generated per operation that returns them.

Improvements

  • OAuth2 scopes now apply to client_credentials, per RFC 6749 §4.4.2, and match how the Access Control plugin enforces them — one flow-agnostic scope set per operation, since Principal.authenticatedClientScopes() is a flat client-level set that carries no notion of grant flow.
  • @OasOperationInfo gained summary and description as overrides over the generated text. The documented example used them already and did not compile.
  • Scopes decoupled from entitlements. x-scopes-required-entitlements and scopes[*].requiredEntitlements were a leftover of the removed ordered Access Tiers: mechanically renamed when tiers went away, the mapping lost its meaning. Nothing consumed it, no sample configured it, it was unreachable on Quarkus, and the Access Control plugin treats scopes and entitlements as independent gates. Removed.
  • PrimaryAndNestedSchemas copies its nested list, so addToNested on a result of generateAllSchemasFromType no longer throws UnsupportedOperationException.
  • Shared OasLinkageMetaUtil — request and response sides now build the typed linkage identifier with one code path, so they cannot drift.

Verification

  • Golden document tests pass on all three sample apps (Servlet, Quarkus, Spring Boot), which is the guarantee that every integration emits an identical document.
  • 32 unit tests across the customizers, up from 2. The regression test for the to-one linkage defect was confirmed to fail without the fix.
  • The generated document was checked for dangling $refs and unreferenced schemas: none of either.

Not addressed

Tracked separately: operations that return no 2xx (9 of 19), error examples serialized as escaped strings rather than JSON, two inverted conditions in CommonOpenApiCustomizer, the unsynchronized document cache in OasServlet, and the absence of operationId.

Activity

  1. added 4 commits that reference this issue on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions