Skip to content

feat(output-options): add resolve-type-name-collisions to avoid name collisions#200

Merged
mromaszewicz merged 6 commits intooapi-codegen:mainfrom
mgurevin:bug-duplicate_type_names
Feb 10, 2026
Merged

feat(output-options): add resolve-type-name-collisions to avoid name collisions#200
mromaszewicz merged 6 commits intooapi-codegen:mainfrom
mgurevin:bug-duplicate_type_names

Conversation

@mgurevin
Copy link
Contributor

@mgurevin mgurevin commented Jul 1, 2020

Generated code can't compile if the spec contains multiple definitions with the same name even they defined in different sections.

It may better idea to separate the generated code into different packages.

But I need a solution to generate code from someone else's spec and it works for me.

E.g.:

openapi: 3.0.1

info:
  title: ...
  version: 0.0.0

paths:
  /foo:
    post:
      parameters: 
        - $ref: '#/components/parameters/Bar'
      requestBody:
        $ref: '#/components/requestBodies/Bar'
      responses:
        200:
          $ref: '#/components/responses/Bar'

components:
  schemas:
    Bar:
      type: object
      properties:
        value:
          type: string
    Bar2:
      type: object
      properties:
        value:
          type: number
    BarParam:
      type: array
      items:
        type: integer
    BarParam2:
      type: array
      items:
        type: integer
 
  headers:
    Bar:
      schema:
        type: boolean

  parameters:
    Bar:
      name: Bar
      in: query
      schema:
        type: string

  requestBodies:
    Bar:
      content:
        application/json:
          schema:
            type: object
            properties:
              value:
                type: integer

  responses:
    Bar:
      description: ...
      headers:
        X-Bar:
          $ref: '#/components/headers/Bar'
      content:
        application/json:
          schema:
            type: object
            properties:
              value1:
                $ref: '#/components/schemas/Bar'
              value2:
                $ref: '#/components/schemas/Bar2'
              value3:
                $ref: '#/components/schemas/BarParam'
              value4:
                $ref: '#/components/schemas/BarParam2'

@mgurevin mgurevin marked this pull request as ready for review July 2, 2020 16:39
@mgurevin mgurevin force-pushed the bug-duplicate_type_names branch from 28ce6de to 9c70652 Compare July 2, 2020 17:13
@tooolbox
Copy link
Contributor

On the surface, seems like this might fix #254 and #256

Any thoughts @deepmap-marcinr ?

@mgurevin
Copy link
Contributor Author

On the surface, seems like this might fix #254 and #256

Any thoughts @deepmap-marcinr ?

I agree with you, I had a similar issue with #254 and #256 before. So this PR was solved my problem. If this PR be merged, I'll get rid of maintaining my fork.

@tooolbox
Copy link
Contributor

I tried out your PR on a spec I have, and while it solved a name conflict between a Component and a RequestBody, there were other conflicts relating to Responses that prevented the generating code from compiling.

I think that there was some kind of object named FooResponse, and then oapi-codegen took a Foo object and added Response as a suffix in the templates, resulting in a conflict. So, different issue I guess, but just as annoying.

@deepmap-marcinr
Copy link
Contributor

deepmap-marcinr commented Jan 11, 2021 via email

@mgurevin
Copy link
Contributor Author

It looks like your code basically no-ops when there are no conflicts, right?

correct.

@deepmap-marcinr deepmap-marcinr added the ☢️ breaking change This change would break existing users' code label Apr 2, 2021
@guiguan
Copy link

guiguan commented Jul 23, 2021

maybe we separate them into different packages?

@guiguan guiguan mentioned this pull request Jul 23, 2021
migueleliasweb added a commit to migueleliasweb/oapi-codegen that referenced this pull request Aug 29, 2021
mromaszewicz and others added 3 commits February 10, 2026 09:47
Resolved conflicts in schema.go and utils.go to integrate the
FixDuplicateTypeNames feature with the current codebase.
…codegen#200

- Rename auto-dedup suffixes to use full words: Parameter, Response,
  RequestBody (instead of Param, Resp, ReqBody)
- Add internal/test/issues/issue-200/ with spec, config, generated code,
  and a compile-time regression test that instantiates every expected type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ResolveTypeNameCollisions bool to OutputOptions and the JSON schema.
When false (the default), the codegen errors on duplicate type names as
before. When true, FixDuplicateTypeNames auto-renames colliding types.

Also cleans up ComponentType: removes unused constants, improves doc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mromaszewicz mromaszewicz removed the ☢️ breaking change This change would break existing users' code label Feb 10, 2026
@mromaszewicz
Copy link
Member

I'm revisiting this ancient PR. It's a good idea, and it's implemented nicely. I've tested it and put it behind a configuration option to maintain old behavior.

