@azure-tools/typespec-rust 0.21.0 → 0.23.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.
Files changed (38) hide show
  1. package/dist/src/codegen/clients.d.ts.map +1 -1
  2. package/dist/src/codegen/clients.js +203 -178
  3. package/dist/src/codegen/clients.js.map +1 -1
  4. package/dist/src/codegen/context.d.ts.map +1 -1
  5. package/dist/src/codegen/context.js +27 -11
  6. package/dist/src/codegen/context.js.map +1 -1
  7. package/dist/src/codegen/helpers.d.ts +5 -2
  8. package/dist/src/codegen/helpers.d.ts.map +1 -1
  9. package/dist/src/codegen/helpers.js +22 -16
  10. package/dist/src/codegen/helpers.js.map +1 -1
  11. package/dist/src/codegen/mod.d.ts.map +1 -1
  12. package/dist/src/codegen/mod.js +4 -2
  13. package/dist/src/codegen/mod.js.map +1 -1
  14. package/dist/src/codemodel/client.d.ts +38 -9
  15. package/dist/src/codemodel/client.d.ts.map +1 -1
  16. package/dist/src/codemodel/client.js +15 -3
  17. package/dist/src/codemodel/client.js.map +1 -1
  18. package/dist/src/codemodel/types.d.ts +6 -6
  19. package/dist/src/codemodel/types.js +3 -3
  20. package/dist/src/emitter.d.ts.map +1 -1
  21. package/dist/src/emitter.js +9 -1
  22. package/dist/src/emitter.js.map +1 -1
  23. package/dist/src/lib.d.ts +9 -9
  24. package/dist/src/lib.js +3 -3
  25. package/dist/src/lib.js.map +1 -1
  26. package/dist/src/tcgcadapter/adapter.d.ts +6 -0
  27. package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
  28. package/dist/src/tcgcadapter/adapter.js +99 -63
  29. package/dist/src/tcgcadapter/adapter.js.map +1 -1
  30. package/dist/src/tcgcadapter/helpers.d.ts +0 -17
  31. package/dist/src/tcgcadapter/helpers.d.ts.map +1 -1
  32. package/dist/src/tcgcadapter/helpers.js +1 -71
  33. package/dist/src/tcgcadapter/helpers.js.map +1 -1
  34. package/dist/src/tcgcadapter/naming.d.ts +16 -0
  35. package/dist/src/tcgcadapter/naming.d.ts.map +1 -1
  36. package/dist/src/tcgcadapter/naming.js +36 -0
  37. package/dist/src/tcgcadapter/naming.js.map +1 -1
  38. package/package.json +22 -22
@@ -23,6 +23,12 @@ export class AdapterError extends Error {
23
23
  this.target = target !== null && target !== void 0 ? target : NoTarget;
24
24
  }
25
25
  }
26
+ /**
27
+ * ExternalError is thrown when an external component reports a
28
+ * diagnostic error that would prevent the emitter from proceeding.
29
+ */
30
+ export class ExternalError extends Error {
31
+ }
26
32
  /** Adapter converts the tcgc code model to a Rust Crate */
