Skip to content

Commit f380f82

Browse files
authored
Use method param data as source of truth (#879)
* Use method param data as source of truth Only use values from the operation param data that aren't captured in the method param data (e.g. header/path/query type etc). Update KV tsp to preserve existing behavior. * ignore spelling
1 parent cf52cd5 commit f380f82

6 files changed

Lines changed: 69 additions & 48 deletions

File tree

packages/typespec-rust/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release History
22

3+
## 0.36.0 (2026-02-26)
4+
5+
### Breaking Changes
6+
7+
* Fixed some edge cases where a method parameter's optionality wasn't correctly handled.
8+
39
## 0.35.0 (2026-02-13)
410

511
### Breaking Changes

packages/typespec-rust/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azure-tools/typespec-rust",
3-
"version": "0.35.0",
3+
"version": "0.36.0",
44
"description": "TypeSpec emitter for Rust SDKs",
55
"type": "module",
66
"packageManager": "pnpm@10.10.0",

packages/typespec-rust/src/tcgcadapter/adapter.ts

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
// cspell: ignore addl responseheader subclients lropaging
6+
// cspell: ignore addl requiredness responseheader subclients lropaging
77

88
import * as tsp from '@typespec/compiler';
99
import * as http from '@typespec/http';
@@ -1593,7 +1593,7 @@ export class Adapter {
15931593
) {
15941594
adaptedParam = this.adaptMethodSpreadParameter(param, this.adaptPayloadFormat(opParam.defaultContentType), opParam.type);
15951595
} else {
1596-
adaptedParam = this.adaptMethodParameter(opParam);
1596+
adaptedParam = this.adaptMethodParameter(opParam, param);
15971597
}
15981598

15991599
switch (adaptedParam.kind) {
@@ -2104,12 +2104,17 @@ export class Adapter {
21042104
}
21052105

21062106
/**
2107-
* converts a tcgc operation parameter into a Rust method parameter
2107+
* converts a tcgc operation parameter into a Rust method parameter.
2108+
* note that when methodParam is present, we must use all applicable
2109+
* values from methodParam as the source of truth. e.g. when overriding
2110+
* a method to make an optional param required, the requiredness will
2111+
* be reflected in the method param, _not_ the operation param.
21082112
*
2109-
* @param param the tcgc operation parameter to convert
2113+
* @param opParam the tcgc operation parameter to convert
2114+
* @param methodParam the tcgc method parameter associated with opParam
21102115
* @returns a Rust method parameter
21112116
*/
2112-
private adaptMethodParameter(param: tcgc.SdkHttpParameter): rust.MethodParameter {
2117+
private adaptMethodParameter(opParam: tcgc.SdkHttpParameter, methodParam?: tcgc.SdkMethodParameter): rust.MethodParameter {
21132118
/**
21142119
* used to create keys for this.clientMethodParams
21152120
* @param param the param for which to create a key
@@ -2121,11 +2126,11 @@ export class Adapter {
21212126
return `${param.name}-${param.kind}`;
21222127
};
21232128

2124-
const paramLoc = param.onClient ? 'client' : 'method';
2129+
const paramLoc = opParam.onClient ? 'client' : 'method';
21252130

21262131
// if this is a client method param, check if we've already adapted it
21272132
if (paramLoc === 'client') {
2128-
const clientMethodParam = this.clientMethodParams.get(getClientParamsKey(param));
2133+
const clientMethodParam = this.clientMethodParams.get(getClientParamsKey(opParam));
21292134
if (clientMethodParam) {
21302135
return clientMethodParam;
21312136
}
@@ -2143,59 +2148,60 @@ export class Adapter {
21432148
return param.name;
21442149
};
21452150

2146-
const paramName = naming.getEscapedReservedName(utils.snakeCaseName(getCorrespondingClientParamName(param)), 'param', reservedParams);
2147-
let paramType = this.getType(param.type);
2151+
const paramName = naming.getEscapedReservedName(utils.snakeCaseName(getCorrespondingClientParamName(opParam)), 'param', reservedParams);
2152+
const paramOptional = methodParam ? methodParam.optional : opParam.optional;
2153+
let paramType = this.getType(methodParam ? methodParam.type : opParam.type);
21482154

21492155
// for required header/path/query method string params, we might emit them as borrowed types
2150-
if (!param.optional && !param.onClient && (param.kind === 'header' || param.kind === 'path' || param.kind === 'query')) {
2151-
const borrowedType = this.canBorrowMethodParam(paramType, param.kind);
2156+
if (!paramOptional && paramLoc !== 'client' && (opParam.kind === 'header' || opParam.kind === 'path' || opParam.kind === 'query')) {
2157+
const borrowedType = this.canBorrowMethodParam(paramType, opParam.kind);
21522158
if (borrowedType) {
21532159
paramType = borrowedType;
21542160
}
21552161
}
21562162

21572163
let adaptedParam: rust.MethodParameter;
2158-
switch (param.kind) {
2164+
switch (opParam.kind) {
21592165
case 'body': {
21602166
let requestType: rust.Bytes | rust.Payload;
2161-
if (param.type.kind === 'bytes' && param.type.encode === 'bytes') {
2167+
if (opParam.type.kind === 'bytes' && opParam.type.encode === 'bytes') {
21622168
// bytes encoding indicates a streaming binary request
21632169
requestType = new rust.Bytes(this.crate);
21642170
} else {
2165-
requestType = new rust.Payload(this.typeToWireType(paramType), this.adaptPayloadFormat(param.defaultContentType));
2171+
requestType = new rust.Payload(this.typeToWireType(paramType), this.adaptPayloadFormat(opParam.defaultContentType));
21662172
}
2167-
const requestFormatType = this.adaptPayloadFormatType(param.defaultContentType);
2168-
adaptedParam = new rust.BodyParameter(paramName, paramLoc, param.optional, new rust.RequestContent(this.crate, requestType, requestFormatType));
2173+
const requestFormatType = this.adaptPayloadFormatType(opParam.defaultContentType);
2174+
adaptedParam = new rust.BodyParameter(paramName, paramLoc, paramOptional, new rust.RequestContent(this.crate, requestType, requestFormatType));
21692175
break;
21702176
}
21712177
case 'cookie':
21722178
// TODO: https://github.com/Azure/typespec-rust/issues/192
2173-
throw new AdapterError('UnsupportedTsp', 'cookie parameters are not supported', param.__raw?.node);
2179+
throw new AdapterError('UnsupportedTsp', 'cookie parameters are not supported', opParam.__raw?.node);
21742180
case 'header':
2175-
if (param.collectionFormat) {
2181+
if (opParam.collectionFormat) {
21762182
if (paramType.kind !== 'Vec' && !isRefSlice(paramType)) {
2177-
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for HeaderCollectionParameter`, param.__raw?.node);
2183+
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for HeaderCollectionParameter`, opParam.__raw?.node);
21782184
}
21792185
let format: rust.CollectionFormat;
2180-
switch (param.collectionFormat) {
2186+
switch (opParam.collectionFormat) {
21812187
case 'csv':
21822188
case 'simple':
21832189
format = 'csv';
21842190
break;
21852191
case 'pipes':
21862192
case 'ssv':
21872193
case 'tsv':
2188-
format = param.collectionFormat;
2194+
format = opParam.collectionFormat;
21892195
break;
21902196
default:
2191-
throw new AdapterError('InternalError', `unexpected format ${param.collectionFormat} for HeaderCollectionParameter`, param.__raw?.node);
2197+
throw new AdapterError('InternalError', `unexpected format ${opParam.collectionFormat} for HeaderCollectionParameter`, opParam.__raw?.node);
21922198
}
2193-
adaptedParam = new rust.HeaderCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, format);
2194-
} else if (param.serializedName === 'x-ms-meta') {
2199+
adaptedParam = new rust.HeaderCollectionParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, format);
2200+
} else if (opParam.serializedName === 'x-ms-meta') {
21952201
if (paramType.kind !== 'hashmap' && !isRefHashMap(paramType)) {
2196-
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for header ${param.serializedName}`, param.__raw?.node);
2202+
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for header ${opParam.serializedName}`, opParam.__raw?.node);
21972203
}
2198-
adaptedParam = new rust.HeaderHashMapParameter(paramName, param.serializedName, paramLoc, param.optional, paramType);
2204+
adaptedParam = new rust.HeaderHashMapParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType);
21992205
} else {
22002206
paramType = this.typeToWireType(paramType);
22012207
switch (paramType.kind) {
@@ -2205,70 +2211,70 @@ export class Adapter {
22052211
case 'slice':
22062212
case 'str':
22072213
case 'Vec':
2208-
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for scalar header ${param.serializedName}`, param.__raw?.node);
2214+
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for scalar header ${opParam.serializedName}`, opParam.__raw?.node);
22092215
}
2210-
adaptedParam = new rust.HeaderScalarParameter(paramName, param.serializedName, paramLoc, param.optional, paramType);
2211-
adaptedParam.isApiVersion = param.isApiVersionParam;
2216+
adaptedParam = new rust.HeaderScalarParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType);
2217+
adaptedParam.isApiVersion = opParam.isApiVersionParam;
22122218
}
22132219
break;
22142220
case 'path': {
22152221
paramType = this.typeToWireType(paramType);
22162222
let style: rust.ParameterStyle = 'simple';
2217-
const tspStyleString = (param.style as string);
2223+
const tspStyleString = (opParam.style as string);
22182224
if (!['simple', 'path', 'label', 'matrix'].includes(tspStyleString)) {
2219-
throw new AdapterError('InternalError', `unsupported style ${tspStyleString} for parameter ${param.serializedName}`, param.__raw?.node);
2225+
throw new AdapterError('InternalError', `unsupported style ${tspStyleString} for parameter ${opParam.serializedName}`, opParam.__raw?.node);
22202226
} else {
22212227
style = tspStyleString as rust.ParameterStyle;
22222228
}
22232229

22242230
if (isRefSlice(paramType)) {
2225-
adaptedParam = new rust.PathCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, param.allowReserved, style, param.explode);
2231+
adaptedParam = new rust.PathCollectionParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, opParam.allowReserved, style, opParam.explode);
22262232
} else if (paramType.kind === 'hashmap' || isRefHashMap(paramType)) {
2227-
adaptedParam = new rust.PathHashMapParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, param.allowReserved, style, param.explode);
2233+
adaptedParam = new rust.PathHashMapParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, opParam.allowReserved, style, opParam.explode);
22282234
} else {
22292235
switch (paramType.kind) {
22302236
case 'jsonValue':
22312237
case 'model':
22322238
case 'slice':
22332239
case 'str':
22342240
case 'Vec':
2235-
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for scalar path ${param.serializedName}`, param.__raw?.node);
2241+
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for scalar path ${opParam.serializedName}`, opParam.__raw?.node);
22362242
}
22372243

2238-
adaptedParam = new rust.PathScalarParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, param.allowReserved, style);
2244+
adaptedParam = new rust.PathScalarParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, opParam.allowReserved, style);
22392245
}
22402246
} break;
22412247
case 'query':
22422248
paramType = this.typeToWireType(paramType);
22432249
if (paramType.kind === 'Vec' || isRefSlice(paramType)) {
2244-
let format: rust.ExtendedCollectionFormat = param.explode ? 'multi' : 'csv';
2245-
if (param.collectionFormat) {
2246-
format = param.collectionFormat === 'simple' ? 'csv' : (param.collectionFormat === 'form' ? 'multi' : param.collectionFormat);
2250+
let format: rust.ExtendedCollectionFormat = opParam.explode ? 'multi' : 'csv';
2251+
if (opParam.collectionFormat) {
2252+
format = opParam.collectionFormat === 'simple' ? 'csv' : (opParam.collectionFormat === 'form' ? 'multi' : opParam.collectionFormat);
22472253
}
22482254
// TODO: hard-coded encoding setting, https://github.com/Azure/typespec-azure/issues/1314
2249-
adaptedParam = new rust.QueryCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true, format);
2255+
adaptedParam = new rust.QueryCollectionParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, true, format);
22502256
} else if (paramType.kind === 'hashmap' || isRefHashMap(paramType)) {
22512257
// TODO: hard-coded encoding setting, https://github.com/Azure/typespec-azure/issues/1314
2252-
adaptedParam = new rust.QueryHashMapParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true, param.explode);
2258+
adaptedParam = new rust.QueryHashMapParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, true, opParam.explode);
22532259
} else {
22542260
switch (paramType.kind) {
22552261
case 'jsonValue':
22562262
case 'model':
22572263
case 'slice':
22582264
case 'str':
2259-
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for scalar query ${param.serializedName}`, param.__raw?.node);
2265+
throw new AdapterError('InternalError', `unexpected kind ${paramType.kind} for scalar query ${opParam.serializedName}`, opParam.__raw?.node);
22602266
}
22612267
// TODO: hard-coded encoding setting, https://github.com/Azure/typespec-azure/issues/1314
2262-
adaptedParam = new rust.QueryScalarParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true);
2263-
adaptedParam.isApiVersion = param.isApiVersionParam;
2268+
adaptedParam = new rust.QueryScalarParameter(paramName, opParam.serializedName, paramLoc, paramOptional, paramType, true);
2269+
adaptedParam.isApiVersion = opParam.isApiVersionParam;
22642270
}
22652271
break;
22662272
}
22672273

2268-
adaptedParam.docs = this.adaptDocs(param.summary, param.doc);
2274+
adaptedParam.docs = this.adaptDocs(methodParam ? methodParam.summary : opParam.summary, methodParam ? methodParam.doc : opParam.doc);
22692275

22702276
if (paramLoc === 'client') {
2271-
this.clientMethodParams.set(getClientParamsKey(param), adaptedParam);
2277+
this.clientMethodParams.set(getClientParamsKey(opParam), adaptedParam);
22722278
}
22732279

22742280
return adaptedParam;

packages/typespec-rust/test/sdk/keyvault_secrets/src/generated/clients/secret_client.rs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/typespec-rust/test/sdk/keyvault_secrets/src/generated/models/method_options.rs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/typespec-rust/test/tsp/Security.KeyVault.Secrets/client.tsp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ op getSecret is KeyVaultOperation<
7676
SecretBundle
7777
>;
7878

79-
@@override(KeyVault.updateSecret, updateSecret);
80-
@@override(KeyVault.getSecret, getSecret);
79+
@@override(KeyVault.updateSecret, updateSecret, "!rust");
80+
@@override(KeyVault.getSecret, getSecret, "!rust");
8181

8282
// go renames
8383

0 commit comments

Comments
 (0)