Skip to content

Fix duplicate API version field names for multi-service clients with colliding namespaces#10061

Merged
JoshLove-msft merged 7 commits intomainfrom
copilot/fix-duplicate-field-names
Mar 17, 2026
Merged

Fix duplicate API version field names for multi-service clients with colliding namespaces#10061
JoshLove-msft merged 7 commits intomainfrom
copilot/fix-duplicate-field-names

Conversation

Copy link
Contributor

Copilot AI commented Mar 17, 2026

ClientOptionsProvider.BuildVersionProperties() and ApiVersionEnumProvider.BuildName() both derived names from inputEnum.Namespace via BuildNameForService(), which extracts only the last namespace segment. When two services have different full namespaces but the same last segment, identical names are generated, crashing ClientProvider.BuildMethods() at Fields.ToDictionary(). This blocks the Azure management plane generator for multi-service SDKs like Azure Compute.

Changes

  • ApiVersionEnumProvider.cs: For multi-service clients, use the full namespace (namespace.ToIdentifierName() + "Version") instead of BuildNameForService() to derive service version enum names. This guarantees uniqueness since different services always have different full namespaces.
  • ClientOptionsProvider.cs: For multi-service clients, use the full namespace (inputEnum.Namespace.ToIdentifierName() + "ApiVersion") instead of BuildNameForService() to derive version property names. Also simplified constructor parameter naming to use the enum name directly (serviceVersionEnum.Name.ToVariableName()).
  • ClientHelper.cs / ClientHelperTests.cs: Removed the now-unused BuildNameForService helper method and its tests, since both call sites were replaced with full-namespace-based naming.
  • ClientProviderTests.cs: Regression test with two services having different full namespaces but the same last segment (Azure.ServiceOne.Tests vs Azure.ServiceTwo.Tests), verifying no crash and distinct field names (_azureServiceOneTestsApiVersion, _azureServiceTwoTestsApiVersion).
  • ClientOptionsProviderTests.cs: Test validating that generated service version enums have no naming collisions when namespaces share the same last segment.
  • ApiVersionEnumProviderTests.cs: Updated assertions to match new full-namespace-based enum naming pattern.
  • Golden test data files: Updated expected output to reflect the new full-namespace-based naming pattern.
// Before: "Sample.ServiceA" → last segment "ServiceA" → enum "ServiceAVersion", property "ServiceAApiVersion"
//         (collides when last segments match across different namespaces)
// After:  "Sample.ServiceA" → full namespace "SampleServiceA" → enum "SampleServiceAVersion", property "SampleServiceAApiVersion"
//         (guaranteed unique since full namespaces differ)
Original prompt

This section details on the original issue you should resolve

<issue_title>[CSharp] ClientProvider.BuildApiVersionFields produces duplicate field names for multi-service clients</issue_title>
<issue_description>## Bug Report

Description

When a multi-service client is created via @client({ service: [ServiceA, ServiceB] }), ClientProvider.BuildApiVersionFields() can produce duplicate field names, causing BuildMethods() to crash with:

An item with the same key has already been added. Key: serviceTestsApiVersion
   at Dictionary2.Add(TKey key, TValue value)
   at Enumerable.ToDictionary(...)
   at ClientProvider.BuildMethods()

Impact

This crashes the Azure management plane generator (@azure-typespec/http-client-csharp-mgmt) when generating multi-service SDKs such as Azure Compute (which combines 4 services via @client({ service: [Compute, ComputeDisk, ComputeGallery, ComputeSku] })).

The mgmt generator does not use the ClientProvider or its API version fields at all — it replaces them with its own ARM resource-based client hierarchy. However, the crash occurs during ClientProvider.BuildMethods() before the mgmt generator gets a chance to override it, so the mgmt generator cannot work around this without patching the base ClientProvider.

Root Cause

In ClientOptionsProvider.BuildVersionProperties(), the property name for each service version is computed via:

