Skip to content

openapi: a key the source model does not name is dropped with no trace #297

Description

@OmarAlJarrah

Summary

Every OpenAPI object drops the keys speakeasy's model has no field for. x-* extensions
are kept under Unmodeled; anything else — a typo, a key from a later specification
revision, a vendor key not spelled x- — reaches neither an IR field, nor Unmodeled, nor
a diagnostic. The compiler cannot see it: the key is gone before lowering runs, because the
parsed model is the only thing lowering reads.

"Lossless by default" says a construct the IR does not model survives verbatim with a
reason recorded. These survive in no form at all, and unlike the Unmodeled-with-a-reason
case there is nothing for a consumer to route.

Reproduction

Verified at b22e685. One bogus* key on each of eight object types, then a search of the
serialized document for each:

openapi: 3.1.0
info: {title: T, version: "1", bogusInfo: 1}
servers:
  - {url: 'https://a', bogusServer: 1}
paths:
  /x:
    get:
      operationId: x
      bogusOperation: 1
      parameters:
        - {name: p, in: query, schema: {type: string}, bogusParam: 1}
      responses:
        "200": {description: ok, bogusResponse: 1}
components:
  securitySchemes:
    s: {type: apiKey, in: header, name: X, bogusScheme: 1}
  schemas:
    S: {type: object, bogusSchema: 1}

Seven of the seven keys above are absent from the compiled document, and the compile
reports nothing at all — no error, no warning, no info. A path item is the one object that
notices, and only by accident: an unknown key there is read as an HTTP method, so it draws
a validation-type-mismatch about the wrong thing rather than a report that a key was
dropped.

Why nothing catches it

The conformance corpus asserts that what a spec declares survives, and every spec in it
declares only keys the model names. No oracle compares a document's own key set against
what the IR carries, so a dropped key looks exactly like a key that was never written.

Notes

  • This is a different mechanism from openapi: a securityScheme with no type interns as a custom scheme naming nothing #294, which was about fields the model does surface
    being unread by the type that declared them. Those now survive; these never reach a
    reader at all.
  • The fix has to read the raw node rather than the parsed model, so it is a per-object-type
    change across the compiler, not a single site. annotation.RawChildNode already reads
    raw children for the handful of keywords preserved that way; the missing piece is the
    complement — the keys present in the node and absent from the model.
  • Worth deciding with it: whether an unknown key is ReasonNoIRHome (a gap that may close)
    or ReasonOutOfScope, and whether it is reported at all or kept silently. A typo'd key
    and a key from a newer revision are indistinguishable here, which argues for keeping both
    and reporting neither above info.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions