Skip to content

Fix duplicate client/operation names when operation IDs contain multiple underscores#5348

Merged
lahma merged 2 commits intomasterfrom
copilot/fix-duplicate-classes-issue
Mar 6, 2026
Merged

Fix duplicate client/operation names when operation IDs contain multiple underscores#5348
lahma merged 2 commits intomasterfrom
copilot/fix-duplicate-classes-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 6, 2026

MultipleClientsFromOperationIdOperationNameGenerator used the second-to-last underscore segment as client name and last segment as operation name. Any two operation IDs sharing the same suffix (e.g. Orders_items_get and Products_items_get) mapped to the same (client="items", operation="get") pair, producing duplicate generated classes and methods.

Changes

  • GetClientName: switched from second-to-last segment to everything before the first _
  • GetOperationName: switched from LastIndexOf('_') to IndexOf('_') — returns everything after the first _
Operation ID Old client → operation New client → operation
OperationId_Test OperationIdTest OperationIdTest
Orders_items_get itemsget Ordersitems_get
Products_items_get itemsget ⚠️ duplicate Productsitems_get

Since OpenAPI mandates globally unique operation IDs, splitting at the first _ guarantees that no two operation IDs can ever produce the same (client, operation) pair. The fix also propagates to MultipleClientsFromFirstTagAndOperationNameGenerator, which inherits GetOperationName from the base class.

Tests

  • Updated existing test expectations to match the new behavior
  • Added explicit operation-name tests for MultipleClientsFromOperationId
  • Added regression tests asserting that distinct operation IDs always yield distinct (client, operation) pairs
Original prompt

This section details on the original issue you should resolve

<issue_title>Underscores - the source of all evil when used, but should work out of the box.</issue_title>
<issue_description>We have a customer openapi.json which we query at runtime to generate our bindings.
In our case we are getting duplicate classes, due to the standard implementation using the underscore as separator, and only giving back the last chunk, which is obviously bad.

Example: https://github.com/RicoSuter/NSwag/blob/master/src/NSwag.CodeGeneration/OperationNameGenerators/MultipleClientsFromOperationIdOperationNameGenerator.cs#L108

This also applies to
#4178 #3450 #1749 #596

Neither we, nor the customer can change those definition file. Manually iterating the definition for replacement is also a huge pain, as we never know where stuff is referenced. And even if we manage to find all references, we cannot rely on removing the underscore, as this might actually generate duplicate identifiers.

  • Having an underscore in an OperationId should work.
  • Having an underscore in a schema name should also work.

I would suggest using "/" as separator char, as it is invalid anyway, but an "_" underscore is very likely to occur.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…iple underscores

Use the first underscore as the separator in MultipleClientsFromOperationIdOperationNameGenerator:
- GetClientName: everything before the first '_' = client name
- GetOperationName: everything after the first '_' = operation name

This guarantees uniqueness since OpenAPI requires globally unique operation IDs.
Previously, 'Orders_items_get' and 'Products_items_get' both produced
client='items', operation='get', causing duplicate generated code.

Co-authored-by: lahma <171892+lahma@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix duplicate class generation due to underscore usage Fix duplicate client/operation names when operation IDs contain multiple underscores Mar 6, 2026
Copilot finished work on behalf of lahma March 6, 2026 10:25
@lahma lahma requested a review from Copilot March 6, 2026 20:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a long-standing bug in MultipleClientsFromOperationIdOperationNameGenerator where operation IDs with multiple underscores (e.g., Orders_items_get and Products_items_get) would map to duplicate (client, operation) pairs, causing duplicate generated classes and methods. The fix simplifies the splitting strategy to always use the first underscore as the delimiter — everything before it becomes the client name, everything after becomes the operation name. Since OpenAPI mandates globally unique operation IDs, this guarantees uniqueness of (client, operation) pairs.

