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

[tcgc] add getHttpOperationParameter helper function #2010

Merged
merged 6 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

add `getHttpOperationParameter` helper function
1 change: 1 addition & 0 deletions packages/typespec-client-generator-core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function createTCGCContext(program: Program, emitterName?: string): TCGCC
previewStringRegex: /-preview$/,
disableUsageAccessPropagationToBase: false,
__pagedResultSet: new Set(),
__paramMapping: new Map(),
};
}

Expand Down
26 changes: 20 additions & 6 deletions packages/typespec-client-generator-core/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ export function getCorrespondingMethodParams(
): [SdkModelPropertyType[], readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();

// 1. To see if the service parameter is a client parameter.
const operationLocation = getLocationOfOperation(operation)!;
let clientParams = context.__clientToParameters.get(operationLocation);
if (!clientParams) {
Expand All @@ -535,8 +536,11 @@ export function getCorrespondingMethodParams(
twoParamsEquivalent(context, x.__raw, serviceParam.__raw) ||
(x.__raw?.kind === "ModelProperty" && getParamAlias(context, x.__raw) === serviceParam.name),
);
if (correspondingClientParams.length > 0) return diagnostics.wrap(correspondingClientParams);
if (correspondingClientParams.length > 0) {
return diagnostics.wrap(correspondingClientParams);
}

// 2. To see if the service parameter is api version parameter that has been elevated to client.
if (serviceParam.isApiVersionParam && serviceParam.onClient) {
const existingApiVersion = clientParams?.find((x) => isApiVersion(context, x));
if (!existingApiVersion) {
Expand All @@ -552,8 +556,10 @@ export function getCorrespondingMethodParams(
);
return diagnostics.wrap([]);
}
return diagnostics.wrap(clientParams.filter((x) => isApiVersion(context, x)));
return diagnostics.wrap(existingApiVersion ? [existingApiVersion] : []);
}

// 3. To see if the service parameter is subscription parameter that has been elevated to client (only for arm service).
qiaozha marked this conversation as resolved.
Show resolved Hide resolved
if (isSubscriptionId(context, serviceParam)) {
const subId = clientParams.find((x) => isSubscriptionId(context, x));
if (!subId) {
Expand All @@ -572,13 +578,13 @@ export function getCorrespondingMethodParams(
return diagnostics.wrap(subId ? [subId] : []);
}

// to see if the service parameter is a method parameter or a property of a method parameter
// 4. To see if the service parameter is a method parameter or a property of a method parameter.
const directMapping = findMapping(methodParameters, serviceParam);
if (directMapping) {
return diagnostics.wrap([directMapping]);
}

// to see if all the property of service parameter could be mapped to a method parameter or a property of a method parameter
// 5. To see if all the property of the service parameter could be mapped to a method parameter or a property of a method parameter.
if (serviceParam.kind === "body" && serviceParam.type.kind === "model") {
const retVal = [];
for (const serviceParamProp of serviceParam.type.properties) {
Expand All @@ -592,6 +598,7 @@ export function getCorrespondingMethodParams(
}
}

// If mapping could not be found, TCGC will report error since we can't generate the client code without this mapping.
diagnostics.add(
createDiagnostic({
code: "no-corresponding-method-param",
Expand All @@ -605,6 +612,12 @@ export function getCorrespondingMethodParams(
return diagnostics.wrap([]);
}

/**
* Try to find the mapping of a service paramete or a property of a service parameter to a method parameter or a property of a method parameter.
* @param methodParameters
* @param serviceParam
* @returns
*/
function findMapping(
methodParameters: SdkModelPropertyType[],
serviceParam: SdkHttpParameter | SdkModelPropertyType,
Expand All @@ -613,15 +626,15 @@ function findMapping(
const visited: Set<SdkModelType> = new Set();
while (queue.length > 0) {
const methodParam = queue.shift()!;
// http operation parameter/body parameter/property of body parameter could either from an operation parameter directly or from a property of an operation parameter
// HTTP operation parameter/body parameter/property of body parameter could either from an operation parameter directly or from a property of an operation parameter.
if (
methodParam.__raw &&
serviceParam.__raw &&
findRootSourceProperty(methodParam.__raw) === findRootSourceProperty(serviceParam.__raw)
) {
return methodParam;
}
// this following two hard code mapping is for the case that TCGC help to add content type and accept header is not exist
// Two following two hard coded mapping is for the case that TCGC help to add content type and accept header when not exists.
if (
serviceParam.kind === "header" &&
serviceParam.serializedName === "Content-Type" &&
Expand All @@ -636,6 +649,7 @@ function findMapping(
) {
return methodParam;
}
// BFS to find the mapping.
if (methodParam.type.kind === "model" && !visited.has(methodParam.type)) {
visited.add(methodParam.type);
let current: SdkModelType | undefined = methodParam.type;
Expand Down
10 changes: 10 additions & 0 deletions packages/typespec-client-generator-core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ export interface TCGCContext {
previewStringRegex: RegExp;
disableUsageAccessPropagationToBase: boolean;
__pagedResultSet: Set<SdkType>;
__paramMapping: Map<
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
SdkMethodParameter,
(
| SdkPathParameter
| SdkQueryParameter
| SdkHeaderParameter
| SdkCookieParameter
| SdkBodyParameter
)[]
>;
}

export interface SdkContext<
Expand Down
67 changes: 67 additions & 0 deletions packages/typespec-client-generator-core/src/public-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,17 @@ import {
listOperationsInOperationGroup,
} from "./decorators.js";
import {
SdkBodyParameter,
SdkClientType,
SdkCookieParameter,
SdkHeaderParameter,
SdkHttpOperation,
SdkHttpOperationExample,
SdkModelPropertyType,
SdkModelType,
SdkPathParameter,
SdkQueryParameter,
SdkServiceMethod,
SdkServiceOperation,
SdkType,
TCGCContext,
Expand Down Expand Up @@ -698,3 +707,61 @@ export function isAzureCoreModel(t: SdkType): boolean {
export function isPagedResultModel(context: TCGCContext, t: SdkType): boolean {
return context.__pagedResultSet.has(t);
}

/**
* Find corresponding http parameter list for a client initialization or service method parameter or the property of that parameter.
*
* @param method
* @param param
* @returns
*/
export function getHttpOperationParameter(
qiaozha marked this conversation as resolved.
Show resolved Hide resolved
method: SdkServiceMethod<SdkHttpOperation>,
param: SdkModelPropertyType,
):
| SdkPathParameter
| SdkQueryParameter
| SdkHeaderParameter
| SdkCookieParameter
| SdkBodyParameter
| undefined {
const operation = method.operation;
const queue: SdkModelPropertyType[] = [param];
const visited: Set<SdkModelType> = new Set();
// BFS to find the corresponding http parameter.
// An http parameter will be mapped to a method/client parameter, several method/client parameters (body spread case), or one property of a method property (metadata on property case).
// So, when we try to find which http parameter a method parameter corresponds to, we compare the `correspondingMethodParams` list directly.
// When we try to find which http parameter a property corresponds to, we need to consider the following cases:
// 1. The property itself is an http parameter.
// 2. The property is a model, and the properties of the model are http parameters, recursively.
// 3. The property is a model, and the base model of the model has http parameters, recursively.
// When we find one, we return directly since a method/client parameter or property could only be used in one http parameter.
tadelesh marked this conversation as resolved.
Show resolved Hide resolved
while (queue.length > 0) {
const param = queue.pop();
for (const p of operation.parameters) {
for (const cp of p.correspondingMethodParams) {
if (cp === param) {
return p;
}
}
}
if (operation.bodyParam) {
archerzz marked this conversation as resolved.
Show resolved Hide resolved
for (const cp of operation.bodyParam.correspondingMethodParams) {
if (cp === param) {
return operation.bodyParam;
}
}
}
if (param?.kind === "property" && param?.type.kind === "model" && !visited.has(param.type)) {
visited.add(param.type);
let current: SdkModelType | undefined = param.type;
while (current) {
for (const prop of param.type.properties) {
queue.push(prop);
}
current = current.baseModel;
qiaozha marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
return undefined;
}
Loading
Loading