Skip to content

openapi: a constraint beside allOf misses the Constraints field its node has #407

Description

@OmarAlJarrah

The same value constraint reaches two different channels depending on which lowering built the
model, and the diagnostic that explains the second case states a reason that is not true.

Reproduction

openapi: 3.1.0
info: {title: T, version: "1"}
paths: {}
components:
  schemas:
    Base:     {type: object, properties: {id: {type: string}}}
    ViaModel: {type: object, properties: {a: {type: string}}, minProperties: 2}
    ViaAllOf: {allOf: [{$ref: '#/components/schemas/Base'}], minProperties: 2}
$ morphic compile mp.yaml -skip-validate | jq -c '.types["t/openapi/components/schemas/ViaModel"] | {kind, constraints, u: (.unmodeled|keys)}'
{"kind":"model","constraints":{"exclusiveMin":false,"exclusiveMax":false,"uniqueItems":false,"minProps":2},"u":null}

$ morphic compile mp.yaml -skip-validate | jq -c '.types["t/openapi/components/schemas/ViaAllOf"] | {kind, constraints, u: (.unmodeled|keys)}'
{"kind":"model","constraints":null,"u":["openapi:minProperties"]}

info openapi/degraded-construct mp.yaml#/components/schemas/ViaAllOf: this position lowered to a
node of kind "model", which has no home for minProperties declared beside it; kept verbatim
under Unmodeled

Both positions lower to an ir.Model. One puts minProperties in Constraints.MinProps; the
other puts it in Unmodeled and reports that the node has nowhere to put it — while the node
beside it demonstrably does.

Why it happens

lowerModel reads the position's constraints and fills ir.Model.Constraints. lowerAllOf
builds its ir.Model without doing so, so the field is left nil. The census then runs, finds
no value in Constraints, and concludes the keyword is homeless — a conclusion drawn from
what the lowering happened to fill rather than from what the node can hold.

Nothing is lost, so this is not a repeat of #268 (fixed by #348): that work is what keeps the
keyword at all. What is wrong is where it lands and what is said about it.

Impact

A consumer reading Model.Constraints gets minProps for one spelling of the same
constraint and must also scan Unmodeled for the other, with no signal telling it which to
expect. The diagnostic actively misleads: it attributes the placement to a missing field.

This is distinct from #10, which is about reconciling constraints declared on the allOf
branches. Here the constraint is declared at the composing position itself.

Expected

lowerAllOf fills the Constraints of the model it builds from the position's own schema, as
lowerModel does, so minProperties reaches the same field either way and the census reports
homelessness only for a keyword the node genuinely cannot hold.

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