Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.

Commit c7b1de1

Browse files
mromaszewiczclaude
andauthored
Fix duplicate type declarations (#16)
When libopenapi resolves a $ref, it copies extensions from the target component schema to the resolved proxy. This caused property-level refs (e.g., pagination: $ref Pagination) to inherit x-go-type from the target, which made the gatherer treat each ref site as a separate type-generating schema. Since all child schemas under the same response got the same operationId-based name, this produced duplicate type declarations that would not compile. Fix: skip extension checks and extension copying for reference schemas in gatherFromSchemaProxy(). Extensions belong to the component schema descriptor, not to every reference site that points to it. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e826fdb commit c7b1de1

6 files changed

Lines changed: 201 additions & 658 deletions

File tree

experimental/internal/codegen/gather.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,15 @@ func (g *gatherer) gatherFromSchemaProxy(proxy *base.SchemaProxy, path SchemaPat
362362
// Get the resolved schema
363363
schema := proxy.Schema()
364364

365-
// Check if schema has extensions that require type generation
366-
hasTypeOverride := schema != nil && schema.Extensions != nil && hasExtension(schema.Extensions, ExtTypeOverride, legacyExtGoType)
367-
hasTypeNameOverride := schema != nil && schema.Extensions != nil && hasExtension(schema.Extensions, ExtTypeNameOverride, legacyExtGoTypeName)
365+
// Check if schema has extensions that require type generation.
366+
// Skip extension checks for references — libopenapi copies extensions from the
367+
// resolved target, but those extensions belong to the component schema, not to
368+
// each reference site. Without this guard, a property like
369+
// pagination: { $ref: '#/components/schemas/Pagination' }
370+
// would inherit Pagination's x-go-type and be gathered as a separate type,
371+
// producing duplicate declarations. (See oapi-codegen-exp#14.)
372+
hasTypeOverride := !isRef && schema != nil && schema.Extensions != nil && hasExtension(schema.Extensions, ExtTypeOverride, legacyExtGoType)
373+
hasTypeNameOverride := !isRef && schema != nil && schema.Extensions != nil && hasExtension(schema.Extensions, ExtTypeNameOverride, legacyExtGoTypeName)
368374

369375
// Only gather schemas that need a generated type
370376
// References are always gathered (they point to real schemas)
@@ -387,8 +393,11 @@ func (g *gatherer) gatherFromSchemaProxy(proxy *base.SchemaProxy, path SchemaPat
387393
ContentType: g.currentContentType,
388394
}
389395

390-
// Parse extensions from the schema
391-
if schema != nil && schema.Extensions != nil {
396+
// Parse extensions from the schema — but not for references.
397+
// When libopenapi resolves a $ref, the resolved schema carries extensions
398+
// from the target (e.g., x-go-type on a component schema). Those extensions
399+
// belong to the component schema descriptor, not to every reference site.
400+
if !isRef && schema != nil && schema.Extensions != nil {
392401
ext, err := ParseExtensions(schema.Extensions, path.String())
393402
if err != nil {
394403
slog.Warn("failed to parse extensions",

0 commit comments

Comments
 (0)