Skip to content

MergeSchemas silently drops a member's type when an earlier allOf member has none (order-dependent output) #2524

Description

@mromaszewicz

Found during the review of #2522; pre-existing on main and independent of that PR.

Problem

MergeSchemas (pkg/codegen/merge_schemas.go) only errors on a type conflict when both members declare a type:

if s1.Type.Slice() != nil && s2.Type.Slice() != nil && !equalTypes(s1.Type, s2.Type) {
    return openapi3.Schema{}, fmt.Errorf("can not merge incompatible types: ...")
}
result.Type = s1.Type

When s1 has no type and s2 does, result.Type = s1.Type silently discards s2's type. Because allOf members are merged pairwise in order, the generated Go shape depends on member ordering.

Reproduction

AllOfUnionMember:
  allOf:
    - properties:
        name:
          type: string
    - type: [string, number]

generates a plain struct — the second member's type constraint vanishes entirely:

type AllOfUnionMember struct {
    Name *string `json:"name,omitempty"`
}

Reversing the member order flips which constraint silently wins: result.Type then carries the union, the schema lowers to any, and the properties are dropped instead. The same mechanism applies to single types (- type: string as the second member is discarded the same way); the multi-type union case from #2521/#2522 just made it easier to notice, and converted some of these from hard generation errors into silent, order-dependent output.

Expected

Order should not change the result. Either:

  • propagate the type when exactly one member declares one (result.Type = firstNonNil(s1.Type, s2.Type)), keeping the existing incompatibility error when both declare different types, or
  • error loudly on the typeless+typed combination if propagation has unwanted interactions (e.g. a propagated primitive type alongside merged properties still silently drops the properties at the dispatch stage — propagation alone doesn't make that combination coherent).

The first option fixes the common case (a typeless member contributing only properties/required alongside a typed member); the second at least removes the silent order dependency. Behavior change to generated code either way, so this likely wants a look at what existing specs in the wild rely on before choosing.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions