@azure-tools/typespec-rust 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,7 @@ import * as naming from './naming.js';
11
11
  import * as shared from '../shared/shared.js';
12
12
  import * as tcgc from '@azure-tools/typespec-client-generator-core';
13
13
  import * as rust from '../codemodel/index.js';
14
+ import { FinalStateValue } from "@azure-tools/typespec-azure-core";
14
15
  /**
15
16
  * AdapterError is thrown when the emitter fails to convert part of the tcgc code
16
17
  * model to the emitter code model. this could be due to the emitter not supporting
@@ -42,7 +43,7 @@ export class Adapter {
42
43
  // same as TypeSpec.Xml.@name. however, it's filtered out by default
43
44
  // so we need to add it to the allow list of decorators
44
45
  const ctx = await tcgc.createSdkContext(context, '@azure-tools/typespec-rust', {
45
- additionalDecorators: ['TypeSpec\\.@encodedName', '@clientName'],
46
+ additionalDecorators: ['TypeSpec\\.@encodedName', '@clientName', 'Azure.ClientGenerator.Core.@deserializeEmptyStringAsNull'],
46
47
  disableUsageAccessPropagationToBase: true,
47
48
  });
48
49
  context.program.reportDiagnostics(ctx.diagnostics);
@@ -78,7 +79,7 @@ export class Adapter {
78
79
  this.crate.addDependency(new rust.CrateDependency('serde'));
79
80
  }
80
81
  if (this.crate.clients.length > 0 || this.crate.enums.length > 0 || this.crate.models.length > 0 || this.crate.unions.length > 0) {
81
- this.crate.addDependency(new rust.CrateDependency('typespec_client_core', ['derive']));
82
+ this.crate.addDependency(new rust.CrateDependency('azure_core'));
82
83
  }
83
84
  this.crate.sortContent();
84
85
  return this.crate;
@@ -120,8 +121,6 @@ export class Adapter {
120
121
  }
121
122
  const rustModel = this.getModel(model);
122
123
  this.crate.models.push(rustModel);
123
- // presence of models requires the derive feature
124
- this.crate.addDependency(new rust.CrateDependency('typespec_client_core', ['derive']));
125
124
  }
126
125
  }
127
126
  /**
@@ -131,13 +130,36 @@ export class Adapter {
131
130
  * @returns a Rust enum
132
131
  */