@mromaszewicz mromaszewicz requested a review from a team as a code owner February 10, 2026 18:19
@mromaszewicz mromaszewicz merged commit 13eb5ac into oapi-codegen:main Feb 10, 2026
31 checks passed
szarowski pushed a commit to szarowski/oapi-codegen that referenced this pull request Feb 10, 2026
* fixed duplicate type names

* add typename dedup functions

* Fixup: use full-word suffixes and add regression test for issue oapi-codegen#200

- Rename auto-dedup suffixes to use full words: Parameter, Response,
  RequestBody (instead of Param, Resp, ReqBody)
- Add internal/test/issues/issue-200/ with spec, config, generated code,
  and a compile-time regression test that instantiates every expected type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Gate duplicate type name resolution behind output-options config flag

Add ResolveTypeNameCollisions bool to OutputOptions and the JSON schema.
When false (the default), the codegen errors on duplicate type names as
before. When true, FixDuplicateTypeNames auto-renames colliding types.

Also cleans up ComponentType: removes unused constants, improves doc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Marcin Romaszewicz <marcinr@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 11, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 12, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 13, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 15, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 22, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 24, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 26, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
@jamietanna jamietanna changed the title fixed duplicate type names feat(output-options): add resolve-type-name-collisions to avoid name collisions Feb 27, 2026
@jamietanna jamietanna added the enhancement New feature or request label Feb 27, 2026
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Feb 27, 2026
I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue oapi-codegen#1474 (client response wrapper type colliding with
a component schema of the same name and improves issue oapi-codegen#200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- oapi-codegen#1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- oapi-codegen#1713 Schema name vs client wrapper (CreateBlueprintResponse)
- oapi-codegen#1450 Schema name vs client wrapper (DeleteBusinessResponse)
- oapi-codegen#2097 Path response type vs schema definition (Status)
- oapi-codegen#255  Endpoint path vs response type (QueryResponse)
- oapi-codegen#899  Duplicate types from response wrapper vs schema (AccessListResponse)
- oapi-codegen#1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- oapi-codegen#254  Cross-section: requestBodies vs schemas (Pet)
- oapi-codegen#407  Cross-section: requestBodies vs schemas (myThing)
- oapi-codegen#1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- oapi-codegen#292  Parameter structures params postfix (superseded by context suffix)
- oapi-codegen#1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)
mromaszewicz added a commit that referenced this pull request Mar 2, 2026
* feat: multi-pass type name resolution

I've been meaning to use this approach for a long time, because
the attempts at avoiding type collisions via structure suffixes or
prefixes work sporadically, at best.

Conflict resolution is fundamentally a global problem, not a local
problem when doing recursive traversal, so this PR splits the code
generation into two parts. First, the OAPI document structure is
traversed, and all the schemas that we generate are gathered up
into a list of candidates, then we do global conflict resolution
across the space of all schemas. This allows us to preserve the
functionality of things affected by schema name - `$ref`, `required`
properties, and so forth.

This fixes issue #1474 (client response wrapper type colliding with
a component schema of the same name and improves issue #200 handling
(same name across schemas, parameters, responses, requestBodies,
headers).

The new system is gated behind the existing `resolve-type-name-collisions`
output option. When disabled, behavior is unchanged, oapi-codegen exits
with an error. This flag is default false, so there is no behavior change
to oapi-codegen unless it's specified. All current test files regenerate
without any differences.

Added a comprehensive test which reproduces the scenarios in all the
PR's and Issues below, and adds a few more, to make sure that references
to renamed targets are also correct..

Key changes:
- gather.go: walks entire spec collecting schemas with location metadata
- resolve_names.go: assigns unique names via context suffix, per-schema
  disambiguation, and numeric fallback strategies
- Component schemas are privileged and keep bare names on collision
- Client response wrapper types now participate in collision detection
- Removed ComponentType/DefinedComp from Schema struct
- Removed FixDuplicateTypeNames and related functions from utils.go

Obsoletes issues:
- #1474 Schema name vs client wrapper (CreateChatCompletionResponse)
- #1713 Schema name vs client wrapper (CreateBlueprintResponse)
- #1450 Schema name vs client wrapper (DeleteBusinessResponse)
- #2097 Path response type vs schema definition (Status)
- #255  Endpoint path vs response type (QueryResponse)
- #899  Duplicate types from response wrapper vs schema (AccessListResponse)
- #1357 Schema vs operationId response (ListAssistantsResponse, OpenAI spec)
- #254  Cross-section: requestBodies vs schemas (Pet)
- #407  Cross-section: requestBodies vs schemas (myThing)
- #1881 Cross-section: requestBodies with multiple content types