Changes:

  • GetClientName: Returns everything before the first underscore (instead of the second-to-last segment)
  • GetOperationName: Returns everything after the first underscore (instead of the last segment)
  • Test updates and additions to cover the new behavior and regression-test uniqueness guarantees

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/NSwag.CodeGeneration/OperationNameGenerators/MultipleClientsFromOperationIdOperationNameGenerator.cs Simplified GetClientName and GetOperationName to split on the first underscore instead of the last
src/NSwag.CodeGeneration.Tests/CodeGenerationTests.cs Updated existing test expectations to match new behavior, added operation-name tests for MultipleClientsFromOperationId, and added regression tests asserting uniqueness of (client, operation) pairs

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@lahma lahma marked this pull request as ready for review March 6, 2026 21:00
@lahma lahma merged commit eac9c1f into master Mar 6, 2026
7 checks passed
@lahma lahma deleted the copilot/fix-duplicate-classes-issue branch March 6, 2026 21:00
github-actions bot pushed a commit to EelcoLos/nx-tinkering that referenced this pull request Apr 14, 2026
Updated [NSwag.Core](https://github.com/RicoSuter/NSwag) from 14.6.3 to
14.7.0.

<details>
<summary>Release notes</summary>

_Sourced from [NSwag.Core's
releases](https://github.com/RicoSuter/NSwag/releases)._

## 14.7.0

## What's Changed
* Upgrade to NUKE 10 by @​lahma in
RicoSuter/NSwag#5310
* Upgrade to NUKE 10.1.0 by @​lahma in
RicoSuter/NSwag#5314
* Fix specifying runtime for `NSwag.Npm` resulting in a
`NConsole.UnusedArgumentException` by @​ptasev in
RicoSuter/NSwag#5320
* fix: nullpointer by @​MeikelLP in
RicoSuter/NSwag#5337
* fix axios template: [object Object] is not valid JSON. by
@​adnanalbeda in RicoSuter/NSwag#5283
* Fix legacy document transformation by @​copyleftproducts in
RicoSuter/NSwag#5315
* Fix compile error in generated C# client when media type contains
quotes by @​bkoelman in RicoSuter/NSwag#5345
* Fix duplicate client/operation names when operation IDs contain
multiple underscores by @​Copilot in
RicoSuter/NSwag#5348
* Update to NJsonSchema v11.6.0 and Namotion.Reflection v3.5.0 (v14.7.0)
by @​RicoSuter in RicoSuter/NSwag#5357

## NJsonSchema v11.6.0 (potentially breaking changes)

This release updates to [NJsonSchema
v11.6.0](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0)
and [Namotion.Reflection
v3.5.0](https://github.com/RicoSuter/Namotion.Reflection/commits/master/),
which include the following changes that may affect generated code:

- **C# 11 `required` keyword now correctly recognized**: Properties
using the C# 11 `required` keyword (via `RequiredMemberAttribute` /
`JsonRequiredAttribute`) are now properly treated as required in the
schema and generated code. Previously these were incorrectly treated as
optional.
- **Removed extra blank line** before class declarations in generated C#
controller code (cosmetic).
- New `WriteAccessor` setting to control property setter syntax (`set`
vs `init`).
- New `JsonLibraryVersion` setting for controlling enum attribute
generation with System.Text.Json.
- Fixed `MinLength` validation no longer incorrectly applied to DateTime
properties.
- Fixed nullable enum array detection for string enum converters.
- Support for public fields with System.Text.Json when `IncludeFields`
is enabled.

For the full list of changes, see the [NJsonSchema v11.6.0 release
notes](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0).

## New Contributors
* @​ptasev made their first contribution in
RicoSuter/NSwag#5320
* @​MeikelLP made their first contribution in
RicoSuter/NSwag#5337
* @​adnanalbeda made their first contribution in
RicoSuter/NSwag#5283
* @​copyleftproducts made their first contribution in
RicoSuter/NSwag#5315
* @​Copilot made their first contribution in
RicoSuter/NSwag#5348

**Full Changelog**:
RicoSuter/NSwag@v14.6.3...v14.7.0

Commits viewable in [compare
view](RicoSuter/NSwag@v14.6.3...v14.7.0).
</details>

Updated [NSwag.Core.Yaml](https://github.com/RicoSuter/NSwag) from
14.6.3 to 14.7.0.

<details>
<summary>Release notes</summary>

_Sourced from [NSwag.Core.Yaml's
releases](https://github.com/RicoSuter/NSwag/releases)._

## 14.7.0

## What's Changed
* Upgrade to NUKE 10 by @​lahma in
RicoSuter/NSwag#5310
* Upgrade to NUKE 10.1.0 by @​lahma in
RicoSuter/NSwag#5314
* Fix specifying runtime for `NSwag.Npm` resulting in a
`NConsole.UnusedArgumentException` by @​ptasev in
RicoSuter/NSwag#5320
* fix: nullpointer by @​MeikelLP in
RicoSuter/NSwag#5337
* fix axios template: [object Object] is not valid JSON. by
@​adnanalbeda in RicoSuter/NSwag#5283
* Fix legacy document transformation by @​copyleftproducts in
RicoSuter/NSwag#5315
* Fix compile error in generated C# client when media type contains
quotes by @​bkoelman in RicoSuter/NSwag#5345
* Fix duplicate client/operation names when operation IDs contain
multiple underscores by @​Copilot in
RicoSuter/NSwag#5348
* Update to NJsonSchema v11.6.0 and Namotion.Reflection v3.5.0 (v14.7.0)
by @​RicoSuter in RicoSuter/NSwag#5357

## NJsonSchema v11.6.0 (potentially breaking changes)

This release updates to [NJsonSchema
v11.6.0](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0)
and [Namotion.Reflection
v3.5.0](https://github.com/RicoSuter/Namotion.Reflection/commits/master/),
which include the following changes that may affect generated code:

- **C# 11 `required` keyword now correctly recognized**: Properties
using the C# 11 `required` keyword (via `RequiredMemberAttribute` /
`JsonRequiredAttribute`) are now properly treated as required in the
schema and generated code. Previously these were incorrectly treated as
optional.
- **Removed extra blank line** before class declarations in generated C#
controller code (cosmetic).
- New `WriteAccessor` setting to control property setter syntax (`set`
vs `init`).
- New `JsonLibraryVersion` setting for controlling enum attribute
generation with System.Text.Json.
- Fixed `MinLength` validation no longer incorrectly applied to DateTime
properties.
- Fixed nullable enum array detection for string enum converters.
- Support for public fields with System.Text.Json when `IncludeFields`
is enabled.

For the full list of changes, see the [NJsonSchema v11.6.0 release
notes](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0).

## New Contributors
* @​ptasev made their first contribution in
RicoSuter/NSwag#5320
* @​MeikelLP made their first contribution in
RicoSuter/NSwag#5337
* @​adnanalbeda made their first contribution in
RicoSuter/NSwag#5283
* @​copyleftproducts made their first contribution in
RicoSuter/NSwag#5315
* @​Copilot made their first contribution in
RicoSuter/NSwag#5348

**Full Changelog**:
RicoSuter/NSwag@v14.6.3...v14.7.0

Commits viewable in [compare
view](RicoSuter/NSwag@v14.6.3...v14.7.0).
</details>

Updated
[NSwag.Generation.AspNetCore](https://github.com/RicoSuter/NSwag) from
14.6.3 to 14.7.0.

<details>
<summary>Release notes</summary>

_Sourced from [NSwag.Generation.AspNetCore's
releases](https://github.com/RicoSuter/NSwag/releases)._

## 14.7.0

## What's Changed
* Upgrade to NUKE 10 by @​lahma in
RicoSuter/NSwag#5310
* Upgrade to NUKE 10.1.0 by @​lahma in
RicoSuter/NSwag#5314
* Fix specifying runtime for `NSwag.Npm` resulting in a
`NConsole.UnusedArgumentException` by @​ptasev in
RicoSuter/NSwag#5320
* fix: nullpointer by @​MeikelLP in
RicoSuter/NSwag#5337
* fix axios template: [object Object] is not valid JSON. by
@​adnanalbeda in RicoSuter/NSwag#5283
* Fix legacy document transformation by @​copyleftproducts in
RicoSuter/NSwag#5315
* Fix compile error in generated C# client when media type contains
quotes by @​bkoelman in RicoSuter/NSwag#5345
* Fix duplicate client/operation names when operation IDs contain
multiple underscores by @​Copilot in
RicoSuter/NSwag#5348
* Update to NJsonSchema v11.6.0 and Namotion.Reflection v3.5.0 (v14.7.0)
by @​RicoSuter in RicoSuter/NSwag#5357

## NJsonSchema v11.6.0 (potentially breaking changes)

This release updates to [NJsonSchema
v11.6.0](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0)
and [Namotion.Reflection
v3.5.0](https://github.com/RicoSuter/Namotion.Reflection/commits/master/),
which include the following changes that may affect generated code:

- **C# 11 `required` keyword now correctly recognized**: Properties
using the C# 11 `required` keyword (via `RequiredMemberAttribute` /
`JsonRequiredAttribute`) are now properly treated as required in the
schema and generated code. Previously these were incorrectly treated as
optional.
- **Removed extra blank line** before class declarations in generated C#
controller code (cosmetic).
- New `WriteAccessor` setting to control property setter syntax (`set`
vs `init`).
- New `JsonLibraryVersion` setting for controlling enum attribute
generation with System.Text.Json.
- Fixed `MinLength` validation no longer incorrectly applied to DateTime
properties.
- Fixed nullable enum array detection for string enum converters.
- Support for public fields with System.Text.Json when `IncludeFields`
is enabled.

For the full list of changes, see the [NJsonSchema v11.6.0 release
notes](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0).

## New Contributors
* @​ptasev made their first contribution in
RicoSuter/NSwag#5320
* @​MeikelLP made their first contribution in
RicoSuter/NSwag#5337
* @​adnanalbeda made their first contribution in
RicoSuter/NSwag#5283
* @​copyleftproducts made their first contribution in
RicoSuter/NSwag#5315
* @​Copilot made their first contribution in
RicoSuter/NSwag#5348

**Full Changelog**:
RicoSuter/NSwag@v14.6.3...v14.7.0

Commits viewable in [compare
view](RicoSuter/NSwag@v14.6.3...v14.7.0).
</details>

Updated [NSwag.MSBuild](https://github.com/RicoSuter/NSwag) from 14.6.3
to 14.7.0.

<details>
<summary>Release notes</summary>

_Sourced from [NSwag.MSBuild's
releases](https://github.com/RicoSuter/NSwag/releases)._

## 14.7.0

## What's Changed
* Upgrade to NUKE 10 by @​lahma in
RicoSuter/NSwag#5310
* Upgrade to NUKE 10.1.0 by @​lahma in
RicoSuter/NSwag#5314
* Fix specifying runtime for `NSwag.Npm` resulting in a
`NConsole.UnusedArgumentException` by @​ptasev in
RicoSuter/NSwag#5320
* fix: nullpointer by @​MeikelLP in
RicoSuter/NSwag#5337
* fix axios template: [object Object] is not valid JSON. by
@​adnanalbeda in RicoSuter/NSwag#5283
* Fix legacy document transformation by @​copyleftproducts in
RicoSuter/NSwag#5315
* Fix compile error in generated C# client when media type contains
quotes by @​bkoelman in RicoSuter/NSwag#5345
* Fix duplicate client/operation names when operation IDs contain
multiple underscores by @​Copilot in
RicoSuter/NSwag#5348
* Update to NJsonSchema v11.6.0 and Namotion.Reflection v3.5.0 (v14.7.0)
by @​RicoSuter in RicoSuter/NSwag#5357

## NJsonSchema v11.6.0 (potentially breaking changes)

This release updates to [NJsonSchema
v11.6.0](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0)
and [Namotion.Reflection
v3.5.0](https://github.com/RicoSuter/Namotion.Reflection/commits/master/),
which include the following changes that may affect generated code:

- **C# 11 `required` keyword now correctly recognized**: Properties
using the C# 11 `required` keyword (via `RequiredMemberAttribute` /
`JsonRequiredAttribute`) are now properly treated as required in the
schema and generated code. Previously these were incorrectly treated as
optional.
- **Removed extra blank line** before class declarations in generated C#
controller code (cosmetic).
- New `WriteAccessor` setting to control property setter syntax (`set`
vs `init`).
- New `JsonLibraryVersion` setting for controlling enum attribute
generation with System.Text.Json.
- Fixed `MinLength` validation no longer incorrectly applied to DateTime
properties.
- Fixed nullable enum array detection for string enum converters.
- Support for public fields with System.Text.Json when `IncludeFields`
is enabled.

For the full list of changes, see the [NJsonSchema v11.6.0 release
notes](https://github.com/RicoSuter/NJsonSchema/releases/tag/v11.6.0).

## New Contributors
* @​ptasev made their first contribution in
RicoSuter/NSwag#5320
* @​MeikelLP made their first contribution in
RicoSuter/NSwag#5337
* @​adnanalbeda made their first contribution in
RicoSuter/NSwag#5283
* @​copyleftproducts made their first contribution in
RicoSuter/NSwag#5315
* @​Copilot made their first contribution in
RicoSuter/NSwag#5348

**Full Changelog**:
RicoSuter/NSwag@v14.6.3...v14.7.0

Commits viewable in [compare
view](RicoSuter/NSwag@v14.6.3...v14.7.0).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eelco Los <5102501+EelcoLos@users.noreply.github.com>
This was referenced Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Underscores - the source of all evil when used, but should work out of the box.

3 participants