133
132
  getEnum(sdkEnum) {
134
- var _a;
133
+ var _a, _b;
135
134
  const enumName = codegen.deconstruct(sdkEnum.name).map((each) => codegen.capitalize(each)).join('');
136
135
  let rustEnum = this.types.get(enumName);
137
136
  if (rustEnum) {
138
137
  return rustEnum;
139
138
  }
140
- rustEnum = new rust.Enum(enumName, sdkEnum.access === 'public', !sdkEnum.isFixed);
139
+ let enumType;
140
+ switch (sdkEnum.valueType.kind) {
141
+ case 'float':
142
+ case 'float32':
143
+ enumType = 'f32';
144
+ break;
145
+ case 'float64':
146
+ enumType = 'f64';
147
+ break;
148
+ case 'int8':
149
+ case 'int16':
150
+ case 'int32':
151
+ enumType = 'i32';
152
+ break;
153
+ case 'int64':
154
+ enumType = 'i64';
155
+ break;
156
+ case 'string':
157
+ enumType = 'String';
158
+ break;
159
+ default:
160
+ throw new AdapterError('UnsupportedTsp', `unsupported enum underlying type ${sdkEnum.valueType.kind}`, (_a = sdkEnum.__raw) === null || _a === void 0 ? void 0 : _a.node);
161
+ }
162
+ rustEnum = new rust.Enum(enumName, sdkEnum.access === 'public', !sdkEnum.isFixed, enumType);
141
163
  rustEnum.docs = this.adaptDocs(sdkEnum.summary, sdkEnum.doc);
142
164
  this.types.set(enumName, rustEnum);
143
165
  const rustEnumNameToSdkEnumName = new Map();
@@ -145,7 +167,7 @@ export class Adapter {
145
167
  const enumValueName = naming.fixUpEnumValueName(value);
146
168
  const existingMapping = rustEnumNameToSdkEnumName.get(enumValueName);
147
169
  if (existingMapping) {
148
- throw new AdapterError('NameCollision', `enum values ${value.name} and ${existingMapping} coalesce into the same name ${enumValueName}`, (_a = sdkEnum.__raw) === null || _a === void 0 ? void 0 : _a.node);
170
+ throw new AdapterError('NameCollision', `enum values ${value.name} and ${existingMapping} coalesce into the same name ${enumValueName}`, (_b = sdkEnum.__raw) === null || _b === void 0 ? void 0 : _b.node);
149
171
  }
150
172
  rustEnumNameToSdkEnumName.set(enumValueName, value.name);
151
173
  const rustEnumValue = new rust.EnumValue(enumValueName, rustEnum, value.value);
@@ -177,14 +199,16 @@ export class Adapter {
177
199
  *
178
200
  * @param model the tcgc model to convert
179
201
  * @param stack is a stack of model type names used to detect recursive type definitions
202
+ * @param modelName optional parameter to override model name
180
203
  * @returns a Rust model
181
204
  */
182
- getModel(model, stack) {
205
+ getModel(model, stack, modelName) {
183
206
  var _a, _b, _c, _d;
184
- if (model.name.length === 0) {
207
+ modelName = modelName !== null && modelName !== void 0 ? modelName : model.name;
208
+ if (modelName.length === 0) {
185
209
  throw new AdapterError('InternalError', 'unnamed model', (_a = model.__raw) === null || _a === void 0 ? void 0 : _a.node); // TODO: this might no longer be an issue
186
210
  }
187
- const modelName = codegen.capitalize(model.name);
211
+ modelName = codegen.capitalize(modelName);
188
212
  let rustModel = this.types.get(modelName);
189
213
  if (rustModel) {
190
214
  return rustModel;
@@ -409,6 +433,9 @@ export class Adapter {
409
433
  if (!this.fieldsMap.get(property)) {
410
434
  this.fieldsMap.set(property, modelField);
411
435
  }
436
+ if (property.decorators.find((decorator) => decorator.name === 'Azure.ClientGenerator.Core.@deserializeEmptyStringAsNull') !== undefined) {
437
+ modelField.flags |= rust.ModelFieldFlags.DeserializeEmptyStringAsNone;
438
+ }
412
439
  return modelField;
413
440
  }
414
441
  /**
@@ -1106,7 +1133,7 @@ export class Adapter {
1106
1133
  * @param rustClient the client to which the method belongs
1107
1134
  */
1108
1135
  adaptMethod(method, rustClient) {
1109
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
1136
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
1110
1137
  let srcMethodName = method.name;
1111
1138
  if (method.kind === 'paging' && !srcMethodName.match(/^list/i)) {
1112
1139
  const chunks = codegen.deconstruct(srcMethodName);
@@ -1138,7 +1165,14 @@ export class Adapter {
1138
1165
  const methodOptionsStruct = new rust.Struct(`${rustClient.name}${codegen.pascalCase(srcMethodName, false)}Options`, 'pub');
1139
1166
  methodOptionsStruct.lifetime = optionsLifetime;
1140
1167
  methodOptionsStruct.docs.summary = `Options to be passed to ${this.asDocLink(`${rustClient.name}::${methodName}()`, `crate::generated::clients::${rustClient.name}::${methodName}()`)}`;
1141
- const clientMethodOptions = new rust.ExternalType(this.crate, 'ClientMethodOptions', 'azure_core::http');
1168
+ let clientMethodOptions;
1169
+ switch (method.kind) {
1170
+ case 'paging':
1171
+ clientMethodOptions = new rust.ExternalType(this.crate, 'PagerOptions', 'azure_core::http::pager');
1172
+ break;
1173
+ default:
1174
+ clientMethodOptions = new rust.ExternalType(this.crate, 'ClientMethodOptions', 'azure_core::http');
1175
+ }
1142
1176
  clientMethodOptions.lifetime = optionsLifetime;
1143
1177
  const methodOptionsField = new rust.StructField('method_options', 'pub', clientMethodOptions);
1144
1178
  methodOptionsField.docs.summary = 'Allows customization of the method call.';
@@ -1161,10 +1195,29 @@ export class Adapter {
1161
1195
  rustMethod = new rust.PageableMethod(methodName, languageIndependentName, rustClient, pub, methodOptions, httpMethod, method.operation.path);
1162
1196
  break;
1163
1197
  case 'lro':
1164
- rustMethod = new rust.LroMethod(methodName, languageIndependentName, rustClient, pub, methodOptions, httpMethod, method.operation.path);
1198
+ {
1199
+ let lroFinalResultStrategy = new rust.LroFinalResultStrategyOriginalUri();
1200
+ if (method.lroMetadata.finalStateVia !== FinalStateValue.originalUri) {
1201
+ switch (method.lroMetadata.finalStateVia) {
1202
+ case FinalStateValue.operationLocation:
1203
+ lroFinalResultStrategy = new rust.LroFinalResultStrategyHeader('operation-location');
1204
+ break;
1205
+ case FinalStateValue.location:
1206
+ lroFinalResultStrategy = new rust.LroFinalResultStrategyHeader('location');
1207
+ break;
1208
+ case FinalStateValue.azureAsyncOperation:
1209
+ lroFinalResultStrategy = new rust.LroFinalResultStrategyHeader('azure-asyncoperation');
1210
+ break;
1211
+ default:
1212
+ throw new AdapterError('UnsupportedTsp', `lroMetadata.finalStateVia ${method.lroMetadata.finalStateVia} NYI`, (_d = method.__raw) === null || _d === void 0 ? void 0 : _d.node);
1213
+ }
1214
+ lroFinalResultStrategy.propertyName = method.lroMetadata.finalResultPath;
1215
+ }
1216
+ rustMethod = new rust.LroMethod(methodName, languageIndependentName, rustClient, pub, methodOptions, httpMethod, method.operation.path, lroFinalResultStrategy);
1217
+ }
1165
1218
  break;
1166
1219
  default:
1167
- throw new AdapterError('UnsupportedTsp', `method kind ${method.kind} NYI`, (_d = method.__raw) === null || _d === void 0 ? void 0 : _d.node);
1220
+ throw new AdapterError('UnsupportedTsp', `method kind ${method.kind} NYI`, (_e = method.__raw) === null || _e === void 0 ? void 0 : _e.node);
1168
1221
  }
1169
1222
  rustMethod.docs = this.adaptDocs(method.summary, method.doc);
1170
1223
  rustClient.methods.push(rustMethod);
@@ -1187,7 +1240,12 @@ export class Adapter {
1187
1240
  }).any();
1188
1241
  }).first();
1189
1242
  if (!opParam) {
1190
- throw new AdapterError('InternalError', `didn't find operation parameter for method ${method.name} parameter ${param.name}`, (_e = param.__raw) === null || _e === void 0 ? void 0 : _e.node);
1243
+ throw new AdapterError('InternalError', `didn't find operation parameter for method ${method.name} parameter ${param.name}`, (_f = param.__raw) === null || _f === void 0 ? void 0 : _f.node);
1244
+ }
1245
+ if (opParam.kind === 'header' && opParam.serializedName.toLowerCase() === 'x-ms-client-request-id') {
1246
+ // x-ms-client-request-id is automatically inserted into requests via
1247
+ // a pipeline policy. so we don't want to expose this as an actual param.
1248
+ continue;
1191
1249
  }
1192
1250
  let adaptedParam;
1193
1251
  // for spread params there are two cases we need to consider.
@@ -1311,16 +1369,16 @@ export class Adapter {
1311
1369
  const responseFormat = getResponseFormat();
1312
1370
  if (method.kind === 'paging') {
1313
1371
  if (responseFormat === 'NoFormat') {
1314
- throw new AdapterError('InternalError', `paged method ${method.name} unexpected response format ${responseFormat}`, (_f = method.__raw) === null || _f === void 0 ? void 0 : _f.node);
1372
+ throw new AdapterError('InternalError', `paged method ${method.name} unexpected response format ${responseFormat}`, (_g = method.__raw) === null || _g === void 0 ? void 0 : _g.node);
1315
1373
  }
1316
1374
  // for paged methods, tcgc models method.response.type as an Array<T>.
1317
1375
  // however, we want the synthesized paged response envelope type instead.
1318
1376
  const synthesizedType = method.operation.responses[0].type;
1319
1377
  if (!synthesizedType) {
1320
- throw new AdapterError('InternalError', `paged method ${method.name} has no synthesized response type`, (_g = method.__raw) === null || _g === void 0 ? void 0 : _g.node);
1378
+ throw new AdapterError('InternalError', `paged method ${method.name} has no synthesized response type`, (_h = method.__raw) === null || _h === void 0 ? void 0 : _h.node);
1321
1379
  }
1322
1380
  else if (synthesizedType.kind !== 'model') {
1323
- throw new AdapterError('UnsupportedTsp', `paged method ${method.name} synthesized response type has unexpected kind ${synthesizedType.kind}`, (_h = method.__raw) === null || _h === void 0 ? void 0 : _h.node);
1381
+ throw new AdapterError('UnsupportedTsp', `paged method ${method.name} synthesized response type has unexpected kind ${synthesizedType.kind}`, (_j = method.__raw) === null || _j === void 0 ? void 0 : _j.node);
1324
1382
  }
1325
1383
  const synthesizedModel = this.getModel(synthesizedType, new Array());
1326
1384
  if (!this.crate.models.includes(synthesizedModel)) {
@@ -1328,7 +1386,7 @@ export class Adapter {
1328
1386
  }
1329
1387
  // for the pager response type, remove the Option<T> around the Vec<T> for the page items
1330
1388
  if (!method.pagingMetadata.pageItemsSegments) {
1331
- throw new AdapterError('InternalError', `paged method ${method.name} has no pageItemsSegments`, (_j = method.__raw) === null || _j === void 0 ? void 0 : _j.node);
1389
+ throw new AdapterError('InternalError', `paged method ${method.name} has no pageItemsSegments`, (_k = method.__raw) === null || _k === void 0 ? void 0 : _k.node);
1332
1390
  }
1333
1391
  // unwrap all of the segments for the paged response
1334
1392
  let unwrappedCount = 0;
@@ -1339,13 +1397,13 @@ export class Adapter {
1339
1397
  switch (responseFormat) {
1340
1398
  case 'JsonFormat':
1341
1399
  if (segment.kind !== 'property' || !segment.serializationOptions.json) {
1342
- throw new AdapterError('InternalError', `paged method ${method.name} is missing JSON serialization data`, (_k = method.__raw) === null || _k === void 0 ? void 0 : _k.node);
1400
+ throw new AdapterError('InternalError', `paged method ${method.name} is missing JSON serialization data`, (_l = method.__raw) === null || _l === void 0 ? void 0 : _l.node);
1343
1401
  }
1344
1402
  serde = segment.serializationOptions.json.name;
1345
1403
  break;
1346
1404
  case 'XmlFormat':
1347
1405
  if (segment.kind !== 'property' || !segment.serializationOptions.xml) {
1348
- throw new AdapterError('InternalError', `paged method ${method.name} is missing XML serialization data`, (_l = method.__raw) === null || _l === void 0 ? void 0 : _l.node);
1406
+ throw new AdapterError('InternalError', `paged method ${method.name} is missing XML serialization data`, (_m = method.__raw) === null || _m === void 0 ? void 0 : _m.node);
1349
1407
  }
1350
1408
  serde = segment.serializationOptions.xml.name;
1351
1409
  break;
@@ -1371,16 +1429,16 @@ export class Adapter {
1371
1429
  }
1372
1430
  }
1373
1431
  if (unwrappedCount !== method.pagingMetadata.pageItemsSegments.length) {
1374
- throw new AdapterError('InternalError', `failed to unwrap paged items for method ${method.name}`, (_m = method.__raw) === null || _m === void 0 ? void 0 : _m.node);
1432
+ throw new AdapterError('InternalError', `failed to unwrap paged items for method ${method.name}`, (_o = method.__raw) === null || _o === void 0 ? void 0 : _o.node);
1375
1433
  }
1376
1434
  this.crate.addDependency(new rust.CrateDependency('async-trait'));
1377
1435
  rustMethod.returns = new rust.Result(this.crate, new rust.Pager(this.crate, new rust.Response(this.crate, synthesizedModel, responseFormat)));
1378
1436
  }
1379
1437
  else if (method.kind === 'lro') {
1380
1438
  const format = responseFormat === 'NoFormat' ? 'JsonFormat' : responseFormat;
1381
- const responseType = this.typeToWireType(this.getModel(method.lroMetadata.logicalResult));
1382
- if (responseType.kind !== 'model') {
1383
- throw new AdapterError('InternalError', `envelope result for ${method.name} is not a model`, (_o = method.__raw) === null || _o === void 0 ? void 0 : _o.node);
1439
+ const statusType = this.typeToWireType(this.getModel(method.lroMetadata.pollingInfo.responseModel, undefined, `${rustClient.name}${codegen.pascalCase(rustMethod.name, false)}OperationStatus`));
1440
+ if (statusType.kind !== 'model') {
1441
+ throw new AdapterError('InternalError', `status type for an LRO method '${method.name}' is not a model`, (_p = method.__raw) === null || _p === void 0 ? void 0 : _p.node);
1384
1442
  }
1385
1443
  const pushModels = (model, crate) => {
1386
1444
  if (crate.models.some(m => m === model)) {
@@ -1396,8 +1454,17 @@ export class Adapter {
1396
1454
  }
1397
1455
  }
1398
1456
  };
1399
- pushModels(responseType, this.crate);
1400
- rustMethod.returns = new rust.Result(this.crate, new rust.Poller(this.crate, new rust.Response(this.crate, responseType, format)));
1457
+ pushModels(statusType, this.crate);
1458
+ const poller = new rust.Poller(this.crate, new rust.Response(this.crate, statusType, format));
1459
+ if (((_q = method.response.type) === null || _q === void 0 ? void 0 : _q.kind) === "model") {
1460
+ const responseType = this.typeToWireType(this.getModel(method.response.type));
1461
+ if (responseType.kind !== 'model') {
1462
+ throw new AdapterError('InternalError', `response type for an LRO method '${method.name}' is not a model`, (_r = method.__raw) === null || _r === void 0 ? void 0 : _r.node);
1463
+ }
1464
+ pushModels(responseType, this.crate);
1465
+ poller.resultType = new rust.Response(this.crate, responseType, format);
1466
+ }
1467
+ rustMethod.returns = new rust.Result(this.crate, poller);
1401
1468
  }
1402
1469
  else if (method.response.type && !(method.response.type.kind === 'bytes' && method.response.type.encode === 'bytes')) {
1403
1470
  const response = new rust.Response(this.crate, this.typeToWireType(this.getType(method.response.type)), responseFormat);
@@ -1441,7 +1508,7 @@ export class Adapter {
1441
1508
  // can't do this until the method has been completely adapted
1442
1509
  const pageableMethod = rustMethod;
1443
1510
  pageableMethod.strategy = this.adaptPageableMethodStrategy(method, paramsMap, responseHeadersMap);
1444
- if (((_p = pageableMethod.strategy) === null || _p === void 0 ? void 0 : _p.kind) === 'nextLink') {
1511
+ if (((_s = pageableMethod.strategy) === null || _s === void 0 ? void 0 : _s.kind) === 'nextLink') {
1445
1512
  pageableMethod.strategy.reinjectedParams = this.adaptPageableMethodReinjectionParams(method, paramsMap);
1446
1513
  }
1447
1514
  }