Skip to content

openapi: first-match schema dispatch silently drops co-declared composition keywords #35

Description

@OmarAlJarrah

Summary

The schema lowering dispatch picks one keyword and never revisits the rest of the schema body, silently dropping legal JSON Schema conjunctions in two shapes:

  1. oneOf + anyOf co-declared. buildUnion (and nullUnionCollapse) picks oneOf when non-empty and only falls back to anyOf when oneOf is empty. hasUnionSiblings does not treat the co-present other union keyword as a sibling, so the losing branch set gets no Extensions entry and no diagnostic.
  2. allOf co-declared with enum/const. lower() dispatches on const, then enum, then allOf — first match wins. The narrowing idiom allOf: [$ref: Base] + enum: [...] loses the composition entirely: no Base/Mixin, no preservation, no diagnostic.

Both violate invariant 2 (lossless by default) — contrast with the oneOf/anyOf-next-to-structural-keywords case, which lowerWithUnionSiblings carefully preserves.

Reproduction

# case 1 — anyOf vanishes
S: {oneOf: [{type: string}, {type: integer}], anyOf: [{type: number}, {type: boolean}]}
# → Union with 2 variants, Extensions empty, zero diagnostics

# case 2 — allOf vanishes
Constrained: {allOf: [{$ref: '#/components/schemas/Base'}], enum: [a, b]}
# → plain Enum{a,b}; relationship to Base gone, zero diagnostics

Root cause

compilers/openapi/compose.go:178-182 (buildUnion preference), schema.go:152-169 (hasUnionSiblings blind to the other union keyword), schema.go:300-319 (const→enum→allOf precedence).

Expected

When a schema co-declares keywords the dispatch cannot jointly model, the losing keyword set must be preserved raw under Extensions with a degraded-construct diagnostic — the same treatment lowerWithUnionSiblings already gives structural siblings. Silence is never acceptable here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions