Skip to content

openapi: an allOf redeclaration of a property with an unknown format is resolved by branch order #782

Description

@OmarAlJarrah

Problem

When two allOf branches redeclare a property and one of them writes an unknown format, the merged property depends on which branch comes first. The property is typed by one declaration, the other is parked, and nothing is reported.

openapi: 3.1.0
info: {title: t, version: "1"}
paths: {}
components:
  schemas:
    Holder:
      allOf:
        - {type: object, properties: {token: {type: string}}}
        - {type: object, properties: {token: {type: string, format: hex-token, minLength: 8, default: x, description: the token}}}
Branch order token type default minLength Diagnostics
plain first (above) t/prim/string absent absent none
hex-token first the hoisted t/anon/.../allOf/0/properties/token x 8 none

In both orders the losing declaration is kept verbatim under openapi:conflicting-redeclaration/<pointer> on the property, so no keyword leaves the document. The typed fields differ, though. With the plain branch first, the property has no default, no constraints and no format, and the format-bearing declaration survives only as an unreferenced node plus that Unmodeled entry. Reordering two branches changes the merged field.

format: password joins this family once #592 lands, because it stops resolving to the shared string primitive. A format that hoists over a different base, such as byte, is not silent: it reports an incompatible-types warning, and it also keeps whichever declaration comes first.

Cause

Merger.recordRedeclarationConflict (compilers/openapi/internal/merge/merge.go) treats two declarations with different Type.Target as one dropping the other. typesConflict resolves each side to its primitive kind and finds both string, so it reports nothing. The hoisted Scalar and the shared primitive are different targets of the same kind.

This is a different mechanism from #446 and #598. Those are about typesConflict reporting a conflict between primitive kinds that can both hold. Here no conflict is reported, and the shape fold still discards one declaration.

Expected

A format that hoists a node over the same base kind narrows the bare type, as uri narrows string. The narrower declaration should win the shape whatever the branch order, and the other declaration's details (default, constraints, description, examples) should fold in as they do for a redeclaration that does not hoist. TestAllOf_ReconcileAccumulatesRicherDetailWhateverTheOrder already states this for branch order.

Acceptance

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

    priority:P2Matters to the first emitter; not blockingtype:bugDefect in shipped behavior

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions