@azure-tools/typespec-rust 0.20.0 → 0.22.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 (46) hide show
  1. package/dist/src/codegen/cargotoml.d.ts.map +1 -1
  2. package/dist/src/codegen/cargotoml.js +3 -1
  3. package/dist/src/codegen/cargotoml.js.map +1 -1
  4. package/dist/src/codegen/clients.d.ts.map +1 -1
  5. package/dist/src/codegen/clients.js +272 -69
  6. package/dist/src/codegen/clients.js.map +1 -1
  7. package/dist/src/codegen/context.d.ts +10 -0
  8. package/dist/src/codegen/context.d.ts.map +1 -1
  9. package/dist/src/codegen/context.js +44 -2
  10. package/dist/src/codegen/context.js.map +1 -1
  11. package/dist/src/codegen/docTests.d.ts +12 -0
  12. package/dist/src/codegen/docTests.d.ts.map +1 -0
  13. package/dist/src/codegen/docTests.js +46 -0
  14. package/dist/src/codegen/docTests.js.map +1 -0
  15. package/dist/src/codegen/headerTraits.d.ts.map +1 -1
  16. package/dist/src/codegen/headerTraits.js +5 -0
  17. package/dist/src/codegen/headerTraits.js.map +1 -1
  18. package/dist/src/codegen/helpers.d.ts +38 -3
  19. package/dist/src/codegen/helpers.d.ts.map +1 -1
  20. package/dist/src/codegen/helpers.js +66 -5
  21. package/dist/src/codegen/helpers.js.map +1 -1
  22. package/dist/src/codegen/lib.d.ts.map +1 -1
  23. package/dist/src/codegen/lib.js +2 -0
  24. package/dist/src/codegen/lib.js.map +1 -1
  25. package/dist/src/codegen/models.d.ts.map +1 -1
  26. package/dist/src/codegen/models.js +172 -9
  27. package/dist/src/codegen/models.js.map +1 -1
  28. package/dist/src/codemodel/client.d.ts +38 -5
  29. package/dist/src/codemodel/client.d.ts.map +1 -1
  30. package/dist/src/codemodel/client.js +21 -1
  31. package/dist/src/codemodel/client.js.map +1 -1
  32. package/dist/src/codemodel/crate.js.map +1 -1
  33. package/dist/src/codemodel/types.d.ts +33 -11
  34. package/dist/src/codemodel/types.d.ts.map +1 -1
  35. package/dist/src/codemodel/types.js +16 -3
  36. package/dist/src/codemodel/types.js.map +1 -1
  37. package/dist/src/emitter.d.ts.map +1 -1
  38. package/dist/src/emitter.js +7 -1
  39. package/dist/src/emitter.js.map +1 -1
  40. package/dist/src/tcgcadapter/adapter.d.ts +11 -0
  41. package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
  42. package/dist/src/tcgcadapter/adapter.js +156 -44
  43. package/dist/src/tcgcadapter/adapter.js.map +1 -1
  44. package/dist/src/tcgcadapter/helpers.js +1 -1
  45. package/dist/src/tcgcadapter/helpers.js.map +1 -1
  46. package/package.json +29 -29