27
33
  export class Adapter {
28
34
  /**
@@ -40,6 +46,15 @@ export class Adapter {
40
46
  disableUsageAccessPropagationToBase: true,
41
47
  });
42
48
  context.program.reportDiagnostics(ctx.diagnostics);
49
+ for (const diag of ctx.diagnostics) {
50
+ if (diag.severity === 'error') {
51
+ // there's no point in continuing if tcgc
52
+ // has reported diagnostic errors, so exit.
53
+ // this prevents spurious crashes in the
54
+ // emitter as our input state is invalid.
55
+ throw new ExternalError();
56
+ }
57
+ }
43
58
  return new Adapter(ctx, context.options);
44
59
  }
45
60
  constructor(ctx, options) {
@@ -66,25 +81,6 @@ export class Adapter {
66
81
  if (this.crate.clients.length > 0 || this.crate.enums.length > 0 || this.crate.models.length > 0) {
67
82
  this.crate.addDependency(new rust.CrateDependency('typespec_client_core', ['derive']));
68
83
  }
69
- // TODO: remove once https://github.com/Azure/typespec-rust/issues/22 is fixed
70
- for (const client of this.crate.clients) {
71
- let done = false;
72
- for (const method of client.methods) {
73
- if (method.kind === 'pageable') {
74
- if (method.returns.type.kind === 'pager') {
75
- this.crate.addDependency(new rust.CrateDependency('async-trait')); // required for azure_core::http::Page trait
76
- }
77
- // TODO: why is this here?
78
- this.crate.addDependency(new rust.CrateDependency('futures'));
79
- done = true;
80
- break;
81
- }
82
- }
83
- if (done) {
84
- break;
85
- }
86
- }
87
- // end TODO
88
84
  this.crate.sortContent();
89
85
  return this.crate;
90
86
  }
@@ -132,6 +128,7 @@ export class Adapter {
132
128
  * @returns a Rust enum
133
129
  */
134
130
  getEnum(sdkEnum) {
131
+ var _a;
135
132
  const enumName = codegen.capitalize(sdkEnum.name);
136
133
  let rustEnum = this.types.get(enumName);
137
134
  if (rustEnum) {
@@ -140,8 +137,15 @@ export class Adapter {
140
137
  rustEnum = new rust.Enum(enumName, sdkEnum.access === 'public', !sdkEnum.isFixed);
141
138
  rustEnum.docs = this.adaptDocs(sdkEnum.summary, sdkEnum.doc);
142
139
  this.types.set(enumName, rustEnum);
140
+ const rustEnumNameToSdkEnumName = new Map();
143
141
  for (const value of sdkEnum.values) {
144
- const rustEnumValue = new rust.EnumValue(helpers.fixUpEnumValueName(value), rustEnum, value.value);
142
+ const enumValueName = naming.fixUpEnumValueName(value);
143
+ const existingMapping = rustEnumNameToSdkEnumName.get(enumValueName);
144
+ if (existingMapping) {
145
+ 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);
146
+ }
147
+ rustEnumNameToSdkEnumName.set(enumValueName, value.name);
148
+ const rustEnumValue = new rust.EnumValue(enumValueName, rustEnum, value.value);
145
149
  rustEnumValue.docs = this.adaptDocs(value.summary, value.doc);
146
150
  rustEnum.values.push(rustEnumValue);
147
151
  }
@@ -159,7 +163,7 @@ export class Adapter {
159
163
  const enumType = this.getEnum(sdkEnumValue.enumType);
160
164
  // find the specified enum value
161
165
  for (const value of enumType.values) {
162
- if (value.name === helpers.fixUpEnumValueName(sdkEnumValue)) {
166
+ if (value.name === naming.fixUpEnumValueName(sdkEnumValue)) {
163
167
  return value;
164
168
  }
165
169
  }
@@ -673,7 +677,7 @@ export class Adapter {
673
677
  var _a, _b, _c, _d, _e;
674
678
  let clientName = client.name;
675
679
  // NOTE: if the client has the @clientName decorator applied then use that verbatim
676
- if (parent && !client.decorators.find((decorator) => decorator.name === 'Azure.ClientGenerator.Core.@clientName')) {
680
+ if (parent && !hasClientNameDecorator(client.decorators)) {
677
681
  // for hierarchical clients, the child client names are built
678
682
  // from the parent client name. this is because tsp allows subclients
679
683
  // with the same name. consider the following example.
@@ -787,12 +791,13 @@ export class Adapter {
787
791
  if (i === 0) {
788
792
  // the first template arg is always the endpoint parameter.
789
793
  // note that the types of the param and the field are different.
790
- // NOTE: we use param.name here instead of templateArg.name as
791
- // the former has the fixed name "endpoint" which is what we want.
792
- const adaptedParam = new rust.ClientMethodParameter(param.name, this.getRefType(this.getStringSlice()), false);
794
+ // we default to "endpoint" and will use the defined name IFF
795
+ // it has the @clientName decorator applied
796
+ const endpointName = hasClientNameDecorator(templateArg.decorators) ? snakeCaseName(templateArg.name) : 'endpoint';
797
+ const adaptedParam = new rust.ClientEndpointParameter(endpointName);
793
798
  adaptedParam.docs = this.adaptDocs(param.summary, param.doc);
794
799
  ctorParams.push(adaptedParam);
795
- rustClient.fields.push(new rust.StructField(param.name, 'pubCrate', new rust.Url(this.crate)));
800
+ rustClient.fields.push(new rust.StructField(endpointName, 'pubCrate', new rust.Url(this.crate)));
796
801
  // if the server's URL is *only* the endpoint parameter then we're done.
797
802
  // this is the param.type.kind === 'endpoint' case.
798
803
  if (endpointType.serverUrl === `{${templateArg.serializedName}}`) {
@@ -817,7 +822,7 @@ export class Adapter {
817
822
  continue;
818
823
  }
819
824
  const clientParam = this.adaptClientParameter(templateArg, rustClient.constructable);
820
- if (clientParam.kind !== 'clientEndpoint') {
825
+ if (clientParam.kind !== 'clientSupplementalEndpoint') {
821
826
  throw new AdapterError('InternalError', `unexpected client parameter kind ${clientParam.kind}`, (_b = templateArg.__raw) === null || _b === void 0 ? void 0 : _b.node);
822
827
  }
823
828
  (_c = rustClient.constructable.endpoint) === null || _c === void 0 ? void 0 : _c.parameters.push(clientParam);
@@ -910,7 +915,7 @@ export class Adapter {
910
915
  throw new AdapterError('InternalError', 'scopes must contain at least one entry', cred.model);
911
916
  }
912
917
  const ctorTokenCredential = new rust.Constructor('new');
913
- const tokenCredParam = new rust.ClientMethodParameter('credential', new rust.Arc(new rust.TokenCredential(this.crate, scopes)), false);
918
+ const tokenCredParam = new rust.ClientCredentialParameter('credential', new rust.Arc(new rust.TokenCredential(this.crate, scopes)));
914
919
  tokenCredParam.docs.summary = `An implementation of [\`TokenCredential\`](azure_core::credentials::TokenCredential) that can provide an Entra ID token to use when authenticating.`;
915
920
  ctorTokenCredential.params.push(tokenCredParam);
916
921
  ctorTokenCredential.docs.summary = `Creates a new ${rustClient.name}, using Entra ID authentication.`;
@@ -957,7 +962,7 @@ export class Adapter {
957
962
  adaptedParam = new rust.ClientMethodParameter(paramName, paramType, optional);
958
963
  break;
959
964
  case 'path':
960
- adaptedParam = new rust.ClientEndpointParameter(paramName, paramType, optional, param.serializedName);
965
+ adaptedParam = new rust.ClientSupplementalEndpointParameter(paramName, paramType, optional, param.serializedName);
961
966
  break;
962
967
  }
963
968
  adaptedParam.docs = this.adaptDocs(param.summary, param.doc);
@@ -1160,6 +1165,21 @@ export class Adapter {
1160
1165
  }
1161
1166
  return this.adaptPayloadFormatType(defaultContentType);
1162
1167
  };
1168
+ const getStatusCodes = function (httpOp) {
1169
+ const statusCodes = new Array();
1170
+ for (const response of httpOp.responses) {
1171
+ const statusCode = response.statusCodes;
1172
+ if (isHttpStatusCodeRange(statusCode)) {
1173
+ for (let code = statusCode.start; code <= statusCode.end; ++code) {
1174
+ statusCodes.push(code);
1175
+ }
1176
+ }
1177
+ else {
1178
+ statusCodes.push(statusCode);
1179
+ }
1180
+ }
1181
+ return statusCodes;
1182
+ };
1163
1183
  // add any response headers
1164
1184
  const responseHeaders = new Array();
1165
1185
  for (const httpResp of method.operation.responses) {
@@ -1260,6 +1280,7 @@ export class Adapter {
1260
1280
  rustMethod.returns = new rust.Result(this.crate, new rust.PageIterator(this.crate, new rust.Response(this.crate, synthesizedModel, responseFormat)));
1261
1281
  }
1262
1282
  else {
1283
+ this.crate.addDependency(new rust.CrateDependency('async-trait'));
1263
1284
  rustMethod.returns = new rust.Result(this.crate, new rust.Pager(this.crate, new rust.Response(this.crate, synthesizedModel, responseFormat)));
1264
1285
  }
1265
1286
  }
@@ -1300,11 +1321,20 @@ export class Adapter {
1300
1321
  }
1301
1322
  else if (method.response.type && method.response.type.kind === 'bytes' && method.response.type.encode === 'bytes') {
1302
1323
  // bytes encoding indicates a streaming binary response
1303
- rustMethod.returns = new rust.Result(this.crate, new rust.RawResponse(this.crate));
1324
+ rustMethod.returns = new rust.Result(this.crate, new rust.BufResponse(this.crate));
1304
1325
  }
1305
1326
  else {
1306
1327
  rustMethod.returns = new rust.Result(this.crate, new rust.Response(this.crate, this.getUnitType(), responseFormat));
1307
1328
  }
1329
+ // Mark the set of success status codes expected from this method.
1330
+ rustMethod.statusCodes = getStatusCodes(method.operation);
1331
+ // For long running operations, we add 200 OK if not already present for the LRO polling and terminal states.
1332
+ if (method.kind === 'lro') {
1333
+ if (!rustMethod.statusCodes.includes(200)) {
1334
+ rustMethod.statusCodes.push(200);
1335
+ }
1336
+ }
1337
+ rustMethod.statusCodes.sort((a, b) => a - b);
1308
1338
  const responseHeadersMap = this.adaptResponseHeaders(responseHeaders);
1309
1339
  rustMethod.responseHeaders = this.adaptResponseHeadersTrait(rustClient, rustMethod, Array.from(responseHeadersMap.values()));
1310
1340
  if (method.kind === 'paging') {
@@ -1397,7 +1427,7 @@ export class Adapter {
1397
1427
  implFor = method.returns.type;
1398
1428
  break;
1399
1429
  default:
1400
- // this is RawResponse which should have been previously skipped
1430
+ // this is BufResponse which should have been previously skipped
1401
1431
  throw new AdapterError('InternalError', `unexpected method return kind ${method.returns.type.kind}`);
1402
1432
  }
1403
1433
  const traitName = `${recursiveTypeName(implFor.content)}Headers`;
@@ -1416,35 +1446,32 @@ export class Adapter {
1416
1446
  * @returns the pageable strategy
1417
1447
  */
1418
1448
  adaptPageableMethodStrategy(method, paramsMap, respHeadersMap) {
1419
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1449
+ var _a, _b, _c, _d, _e;
1450
+ const buildNextLinkPath = (segments) => {
1451
+ var _a, _b;
1452
+ // build the field path for the next link segments
1453
+ const nextLinkPath = new Array();
1454
+ for (const segment of segments) {
1455
+ if (segment.kind !== 'property') {
1456
+ throw new AdapterError('InternalError', `unexpected kind ${segment.kind} for next link segment in operation ${method.name}`, (_a = method.__raw) === null || _a === void 0 ? void 0 : _a.node);
1457
+ }
1458
+ const nextLinkField = this.fieldsMap.get(segment);
1459
+ if (!nextLinkField) {
1460
+ // the most likely explanation for this is lack of reference equality
1461
+ throw new AdapterError('InternalError', `missing next link field name ${segment.name} for operation ${method.name}`, (_b = method.__raw) === null || _b === void 0 ? void 0 : _b.node);
1462
+ }
1463
+ nextLinkPath.push(nextLinkField);
1464
+ }
1465
+ return nextLinkPath;
1466
+ };
1420
1467
  if (method.pagingMetadata.nextLinkOperation) {
1421
1468
  // TODO: https://github.com/Azure/autorest.rust/issues/103
1422
1469
  throw new AdapterError('UnsupportedTsp', 'next page operation NYI', (_a = method.__raw) === null || _a === void 0 ? void 0 : _a.node);
1423
1470
  }
1424
1471
  else if (method.pagingMetadata.nextLinkSegments) {
1425
- if (method.pagingMetadata.nextLinkSegments.length > 1) {
1426
- // TODO: https://github.com/Azure/autorest.rust/issues/102
1427
- throw new AdapterError('UnsupportedTsp', 'nested next link path NYI', (_b = method.__raw) === null || _b === void 0 ? void 0 : _b.node);
1428
- }
1429
- // this is the field in the response type that contains the next link URL
1430
- const nextLinkSegment = method.pagingMetadata.nextLinkSegments[0];
1431
- if (nextLinkSegment.kind !== 'property') {
1432
- throw new AdapterError('InternalError', `unexpected kind ${nextLinkSegment.kind} for next link segment in operation ${method.name}`, (_c = method.__raw) === null || _c === void 0 ? void 0 : _c.node);
1433
- }
1434
- const nextLinkField = this.fieldsMap.get(nextLinkSegment);
1435
- if (!nextLinkField) {
1436
- // the most likely explanation for this is lack of reference equality
1437
- throw new AdapterError('InternalError', `missing next link field name ${nextLinkSegment.name} for operation ${method.name}`, (_d = method.__raw) === null || _d === void 0 ? void 0 : _d.node);
1438
- }
1439
- return new rust.PageableStrategyNextLink(nextLinkField);
1472
+ return new rust.PageableStrategyNextLink(buildNextLinkPath(method.pagingMetadata.nextLinkSegments));
1440
1473
  }
1441
1474
  else if (method.pagingMetadata.continuationTokenParameterSegments && method.pagingMetadata.continuationTokenResponseSegments) {
1442
- if (method.pagingMetadata.continuationTokenParameterSegments.length > 1) {
1443
- throw new AdapterError('UnsupportedTsp', `nested continuationTokenParameterSegments NYI`, (_e = method.__raw) === null || _e === void 0 ? void 0 : _e.node);
1444
- }
1445
- if (method.pagingMetadata.continuationTokenResponseSegments.length > 1) {
1446
- throw new AdapterError('UnsupportedTsp', `nested continuationTokenResponseSegments NYI`, (_f = method.__raw) === null || _f === void 0 ? void 0 : _f.node);
1447
- }
1448
1475
  const tokenReq = method.pagingMetadata.continuationTokenParameterSegments[0];
1449
1476
  const tokenResp = method.pagingMetadata.continuationTokenResponseSegments[0];
1450
1477
  // find the continuation token parameter
@@ -1453,29 +1480,25 @@ export class Adapter {
1453
1480
  case 'method': {
1454
1481
  const tokenParam = paramsMap.get(tokenReq);
1455
1482
  if (!tokenParam) {
1456
- throw new AdapterError('InternalError', `missing continuation token request parameter name ${tokenResp.name} for operation ${method.name}`, (_g = method.__raw) === null || _g === void 0 ? void 0 : _g.node);
1483
+ throw new AdapterError('InternalError', `missing continuation token request parameter name ${tokenResp.name} for operation ${method.name}`, (_b = method.__raw) === null || _b === void 0 ? void 0 : _b.node);
1457
1484
  }
1458
1485
  requestToken = tokenParam;
1459
1486
  break;
1460
1487
  }
1461
1488
  default:
1462
- throw new AdapterError('InternalError', `unhandled continuationTokenParameterSegment kind ${tokenReq.kind}`, (_h = tokenReq.__raw) === null || _h === void 0 ? void 0 : _h.node);
1489
+ throw new AdapterError('InternalError', `unhandled continuationTokenParameterSegment kind ${tokenReq.kind}`, (_c = tokenReq.__raw) === null || _c === void 0 ? void 0 : _c.node);
1463
1490
  }
1464
1491
  // find the continuation token response
1465
1492
  let responseToken;
1466
1493
  switch (tokenResp.kind) {
1467
1494
  case 'property': {
1468
- const tokenField = this.fieldsMap.get(tokenResp);
1469
- if (!tokenField) {
1470
- throw new AdapterError('InternalError', `missing continuation token response field name ${tokenResp.name} for operation ${method.name}`, (_j = method.__raw) === null || _j === void 0 ? void 0 : _j.node);
1471
- }
1472
- responseToken = tokenField;
1495
+ responseToken = new rust.PageableStrategyNextLink(buildNextLinkPath(method.pagingMetadata.continuationTokenResponseSegments));
1473
1496
  break;
1474
1497
  }
1475
1498
  case 'responseheader': {
1476
1499
  const tokenHeader = respHeadersMap.get(tokenResp);
1477
1500
  if (!tokenHeader) {
1478
- throw new AdapterError('InternalError', `missing continuation token response header name ${tokenResp.name} for operation ${method.name}`, (_k = method.__raw) === null || _k === void 0 ? void 0 : _k.node);
1501
+ throw new AdapterError('InternalError', `missing continuation token response header name ${tokenResp.name} for operation ${method.name}`, (_d = method.__raw) === null || _d === void 0 ? void 0 : _d.node);
1479
1502
  }
1480
1503
  if (tokenHeader.kind !== 'responseHeaderScalar') {
1481
1504
  throw new AdapterError('InternalError', `unexpected response header kind ${tokenHeader.kind}`);
@@ -1484,12 +1507,13 @@ export class Adapter {
1484
1507
  break;
1485
1508
  }
1486
1509
  default:
1487
- throw new AdapterError('InternalError', `missing continuation token`, (_l = method.__raw) === null || _l === void 0 ? void 0 : _l.node);
1510
+ throw new AdapterError('InternalError', `missing continuation token`, (_e = method.__raw) === null || _e === void 0 ? void 0 : _e.node);
1488
1511
  }
1489
1512
  return new rust.PageableStrategyContinuationToken(requestToken, responseToken);
1490
1513
  }
1491
1514
  else {
1492
- throw new AdapterError('InternalError', `unknown paging strategy for operation ${method.name}`, (_m = method.__raw) === null || _m === void 0 ? void 0 : _m.node);
1515
+ // operation is pageable but doesn't yet support fetching subsequent pages
1516
+ return undefined;
1493
1517
  }
1494
1518
  }
1495
1519
  /**
@@ -1959,4 +1983,16 @@ function getXMLKind(decorators, field) {
1959
1983
  }
1960
1984
  return undefined;
1961
1985
  }
1986
+ /**
1987
+ * returns true if decorators contains `@clientName`
1988
+ *
1989
+ * @param decorators the array of decorators to inspect
1990
+ * @returns true if `@clientName` is found in decorators
1991
+ */
1992
+ function hasClientNameDecorator(decorators) {
1993
+ return decorators.find((decorator) => decorator.name === 'Azure.ClientGenerator.Core.@clientName') !== undefined;
1994
+ }
1995
+ function isHttpStatusCodeRange(statusCode) {
1996
+ return statusCode.start !== undefined;
1997
+ }
1962
1998
  //# sourceMappingURL=adapter.js.map