-
Notifications
You must be signed in to change notification settings - Fork 351
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.