Skip to content

x-go-type on a schema with allOf is silently ignored; first member's x-go-type leaks through instead #2335

@mromaszewicz

Description

@mromaszewicz

Summary

When a schema has both allOf and x-go-type, the x-go-type directive on the schema itself is silently ignored. Worse, if any allOf member has its own x-go-type, that override leaks through and becomes the alias target for the outer schema — producing a nonsensical alias that drops fields the user expects.

This was originally surfaced as an overlay issue in #2087, but the bug is unrelated to overlays. Overlays merely make it easy to add x-go-type to a third-party spec; the same misbehavior reproduces with a hand-edited spec containing no overlay at all.

Reproduction

Spec:

components:
  schemas:
    Client:
      x-go-type: OverlayClient
      type: object
      required: [name]
      properties:
        name: { type: string }
    ClientWithId:
      x-go-type: OverlayClientWithId
      allOf:
        - $ref: '#/components/schemas/Client'
        - properties:
            id: { type: integer }
          required: [id]

User-provided Go types OverlayClient{Name} and OverlayClientWithId{Id, Name}.

Expected output:

type Client       = OverlayClient
type ClientWithId = OverlayClientWithId

Actual output:

type Client       = OverlayClient
type ClientWithId = OverlayClient   // wrong

PR #2087 contains a runnable demonstration in examples/anyof-allof-oneof/.

Root cause

There are two compounding bugs.

(1) pkg/codegen/schema.go:335-342 — The allOf branch in GenerateGoSchema returns before the x-go-type check at line 344:

if schema.AllOf \!= nil {
    mergedSchema, err := MergeSchemas(schema.AllOf, path)
    ...
    return mergedSchema, nil   // early return; x-go-type never consulted
}

// Check x-go-type, which will completely override the definition ...
if extension, ok := schema.Extensions[extPropGoType]; ok { ... }

The code comment explicitly states x-go-type "will completely override the definition of this schema" — this contract is violated when the outer schema uses allOf.

(2) pkg/codegen/merge_schemas.go:113-116mergeOpenapiSchemas unions extensions from both sides without filtering out Go-specific directives:

result.Extensions = make(map[string]any, len(s1.Extensions)+len(s2.Extensions))
maps.Copy(result.Extensions, s1.Extensions)
maps.Copy(result.Extensions, s2.Extensions)

So Client's x-go-type: OverlayClient is copied into the merged schema's extensions. mergeSchemas then recursively calls GenerateGoSchema on the merged schema (line 62), which this time has no AllOf, hits the x-go-type branch, and emits type ClientWithId = OverlayClient.

Version

Reproduced on main as of PR #2087 (commit f64f92b).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions