Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align the client type shape from TCGC in our emitter #6179

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
making fixes on those test cases for clientprovider
  • Loading branch information
ArcturusZhang committed Mar 3, 2025
commit f020f15096b4dfeeced8056277042da5c23f24a7
112 changes: 100 additions & 12 deletions packages/http-client-csharp/emitter/src/lib/client-converter.ts
Original file line number Diff line number Diff line change
@@ -3,39 +3,60 @@

import {
SdkClientType as SdkClientTypeOfT,
SdkEndpointParameter,
SdkEndpointType,
SdkHttpOperation,
} from "@azure-tools/typespec-client-generator-core";
import { NoTarget } from "@typespec/compiler";
import { CSharpEmitterContext } from "../sdk-context.js";
import { InputClientType } from "../type/input-type.js";
import { InputOperationParameterKind } from "../type/input-operation-parameter-kind.js";
import { InputParameter } from "../type/input-parameter.js";
import { InputClientType, InputType } from "../type/input-type.js";
import { RequestLocation } from "../type/request-location.js";
import { fromSdkServiceMethod, getParameterDefaultValue } from "./operation-converter.js";
import { fromSdkType } from "./type-converter.js";

type SdkClientType = SdkClientTypeOfT<SdkHttpOperation>;

export function fromSdkClients(
sdkContext: CSharpEmitterContext,
clients: SdkClientType[],
rootApiVersions: string[],
): InputClientType[] {
const inputClients: InputClientType[] = [];
for (const client of clients) {
const inputClient = fromSdkClient(sdkContext, client);
const inputClient = fromSdkClient(sdkContext, client, rootApiVersions);
inputClients.push(inputClient);
}

return inputClients;
}

function fromSdkClient(sdkContext: CSharpEmitterContext, client: SdkClientType): InputClientType {
function fromSdkClient(
sdkContext: CSharpEmitterContext,
client: SdkClientType,
rootApiVersions: string[],
): InputClientType {
let inputClient: InputClientType | undefined = sdkContext.__typeCache.clients.get(client);
if (inputClient) {
return inputClient;
}
const endpointParameter = client.clientInitialization.parameters.find(
(p) => p.kind === "endpoint",
) as SdkEndpointParameter;
const uri = getMethodUri(endpointParameter);
const clientParameters = fromSdkEndpointParameter(endpointParameter);

inputClient = {
kind: "client",
name: client.name,
namespace: client.namespace,
doc: client.doc,
summary: client.summary,
operations: [],
parameters: clientParameters,
operations: client.methods
.filter((m) => m.kind !== "clientaccessor")
.map((m) => fromSdkServiceMethod(sdkContext, m, uri, rootApiVersions)),
apiVersions: client.apiVersions,
crossLanguageDefinitionId: client.crossLanguageDefinitionId,
parent: undefined,
@@ -44,22 +65,78 @@ function fromSdkClient(sdkContext: CSharpEmitterContext, client: SdkClientType):

updateSdkClientTypeReferences(sdkContext, client, inputClient);

// fill operations - TODO
// fill parent
if (client.parent) {
const parent = fromSdkClient(sdkContext, client.parent);
inputClient.parent = parent;
inputClient.parent = fromSdkClient(sdkContext, client.parent, rootApiVersions);
}
// fill children
if (client.children) {
const children: InputClientType[] = [];
for (const child of client.children) {
children.push(fromSdkClient(sdkContext, child));
}
inputClient.children = children;
inputClient.children = client.children.map((c) =>
fromSdkClient(sdkContext, c, rootApiVersions),
);
}

return inputClient;

function fromSdkEndpointParameter(p: SdkEndpointParameter): InputParameter[] {
if (p.type.kind === "union") {
return fromSdkEndpointType(p.type.variantTypes[0]);
} else {
return fromSdkEndpointType(p.type);
}
}

function fromSdkEndpointType(type: SdkEndpointType): InputParameter[] {
// TODO: support free-style endpoint url with multiple parameters
const endpointExpr = type.serverUrl
.replace("https://", "")
.replace("http://", "")
.split("/")[0];
if (!/^\{\w+\}$/.test(endpointExpr)) {
sdkContext.logger.reportDiagnostic({
code: "unsupported-endpoint-url",
format: { endpoint: type.serverUrl },
target: NoTarget,
});
return [];
}
const endpointVariableName = endpointExpr.substring(1, endpointExpr.length - 1);

const parameters: InputParameter[] = [];
for (const parameter of type.templateArguments) {
const isEndpoint = parameter.name === endpointVariableName;
const parameterType: InputType = isEndpoint
? {
kind: "url",
name: "url",
crossLanguageDefinitionId: "TypeSpec.url",
}
: fromSdkType(sdkContext, parameter.type); // TODO: consolidate with converter.fromSdkEndpointType
parameters.push({
Name: parameter.name,
NameInRequest: parameter.serializedName,
Summary: parameter.summary,
Doc: parameter.doc,
// TODO: we should do the magic in generator
Type: parameterType,
Location: RequestLocation.Uri,
IsApiVersion: parameter.isApiVersionParam,
IsResourceParameter: false,
IsContentType: false,
IsRequired: !parameter.optional,
IsEndpoint: isEndpoint,
SkipUrlEncoding: false,
Explode: false,
Kind: InputOperationParameterKind.Client,
DefaultValue: getParameterDefaultValue(
sdkContext,
parameter.clientDefaultValue,
parameterType,
),
});
}
return parameters;
}
}

function updateSdkClientTypeReferences(
@@ -73,3 +150,14 @@ function updateSdkClientTypeReferences(
sdkClient.__raw.type,
);
}

function getMethodUri(p: SdkEndpointParameter | undefined): string {
if (!p) return "";

if (p.type.kind === "endpoint" && p.type.templateArguments.length > 0) return p.type.serverUrl;

if (p.type.kind === "union" && p.type.variantTypes.length > 0)
return (p.type.variantTypes[0] as SdkEndpointType).serverUrl;

return `{${p.name}}`;
}
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
? sdkApiVersionEnums[0].values.map((v) => v.value as string).flat()
: rootClients[0].apiVersions;

const inputClients = fromSdkClients(sdkContext, rootClients);
const inputClients = fromSdkClients(sdkContext, rootClients, rootApiVersions);

const clientModel: CodeModel = {
// rootNamespace is really coalescing the `package-name` option and the first namespace found.
@@ -50,143 +50,4 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
};

return clientModel;

// function fromSdkClients(
// clients: SdkClientType<SdkHttpOperation>[],
// inputClients: InputClient[],
// parentClientNames: string[],
// ) {
// for (const client of clients) {
// const inputClient = fromSdkClient(client, parentClientNames);
// inputClients.push(inputClient);
// const subClients = client.methods
// .filter((m) => m.kind === "clientaccessor")
// .map((m) => m.response as SdkClientType<SdkHttpOperation>);
// parentClientNames.push(inputClient.Name);
// fromSdkClients(subClients, inputClients, parentClientNames);
// parentClientNames.pop();
// }
// }

// function fromSdkClient(
// client: SdkClientType<SdkHttpOperation>,
// parentNames: string[],
// ): InputClient {
// const endpointParameter = client.initialization.properties.find(
// (p) => p.kind === "endpoint",
// ) as SdkEndpointParameter;
// const uri = getMethodUri(endpointParameter);
// const clientParameters = fromSdkEndpointParameter(endpointParameter);
// const clientName = getClientName(client, parentNames);

// sdkContext.__typeCache.crossLanguageDefinitionIds.set(
// client.crossLanguageDefinitionId,
// client.__raw.type,
// );
// return {
// Name: clientName,
// Namespace: client.namespace,
// Summary: client.summary,
// Doc: client.doc,
// Operations: client.methods
// .filter((m) => m.kind !== "clientaccessor")
// .map((m) =>
// fromSdkServiceMethod(
// sdkContext,
// m as SdkServiceMethod<SdkHttpOperation>,
// uri,
// rootApiVersions,
// ),
// ),
// Protocol: {},
// Parent: parentNames.length > 0 ? parentNames[parentNames.length - 1] : undefined,
// Parameters: clientParameters,
// Decorators: client.decorators,
// CrossLanguageDefinitionId: client.crossLanguageDefinitionId,
// };
// }

// function getClientName(
// client: SdkClientType<SdkHttpOperation>,
// parentClientNames: string[],
// ): string {
// const clientName = client.name;

// if (parentClientNames.length === 0) return clientName;
// if (parentClientNames.length >= 2)
// return `${parentClientNames.slice(parentClientNames.length - 1).join("")}${clientName}`;

// return clientName;
// }

// function fromSdkEndpointParameter(p: SdkEndpointParameter): InputParameter[] {
// if (p.type.kind === "union") {
// return fromSdkEndpointType(p.type.variantTypes[0]);
// } else {
// return fromSdkEndpointType(p.type);
// }
// }

// function fromSdkEndpointType(type: SdkEndpointType): InputParameter[] {
// // TODO: support free-style endpoint url with multiple parameters
// const endpointExpr = type.serverUrl
// .replace("https://", "")
// .replace("http://", "")
// .split("/")[0];
// if (!/^\{\w+\}$/.test(endpointExpr)) {
// sdkContext.logger.reportDiagnostic({
// code: "unsupported-endpoint-url",
// format: { endpoint: type.serverUrl },
// target: NoTarget,
// });
// return [];
// }
// const endpointVariableName = endpointExpr.substring(1, endpointExpr.length - 1);

// const parameters: InputParameter[] = [];
// for (const parameter of type.templateArguments) {
// const isEndpoint = parameter.name === endpointVariableName;
// const parameterType: InputType = isEndpoint
// ? {
// kind: "url",
// name: "url",
// crossLanguageDefinitionId: "TypeSpec.url",
// }
// : fromSdkType(sdkContext, parameter.type); // TODO: consolidate with converter.fromSdkEndpointType
// parameters.push({
// Name: parameter.name,
// NameInRequest: parameter.serializedName,
// Summary: parameter.summary,
// Doc: parameter.doc,
// // TODO: we should do the magic in generator
// Type: parameterType,
// Location: RequestLocation.Uri,
// IsApiVersion: parameter.isApiVersionParam,
// IsResourceParameter: false,
// IsContentType: false,
// IsRequired: !parameter.optional,
// IsEndpoint: isEndpoint,
// SkipUrlEncoding: false,
// Explode: false,
// Kind: InputOperationParameterKind.Client,
// DefaultValue: getParameterDefaultValue(
// sdkContext,
// parameter.clientDefaultValue,
// parameterType,
// ),
// });
// }
// return parameters;
// }
}

// function getMethodUri(p: SdkEndpointParameter | undefined): string {
// if (!p) return "";

// if (p.type.kind === "endpoint" && p.type.templateArguments.length > 0) return p.type.serverUrl;

// if (p.type.kind === "union" && p.type.variantTypes.length > 0)
// return (p.type.variantTypes[0] as SdkEndpointType).serverUrl;

// return `{${p.name}}`;
// }
2 changes: 2 additions & 0 deletions packages/http-client-csharp/emitter/src/type/input-type.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import {
} from "@azure-tools/typespec-client-generator-core";
import { DateTimeKnownEncoding, DurationKnownEncoding } from "@typespec/compiler";
import { InputOperation } from "./input-operation.js";
import { InputParameter } from "./input-parameter.js";

export interface InputClientType extends DecoratedType {
kind: "client";
@@ -18,6 +19,7 @@ export interface InputClientType extends DecoratedType {
doc?: string;
summary?: string;
// clientInitialization: TODO;
parameters?: InputParameter[]; // TODO -- this should be replaced by clientInitialization when the clientInitialization related stuffs are done.
operations: InputOperation[];
apiVersions: string[];
crossLanguageDefinitionId: string;
Original file line number Diff line number Diff line change
@@ -275,9 +275,9 @@ public async Task CanRenameSubClient()
InputFactory.Parameter("p1", InputFactory.Array(InputPrimitiveType.String))
]);
var inputClient = InputFactory.Client("TestClient", operations: [inputOperation]);
InputClient subClient = InputFactory.Client("custom", parent: inputClient.Name);
InputClient subClient = InputFactory.Client("custom", parent: inputClient);
var plugin = await MockHelpers.LoadMockPluginAsync(
clients: () => [inputClient, subClient],
clients: () => [inputClient],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

// Find the sub-client provider
@@ -302,7 +302,7 @@ public async Task CanRemoveCachingField()
InputFactory.Parameter("p1", InputFactory.Array(InputPrimitiveType.String))
]);
var inputClient = InputFactory.Client("TestClient", operations: [inputOperation]);
InputClient subClient = InputFactory.Client("dog", operations: [], parameters: [], parent: inputClient.Name);
InputClient subClient = InputFactory.Client("dog", operations: [], parameters: [], parent: inputClient);
var plugin = await MockHelpers.LoadMockPluginAsync(
clients: () => [inputClient, subClient],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
Loading
Oops, something went wrong.