Obsoletes PRs:
- #292  Parameter structures params postfix (superseded by context suffix)
- #1005 Fix generate equals structs (superseded by multi-pass resolution)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)

* Fix linter issues

* fix: apply collision strategies in global phases to prevent oscillation

When multiple content types map to the same short suffix (e.g.,
application/json, application/merge-patch+json, and
application/json-patch+json all mapping to "JSON"), the per-group
strategy cascade caused an infinite oscillation: context suffix
appended "RequestBody", then content type suffix appended "JSON",
then context suffix fired again because the name no longer ended
in "RequestBody", ad infinitum.

Fix by restructuring resolveCollisions to apply each strategy as
a global phase: exhaust one strategy across ALL colliding groups
(re-checking for new collisions after each pass) before advancing
to the next strategy. This ensures numeric fallback is reached
when earlier strategies cannot disambiguate.

Also fix resolvedNameForComponent to accept an optional content
type for exact matching, so each media type variant of a
requestBody or response gets its own resolved name instead of all
variants receiving whichever name the map iterator returns first.

Adds Pattern H test case (TMF622 scenario from PR #2213): a
component that exists in both schemas and requestBodies where the
requestBody has 3 content types that all collapse to the "JSON"
short name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Regenerate files with new code

* test: add Pattern I for inline response with x-go-type $ref properties

Add test case from oapi-codegen-exp#14: an inline response object whose
properties $ref component schemas with x-go-type: string. In the
experimental rewrite (libopenapi), this caused duplicate type declarations
because libopenapi copies extensions from $ref targets. V2 (kin-openapi)
handles this correctly, but the test guards against future regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve type name mismatches for multi-content-type responses

When a component response has multiple JSON content types (e.g.,
application/json, application/json-patch+json, application/merge-patch+json),
three bugs produced uncompilable code:

1. Duplicate inline type declarations: GenerateGoSchema was called with a
   path of [operationId, responseName] (or [responseName] in the component
   phase), which doesn't include the content type. When multiple content
   types share oneOf schemas with inline elements, they produce
   identically-named AdditionalTypes. If the schemas differ, the result is
   conflicting declarations; if they're the same, duplicate declarations.
   Fix: include a mediaTypeToCamelCase(contentType) segment in the schema
   path when jsonCount > 1, giving each content type's inline types unique
   names. This is guarded by jsonCount > 1 for backward compatibility.

2. Undefined types in unmarshal code: GetResponseTypeDefinitions used
   RefPathToGoType to resolve $ref paths like
   #/components/responses/200Resource_Patch, but without content type
   context. resolvedNameForComponent fell into a non-deterministic prefix
   match, returning an arbitrary per-content-type base name that didn't
   match any defined type when mediaTypeToCamelCase was appended.
   Fix: add resolvedNameForRefPath helper that parses the $ref path and
   delegates to resolvedNameForComponent with the specific content type
   for exact matching.

3. Mismatched types in response struct fields: same root cause as bug 2 —
   the struct field types were derived from the same non-deterministic
   resolution path.

Additionally, promote AdditionalTypes from GenerateTypesForResponses and
GenerateTypesForRequestBodies to the top-level type list, matching the
existing pattern in GenerateTypesForSchemas. Without this, inline types
(e.g., oneOf union members, nested objects with additionalProperties)
defined inside response/requestBody schemas were silently dropped from
the output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address greptile's comment

* fix: propagate user name overrides through codegen

The collision resolver had two bugs when resolve-type-name-collisions
was enabled:

1. x-go-name was ignored: generateCandidateName() never consulted the
   x-go-name extension, so schemas/responses/requestBodies/parameters/
   headers with explicit Go name overrides would lose them during
   collision resolution.

2. Client wrapper names bypassed the name normalizer:
   generateCandidateName() used UppercaseFirstCharacter(operationID)
   instead of SchemaNameToTypeName(operationID), so configured
   normalizers (e.g. ToCamelCaseWithInitialisms) were not applied to
   client response wrapper type names.

Changes:
- Add GoNameOverride field to GatheredSchema, populated from x-go-name
  on the parent container (schema, response, requestBody, parameter,
  header) when the component is not a $ref
- Add extractGoNameOverride() helper to read x-go-name from extensions
- Add Pinned field to ResolvedName; pinned names are returned as-is
  from generateCandidateName() and skipped by all collision resolution
  strategies (context suffix, content type, status code, param index,
  numeric fallback)
- Fix client wrapper candidate name to use SchemaNameToTypeName()
- Add unit tests for GoNameOverride population and pinning behaviour
- Add integration test patterns K/L/M (x-go-name on schema, response,
  and requestBody) and regenerate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants