Skip to content

Commit 6fb2508

Browse files
ArcturusZhangmario-guerra
authored andcommitted
Align the client type shape from TCGC in our emitter (#6179)
Fixes #5924 Going forward to the ultimate goal of "only keep minimum code in our emitter, which should only parse and spit out the data from TCGC", this PR removes a few unnecessary code in our emitter, and align the structure of client in our emitter with the client from TCGC. Client initialization stuff has not been started therefore I still keep the property `parameters` on the client type to keep maximum compatibility and minimum the code change. I think the `parameters` could be gone once our client initialization work item was done. Operation stuff is also not changed, because the way TCGC represent operations/methods is different from ours. I also keep that part as a future work to align. About client names: Before, we did client name calculations in the emitter, to assign verbose name to subclients, such as: ``` namespace Service; namespace Foo { op one(): void; namespace Bar { op two(): void; namespace Qux { op three(): void; } } } ``` We will have 4 clients. The hierarchy looks like this: ``` ServiceClient - Foo - Bar - Qux ``` As a subclient, for instance, their full name could be `ServiceClient.Foo.Bar` in this case. Those second level (or deeper) subclients have a chance to have name collisions because we would put them in the same namespace previously, and we could have another subclient like `ServiceClient.Foo2.Bar`. Therefore previously we are making name changes to them automatically for those 2nd-level or deeper subclients by prepending all their parents' name to it. Now in this PR, it is not the goal here that we change that rule - this PR just wants them all to unchange - therefore I put this logic at the place that we have serialized the whole namespace to keep things in parity.
1 parent 98bafa1 commit 6fb2508

File tree

89 files changed

+45139
-44005
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+45139
-44005
lines changed

packages/http-client-csharp/emitter/src/emitter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export async function $onEmit(context: EmitContext<CSharpEmitterOptions>) {
7373
logger: logger,
7474
__typeCache: {
7575
crossLanguageDefinitionIds: new Map(),
76+
clients: new Map(),
7677
types: new Map(),
7778
models: new Map(),
7879
enums: new Map(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
import {
5+
SdkClientType as SdkClientTypeOfT,
6+
SdkEndpointParameter,
7+
SdkEndpointType,
8+
SdkHttpOperation,
9+
} from "@azure-tools/typespec-client-generator-core";
10+
import { NoTarget } from "@typespec/compiler";
11+
import { CSharpEmitterContext } from "../sdk-context.js";
12+
import { InputOperationParameterKind } from "../type/input-operation-parameter-kind.js";
13+
import { InputParameter } from "../type/input-parameter.js";
14+
import { InputClient, InputType } from "../type/input-type.js";
15+
import { RequestLocation } from "../type/request-location.js";
16+
import { fromSdkServiceMethod, getParameterDefaultValue } from "./operation-converter.js";
17+
import { fromSdkType } from "./type-converter.js";
18+
19+
type SdkClientType = SdkClientTypeOfT<SdkHttpOperation>;
20+
21+
export function fromSdkClients(
22+
sdkContext: CSharpEmitterContext,
23+
clients: SdkClientType[],
24+
rootApiVersions: string[],
25+
): InputClient[] {
26+
const inputClients: InputClient[] = [];
27+
for (const client of clients) {
28+
const inputClient = fromSdkClient(sdkContext, client, rootApiVersions);
29+
inputClients.push(inputClient);
30+
}
31+
32+
return inputClients;
33+
}
34+
35+
function fromSdkClient(
36+
sdkContext: CSharpEmitterContext,
37+
client: SdkClientType,
38+
rootApiVersions: string[],
39+
): InputClient {
40+
let inputClient: InputClient | undefined = sdkContext.__typeCache.clients.get(client);
41+
if (inputClient) {
42+
return inputClient;
43+
}
44+
const endpointParameter = client.clientInitialization.parameters.find(
45+
(p) => p.kind === "endpoint",
46+
) as SdkEndpointParameter;
47+
const uri = getMethodUri(endpointParameter);
48+
const clientParameters = fromSdkEndpointParameter(endpointParameter);
49+
50+
inputClient = {
51+
kind: "client",
52+
name: client.name,
53+
namespace: client.namespace,
54+
doc: client.doc,
55+
summary: client.summary,
56+
operations: client.methods
57+
.filter((m) => m.kind !== "clientaccessor")
58+
.map((m) => fromSdkServiceMethod(sdkContext, m, uri, rootApiVersions)),
59+
parameters: clientParameters,
60+
decorators: client.decorators,
61+
crossLanguageDefinitionId: client.crossLanguageDefinitionId,
62+
apiVersions: client.apiVersions,
63+
parent: undefined,
64+
children: undefined,
65+
};
66+
67+
updateSdkClientTypeReferences(sdkContext, client, inputClient);
68+
69+
// fill parent
70+
if (client.parent) {
71+
inputClient.parent = fromSdkClient(sdkContext, client.parent, rootApiVersions);
72+
}
73+
// fill children
74+
if (client.children) {
75+
inputClient.children = client.children.map((c) =>
76+
fromSdkClient(sdkContext, c, rootApiVersions),
77+
);
78+
}
79+
80+
return inputClient;
81+
82+
function fromSdkEndpointParameter(p: SdkEndpointParameter): InputParameter[] {
83+
if (p.type.kind === "union") {
84+
return fromSdkEndpointType(p.type.variantTypes[0]);
85+
} else {
86+
return fromSdkEndpointType(p.type);
87+
}
88+
}
89+
90+
function fromSdkEndpointType(type: SdkEndpointType): InputParameter[] {
91+
// TODO: support free-style endpoint url with multiple parameters
92+
const endpointExpr = type.serverUrl
93+
.replace("https://", "")
94+
.replace("http://", "")
95+
.split("/")[0];
96+
if (!/^\{\w+\}$/.test(endpointExpr)) {
97+
sdkContext.logger.reportDiagnostic({
98+
code: "unsupported-endpoint-url",
99+
format: { endpoint: type.serverUrl },
100+
target: NoTarget,
101+
});
102+
return [];
103+
}
104+
const endpointVariableName = endpointExpr.substring(1, endpointExpr.length - 1);
105+
106+
const parameters: InputParameter[] = [];
107+
for (const parameter of type.templateArguments) {
108+
const isEndpoint = parameter.name === endpointVariableName;
109+
const parameterType: InputType = isEndpoint
110+
? {
111+
kind: "url",
112+
name: "url",
113+
crossLanguageDefinitionId: "TypeSpec.url",
114+
}
115+
: fromSdkType(sdkContext, parameter.type); // TODO: consolidate with converter.fromSdkEndpointType
116+
parameters.push({
117+
name: parameter.name,
118+
nameInRequest: parameter.serializedName,
119+
summary: parameter.summary,
120+
doc: parameter.doc,
121+
type: parameterType,
122+
location: RequestLocation.Uri,
123+
isApiVersion: parameter.isApiVersionParam,
124+
isContentType: false,
125+
isRequired: !parameter.optional,
126+
isEndpoint: isEndpoint,
127+
skipUrlEncoding: false,
128+
explode: false,
129+
kind: InputOperationParameterKind.Client,
130+
defaultValue: getParameterDefaultValue(
131+
sdkContext,
132+
parameter.clientDefaultValue,
133+
parameterType,
134+
),
135+
});
136+
}
137+
return parameters;
138+
}
139+
}
140+
141+
function updateSdkClientTypeReferences(
142+
sdkContext: CSharpEmitterContext,
143+
sdkClient: SdkClientType,
144+
inputClient: InputClient,
145+
) {
146+
sdkContext.__typeCache.clients.set(sdkClient, inputClient);
147+
sdkContext.__typeCache.crossLanguageDefinitionIds.set(
148+
sdkClient.crossLanguageDefinitionId,
149+
sdkClient.__raw.type,
150+
);
151+
}
152+
153+
function getMethodUri(p: SdkEndpointParameter | undefined): string {
154+
if (!p) return "";
155+
156+
if (p.type.kind === "endpoint" && p.type.templateArguments.length > 0) return p.type.serverUrl;
157+
158+
if (p.type.kind === "union" && p.type.variantTypes.length > 0)
159+
return (p.type.variantTypes[0] as SdkEndpointType).serverUrl;
160+
161+
return `{${p.name}}`;
162+
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4-
import {
5-
SdkClientType,
6-
SdkEndpointParameter,
7-
SdkEndpointType,
8-
SdkHttpOperation,
9-
SdkServiceMethod,
10-
UsageFlags,
11-
} from "@azure-tools/typespec-client-generator-core";
4+
import { UsageFlags } from "@azure-tools/typespec-client-generator-core";
125
import { NoTarget } from "@typespec/compiler";
136
import { CSharpEmitterContext } from "../sdk-context.js";
147
import { CodeModel } from "../type/code-model.js";
15-
import { InputClient } from "../type/input-client.js";
16-
import { InputOperationParameterKind } from "../type/input-operation-parameter-kind.js";
17-
import { InputParameter } from "../type/input-parameter.js";
18-
import { InputType } from "../type/input-type.js";
19-
import { RequestLocation } from "../type/request-location.js";
8+
import { fromSdkClients } from "./client-converter.js";
209
import { navigateModels } from "./model.js";
21-
import { fromSdkServiceMethod, getParameterDefaultValue } from "./operation-converter.js";
2210
import { processServiceAuthentication } from "./service-authentication.js";
23-
import { fromSdkType } from "./type-converter.js";
2411
import { getClientNamespaceString } from "./utils.js";
2512

2613
/**
@@ -36,7 +23,7 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
3623

3724
const sdkApiVersionEnums = sdkPackage.enums.filter((e) => e.usage === UsageFlags.ApiVersionEnum);
3825

39-
const rootClients = sdkPackage.clients.filter((c) => c.initialization.access === "public");
26+
const rootClients = sdkPackage.clients;
4027
if (rootClients.length === 0) {
4128
sdkContext.logger.reportDiagnostic({
4229
code: "no-root-client",
@@ -51,8 +38,7 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
5138
? sdkApiVersionEnums[0].values.map((v) => v.value as string).flat()
5239
: rootClients[0].apiVersions;
5340

54-
const inputClients: InputClient[] = [];
55-
fromSdkClients(rootClients, inputClients, []);
41+
const inputClients = fromSdkClients(sdkContext, rootClients, rootApiVersions);
5642

5743
const clientModel: CodeModel = {
5844
// To ensure deterministic library name, customers would need to set the package-name property as the ordering of the namespaces could change
@@ -66,141 +52,4 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
6652
};
6753

6854
return clientModel;
69-
70-
function fromSdkClients(
71-
clients: SdkClientType<SdkHttpOperation>[],
72-
inputClients: InputClient[],
73-
parentClientNames: string[],
74-
) {
75-
for (const client of clients) {
76-
const inputClient = fromSdkClient(client, parentClientNames);
77-
inputClients.push(inputClient);
78-
const subClients = client.methods
79-
.filter((m) => m.kind === "clientaccessor")
80-
.map((m) => m.response as SdkClientType<SdkHttpOperation>);
81-
parentClientNames.push(inputClient.name);
82-
fromSdkClients(subClients, inputClients, parentClientNames);
83-
parentClientNames.pop();
84-
}
85-
}
86-
87-
function fromSdkClient(
88-
client: SdkClientType<SdkHttpOperation>,
89-
parentNames: string[],
90-
): InputClient {
91-
const endpointParameter = client.initialization.properties.find(
92-
(p) => p.kind === "endpoint",
93-
) as SdkEndpointParameter;
94-
const uri = getMethodUri(endpointParameter);
95-
const clientParameters = fromSdkEndpointParameter(endpointParameter);
96-
const clientName = getClientName(client, parentNames);
97-
98-
sdkContext.__typeCache.crossLanguageDefinitionIds.set(
99-
client.crossLanguageDefinitionId,
100-
client.__raw.type,
101-
);
102-
return {
103-
name: clientName,
104-
namespace: client.namespace,
105-
summary: client.summary,
106-
doc: client.doc,
107-
operations: client.methods
108-
.filter((m) => m.kind !== "clientaccessor")
109-
.map((m) =>
110-
fromSdkServiceMethod(
111-
sdkContext,
112-
m as SdkServiceMethod<SdkHttpOperation>,
113-
uri,
114-
rootApiVersions,
115-
),
116-
),
117-
parent: parentNames.length > 0 ? parentNames[parentNames.length - 1] : undefined,
118-
parameters: clientParameters,
119-
decorators: client.decorators,
120-
crossLanguageDefinitionId: client.crossLanguageDefinitionId,
121-
};
122-
}
123-
124-
function getClientName(
125-
client: SdkClientType<SdkHttpOperation>,
126-
parentClientNames: string[],
127-
): string {
128-
const clientName = client.name;
129-
130-
if (parentClientNames.length === 0) return clientName;
131-
if (parentClientNames.length >= 2)
132-
return `${parentClientNames.slice(parentClientNames.length - 1).join("")}${clientName}`;
133-
134-
return clientName;
135-
}
136-
137-
function fromSdkEndpointParameter(p: SdkEndpointParameter): InputParameter[] {
138-
if (p.type.kind === "union") {
139-
return fromSdkEndpointType(p.type.variantTypes[0]);
140-
} else {
141-
return fromSdkEndpointType(p.type);
142-
}
143-
}
144-
145-
function fromSdkEndpointType(type: SdkEndpointType): InputParameter[] {
146-
// TODO: support free-style endpoint url with multiple parameters
147-
const endpointExpr = type.serverUrl
148-
.replace("https://", "")
149-
.replace("http://", "")
150-
.split("/")[0];
151-
if (!/^\{\w+\}$/.test(endpointExpr)) {
152-
sdkContext.logger.reportDiagnostic({
153-
code: "unsupported-endpoint-url",
154-
format: { endpoint: type.serverUrl },
155-
target: NoTarget,
156-
});
157-
return [];
158-
}
159-
const endpointVariableName = endpointExpr.substring(1, endpointExpr.length - 1);
160-
161-
const parameters: InputParameter[] = [];
162-
for (const parameter of type.templateArguments) {
163-
const isEndpoint = parameter.name === endpointVariableName;
164-
const parameterType: InputType = isEndpoint
165-
? {
166-
kind: "url",
167-
name: "url",
168-
crossLanguageDefinitionId: "TypeSpec.url",
169-
}
170-
: fromSdkType(sdkContext, parameter.type); // TODO: consolidate with converter.fromSdkEndpointType
171-
parameters.push({
172-
name: parameter.name,
173-
nameInRequest: parameter.serializedName,
174-
summary: parameter.summary,
175-
doc: parameter.doc,
176-
// TODO: we should do the magic in generator
177-
type: parameterType,
178-
location: RequestLocation.Uri,
179-
isApiVersion: parameter.isApiVersionParam,
180-
isContentType: false,
181-
isRequired: !parameter.optional,
182-
isEndpoint: isEndpoint,
183-
skipUrlEncoding: false,
184-
explode: false,
185-
kind: InputOperationParameterKind.Client,
186-
defaultValue: getParameterDefaultValue(
187-
sdkContext,
188-
parameter.clientDefaultValue,
189-
parameterType,
190-
),
191-
});
192-
}
193-
return parameters;
194-
}
195-
}
196-
197-
function getMethodUri(p: SdkEndpointParameter | undefined): string {
198-
if (!p) return "";
199-
200-
if (p.type.kind === "endpoint" && p.type.templateArguments.length > 0) return p.type.serverUrl;
201-
202-
if (p.type.kind === "union" && p.type.variantTypes.length > 0)
203-
return (p.type.variantTypes[0] as SdkEndpointType).serverUrl;
204-
205-
return `{${p.name}}`;
20655
}

packages/http-client-csharp/emitter/src/sdk-context.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4-
import { SdkContext, SdkType } from "@azure-tools/typespec-client-generator-core";
4+
import {
5+
SdkClientType,
6+
SdkContext,
7+
SdkHttpOperation,
8+
SdkType,
9+
} from "@azure-tools/typespec-client-generator-core";
510
import { Type } from "@typespec/compiler";
611
import { Logger } from "./lib/logger.js";
712
import { CSharpEmitterOptions } from "./options.js";
8-
import { InputEnumType, InputModelType, InputType } from "./type/input-type.js";
13+
import { InputClient, InputEnumType, InputModelType, InputType } from "./type/input-type.js";
914

1015
/**
1116
* The emitter context for the CSharp emitter.
@@ -18,6 +23,7 @@ export interface CSharpEmitterContext extends SdkContext<CSharpEmitterOptions> {
1823

1924
export interface SdkTypeMap {
2025
crossLanguageDefinitionIds: Map<string, Type | undefined>;
26+
clients: Map<SdkClientType<SdkHttpOperation>, InputClient>;
2127
types: Map<SdkType, InputType>;
2228
models: Map<string, InputModelType>;
2329
enums: Map<string, InputEnumType>;

packages/http-client-csharp/emitter/src/type/code-model.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
import { InputAuth } from "./input-auth.js";
5-
import { InputClient } from "./input-client.js";
6-
import { InputEnumType, InputModelType } from "./input-type.js";
5+
import { InputClient, InputEnumType, InputModelType } from "./input-type.js";
76

87
export interface CodeModel {
98
name: string;

0 commit comments

Comments
 (0)