@@ -1 +1 @@
1
- {"version":3,"file":"cargotoml.d.ts","sourceRoot":"","sources":["../../../src/codegen/cargotoml.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,IAAI,MAAM,uBAAuB,CAAC;AAE9C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAgBvD"}
1
+ {"version":3,"file":"cargotoml.d.ts","sourceRoot":"","sources":["../../../src/codegen/cargotoml.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,IAAI,MAAM,uBAAuB,CAAC;AAE9C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAkBvD"}
@@ -14,7 +14,9 @@ export function emitCargoToml(crate) {
14
14
  content += 'edition.workspace = true\n';
15
15
  content += 'license.workspace = true\n';
16
16
  content += 'repository.workspace = true\n';
17
- content += 'rust-version.workspace = true\n';
17
+ content += 'rust-version.workspace = true\n\n';
18
+ content += '[features]\n';
19
+ content += 'default = ["azure_core/default"]\n';
18
20
  if (crate.dependencies.length > 0) {
19
21
  content += '\n[dependencies]\n';
20
22
  for (const dependency of crate.dependencies) {
@@ -1 +1 @@
1
- {"version":3,"file":"cargotoml.js","sourceRoot":"","sources":["../../../src/codegen/cargotoml.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAI/F;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,OAAO,GAAG,sBAAsB,KAAK,CAAC,IAAI,iBAAiB,KAAK,CAAC,OAAO,KAAK,CAAC;IAClF,OAAO,IAAI,4BAA4B,CAAC;IACxC,OAAO,IAAI,4BAA4B,CAAC;IACxC,OAAO,IAAI,4BAA4B,CAAC;IACxC,OAAO,IAAI,+BAA+B,CAAC;IAC3C,OAAO,IAAI,iCAAiC,CAAC;IAC7C,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,oBAAoB,CAAC;QAChC,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAC5C,qEAAqE;YACrE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpI,OAAO,IAAI,GAAG,UAAU,CAAC,IAAI,wBAAwB,QAAQ,MAAM,CAAC;QACtE,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"cargotoml.js","sourceRoot":"","sources":["../../../src/codegen/cargotoml.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAI/F;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,OAAO,GAAG,sBAAsB,KAAK,CAAC,IAAI,iBAAiB,KAAK,CAAC,OAAO,KAAK,CAAC;IAClF,OAAO,IAAI,4BAA4B,CAAC;IACxC,OAAO,IAAI,4BAA4B,CAAC;IACxC,OAAO,IAAI,4BAA4B,CAAC;IACxC,OAAO,IAAI,+BAA+B,CAAC;IAC3C,OAAO,IAAI,mCAAmC,CAAC;IAC/C,OAAO,IAAI,cAAc,CAAC;IAC1B,OAAO,IAAI,oCAAoC,CAAC;IAChD,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,oBAAoB,CAAC;QAChC,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAC5C,qEAAqE;YACrE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpI,OAAO,IAAI,GAAG,UAAU,CAAC,IAAI,wBAAwB,QAAQ,MAAM,CAAC;QACtE,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../../src/codegen/clients.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAGxC,OAAO,KAAK,IAAI,MAAM,uBAAuB,CAAC;AAG9C,yBAAyB;AACzB,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,aAAa,GAAG,SAAS,CAiQxE"}
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../../src/codegen/clients.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAGxC,OAAO,KAAK,IAAI,MAAM,uBAAuB,CAAC;AAG9C,yBAAyB;AACzB,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,aAAa,GAAG,SAAS,CAuQxE"}
@@ -5,6 +5,7 @@
5
5
  // cspell: ignore conv
6
6
  import * as codegen from '@azure-tools/codegen';
7
7
  import { values } from '@azure-tools/linq';
8
+ import { emitHeaderTraitDocExample } from './docTests.js';
8
9
  import { CodegenError } from './errors.js';
9
10
  import * as helpers from './helpers.js';
10
11
  import queryString from 'query-string';
@@ -76,23 +77,22 @@ export function emitClients(crate) {
76
77
  if (paramsDocs) {
77
78
  body += paramsDocs;
78
79
  }
79
- body += `${indent.get()}#[tracing::new("${crate.name}")]\n`;
80
+ body += `${indent.get()}#[tracing::new("${client.languageIndependentName}")]\n`;
80
81
  body += `${indent.get()}pub fn ${constructor.name}(${getConstructorParamsSig(constructor.params, client.constructable.options, use)}) -> Result<Self> {\n`;
81
82
  body += `${indent.get()}let options = options.unwrap_or_default();\n`;
82
83
  // by convention, the endpoint param is always the first ctor param
83
84
  const endpointParamName = constructor.params[0].name;
84
- body += `${indent.push().get()}let mut ${endpointParamName} = Url::parse(${endpointParamName})?;\n`;
85
+ body += `${indent.push().get()}let ${client.constructable.endpoint ? 'mut ' : ''}${endpointParamName} = Url::parse(${endpointParamName})?;\n`;
85
86
  body += `${indent.get()}${helpers.buildIfBlock(indent, {
86
87
  condition: `!${endpointParamName}.scheme().starts_with("http")`,
87
88
  body: (indent) => `${indent.get()}return Err(azure_core::Error::message(azure_core::error::ErrorKind::Other, format!("{${endpointParamName}} must use http(s)")));\n`,
88
89
  })}`;
89
- body += `${indent.get()}${endpointParamName}.set_query(None);\n`;
90
90
  // construct the supplemental path and join it to the endpoint
91
91
  if (client.constructable.endpoint) {
92
92
  const supplementalEndpoint = client.constructable.endpoint;
93
93
  body += `${indent.get()}let mut host = String::from("${supplementalEndpoint.path}");\n`;
94
94
  for (const param of supplementalEndpoint.parameters) {
95
- body += `${indent.get()}host = host.replace("{${param.segment}}", ${getClientEndpointParamValue(param)});\n`;
95
+ body += `${indent.get()}host = host.replace("{${param.segment}}", ${getClientSupplementalEndpointParamValue(param)});\n`;
96
96
  }
97
97
  body += `${indent.push().get()}${endpointParamName} = ${endpointParamName}.join(&host)?;\n`;
98
98
  }
@@ -108,15 +108,11 @@ export function emitClients(crate) {
108
108
  // exclude endpoint params as they aren't propagated to clients (they're consumed when creating the complete endpoint)
109
109
  const sortedParams = values([...constructor.params]
110
110
  .sort((a, b) => { return helpers.sortAscending(a.name, b.name); }))
111
- .where((each) => each.kind !== 'clientEndpoint').toArray();
111
+ .where((each) => each.kind !== 'clientSupplementalEndpoint' && each.kind !== 'clientCredential').toArray();
112
112
  for (const param of sortedParams) {
113
113
  if (param.optional) {
114
114
  continue;
115
115
  }
116
- else if (isCredential(param.type)) {
117
- // credential params aren't persisted on the client so skip them
118
- continue;
119
- }
120
116
  if (!client.fields.find((v) => { return v.name === param.name; })) {
121
117
  throw new CodegenError('InternalError', `didn't find field in client ${client.name} for param ${param.name}`);
122
118
  }
@@ -142,7 +138,7 @@ export function emitClients(crate) {
142
138
  body += `${indent.get()}option_env!("CARGO_PKG_VERSION"),\n`;
143
139
  body += `${indent.get()}options.client_options,\n`;
144
140
  body += `${indent.get()}Vec::default(),\n`;
145
- body += `${indent.get()}${authPolicy ? 'vec![auth_policy]' : 'Vec::default()'},\n`;
141
+ body += `${indent.get()}${authPolicy ? 'vec![auth_policy]' : 'Vec::default()'}, None,\n`;
146
142
  body += `${indent.pop().get()}),\n`; // end Pipeline::new
147
143
  body += `${indent.pop().get()}})\n`; // end Ok
148
144
  body += `${indent.pop().get()}}\n`; // end constructor
@@ -183,6 +179,12 @@ export function emitClients(crate) {
183
179
  return getPageableMethodBody(indentation, use, client, method);
184
180
  };
185
181
  break;
182
+ case 'lro':
183
+ isPublicApi = true;
184
+ methodBody = (indentation) => {
185
+ return getLroMethodBody(indentation, use, client, method);
186
+ };
187
+ break;
186
188
  case 'clientaccessor':
187
189
  isSubclientNew = true;
188
190
  methodBody = (indentation) => {
@@ -195,6 +197,10 @@ export function emitClients(crate) {
195
197
  if (paramsDocs) {
196
198
  body += paramsDocs;
197
199
  }
200
+ // client accessors will never have response headers
201
+ if (method.kind !== 'clientaccessor' && method.responseHeaders) {
202
+ body += getHeaderTraitDocComment(indent, crate, method);
203
+ }
198
204
  if (isPublicApi) {
199
205
  body += `${indent.get()}#[tracing::function("${method.languageIndependentName}")]\n`;
200
206
  }
@@ -275,7 +281,7 @@ function getMethodOptions(crate) {
275
281
  }
276
282
  }
277
283
  body += '}\n\n'; // end options
278
- if (method.kind === 'pageable') {
284
+ if (method.kind === 'pageable' || method.kind === 'lro') {
279
285
  body += `impl ${helpers.getTypeDeclaration(method.options.type, true)} {\n`;
280
286
  body += `${indent.get()}pub fn into_owned(self) -> ${method.options.type.name}<'static> {\n`;
281
287
  body += `${indent.push().get()}${method.options.type.name} {\n`;
@@ -316,7 +322,7 @@ function getMethodOptions(crate) {
316
322
  */
317
323
  function getParamsBlockDocComment(indent, callable) {
318
324
  const formatParamBullet = function (paramName) {
319
- return `* \`${paramName}\` - `;
325
+ return `* ${helpers.wrapInBackTicks(paramName)} - `;
320
326
  };
321
327
  let paramsContent = '';
322
328
  for (const param of callable.params) {
@@ -412,6 +418,36 @@ function getMethodParamsSig(method, use) {
412
418
  }
413
419
  return paramsSig.join(', ');
414
420
  }
421
+ /**
422
+ * returns documentation for header trait access if the method has response headers.
423
+ *
424
+ * @param indent the current indentation level
425
+ * @param crate the crate to which method belongs
426
+ * @param method the method for which to generate header trait documentation
427
+ * @returns the header trait documentation or empty string if not applicable
428
+ */
429
+ function getHeaderTraitDocComment(indent, crate, method) {
430
+ if (!method.responseHeaders) {
431
+ return '';
432
+ }
433
+ const traitName = method.responseHeaders.name;
434
+ let headerDocs = `${indent.get()}///\n`;
435
+ headerDocs += `${indent.get()}/// ## Response Headers\n`;
436
+ headerDocs += `${indent.get()}///\n`;
437
+ headerDocs += `${indent.get()}/// The returned [${helpers.wrapInBackTicks('Response')}](azure_core::http::Response) implements the [${helpers.wrapInBackTicks(traitName)}] trait, which provides\n`;
438
+ headerDocs += `${indent.get()}/// access to response headers. For example:\n`;
439
+ headerDocs += `${indent.get()}///\n`;
440
+ headerDocs += emitHeaderTraitDocExample(crate.name, method.responseHeaders, indent);
441
+ headerDocs += `${indent.get()}///\n`;
442
+ headerDocs += `${indent.get()}/// ### Available headers\n`;
443
+ // List all available headers
444
+ for (const header of method.responseHeaders.headers) {
445
+ headerDocs += `${indent.get()}/// * [${helpers.wrapInBackTicks(header.name)}()](crate::generated::models::${traitName}::${header.name}) - ${header.header}\n`;
446
+ }
447
+ headerDocs += `${indent.get()}///\n`;
448
+ headerDocs += `${indent.get()}/// [${helpers.wrapInBackTicks(traitName)}]: crate::generated::models::${traitName}\n`;
449
+ return headerDocs;
450
+ }
415
451
  /**
416
452
  * returns the auth policy instantiation code if the ctor contains a credential param.
417
453
  * the policy will be a local var named auth_policy.
@@ -593,7 +629,7 @@ function getMethodParamGroup(method) {
593
629
  * @returns
594
630
  */
595
631
  function getParamValueHelper(indent, param, inClosure, setter) {
596
- if (param.optional) {
632
+ if (param.optional && param.type.kind !== 'literal') {
597
633
  // optional params are in the unwrapped options local var
598
634
  const op = indent.get() + helpers.buildIfBlock(indent, {
599
635
  condition: `let Some(${param.name}) = ${inClosure ? '&' : ''}options.${param.name}`,
@@ -622,7 +658,10 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
622
658
  }
623
659
  /** returns & if the param needs to be borrowed (which is the majority of cases), else the empty string */
624
660
  const borrowOrNot = function (param) {
625
- if (param.type.kind !== 'ref' || param.type.type.kind === 'encodedBytes' || param.type.type.kind === 'slice') {
661
+ // for string-based enums we call .as_ref() which elides the need to borrow.
662
+ // for numeric-based enums the borrow will be necessary.
663
+ // TODO: https://github.com/Azure/typespec-rust/issues/25
664
+ if (param.type.kind !== 'enum' && (param.type.kind !== 'ref' || param.type.type.kind === 'encodedBytes' || param.type.type.kind === 'slice')) {
626
665
  return '&';
627
666
  }
628
667
  return '';
@@ -645,7 +684,7 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
645
684
  body += `${indent.get()}let mut path = String::from(${path});\n`;
646
685
  for (const pathParam of paramGroups.path) {
647
686
  let wrapSortedVec = (s) => s;
648
- let paramExpression = `${borrowOrNot(pathParam)}${getHeaderPathQueryParamValue(use, pathParam, true)}`;
687
+ let paramExpression = getHeaderPathQueryParamValue(use, pathParam, true);
649
688
  if (pathParam.kind === 'pathHashMap') {
650
689
  wrapSortedVec = (s) => `${indent.get()}{`
651
690
  + `${indent.push().get()}let mut ${pathParam.name}_vec = ${pathParam.name}.iter().collect::<Vec<_>>();\n`
@@ -706,9 +745,22 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
706
745
  case 'matrix':
707
746
  paramExpression = `&format!(";${pathParam.name}={${paramExpression}}")`;
708
747
  break;
748
+ default:
749
+ paramExpression = `${borrowOrNot(pathParam)}${paramExpression}`;
709
750
  }
710
751
  }
711
- body += wrapSortedVec(`${indent.get()}path = path.replace("{${pathParam.segment}}", ${paramExpression});\n`);
752
+ if (pathParam.optional) {
753
+ body += `${indent.get()}path = ${helpers.buildMatch(indent, `options.${pathParam.name}`, [{
754
+ pattern: `Some(${pathParam.name})`,
755
+ body: (indent) => wrapSortedVec(`${indent.get()}path.replace("{${pathParam.segment}}", ${paramExpression})\n`),
756
+ }, {
757
+ pattern: `None`,
758
+ body: (indent) => `${indent.get()}path.replace("{${pathParam.segment}}", "")\n`,
759
+ }])};\n`;
760
+ }
761
+ else {
762
+ body += wrapSortedVec(`${indent.get()}path = path.replace("{${pathParam.segment}}", ${paramExpression});\n`);
763
+ }
712
764
  }
713
765
  path = '&path';
714
766
  body += `${indent.get()}${urlVarName} = ${urlVarName}.join(${path})?;\n`;
@@ -774,27 +826,19 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
774
826
  return body;
775
827
  }
776
828
  /**
777
- * emits the code for building the HTTP request.
778
- * assumes that there's a local var 'url' which is the Url.
779
- * creates a mutable local 'request' which is the Request instance.
829
+ * emits the code for setting HTTP headers in a request.
780
830
  *
781
831
  * @param indent the indentation helper currently in scope
782
832
  * @param use the use statement builder currently in scope
783
833
  * @param method the method for which we're building the body
784
834
  * @param paramGroups the param groups for the provided method
785
835
  * @param inClosure indicates if the request is being constructed within a closure (e.g. pageable methods)
786
- * @returns the request construction code
836
+ * @returns the code which sets HTTP headers for the request
787
837
  */
788
- function constructRequest(indent, use, method, paramGroups, inClosure) {
838
+ function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
789
839
  var _a, _b, _c;
790
- let body = `${indent.get()}let mut request = Request::new(url, Method::${codegen.capitalize(method.httpMethod)});\n`;
791
- let optionalContentTypeParam;
840
+ let body = '';
792
841
  for (const headerParam of paramGroups.header) {
793
- // if the content-type header is optional, we need to emit it inside the "if let Some(body)" clause below.
794
- if (headerParam.kind === 'headerScalar' && headerParam.optional && headerParam.header.toLowerCase() === 'content-type') {
795
- optionalContentTypeParam = headerParam;
796
- continue;
797
- }
798
842
  if (method.kind === 'pageable' && ((_a = method.strategy) === null || _a === void 0 ? void 0 : _a.kind) === 'continuationToken' && ((_b = method.strategy) === null || _b === void 0 ? void 0 : _b.requestToken.kind) === 'headerScalar' && ((_c = method.strategy) === null || _c === void 0 ? void 0 : _c.requestToken) === headerParam) {
799
843
  // we have some special handling for the header continuation token.
800
844
  // if we have a token value, i.e. from the next page, then use that value.
@@ -805,6 +849,12 @@ function constructRequest(indent, use, method, paramGroups, inClosure) {
805
849
  });
806
850
  continue;
807
851
  }
852
+ if (isOptionalContentTypeHeader(headerParam)) {
853
+ // when the body is optional, the Content-Type header
854
+ // will be set IFF the optional body param is not None.
855
+ // this logic happens elsewhere so we skip it here.
856
+ continue;
857
+ }
808
858
  body += getParamValueHelper(indent, headerParam, inClosure, () => {
809
859
  if (headerParam.kind === 'headerHashMap') {
810
860
  let setter = `for (k, v) in &${headerParam.name} {\n`;
@@ -818,6 +868,36 @@ function constructRequest(indent, use, method, paramGroups, inClosure) {
818
868
  return `${indent.get()}request.insert_header("${headerParam.header.toLowerCase()}", ${borrow}${getHeaderPathQueryParamValue(use, headerParam, !inClosure)});\n`;
819
869
  });
820
870
  }
871
+ return body;
872
+ }
873
+ /** type guard to determine if headerParam is an optional Content-Type header */
874
+ function isOptionalContentTypeHeader(headerParam) {
875
+ return headerParam.kind === 'headerScalar' && headerParam.optional && headerParam.header.toLowerCase() === 'content-type';
876
+ }
877
+ /**
878
+ * emits the code for building the HTTP request.
879
+ * assumes that there's a local var 'url' which is the Url.
880
+ * creates a mutable local 'request' which is the Request instance.
881
+ *
882
+ * @param indent the indentation helper currently in scope
883
+ * @param use the use statement builder currently in scope
884
+ * @param method the method for which we're building the body
885
+ * @param paramGroups the param groups for the provided method
886
+ * @param inClosure indicates if the request is being constructed within a closure (e.g. pageable methods)
887
+ * @param cloneUrl indicates if url should be cloned when it is passed to initialize request
888
+ * @param forceMut indicates whether request should get declared as 'mut' regardless of whether there are any headers to set
889
+ * @returns the request construction code
890
+ */
891
+ function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl = false, forceMut = true) {
892
+ let body = `${indent.get()}let ${(forceMut || paramGroups.header.length > 0) ? 'mut ' : ''}request = Request::new(url${cloneUrl ? '.clone()' : ''}, Method::${codegen.capitalize(method.httpMethod)});\n`;
893
+ body += applyHeaderParams(indent, use, method, paramGroups, inClosure);
894
+ let optionalContentTypeParam;
895
+ for (const headerParam of paramGroups.header) {
896
+ // if the content-type header is optional, we need to emit it inside the "if let Some(body)" clause below.
897
+ if (isOptionalContentTypeHeader(headerParam)) {
898
+ optionalContentTypeParam = headerParam;
899
+ }
900
+ }
821
901
  const bodyParam = paramGroups.body;
822
902
  if (bodyParam) {
823
903
  body += getParamValueHelper(indent, bodyParam, inClosure, () => {
@@ -860,24 +940,15 @@ function constructRequest(indent, use, method, paramGroups, inClosure) {
860
940
  return body;
861
941
  }
862
942
  /**
863
- * Return an error if a local RawResponse variable "rsp" has a non-success status code.
943
+ * Return an error if a local BufResponse variable "rsp" has a non-success status code.
864
944
  * @param use the use statement builder currently in scope
865
945
  * @param indent the indentation helper currently in scope
866
946
  * @returns code to return an error if the status code of "rsp" doesn't indicate success
867
947
  */
868
948
  function errIfNotSuccessResponse(use, indent) {
869
- use.add('azure_core', 'Error', 'Result');
870
- use.add('azure_core::error', 'ErrorKind', 'HttpError');
871
- return helpers.buildIfBlock(indent, {
872
- condition: '!rsp.status().is_success()',
873
- body: (indent) => {
874
- let body = `${indent.get()}let status = rsp.status();\n`;
875
- body += `${indent.get()}let http_error = HttpError::new(rsp).await;\n`;
876
- body += `${indent.get()}let error_kind = ErrorKind::http_response(status, http_error.error_code().map(std::borrow::ToOwned::to_owned));\n`;
877
- body += `${indent.get()}return Err(Error::new(error_kind, http_error));\n`;
878
- return body;
879
- },
880
- });
949
+ use.add('azure_core::http', 'check_success');
950
+ const body = `${indent.get()}let rsp = check_success(rsp).await?;\n`;
951
+ return body;
881
952
  }
882
953
  /**
883
954
  * Returns 'mut ' if the Url local var needs to be mutable, else the empty string.
@@ -891,6 +962,58 @@ function urlVarNeedsMut(paramGroups, method) {
891
962
  }
892
963
  return '';
893
964
  }
965
+ /**
966
+ * emits "if path_param is empty then error" checks for string method path parameters
967
+ *
968
+ * @param indent the indentation helper currently in scope
969
+ * @param params the path params to enumerate, can be empty
970
+ * @returns the empty path param checks or the empty string if there are no checks
971
+ */
972
+ function checkEmptyRequiredPathParams(indent, params) {
973
+ let checks = '';
974
+ for (const param of params) {
975
+ if (param.optional || param.location === 'client') {
976
+ continue;
977
+ }
978
+ checks += emitEmptyPathParamCheck(indent, param);
979
+ }
980
+ return checks;
981
+ }
982
+ /**
983
+ * emits the "if path_param is empty then error" check.
984
+ * this is only applicable when the path param's type can
985
+ * be empty (e.g. a string). for types that can't be empty
986
+ * the empty string is returned.
987
+ *
988
+ * @param indent the indentation helper currently in scope
989
+ * @param param the path param for which to emit the check
990
+ * @returns the check or the empty string
991
+ */
992
+ function emitEmptyPathParamCheck(indent, param) {
993
+ let toString = '';
994
+ const paramType = param.type.kind === 'ref' ? param.type.type : param.type;
995
+ switch (paramType.kind) {
996
+ case 'String':
997
+ case 'str':
998
+ // need to check these for zero length
999
+ break;
1000
+ case 'enum':
1001
+ if (!paramType.extensible) {
1002
+ // fixed enums will always have a value
1003
+ return '';
1004
+ }
1005
+ // need to get the underlying string value
1006
+ toString = '.as_ref()';
1007
+ break;
1008
+ default:
1009
+ // no length to check so bail
1010
+ return '';
1011
+ }
1012
+ return helpers.buildIfBlock(indent, {
1013
+ condition: `${param.name}${toString}.is_empty()`,
1014
+ body: (indent) => `${indent.get()}return Err(azure_core::Error::message(azure_core::error::ErrorKind::Other, "parameter ${param.name} cannot be empty"));`,
1015
+ });
1016
+ }
894
1017
  /**
895
1018
  * constructs the body for an async client method
896
1019
  *
@@ -901,17 +1024,18 @@ function urlVarNeedsMut(paramGroups, method) {
901
1024
  * @returns the contents of the method body
902
1025
  */
903
1026
  function getAsyncMethodBody(indent, use, client, method) {
904
- use.add('azure_core::http', 'Context', 'Method', 'Request');
1027
+ use.add('azure_core::http', 'Method', 'Request');
905
1028
  const paramGroups = getMethodParamGroup(method);
906
- let body = 'let options = options.unwrap_or_default();\n';
907
- body += `${indent.get()}let ctx = Context::with_context(&options.method_options.context);\n`;
1029
+ let body = checkEmptyRequiredPathParams(indent, paramGroups.path);
1030
+ body += 'let options = options.unwrap_or_default();\n';
1031
+ body += `${indent.get()}let ctx = options.method_options.context.to_borrowed();\n`;
908
1032
  body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}url = self.${getEndpointFieldName(client)}.clone();\n`;
909
1033
  body += constructUrl(indent, use, method, paramGroups);
910
1034
  body += constructRequest(indent, use, method, paramGroups, false);
911
1035
  body += `${indent.get()}let rsp = self.pipeline.send(&ctx, &mut request).await?;\n`;
912
1036
  body += errIfNotSuccessResponse(use, indent);
913
1037
  let rspInto = 'rsp.into()';
914
- if (method.returns.type.kind === 'rawResponse') {
1038
+ if (method.returns.type.kind === 'bufResponse') {
915
1039
  rspInto = 'rsp';
916
1040
  }
917
1041
  body += `${indent.get()}Ok(${rspInto})\n`;
@@ -931,13 +1055,15 @@ function getPageableMethodBody(indent, use, client, method) {
931
1055
  throw new CodegenError('InternalError', 'paged method with no strategy NYI');
932
1056
  }
933
1057
  const bodyFormat = helpers.convertResponseFormat(method.returns.type.type.format);
934
- use.add('azure_core::http', 'Method', 'PagerResult', 'PagerState', 'Request', 'Url');
1058
+ use.add('azure_core::http', 'Method', 'Request', 'Url');
1059
+ use.add('azure_core::http::pager', 'PagerResult', 'PagerState');
935
1060
  use.add('azure_core', bodyFormat, 'Result');
936
1061
  use.addForType(method.returns.type);
937
1062
  use.addForType(helpers.unwrapType(method.returns.type));
938
1063
  const paramGroups = getMethodParamGroup(method);
939
1064
  const urlVar = 'first_url';
940
- let body = 'let options = options.unwrap_or_default().into_owned();\n';
1065
+ let body = checkEmptyRequiredPathParams(indent, paramGroups.path);
1066
+ body += 'let options = options.unwrap_or_default().into_owned();\n';
941
1067
  body += `${indent.get()}let pipeline = self.pipeline.clone();\n`;
942
1068
  body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}${urlVar} = self.${getEndpointFieldName(client)}.clone();\n`;
943
1069
  body += constructUrl(indent, use, method, paramGroups, urlVar);
@@ -1001,38 +1127,33 @@ function getPageableMethodBody(indent, use, client, method) {
1001
1127
  break;
1002
1128
  }
1003
1129
  }
1004
- let rspType;
1005
- let rspInto;
1130
+ // Pipeline::send() returns a BufResponse, so no reason to declare the type if not something else.
1131
+ let rspType = '';
1132
+ let rspInto = '';
1006
1133
  if (method.strategy.kind === 'continuationToken' && method.strategy.responseToken.kind === 'responseHeaderScalar') {
1007
1134
  // the continuation token comes from a response header. therefore,
1008
1135
  // we need a Response<T> so we have access to the header trait.
1009
1136
  use.addForType(method.returns.type.type);
1010
- rspType = helpers.getTypeDeclaration(method.returns.type.type);
1137
+ const rspTypeDecl = helpers.getTypeDeclaration(method.returns.type.type);
1138
+ rspType = `: ${rspTypeDecl}`;
1011
1139
  rspInto = '.into()';
1012
1140
  }
1013
- else {
1014
- // continuation token comes from the response body.
1015
- // we'll deconstruct the raw response and parse it later.
1016
- use.add('azure_core', 'http::RawResponse');
1017
- rspType = 'RawResponse';
1018
- rspInto = '';
1019
- }
1020
1141
  body += constructRequest(indent, use, method, paramGroups, true);
1021
1142
  body += `${indent.get()}let ctx = options.method_options.context.clone();\n`;
1022
1143
  body += `${indent.get()}let pipeline = pipeline.clone();\n`;
1023
1144
  body += `${indent.get()}async move {\n`;
1024
- body += `${indent.push().get()}let rsp: ${rspType} = pipeline.send(&ctx, &mut request).await?${rspInto};\n`;
1025
- if (rspType === 'RawResponse') {
1145
+ body += `${indent.push().get()}let rsp${rspType} = pipeline.send(&ctx, &mut request).await?${rspInto};\n`;
1146
+ if (rspType === '') {
1026
1147
  body += errIfNotSuccessResponse(use, indent);
1027
1148
  }
1028
1149
  // check if we need to extract the next link field from the response model
1029
1150
  if (method.strategy.kind === 'nextLink' || method.strategy.responseToken.kind === 'modelField') {
1030
- use.add('azure_core', 'http::RawResponse');
1151
+ use.add('azure_core', 'http::BufResponse');
1031
1152
  body += `${indent.get()}let (status, headers, body) = rsp.deconstruct();\n`;
1032
1153
  body += `${indent.get()}let bytes = body.collect().await?;\n`;
1033
1154
  const deserialize = bodyFormat === 'json' ? 'json::from_json' : 'xml::read_xml';
1034
1155
  body += `${indent.get()}let res: ${helpers.getTypeDeclaration(helpers.unwrapType(method.returns.type))} = ${deserialize}(&bytes)?;\n`;
1035
- body += `${indent.get()}let rsp = RawResponse::from_bytes(status, headers, bytes).into();\n`;
1156
+ body += `${indent.get()}let rsp = BufResponse::from_bytes(status, headers, bytes).into();\n`;
1036
1157
  }
1037
1158
  let srcNextPage;
1038
1159
  let nextPageValue;
@@ -1081,13 +1202,100 @@ function getPageableMethodBody(indent, use, client, method) {
1081
1202
  body += `${indent.pop().get()}}))`; // end Ok/Pager::from_callback
1082
1203
  return body;
1083
1204
  }
1205
+ /**
1206
+ * constructs the body for an LRO client method
1207
+ *
1208
+ * @param indent the indentation helper currently in scope
1209
+ * @param use the use statement builder currently in scope
1210
+ * @param client the client to which the method belongs
1211
+ * @param method the method for the body to build
1212
+ * @returns the contents of the method body
1213
+ */
1214
+ function getLroMethodBody(indent, use, client, method) {
1215
+ const bodyFormat = helpers.convertResponseFormat(method.returns.type.type.format);
1216
+ use.add('azure_core::http', 'Method', 'BufResponse', 'Request', 'Url');
1217
+ use.add('azure_core::http::headers', 'RETRY_AFTER', 'X_MS_RETRY_AFTER_MS', 'RETRY_AFTER_MS');
1218
+ use.add('azure_core::http::poller', 'get_retry_after', 'PollerResult', 'PollerState', 'PollerStatus', 'StatusMonitor as _');
1219
+ use.addForType(method.returns.type);
1220
+ use.addForType(helpers.unwrapType(method.returns.type));
1221
+ const paramGroups = getMethodParamGroup(method);
1222
+ const urlVar = 'url';
1223
+ let body = 'let options = options.unwrap_or_default().into_owned();\n';
1224
+ body += `${indent.get()}let pipeline = self.pipeline.clone();\n`;
1225
+ body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}${urlVar} = self.${getEndpointFieldName(client)}.clone();\n`;
1226
+ body += `${constructUrl(indent, use, method, paramGroups, urlVar)}\n\n`;
1227
+ if (paramGroups.apiVersion) {
1228
+ body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true)}.clone();\n\n`;
1229
+ }
1230
+ body += `${indent.get()}Ok(${method.returns.type.name}::from_callback(\n`;
1231
+ body += `${indent.push().get()}move |next_link: PollerState<Url>| {\n`;
1232
+ body += `${indent.push().get()}let (mut request, next_link) = ${helpers.buildMatch(indent, 'next_link', [{
1233
+ pattern: `PollerState::More(next_link)`,
1234
+ body: (indent) => {
1235
+ let body = '';
1236
+ if (paramGroups.apiVersion) {
1237
+ body += `${indent.get()}let qp = next_link.query_pairs().filter(|(name, _)| name.ne("api-version"));\n`;
1238
+ }
1239
+ body += `${indent.get()}let mut next_link = next_link.clone();\n`;
1240
+ if (paramGroups.apiVersion) {
1241
+ body += `${indent.get()}next_link.query_pairs_mut().clear().extend_pairs(qp).append_pair("api-version", &${paramGroups.apiVersion.name});\n\n`;
1242
+ }
1243
+ body += `${indent.get()}let ${paramGroups.header.length > 0 ? 'mut ' : ''}request = Request::new(next_link.clone(), Method::Get);`;
1244
+ body += `\n${applyHeaderParams(indent, use, method, paramGroups, true)}\n\n`;
1245
+ body += `${indent.get()}(request, next_link)\n`;
1246
+ return body;
1247
+ },
1248
+ }, {
1249
+ pattern: 'PollerState::Initial',
1250
+ body: (indent) => {
1251
+ let body = `${constructRequest(indent, use, method, paramGroups, true, true, false)}\n`;
1252
+ body += `${indent.get()}(request, url.clone())\n`;
1253
+ return body;
1254
+ },
1255
+ }])};\n\n`;
1256
+ body += `${indent.get()}let ctx = options.method_options.context.clone();\n`;
1257
+ body += `${indent.get()}let pipeline = pipeline.clone();\n`;
1258
+ body += `${indent.get()}async move {\n`;
1259
+ body += `${indent.push().get()}let rsp = pipeline.send(&ctx, &mut request).await?;\n`;
1260
+ body += `${indent.get()}let (status, headers, body) = rsp.deconstruct();\n`;
1261
+ body += `${indent.get()}let retry_after = get_retry_after(&headers, &[X_MS_RETRY_AFTER_MS, RETRY_AFTER_MS, RETRY_AFTER], &options.poller_options);\n`;
1262
+ body += `${indent.get()}let bytes = body.collect().await?;\n`;
1263
+ let deserialize = '';
1264
+ if (bodyFormat === 'json') {
1265
+ use.add('azure_core', 'json');
1266
+ deserialize = 'json::from_json';
1267
+ }
1268
+ else {
1269
+ deserialize = 'xml::read_xml';
1270
+ }
1271
+ body += `${indent.get()}let res: ${helpers.getTypeDeclaration(helpers.unwrapType(method.returns.type))} = ${deserialize}(&bytes)?;\n`;
1272
+ body += `${indent.get()}let rsp = BufResponse::from_bytes(status, headers, bytes).into();\n`;
1273
+ body += `${indent.get()}Ok(${helpers.buildMatch(indent, 'res.status()', [{
1274
+ pattern: `PollerStatus::InProgress`,
1275
+ body: (indent) => {
1276
+ return `${indent.get()}response: rsp, retry_after, next: next_link\n`;
1277
+ },
1278
+ returns: 'PollerResult::InProgress'
1279
+ }, {
1280
+ pattern: '_',
1281
+ body: (indent) => {
1282
+ return `${indent.get()}response: rsp`;
1283
+ },
1284
+ returns: 'PollerResult::Done'
1285
+ }])})\n`;
1286
+ body += `${indent.pop().get()}}\n`; // end async move
1287
+ body += `${indent.pop().get()}},\n`; // end move
1288
+ body += `${indent.get()}None,\n`;
1289
+ body += `${indent.pop().get()}))`; // end Ok/Poller::from_callback
1290
+ return body;
1291
+ }
1084
1292
  /**
1085
1293
  * contains the code to use when populating a client endpoint parameter value
1086
1294
  * from a parameter of that type.
1087
1295
  * @param param the param for which to get the value
1088
1296
  * @returns the code to use for the param's value
1089
1297
  */
1090
- function getClientEndpointParamValue(param) {
1298
+ function getClientSupplementalEndpointParamValue(param) {
1091
1299
  let paramName = param.name;
1092
1300
  if (param.optional) {
1093
1301
  paramName = 'options.' + paramName;
@@ -1179,8 +1387,8 @@ function getHeaderPathQueryParamValue(use, param, fromSelf) {
1179
1387
  return encodeBytes(paramType, paramName);
1180
1388
  case 'enum':
1181
1389
  case 'scalar':
1182
- if (paramType.kind === 'enum' && param.kind === 'queryScalar') {
1183
- // append_pair wants a reference to the string
1390
+ if (paramType.kind === 'enum' && (param.kind === 'pathScalar' || param.kind === 'queryScalar')) {
1391
+ // append_pair and path.replace() want a reference to the string
1184
1392
  // TODO: https://github.com/Azure/typespec-rust/issues/25
1185
1393
  return `${paramName}.as_ref()`;
1186
1394
  }
@@ -1231,9 +1439,4 @@ function nonCopyableType(type) {
1231
1439
  function isClientMethodOptions(type) {
1232
1440
  return type.kind === 'external' && type.name === 'ClientMethodOptions';
1233
1441
  }
1234
- /** returns true if the type is a credential */
1235
- function isCredential(type) {
1236
- const unwrappedType = helpers.unwrapType(type);
1237
- return unwrappedType.kind === 'tokenCredential';
1238
- }
1239
1442
  //# sourceMappingURL=clients.js.map