in packages/typespec-go/src/tcgcadapter/clients.ts [294:463]
private adaptMethodParameters(sdkMethod: tcgc.SdkServiceMethod<tcgc.SdkHttpOperation>, method: go.Method | go.NextPageMethod): Map<tcgc.SdkHttpParameter, Array<go.Parameter>> {
const paramMapping = new Map<tcgc.SdkHttpParameter, Array<go.Parameter>>();
let optionalGroup: go.ParameterGroup | undefined;
if (go.isMethod(method)) {
// NextPageMethods don't have optional params
optionalGroup = method.optionalParamsGroup;
if (go.isLROMethod(method)) {
optionalGroup.params.push(new go.ResumeTokenParameter());
}
}
// stuff all of the operation parameters into one array for easy traversal
type OperationParamType = tcgc.SdkBodyParameter | tcgc.SdkHeaderParameter | tcgc.SdkPathParameter | tcgc.SdkQueryParameter | tcgc.SdkCookieParameter;
const allOpParams = new Array<OperationParamType>();
allOpParams.push(...sdkMethod.operation.parameters);
if (sdkMethod.operation.bodyParam) {
allOpParams.push(sdkMethod.operation.bodyParam);
}
// we must enumerate parameters, not operation.parameters, as it
// contains the params in tsp order as well as any spread params.
for (const param of sdkMethod.parameters) {
// we need to translate from the method param to its underlying operation param.
// most params have a one-to-one mapping. however, for spread params, there will
// be a many-to-one mapping. i.e. multiple params will map to the same underlying
// operation param. each param corresponds to a field within the operation param.
let opParam = values(allOpParams).where((opParam: OperationParamType) => {
return values(opParam.correspondingMethodParams).where((methodParam: tcgc.SdkModelPropertyType) => {
if (param.type.kind === 'model') {
for (const property of param.type.properties) {
if (property === methodParam) {
return true;
}
}
}
return methodParam === param;
}).any();
}).first();
// special handling for constants that used in path, this will not be in operation parameters since it has been resolved in the url
if (!opParam && param.type.kind === 'constant') {
continue;
}
// special handling for `@bodyRoot`/`@body` on model param's property
if (!opParam && param.type.kind === 'model') {
for (const property of param.type.properties) {
opParam = values(allOpParams).where((opParam: OperationParamType) => {
return values(opParam.correspondingMethodParams).where((methodParam: tcgc.SdkModelPropertyType) => {
return methodParam === property;
}).any();
}).first();
if (opParam) {
break;
}
}
}
if (!opParam) {
throw new AdapterError('InternalError', `didn't find operation parameter for method ${sdkMethod.name} parameter ${param.name}`, sdkMethod.__raw?.node ?? NoTarget);
}
if (opParam.kind === 'header' && opParam.serializedName.match(/^content-type$/i) && param.type.kind === 'constant') {
// if the body param is optional, then the content-type param is also optional.
// for optional constants, this has the side effect of the param being treated like
// a flag which isn't what we want. so, we mark it as required. we ONLY do this
// if the content-type is a constant (i.e. literal value).
// the content-type will be conditionally set based on the requiredness of the body.
opParam.optional = false;
}
let adaptedParam: go.Parameter;
if (opParam.kind === 'body' && opParam.type.kind === 'model' && opParam.type.kind !== param.type.kind) {
const paramKind = this.adaptParameterKind(param);
const byVal = isTypePassedByValue(param.type);
const contentType = this.adaptContentType(opParam.defaultContentType);
const getSerializedNameFromProperty = function(property: tcgc.SdkBodyModelPropertyType): string | undefined {
if (contentType === 'JSON') {
return property.serializationOptions.json?.name;
}
if (contentType === 'XML') {
return property.serializationOptions.xml?.name;
}
if (contentType === 'binary') {
return property.serializationOptions.multipart?.name;
}
return undefined;
};
switch (contentType) {
case 'JSON':
case 'XML': {
// find the corresponding field within the model param so we can get the serialized name
let serializedName: string | undefined;
for (const property of opParam.type.properties) {
if (property.name === param.name) {
serializedName = getSerializedNameFromProperty(<tcgc.SdkBodyModelPropertyType>property);
break;
}
}
if (!serializedName) {
throw new AdapterError('InternalError', `didn't find body model property for spread parameter ${param.name}`, param.__raw?.node ?? NoTarget);
}
adaptedParam = new go.PartialBodyParameter(param.name, serializedName, contentType, this.ta.getPossibleType(param.type, true, true), paramKind, byVal);
break;
}
case 'binary':
if (opParam.defaultContentType.match(/multipart/i)) {
adaptedParam = new go.MultipartFormBodyParameter(param.name, this.ta.getReadSeekCloser(false), paramKind, byVal);
} else {
adaptedParam = new go.BodyParameter(param.name, contentType, `"${opParam.defaultContentType}"`, this.ta.getReadSeekCloser(false), paramKind, byVal);
}
break;
default:
throw new AdapterError('UnsupportedTsp', `unsupported spread param content type ${contentType}`, opParam.__raw?.node ?? NoTarget);
}
} else {
adaptedParam = this.adaptMethodParameter(opParam);
}
adaptedParam.docs.summary = param.summary;
adaptedParam.docs.description = param.doc;
method.parameters.push(adaptedParam);
if (!paramMapping.has(opParam)) {
paramMapping.set(opParam, new Array<go.Parameter>());
}
paramMapping.get(opParam)?.push(adaptedParam);
if (adaptedParam.kind !== 'required' && adaptedParam.kind !== 'literal') {
// add optional method param to the options param group
if (!optionalGroup) {
throw new AdapterError('InternalError', `optional parameter ${param.name} has no optional parameter group`, param.__raw?.node ?? NoTarget);
}
adaptedParam.group = optionalGroup;
optionalGroup.params.push(adaptedParam);
}
}
// client params aren't included in method.parameters so
// look for them in the operation parameters.
for (const opParam of allOpParams) {
if (opParam.onClient) {
const adaptedParam = this.adaptMethodParameter(opParam);
adaptedParam.docs.summary = opParam.summary;
adaptedParam.docs.description = opParam.doc;
method.parameters.unshift(adaptedParam);
if (!paramMapping.has(opParam)) {
paramMapping.set(opParam, new Array<go.Parameter>());
}
paramMapping.get(opParam)?.push(adaptedParam);
// if the adapted client param is a literal then don't add it to
// the array of client params as it's not a formal parameter.
if (go.isLiteralParameter(adaptedParam)) {
continue;
}
// we must check via param name and not reference equality. this is because a client param
// can be used in multiple ways. e.g. a client param "apiVersion" that's used as a path param
// in one method and a query param in another.
if (!method.client.parameters.find((v: go.Parameter) => {
return v.name === adaptedParam.name;
})) {
method.client.parameters.push(adaptedParam);
}
}
}
return paramMapping;
}