Support multi-service C# clients without @client decorator#10438
Support multi-service C# clients without @client decorator#10438
Conversation
Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/10b8061a-9ce0-4c76-ab95-d30a73d2a6df Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
…ant test comment Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/4bd1ac3b-75cd-410c-bd84-0700b628702e Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
commit: |
| // Multiple root clients (one per @service namespace): use their common parent | ||
| // namespace so shared library artifacts are grouped together. Otherwise (a single | ||
| // combined multi-service root, e.g. via `@client({ service: [...] })`), use the | ||
| // root client's namespace. |
| const services = new Set<unknown>(); | ||
| for (const client of rootClients) { | ||
| for (const service of client.__raw.services) { | ||
| services.add(service); |
There was a problem hiding this comment.
@copilot shouldn't we be calling isMultiServiceClient for each client and simply returning true when isMultiServiceClient returns true?
There was a problem hiding this comment.
Done in c41d3e6. containsMultiServiceClient is now just rootClients.some(isMultiServiceClient). The multi-root-multi-service case (multiple @service namespaces, each a separate single-service root) is handled directly in getClientNamespaceString via serviceNamespaces.length > 1, which is the actual signal we care about for picking the library namespace.
| it("produces a separate root client per service with expected names, namespaces, and sub-clients", async () => { | ||
| const program = await typeSpecCompile( | ||
| ` | ||
| namespace Service.MultipleServices; |
There was a problem hiding this comment.
@copilot lets add a unit test in the csharp generator to validate that a client shape like this correctly generates two client option classes. It is likely that we don't and we'll have to fix that.
There was a problem hiding this comment.
Added MultipleRootClients_WithoutClientDecorator_EachGeneratesItsOwnClientOptions in ClientOptionsProviderTests.cs (c41d3e6). Your suspicion was correct — the test initially failed because both root clients received the same singleton ClientOptionsProvider instance. Fixed ClientOptionsProvider.UseSingletonInstance to also return false when the library contains more than one ApiVersionEnum (i.e. it spans multiple services), so each per-service root client now gets its own ServiceAClientOptions / ServiceBClientOptions class with its own api version property. All 36 ClientOptions tests and 264 ClientProvider tests still pass.
…for multi-service libraries Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/b533b9d9-348c-43f4-bd92-bb9ad5af287f Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
When a TypeSpec library declares multiple
@service-decorated namespaces without an explicit@client(theservice/multiple-servicesspector scenario), TCGC emits one root client per service. The C# emitter's library-level logic didn't recognize this shape and resolved the package namespace to the first service's namespace instead of the shared parent, and the generator incorrectly shared a singleClientOptionsinstance across the per-service root clients.Emitter changes (
emitter/src/lib/utils.ts)getClientNamespaceString— when there are multiple@servicenamespaces (i.e.serviceNamespaces.length > 1), resolves the library namespace to the TypeSpec parentNamespaceshared by those services (viafirstNamespace.namespacefromlistAllServiceNamespaces) and passes it through the existing namespace-based helper path. The existing combined-@client({ service: [...] })path is unchanged.containsMultiServiceClient— simplified torootClients.some(isMultiServiceClient)so it accurately reflects its name (any root client is itself a combined multi-service client). The multi-root-multi-service case is handled directly ingetClientNamespaceStringvia theserviceNamespaces.length > 1check.For the spector spec, the library namespace now resolves to
Service.MultipleServicesinstead ofService.MultipleServices.ServiceA, withServiceAClientandServiceBClientas sibling root clients each exposing their ownOperations/SubNamespacesub-clients and per-service API version enums.Generator changes (
ClientOptionsProvider.cs)UseSingletonInstance— also returnsfalsewhen the input library contains more than oneApiVersionEnum(i.e. spans multiple services). Previously, when multiple per-service single-service root clients existed, both would share the singletonClientOptionsProvider, producing a single options class with only the first service's api version. Each root client now gets its ownClientOptionsclass (e.g.ServiceAClientOptions,ServiceBClientOptions) with its own service-specific api version property.Tests
emitter/test/Unit/client-converter.test.ts— newdescribe("multiple services without @client decorator", ...)block mirroring the spector scenario, asserting root count/names/namespaces, sub-client shape, per-serviceapiVersions, and that the library spans multiple distinct services.generator/.../ClientOptionsProviderTests.cs— newMultipleRootClients_WithoutClientDecorator_EachGeneratesItsOwnClientOptionstest verifying that two per-service root clients each produce their own distinctClientOptionstype with the expected names.