Skip to content

Commit

Permalink
[tcgc] add getHttpOperationParameter helper function (#2010)
Browse files Browse the repository at this point in the history
resolve: #1441
  • Loading branch information
tadelesh authored Jan 6, 2025
1 parent 532d795 commit a7cf311
Show file tree
Hide file tree
Showing 5 changed files with 593 additions and 7 deletions.
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
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).
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
54 changes: 54 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 {
SdkBodyModelPropertyType,
SdkBodyParameter,
SdkClientType,
SdkCookieParameter,
SdkHeaderParameter,
SdkHttpOperation,
SdkHttpOperationExample,
SdkModelPropertyType,
SdkPathParameter,
SdkQueryParameter,
SdkServiceMethod,
SdkServiceOperation,
SdkType,
TCGCContext,
Expand Down Expand Up @@ -698,3 +707,48 @@ 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 parameter, a service method parameter or a property of a service method parameter.
*
* @param method
* @param param
* @returns
*/
export function getHttpOperationParameter(
method: SdkServiceMethod<SdkHttpOperation>,
param: SdkModelPropertyType,
):
| SdkPathParameter
| SdkQueryParameter
| SdkHeaderParameter
| SdkCookieParameter
| SdkBodyParameter
| SdkBodyModelPropertyType
| undefined {
const operation = method.operation;
// 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 parameter or property corresponds to, we compare the `correspondingMethodParams` list directly.
// If a method parameter is spread case, then we need to find the cooresponding http body parameter's property.
for (const p of operation.parameters) {
for (const cp of p.correspondingMethodParams) {
if (cp === param) {
return p;
}
}
}
if (operation.bodyParam) {
for (const cp of operation.bodyParam.correspondingMethodParams) {
if (cp === param) {
if (operation.bodyParam.type.kind === "model" && operation.bodyParam.type !== param.type) {
return operation.bodyParam.type.properties.find(
(p) => p.kind === "property" && p.name === param.name,
) as SdkBodyModelPropertyType | undefined;
}
return operation.bodyParam;
}
}
}
return undefined;
}
Loading

0 comments on commit a7cf311

Please sign in to comment.