string name = _inputClient.IsMultiServiceClient 
    ? ClientHelper.BuildNameForService(inputEnumType.Namespace, "Service", "ApiVersion") 
    : "Version";

When two services have namespaces that reduce to the same identifier (e.g. both share a common namespace segment from the C# output namespace), BuildNameForService produces the same property name for both services.

Then in ClientProvider.BuildApiVersionFields(), when VersionProperties.Count > 1:

string name = (count > 1) ? ("_" + propertyProvider.Name.ToVariableName()) : text;

Both services get the same field name (e.g. _serviceTestsApiVersion), and Fields.ToDictionary() in BuildMethods() crashes on the duplicate key.

Reproduction

Two ARM services combined via @client, with the output C# namespace causing name collisions:

ServiceOne/main.tsp:

@armProviderNamespace
@service(#{ title: "ServiceOne" })
@versioned(ServiceOne.Versions)
namespace ServiceOne;

enum Versions { v2024_01_01: "2024-01-01" }
// ... resource definition ...

ServiceTwo/main.tsp:

@armProviderNamespace
@service(#{ title: "ServiceTwo" })
@versioned(ServiceTwo.Versions)
namespace ServiceTwo;

enum Versions { v2024_06_01: "2024-06-01" }
// ... resource definition ...

client.tsp:

@client({ name: "MultiServiceClient", service: [ServiceOne, ServiceTwo] })
namespace MultiServiceCombine;

tspconfig.yaml with namespace Azure.Generator.MgmtTypeSpec.MultiService.Tests

Expected Behavior

Each service should get a unique field name for its API version field, even when namespace segments collide.

Actual Behavior

Both services produce the same field name serviceTestsApiVersion, causing ToDictionary to crash.</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.

…th colliding namespaces

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Mar 17, 2026
…test

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copilot AI changed the title [WIP] [CSharp] Fix duplicate field names for multi-service clients Fix duplicate API version field names for multi-service clients with colliding namespaces Mar 17, 2026
Copilot AI requested a review from JoshLove-msft March 17, 2026 15:05
…ames per review feedback

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 17, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@10061

commit: b8b550e

@JoshLove-msft JoshLove-msft enabled auto-merge March 17, 2026 15:49
Copilot AI and others added 2 commits March 17, 2026 16:19
…numProvider and add collision test

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
@JoshLove-msft JoshLove-msft enabled auto-merge March 17, 2026 17:32
@JoshLove-msft JoshLove-msft added this pull request to the merge queue Mar 17, 2026
Merged via the queue into main with commit fe45c06 Mar 17, 2026
25 checks passed
@JoshLove-msft JoshLove-msft deleted the copilot/fix-duplicate-field-names branch March 17, 2026 19:19
Copilot AI added a commit that referenced this pull request Mar 20, 2026
… naming

Re-adds ClientHelper.BuildNameForService and its tests that were removed
in PR #10061. Uses BuildNameForService for the non-collision case in both
ApiVersionEnumProvider.BuildName() and
ClientOptionsProvider.BuildVersionProperties(), falling back to the full
namespace only when last segments collide.

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
github-merge-queue bot pushed a commit that referenced this pull request Mar 23, 2026
PR #10061 unconditionally used the full namespace for API version enum
names in multi-service scenarios. This produces unnecessarily verbose
names when services have distinct last namespace segments. This PR
changes the logic to only use the full namespace when there's an actual
collision, and restores the `ClientHelper.BuildNameForService` helper
for proper prefix/suffix-aware naming. The collision-aware naming
applies to all multi-service generated members — enum type names,
version property names, and latest version field names all use
`BuildNameForService` by default, falling back to a segment-by-segment
resolution when last segments collide. The enum type naming follows the
`{X}ServiceVersion` pattern (e.g., `KeyVaultServiceVersion`) rather than
`Service{X}Version`.

When last segments collide, the new
`ClientHelper.GetShortestUniqueNamespacePrefix` method progressively
adds namespace segments from right to left until a unique suffix is
found, producing shorter names than the previous full-namespace
approach. When multiple enums share the exact same full namespace (e.g.,
when the emitter remaps both services to the same C# output namespace),
the enum's input name is appended for disambiguation to prevent
duplicate member names.

### Behavior

| Namespaces | Member | Before (PR #10061) | After (no collision) |
After (last segment collision) | After (full namespace collision) |
|---|---|---|---|---|---|
| `Sample.KeyVault`, `Sample.Storage` | Enum type |
`SampleKeyVaultVersion` | `KeyVaultServiceVersion` | — | — |
| `Sample.KeyVault`, `Sample.Storage` | Version property |
`SampleKeyVaultApiVersion` | `KeyVaultApiVersion` | — | — |
| `Sample.KeyVault`, `Sample.Storage` | Latest field |
`LatestServiceKeyVaultVersion` | `LatestKeyVaultVersion` | — | — |
| `Azure.One.Tests`, `Azure.Two.Tests` | Enum type |
`AzureOneTestsVersion` | — | `ServiceOneTestsVersion` (shortest unique:
2 segments) | — |
| `Azure.One.Tests`, `Azure.Two.Tests` | Version property |
`AzureOneTestsApiVersion` | — | `ServiceOneTestsApiVersion` (shortest
unique: 2 segments) | — |
| Same namespace for both enums | Enum type | — | — | — |
`{Namespace}{EnumName}Version` (disambiguated by input name) |

### Changes

- **`ClientHelper.cs`** — restored `BuildNameForService` helper (removed
in PR #10061) that extracts the last namespace segment and ensures
proper prefix/suffix without duplication; added shared
`GetLastNamespaceSegment` and `HasLastSegmentCollision` static helpers;
added `GetShortestUniqueNamespacePrefix` that progressively adds
segments from right to left until finding a unique suffix, falling back
to appending the enum input name when namespaces are identical
- **`ClientHelperTests.cs`** — restored all 22 tests for
`BuildNameForService`
- **`ApiVersionEnumProvider.BuildName()`** — uses
`ClientHelper.BuildNameForService` with empty prefix and
`"ServiceVersion"` suffix for the non-collision case, producing
`{X}ServiceVersion` names; uses `GetShortestUniqueNamespacePrefix` for
segment-by-segment resolution when last segments collide
- **`ClientOptionsProvider.BuildVersionProperties()`** — uses
`ClientHelper.BuildNameForService` for version property names in the
non-collision case; uses `GetShortestUniqueNamespacePrefix` when last
segments collide
- **`ClientOptionsProvider.BuildLatestVersionsFields()`** — uses
`ClientHelper.BuildNameForService` for latest version field names in the
non-collision case; uses `GetShortestUniqueNamespacePrefix` when last
segments collide
- **Tests** — updated assertions and expected test data files
(ClientOptionsProvider, ClientProvider, RestClientProvider) for member
name changes; added
`MultiServiceClient_WithCollidingLastSegments_UsesShortestUniquePrefix`
test with TestData golden file validation; added
`MultiServiceClient_SameLastSegment_ProducesUniqueVersionEnums` test;
added `MultiServiceClient_UniqueNamespaces_ProducesUniqueVersionEnums`
test simulating distinct service namespaces; added
`MultiServiceClient_SameNamespace_ProducesUniqueVersionEnums` test for
the same-namespace scenario where the emitter remaps both services to
the same C# output namespace

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Only Use Full Namespace for Api Version Enum When There
are Conflicts</issue_title>
> <issue_description>Follow up to
#10061.
> 
> This PR introduced a change to avoid collisions in the generated api
version enums in the multi-service scenario by using the full namespace
of the enum as the name. We should update this to only do this whenever
there is a collision in the generated names.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #10107

<!-- START COPILOT CODING AGENT TIPS -->
---

📱 Kick off Copilot coding agent tasks wherever you are with [GitHub
Mobile](https://gh.io/cca-mobile-docs), available on iOS and Android.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CSharp] ClientProvider.BuildApiVersionFields produces duplicate field names for multi-service clients

5 participants