Skip to content

openapi: an inline structural position hints differently from a $ref to it #353

Description

@OmarAlJarrah

A node hoisted at an inline structural position — items, additionalProperties, a
patternProperties entry, a prefixItems slot — takes its Naming.Hint from whichever lowering
interns it first. The structural lowering composes the enclosing node's hint with the position's
role (compile.SubHint(hint, "item")); hoistSubSchema, reached through an outside $ref naming
that same pointer, falls through subSchemaHint to the pointer's last segment. Both are valid
hints and nothing compares them, so the same components compiled in two declaration orders produce
two different documents — silently, with no diagnostic on either side.

openapi: 3.1.0
info: {title: O, version: "1.0.0"}
paths: {}
components:
  schemas:
    A: {type: array, items: {type: object, properties: {n: {type: string}}}}
    Outsider: {$ref: '#/components/schemas/A/items'}

Compiled with A declared first, t/anon/components/schemas/A/items is hinted a_item; with
Outsider first, items. cmp.Diff over the two documents shows only that field. The four
positions and the two spellings each takes:

pointer structural lowering outside $ref
…/A/items a_item items
…/A/additionalProperties a_value additional_properties
…/A/patternProperties/^x a_pattern x
…/A/prefixItems/0 a_0 0

Why this is filed on its own

This is #181's mechanism at the positions #181 and #281 did not sweep. Both of those closed the
composition-branch family, where the pointer walk can answer what the composition would
(branchPointerHint) because a branch's hint is positional and needs nothing from the enclosing
node. These four cannot be answered that way: the structural spelling is built out of the
enclosing node's hint, which a bare pointer walk does not have, so closing this means either
deriving the whole enclosing chain from the pointer or deciding that the weaker spelling names the
node. That is a naming decision, not an agreement fix.

Deriving the chain from the pointer is also not total: a position under /paths/… takes its hint
from an operationId or a media type key, neither of which the pointer records.

The divergence is why orderInvariantIR() (compilers/openapi/internal/schema/schema_test.go)
excludes ir.Naming.Hint. TestInlinePosition_OutsideRefDoesNotMoveTheHome already permutes all
four positions, so the exclusion is exactly what stands between that test and this bug; closing
this removes the exclusion and turns that test into the regression.

Method

Compiled both orders through the CLI with -skip-validate and diffed the two documents with
sources dropped.

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