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-116 — mergeOpenapiSchemas 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).
Summary
When a schema has both
allOfandx-go-type, thex-go-typedirective on the schema itself is silently ignored. Worse, if anyallOfmember has its ownx-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-typeto a third-party spec; the same misbehavior reproduces with a hand-edited spec containing no overlay at all.Reproduction
Spec:
User-provided Go types
OverlayClient{Name}andOverlayClientWithId{Id, Name}.Expected output:
Actual output:
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— TheallOfbranch inGenerateGoSchemareturns before thex-go-typecheck at line 344:The code comment explicitly states
x-go-type"will completely override the definition of this schema" — this contract is violated when the outer schema usesallOf.(2)
pkg/codegen/merge_schemas.go:113-116—mergeOpenapiSchemasunions extensions from both sides without filtering out Go-specific directives:So
Client'sx-go-type: OverlayClientis copied into the merged schema's extensions.mergeSchemasthen recursively callsGenerateGoSchemaon the merged schema (line 62), which this time has noAllOf, hits thex-go-typebranch, and emitstype ClientWithId = OverlayClient.Version
Reproduced on
mainas of PR #2087 (commit f64f92b).