@azure-tools/typespec-rust 0.24.0 → 0.25.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 (36) hide show
  1. package/dist/src/codegen/clients.d.ts.map +1 -1
  2. package/dist/src/codegen/clients.js +205 -120
  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 +3 -1
  6. package/dist/src/codegen/context.js.map +1 -1
  7. package/dist/src/codegen/enums.d.ts +2 -2
  8. package/dist/src/codegen/enums.js +57 -22
  9. package/dist/src/codegen/enums.js.map +1 -1
  10. package/dist/src/codegen/helpers.d.ts +10 -0
  11. package/dist/src/codegen/helpers.d.ts.map +1 -1
  12. package/dist/src/codegen/helpers.js +29 -3
  13. package/dist/src/codegen/helpers.js.map +1 -1
  14. package/dist/src/codegen/lib.js +1 -1
  15. package/dist/src/codegen/lib.js.map +1 -1
  16. package/dist/src/codegen/models.js +12 -6
  17. package/dist/src/codegen/models.js.map +1 -1
  18. package/dist/src/codemodel/client.d.ts +10 -8
  19. package/dist/src/codemodel/client.d.ts.map +1 -1
  20. package/dist/src/codemodel/client.js +1 -0
  21. package/dist/src/codemodel/client.js.map +1 -1
  22. package/dist/src/codemodel/crate.d.ts.map +1 -1
  23. package/dist/src/codemodel/crate.js +5 -2
  24. package/dist/src/codemodel/crate.js.map +1 -1
  25. package/dist/src/codemodel/types.d.ts +2 -11
  26. package/dist/src/codemodel/types.d.ts.map +1 -1
  27. package/dist/src/codemodel/types.js +0 -7
  28. package/dist/src/codemodel/types.js.map +1 -1
  29. package/dist/src/shared/shared.d.ts.map +1 -1
  30. package/dist/src/shared/shared.js +4 -1
  31. package/dist/src/shared/shared.js.map +1 -1
  32. package/dist/src/tcgcadapter/adapter.d.ts +9 -1
  33. package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
  34. package/dist/src/tcgcadapter/adapter.js +117 -72
  35. package/dist/src/tcgcadapter/adapter.js.map +1 -1
  36. package/package.json +17 -17
@@ -201,13 +201,20 @@ export function emitClients(crate) {
201
201
  if (method.kind !== 'clientaccessor' && method.responseHeaders) {
202
202
  body += getHeaderTraitDocComment(indent, crate, method);
203
203
  }
204
+ const paramsInfo = getMethodParamsCountAndSig(method, use);
205
+ if (paramsInfo.count > 7) {
206
+ // clippy will by default warn on 7+ args in a method.
207
+ // note that this doesn't include self which is included
208
+ // in the count.
209
+ body += `${indent.get()}#[allow(clippy::too_many_arguments)]\n`;
210
+ }
204
211
  if (isPublicApi) {
205
212
  body += `${indent.get()}#[tracing::function("${method.languageIndependentName}")]\n`;
206
213
  }
207
214
  else if (isSubclientNew) {
208
215
  body += `${indent.get()}#[tracing::subclient]\n`;
209
216
  }
210
- body += `${indent.get()}${helpers.emitVisibility(method.visibility)}${async}fn ${method.name}(${getMethodParamsSig(method, use)}) -> ${returnType} {\n`;
217
+ body += `${indent.get()}${helpers.emitVisibility(method.visibility)}${async}fn ${method.name}(${paramsInfo.sig}) -> ${returnType} {\n`;
211
218
  body += `${indent.push().get()}${methodBody(indent)}\n`;
212
219
  body += `${indent.pop().get()}}\n`; // end method
213
220
  if (i + 1 < client.methods.length) {
@@ -381,21 +388,24 @@ function getConstructorParamsSig(params, options, use) {
381
388
  }
382
389
  /**
383
390
  * creates the parameter signature for a client method
384
- * e.g. "foo: i32, bar: String, options: MethodOptions"
391
+ * e.g. "foo: i32, bar: String, options: MethodOptions".
392
+ * also returns the number of parameters in the sig.
385
393
  *
386
394
  * @param method the Rust method for which to create the param sig
387
395
  * @param use the use statement builder currently in scope
388
- * @returns the method params sig
396
+ * @returns the method params count and sig
389
397
  */
390
- function getMethodParamsSig(method, use) {
398
+ function getMethodParamsCountAndSig(method, use) {
391
399
  const paramsSig = new Array();
392
400
  paramsSig.push(formatParamTypeName(method.self));
401
+ let count = 1; // self
393
402
  if (method.kind === 'clientaccessor') {
394
403
  // client accessor params don't have a concept
395
404
  // of optionality nor do they contain literals
396
405
  for (const param of method.params) {
397
406
  use.addForType(param.type);
398
407
  paramsSig.push(`${param.name}: ${formatParamTypeName(param)}`);
408
+ ++count;
399
409
  }
400
410
  }
401
411
  else {
@@ -414,11 +424,13 @@ function getMethodParamsSig(method, use) {
414
424
  if (param.location === 'method' && !param.optional) {
415
425
  use.addForType(param.type);
416
426
  paramsSig.push(`${param.name}: ${formatParamTypeName(param)}`);
427
+ ++count;
417
428
  }
418
429
  }
419
430
  paramsSig.push(`options: ${helpers.getTypeDeclaration(method.options, 'anonymous')}`);
431
+ ++count;
420
432
  }
421
- return paramsSig.join(', ');
433
+ return { count: count, sig: paramsSig.join(', ') };
422
434
  }
423
435
  /**
424
436
  * returns documentation for header trait access if the method has response headers.
@@ -436,7 +448,18 @@ function getHeaderTraitDocComment(indent, crate, method) {
436
448
  let headerDocs = `${indent.get()}///\n`;
437
449
  headerDocs += `${indent.get()}/// ## Response Headers\n`;
438
450
  headerDocs += `${indent.get()}///\n`;
439
- headerDocs += `${indent.get()}/// The returned [${helpers.wrapInBackTicks('Response')}](azure_core::http::Response) implements the [${helpers.wrapInBackTicks(traitName)}] trait, which provides\n`;
451
+ let returnType;
452
+ switch (method.returns.type.kind) {
453
+ case 'asyncResponse':
454
+ case 'response':
455
+ returnType = method.returns.type.name;
456
+ break;
457
+ default:
458
+ // for pagers/pollers we want their generic type argument type name
459
+ returnType = method.returns.type.type.name;
460
+ break;
461
+ }
462
+ headerDocs += `${indent.get()}/// The returned [${helpers.wrapInBackTicks(returnType)}](azure_core::http::${returnType}) implements the [${helpers.wrapInBackTicks(traitName)}] trait, which provides\n`;
440
463
  headerDocs += `${indent.get()}/// access to response headers. For example:\n`;
441
464
  headerDocs += `${indent.get()}///\n`;
442
465
  headerDocs += emitHeaderTraitDocExample(crate.name, method.responseHeaders, indent);
@@ -634,7 +657,7 @@ function getParamValueHelper(indent, param, inClosure, setter) {
634
657
  if (param.optional && param.type.kind !== 'literal') {
635
658
  // optional params are in the unwrapped options local var
636
659
  const op = indent.get() + helpers.buildIfBlock(indent, {
637
- condition: `let Some(${param.name}) = ${inClosure ? '&' : ''}options.${param.name}`,
660
+ condition: `let Some(${param.name}) = ${inClosure && nonCopyableType(param.type) ? '&' : ''}options.${param.name}`,
638
661
  body: setter,
639
662
  });
640
663
  return op + '\n';
@@ -648,45 +671,37 @@ function getParamValueHelper(indent, param, inClosure, setter) {
648
671
  * @param use the use statement builder currently in scope
649
672
  * @param method the method for which we're building the body
650
673
  * @param paramGroups the param groups for the provided method
651
- * @param urlVarName the name of the var that contains the azure_core::Url. defaults to 'url'
674
+ * @param urlVarName the name of the var that contains the azure_core::Url
652
675
  * @returns the URL construction code
653
676
  */
654
- function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
677
+ function constructUrl(indent, use, method, paramGroups, urlVarName) {
655
678
  // for paths that contain query parameters, we must set the query params separately.
656
679
  // including them in the call to set_path() causes the chars to be path-escaped.
657
680
  const pathChunks = method.httpPath.split('?');
658
681
  if (pathChunks.length > 2) {
659
682
  throw new CodegenError('InternalError', 'too many HTTP path chunks');
660
683
  }
661
- /** returns & if the param needs to be borrowed (which is the majority of cases), else the empty string */
662
- const borrowOrNot = function (param) {
663
- // for string-based enums we call .as_ref() which elides the need to borrow.
664
- // for numeric-based enums the borrow will be necessary.
665
- // TODO: https://github.com/Azure/typespec-rust/issues/25
666
- if (param.type.kind !== 'enum' && (param.type.kind !== 'ref' || param.type.type.kind === 'encodedBytes' || param.type.type.kind === 'slice')) {
667
- return '&';
668
- }
669
- return '';
670
- };
671
684
  let body = '';
672
685
  // if path is just "/" no need to set it again, we're already there
673
686
  if (pathChunks[0] !== '/') {
687
+ use.add('azure_core::http', 'UrlExt');
674
688
  let path = `"${pathChunks[0]}"`;
675
689
  if (paramGroups.path.length === 0) {
676
690
  // no path params, just a static path
677
- body += `${indent.get()}${urlVarName} = ${urlVarName}.join(${path})?;\n`;
691
+ body += `${indent.get()}${urlVarName}.append_path(${path});\n`;
678
692
  }
679
693
  else if (paramGroups.path.length === 1 && pathChunks[0] === `{${paramGroups.path[0].segment}}`) {
680
694
  // for a single path param (i.e. "{foo}") we can directly join the path param's value
681
695
  const pathParam = paramGroups.path[0];
682
- body += `${indent.get()}${urlVarName} = ${urlVarName}.join(${borrowOrNot(pathParam)}${getHeaderPathQueryParamValue(use, pathParam, true)})?;\n`;
696
+ body += `${indent.get()}${urlVarName}.append_path(${getHeaderPathQueryParamValue(use, pathParam, true, false)});\n`;
683
697
  }
684
698
  else {
685
699
  // we have path params that need to have their segments replaced with the param values
686
- body += `${indent.get()}let mut path = String::from(${path});\n`;
700
+ const pathVarName = helpers.getUniqueVarName(method.params, ['path', 'path_var']);
701
+ body += `${indent.get()}let mut ${pathVarName} = String::from(${path});\n`;
687
702
  for (const pathParam of paramGroups.path) {
688
703
  let wrapSortedVec = (s) => s;
689
- let paramExpression = getHeaderPathQueryParamValue(use, pathParam, true);
704
+ let paramExpression;
690
705
  if (pathParam.kind === 'pathHashMap') {
691
706
  wrapSortedVec = (s) => `${indent.get()}{`
692
707
  + `${indent.push().get()}let mut ${pathParam.name}_vec = ${pathParam.name}.iter().collect::<Vec<_>>();\n`
@@ -737,6 +752,8 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
737
752
  }
738
753
  }
739
754
  else {
755
+ // skip borrowing by default as we borrow from format!()
756
+ paramExpression = getHeaderPathQueryParamValue(use, pathParam, true, true);
740
757
  switch (pathParam.style) {
741
758
  case 'path':
742
759
  paramExpression = `&format!("/{${paramExpression}}")`;
@@ -748,24 +765,25 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
748
765
  paramExpression = `&format!(";${pathParam.name}={${paramExpression}}")`;
749
766
  break;
750
767
  default:
751
- paramExpression = `${borrowOrNot(pathParam)}${paramExpression}`;
768
+ // use default borrowing calculation logic
769
+ paramExpression = getHeaderPathQueryParamValue(use, pathParam, true, false);
752
770
  }
753
771
  }
754
772
  if (pathParam.optional) {
755
- body += `${indent.get()}path = ${helpers.buildMatch(indent, `options.${pathParam.name}`, [{
773
+ body += `${indent.get()}${pathVarName} = ${helpers.buildMatch(indent, `options.${pathParam.name}`, [{
756
774
  pattern: `Some(${pathParam.name})`,
757
- body: (indent) => wrapSortedVec(`${indent.get()}path.replace("{${pathParam.segment}}", ${paramExpression})\n`),
775
+ body: (indent) => wrapSortedVec(`${indent.get()}${pathVarName}.replace("{${pathParam.segment}}", ${paramExpression})\n`),
758
776
  }, {
759
777
  pattern: `None`,
760
- body: (indent) => `${indent.get()}path.replace("{${pathParam.segment}}", "")\n`,
778
+ body: (indent) => `${indent.get()}${pathVarName}.replace("{${pathParam.segment}}", "")\n`,
761
779
  }])};\n`;
762
780
  }
763
781
  else {
764
- body += wrapSortedVec(`${indent.get()}path = path.replace("{${pathParam.segment}}", ${paramExpression});\n`);
782
+ body += wrapSortedVec(`${indent.get()}${pathVarName} = ${pathVarName}.replace("{${pathParam.segment}}", ${paramExpression});\n`);
765
783
  }
766
784
  }
767
- path = '&path';
768
- body += `${indent.get()}${urlVarName} = ${urlVarName}.join(${path})?;\n`;
785
+ path = `&${pathVarName}`;
786
+ body += `${indent.get()}${urlVarName}.append_path(${path});\n`;
769
787
  }
770
788
  }
771
789
  if (pathChunks.length === 2) {
@@ -819,9 +837,7 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
819
837
  }
820
838
  else {
821
839
  body += getParamValueHelper(indent, queryParam, false, () => {
822
- // for enums we call .as_ref() which elides the need to borrow
823
- const borrow = queryParam.type.kind === 'enum' ? '' : borrowOrNot(queryParam);
824
- return `${indent.get()}${urlVarName}.query_pairs_mut().append_pair("${queryParam.key}", ${borrow}${getHeaderPathQueryParamValue(use, queryParam, !queryParam.optional)});\n`;
840
+ return `${indent.get()}${urlVarName}.query_pairs_mut().append_pair("${queryParam.key}", ${getHeaderPathQueryParamValue(use, queryParam, !queryParam.optional, false)});\n`;
825
841
  });
826
842
  }
827
843
  }
@@ -837,7 +853,7 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
837
853
  * @param inClosure indicates if the request is being constructed within a closure (e.g. pageable methods)
838
854
  * @returns the code which sets HTTP headers for the request
839
855
  */
840
- function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
856
+ function applyHeaderParams(indent, use, method, paramGroups, inClosure, requestVarName) {
841
857
  var _a, _b, _c;
842
858
  let body = '';
843
859
  for (const headerParam of paramGroups.header) {
@@ -857,7 +873,7 @@ function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
857
873
  ]) + ';\n';
858
874
  body += indent.get() + helpers.buildIfBlock(indent, {
859
875
  condition: `let Some(${headerParam.name}) = ${headerParam.name}`,
860
- body: (indent) => `${indent.get()}request.insert_header("${headerParam.header}", ${headerParam.name});\n`,
876
+ body: (indent) => `${indent.get()}${requestVarName}.insert_header("${headerParam.header}", ${headerParam.name});\n`,
861
877
  }) + '\n';
862
878
  continue;
863
879
  }
@@ -869,15 +885,12 @@ function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
869
885
  }
870
886
  body += getParamValueHelper(indent, headerParam, inClosure, () => {
871
887
  if (headerParam.kind === 'headerHashMap') {
872
- let setter = `for (k, v) in &${headerParam.name} {\n`;
873
- setter += `${indent.push().get()}request.insert_header(format!("${headerParam.header}-{k}"), v);\n`;
888
+ let setter = `for (k, v) in ${headerParam.type.kind === 'ref' ? '' : '&'}${headerParam.name} {\n`;
889
+ setter += `${indent.push().get()}${requestVarName}.insert_header(format!("${headerParam.header}-{k}"), v);\n`;
874
890
  setter += `${indent.pop().get()}}\n`;
875
891
  return setter;
876
892
  }
877
- // for non-copyable params (e.g. String), we need to borrow them if they're on the
878
- // client or we're in a closure and the param is required (header params are always owned)
879
- const borrow = nonCopyableType(headerParam.type) && (headerParam.location === 'client' || (inClosure && !headerParam.optional)) ? '&' : '';
880
- return `${indent.get()}request.insert_header("${headerParam.header.toLowerCase()}", ${borrow}${getHeaderPathQueryParamValue(use, headerParam, !inClosure)});\n`;
893
+ return `${indent.get()}${requestVarName}.insert_header("${headerParam.header.toLowerCase()}", ${getHeaderPathQueryParamValue(use, headerParam, !inClosure, false)});\n`;
881
894
  });
882
895
  }
883
896
  return body;
@@ -896,13 +909,17 @@ function isOptionalContentTypeHeader(headerParam) {
896
909
  * @param method the method for which we're building the body
897
910
  * @param paramGroups the param groups for the provided method
898
911
  * @param inClosure indicates if the request is being constructed within a closure (e.g. pageable methods)
912
+ * @param urlVarName the name of var that contains the URL
899
913
  * @param cloneUrl indicates if url should be cloned when it is passed to initialize request
900
914
  * @param forceMut indicates whether request should get declared as 'mut' regardless of whether there are any headers to set
901
915
  * @returns the request construction code
902
916
  */
903
- function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl = false, forceMut = true) {
904
- let body = `${indent.get()}let ${(forceMut || paramGroups.header.length > 0) ? 'mut ' : ''}request = Request::new(url${cloneUrl ? '.clone()' : ''}, Method::${codegen.capitalize(method.httpMethod)});\n`;
905
- body += applyHeaderParams(indent, use, method, paramGroups, inClosure);
917
+ function constructRequest(indent, use, method, paramGroups, inClosure, urlVarName, cloneUrl = false, forceMut = true) {
918
+ // when constructing the request var name we need to ensure
919
+ // that it doesn't collide with any parameter name.
920
+ const requestVarName = helpers.getUniqueVarName(method.params, ['request', 'core_req']);
921
+ let body = `${indent.get()}let ${(forceMut || paramGroups.header.length > 0) ? 'mut ' : ''}${requestVarName} = Request::new(${urlVarName}${cloneUrl ? '.clone()' : ''}, Method::${codegen.capitalize(method.httpMethod)});\n`;
922
+ body += applyHeaderParams(indent, use, method, paramGroups, inClosure, requestVarName);
906
923
  let optionalContentTypeParam;
907
924
  for (const headerParam of paramGroups.header) {
908
925
  // if the content-type header is optional, we need to emit it inside the "if let Some(body)" clause below.
@@ -915,9 +932,9 @@ function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl
915
932
  body += getParamValueHelper(indent, bodyParam, inClosure, () => {
916
933
  let bodyParamContent = '';
917
934
  if (optionalContentTypeParam) {
918
- bodyParamContent = `${indent.get()}request.insert_header("${optionalContentTypeParam.header.toLowerCase()}", ${getHeaderPathQueryParamValue(use, optionalContentTypeParam, !inClosure)});\n`;
935
+ bodyParamContent = `${indent.get()}${requestVarName}.insert_header("${optionalContentTypeParam.header.toLowerCase()}", ${getHeaderPathQueryParamValue(use, optionalContentTypeParam, !inClosure, false)});\n`;
919
936
  }
920
- bodyParamContent += `${indent.get()}request.set_body(${bodyParam.name}${inClosure ? '.clone()' : ''});\n`;
937
+ bodyParamContent += `${indent.get()}${requestVarName}.set_body(${bodyParam.name}${inClosure ? '.clone()' : ''});\n`;
921
938
  return bodyParamContent;
922
939
  });
923
940
  }
@@ -947,9 +964,12 @@ function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl
947
964
  body += `${indent.get()}${initializer},\n`;
948
965
  }
949
966
  body += `${indent.pop().get()}}.try_into()?;\n`;
950
- body += `${indent.get()}request.set_body(body);\n`;
967
+ body += `${indent.get()}${requestVarName}.set_body(body);\n`;
951
968
  }
952
- return body;
969
+ return {
970
+ requestVarName: requestVarName,
971
+ content: body
972
+ };
953
973
  }
954
974
  /**
955
975
  * Returns 'mut ' if the Url local var needs to be mutable, else the empty string.
@@ -1026,13 +1046,15 @@ function emitEmptyPathParamCheck(indent, param) {
1026
1046
  */
1027
1047
  function getAsyncMethodBody(indent, use, client, method) {
1028
1048
  use.add('azure_core::http', 'Method', 'Request');
1049
+ const urlVarName = helpers.getUniqueVarName(method.params, ['url', 'url_var']);
1029
1050
  const paramGroups = getMethodParamGroup(method);
1030
1051
  let body = checkEmptyRequiredPathParams(indent, paramGroups.path);
1031
1052
  body += 'let options = options.unwrap_or_default();\n';
1032
1053
  body += `${indent.get()}let ctx = options.method_options.context.to_borrowed();\n`;
1033
- body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}url = self.${getEndpointFieldName(client)}.clone();\n`;
1034
- body += constructUrl(indent, use, method, paramGroups);
1035
- body += constructRequest(indent, use, method, paramGroups, false);
1054
+ body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}${urlVarName} = self.${getEndpointFieldName(client)}.clone();\n`;
1055
+ body += constructUrl(indent, use, method, paramGroups, urlVarName);
1056
+ const requestResult = constructRequest(indent, use, method, paramGroups, false, urlVarName);
1057
+ body += requestResult.content;
1036
1058
  let pipelineMethod;
1037
1059
  switch (method.returns.type.kind) {
1038
1060
  case 'asyncResponse':
@@ -1042,7 +1064,7 @@ function getAsyncMethodBody(indent, use, client, method) {
1042
1064
  pipelineMethod = 'send';
1043
1065
  break;
1044
1066
  }
1045
- body += `${indent.get()}let rsp = self.pipeline.${pipelineMethod}(&ctx, &mut request, ${getPipelineOptions(indent, use, method)}).await?;\n`;
1067
+ body += `${indent.get()}let rsp = self.pipeline.${pipelineMethod}(&ctx, &mut ${requestResult.requestVarName}, ${getPipelineOptions(indent, use, method)}).await?;\n`;
1046
1068
  body += `${indent.get()}Ok(rsp.into())\n`;
1047
1069
  return body;
1048
1070
  }
@@ -1062,19 +1084,23 @@ function getPageableMethodBody(indent, use, client, method) {
1062
1084
  use.addForType(method.returns.type);
1063
1085
  use.addForType(helpers.unwrapType(method.returns.type));
1064
1086
  const paramGroups = getMethodParamGroup(method);
1065
- const urlVar = method.strategy ? 'first_url' : 'url';
1087
+ const urlVar = method.strategy ? 'first_url' : helpers.getUniqueVarName(method.params, ['url', 'url_var']);
1066
1088
  let body = checkEmptyRequiredPathParams(indent, paramGroups.path);
1067
1089
  body += 'let options = options.unwrap_or_default().into_owned();\n';
1068
1090
  body += `${indent.get()}let pipeline = self.pipeline.clone();\n`;
1069
1091
  body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}${urlVar} = self.${getEndpointFieldName(client)}.clone();\n`;
1070
1092
  body += constructUrl(indent, use, method, paramGroups, urlVar);
1071
- if (paramGroups.apiVersion) {
1072
- body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true)}.clone();\n`;
1073
- }
1074
1093
  // passed to constructRequest. we only need to
1075
1094
  // clone it for the non-continuation case.
1076
1095
  let cloneUrl = false;
1096
+ // this will be either the inner URL var created
1097
+ // during paging or the initial URL var when there's
1098
+ // no paging strategy
1099
+ let srcUrlVar;
1077
1100
  if (method.strategy) {
1101
+ if (paramGroups.apiVersion) {
1102
+ body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true, true)}.clone();\n`;
1103
+ }
1078
1104
  switch (method.strategy.kind) {
1079
1105
  case 'continuationToken': {
1080
1106
  const reqTokenParam = method.strategy.requestToken.name;
@@ -1102,33 +1128,47 @@ function getPageableMethodBody(indent, use, client, method) {
1102
1128
  }
1103
1129
  })}\n`;
1104
1130
  }
1131
+ srcUrlVar = 'url';
1105
1132
  break;
1106
1133
  }
1107
1134
  case 'nextLink': {
1108
1135
  const nextLinkName = method.strategy.nextLinkPath[method.strategy.nextLinkPath.length - 1].name;
1136
+ const reinjectedParams = method.strategy.reinjectedParams;
1109
1137
  body += `${indent.get()}Ok(${method.returns.type.name}::from_callback(move |${nextLinkName}: PagerState<Url>| {\n`;
1110
1138
  body += `${indent.push().get()}let url = ` + helpers.buildMatch(indent, nextLinkName, [{
1111
1139
  pattern: `PagerState::More(${nextLinkName})`,
1112
1140
  body: (indent) => {
1141
+ const cloneNextLink = `${indent.get()}let mut ${nextLinkName} = ${nextLinkName}.clone();\n`;
1142
+ let content = '';
1113
1143
  if (paramGroups.apiVersion && paramGroups.apiVersion.kind === 'queryScalar') {
1114
1144
  const apiVersionKey = `"${paramGroups.apiVersion.key}"`;
1115
1145
  // there are no APIs to set/update an existing query parameter.
1116
1146
  // so, we filter the existing query params to remove the api-version
1117
1147
  // query param. we then add back the filtered set and then add the
1118
1148
  // api-version as specified on the client.
1119
- let setApiVerBody = `${indent.get()}let qp = ${nextLinkName}.query_pairs().filter(|(name, _)| name.ne(${apiVersionKey}));\n`;
1120
- setApiVerBody += `${indent.get()}let mut ${nextLinkName} = ${nextLinkName}.clone();\n`;
1121
- setApiVerBody += `${indent.get()}${nextLinkName}.query_pairs_mut().clear().extend_pairs(qp).append_pair(${apiVersionKey}, &${paramGroups.apiVersion.name});\n`;
1122
- setApiVerBody += `${indent.get()}${nextLinkName}\n`;
1123
- return setApiVerBody;
1149
+ content = `${indent.get()}let qp = ${nextLinkName}.query_pairs().filter(|(name, _)| name.ne(${apiVersionKey}));\n`;
1150
+ content += cloneNextLink;
1151
+ content += `${indent.get()}${nextLinkName}.query_pairs_mut().clear().extend_pairs(qp).append_pair(${apiVersionKey}, &${paramGroups.apiVersion.name});\n`;
1152
+ }
1153
+ else if (reinjectedParams.length > 0) {
1154
+ // if we didn't clone it above, we'll need to clone it for this case
1155
+ content += cloneNextLink;
1124
1156
  }
1125
- return `${indent.get()} ${nextLinkName}\n`;
1157
+ // add query params for reinjection
1158
+ for (const reinjectedParam of reinjectedParams) {
1159
+ content += getParamValueHelper(indent, reinjectedParam, true, () => {
1160
+ return `${indent.get()}${nextLinkName}.query_pairs_mut().append_pair("${reinjectedParam.key}", ${getHeaderPathQueryParamValue(use, reinjectedParam, false, false)});\n`;
1161
+ });
1162
+ }
1163
+ content += `${indent.get()}${nextLinkName}\n`;
1164
+ return content;
1126
1165
  }
1127
1166
  }, {
1128
1167
  pattern: 'PagerState::Initial',
1129
1168
  body: (indent) => `${indent.get()}${urlVar}.clone()\n`
1130
1169
  }]);
1131
1170
  body += ';\n';
1171
+ srcUrlVar = 'url';
1132
1172
  break;
1133
1173
  }
1134
1174
  }
@@ -1136,7 +1176,9 @@ function getPageableMethodBody(indent, use, client, method) {
1136
1176
  else {
1137
1177
  // no next link when there's no strategy
1138
1178
  body += `${indent.get()}Ok(Pager::from_callback(move |_: PagerState<Url>| {\n`;
1179
+ indent.push();
1139
1180
  cloneUrl = true;
1181
+ srcUrlVar = urlVar;
1140
1182
  }
1141
1183
  // Pipeline::send() returns a RawResponse, so no reason to declare the type if not something else.
1142
1184
  let rspType = '';
@@ -1149,17 +1191,18 @@ function getPageableMethodBody(indent, use, client, method) {
1149
1191
  rspType = `: ${rspTypeDecl}`;
1150
1192
  rspInto = '.into()';
1151
1193
  }
1152
- body += constructRequest(indent, use, method, paramGroups, true, cloneUrl);
1194
+ const requestResult = constructRequest(indent, use, method, paramGroups, true, srcUrlVar, cloneUrl);
1195
+ body += requestResult.content;
1153
1196
  body += `${indent.get()}let ctx = options.method_options.context.clone();\n`;
1154
1197
  body += `${indent.get()}let pipeline = pipeline.clone();\n`;
1155
1198
  body += `${indent.get()}async move {\n`;
1156
- body += `${indent.push().get()}let rsp${rspType} = pipeline.send(&ctx, &mut request, ${getPipelineOptions(indent, use, method)}).await?${rspInto};\n`;
1199
+ body += `${indent.push().get()}let rsp${rspType} = pipeline.send(&ctx, &mut ${requestResult.requestVarName}, ${getPipelineOptions(indent, use, method)}).await?${rspInto};\n`;
1157
1200
  // check if we need to extract the next link field from the response model
1158
1201
  if (method.strategy && (method.strategy.kind === 'nextLink' || method.strategy.responseToken.kind === 'nextLink')) {
1159
1202
  const bodyFormat = helpers.convertResponseFormat(method.returns.type.type.format);
1160
1203
  use.add('azure_core', bodyFormat, 'http::RawResponse');
1161
1204
  body += `${indent.get()}let (status, headers, body) = rsp.deconstruct();\n`;
1162
- const deserialize = bodyFormat === 'json' ? 'json::from_json' : 'xml::read_xml';
1205
+ const deserialize = `${bodyFormat}::from_${bodyFormat}`;
1163
1206
  body += `${indent.get()}let res: ${helpers.getTypeDeclaration(helpers.unwrapType(method.returns.type))} = ${deserialize}(&body)?;\n`;
1164
1207
  body += `${indent.get()}let rsp = RawResponse::from_bytes(status, headers, body).into();\n`;
1165
1208
  }
@@ -1248,17 +1291,19 @@ function getLroMethodBody(indent, use, client, method) {
1248
1291
  use.addForType(method.returns.type);
1249
1292
  use.addForType(helpers.unwrapType(method.returns.type));
1250
1293
  const paramGroups = getMethodParamGroup(method);
1251
- const urlVar = 'url';
1294
+ const urlVar = helpers.getUniqueVarName(method.params, ['url', 'url_var']);
1252
1295
  let body = 'let options = options.unwrap_or_default().into_owned();\n';
1253
1296
  body += `${indent.get()}let pipeline = self.pipeline.clone();\n`;
1254
1297
  body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}${urlVar} = self.${getEndpointFieldName(client)}.clone();\n`;
1255
1298
  body += constructUrl(indent, use, method, paramGroups, urlVar);
1256
1299
  if (paramGroups.apiVersion) {
1257
- body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true)}.clone();\n`;
1300
+ body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true, true)}.clone();\n`;
1258
1301
  }
1302
+ // we call this eagerly so that we have access to the request var name
1303
+ const initialRequestResult = constructRequest(indent, use, method, paramGroups, true, urlVar, true, false);
1259
1304
  body += `${indent.get()}Ok(${method.returns.type.name}::from_callback(\n`;
1260
1305
  body += `${indent.push().get()}move |next_link: PollerState<Url>| {\n`;
1261
- body += `${indent.push().get()}let (mut request, next_link) = ${helpers.buildMatch(indent, 'next_link', [{
1306
+ body += `${indent.push().get()}let (mut ${initialRequestResult.requestVarName}, next_link) = ${helpers.buildMatch(indent, 'next_link', [{
1262
1307
  pattern: `PollerState::More(next_link)`,
1263
1308
  body: (indent) => {
1264
1309
  var _a;
@@ -1268,33 +1313,33 @@ function getLroMethodBody(indent, use, client, method) {
1268
1313
  body += `${indent.get()}let mut next_link = next_link.clone();\n`;
1269
1314
  body += `${indent.get()}next_link.query_pairs_mut().clear().extend_pairs(qp).append_pair("${paramGroups.apiVersion.key}", &${paramGroups.apiVersion.name});\n`;
1270
1315
  }
1271
- body += `${indent.get()}let ${paramGroups.header.length > 0 ? 'mut ' : ''}request = Request::new(next_link.clone(), Method::Get);\n`;
1272
- body += applyHeaderParams(indent, use, method, paramGroups, true);
1273
- body += `${indent.get()}(request, next_link)\n`;
1316
+ let mutRequest = '';
1317
+ // if the only header is optional Content-Type it will not be used
1318
+ // by applyHeaderParams() in this case so don't make request mutable
1319
+ if (paramGroups.header.length > 1 || (paramGroups.header.length === 1 && !isOptionalContentTypeHeader(paramGroups.header[0]))) {
1320
+ mutRequest = 'mut ';
1321
+ }
1322
+ body += `${indent.get()}let ${mutRequest}${initialRequestResult.requestVarName} = Request::new(next_link.clone(), Method::Get);\n`;
1323
+ body += applyHeaderParams(indent, use, method, paramGroups, true, initialRequestResult.requestVarName);
1324
+ body += `${indent.get()}(${initialRequestResult.requestVarName}, next_link)\n`;
1274
1325
  return body;
1275
1326
  },
1276
1327
  }, {
1277
1328
  pattern: 'PollerState::Initial',
1278
1329
  body: (indent) => {
1279
- let body = constructRequest(indent, use, method, paramGroups, true, true, false);
1280
- body += `${indent.get()}(request, url.clone())\n`;
1330
+ let body = initialRequestResult.content;
1331
+ body += `${indent.get()}(${initialRequestResult.requestVarName}, url.clone())\n`;
1281
1332
  return body;
1282
1333
  },
1283
1334
  }])};\n`;
1284
1335
  body += `${indent.get()}let ctx = options.method_options.context.clone();\n`;
1285
1336
  body += `${indent.get()}let pipeline = pipeline.clone();\n`;
1286
1337
  body += `${indent.get()}async move {\n`;
1287
- body += `${indent.push().get()}let rsp = pipeline.send(&ctx, &mut request, ${getPipelineOptions(indent, use, method)}).await?;\n`;
1338
+ body += `${indent.push().get()}let rsp = pipeline.send(&ctx, &mut ${initialRequestResult.requestVarName}, ${getPipelineOptions(indent, use, method)}).await?;\n`;
1288
1339
  body += `${indent.get()}let (status, headers, body) = rsp.deconstruct();\n`;
1289
1340
  body += `${indent.get()}let retry_after = get_retry_after(&headers, &[X_MS_RETRY_AFTER_MS, RETRY_AFTER_MS, RETRY_AFTER], &options.poller_options);\n`;
1290
- let deserialize = '';
1291
- if (bodyFormat === 'json') {
1292
- use.add('azure_core', 'json');
1293
- deserialize = 'json::from_json';
1294
- }
1295
- else {
1296
- deserialize = 'xml::read_xml';
1297
- }
1341
+ const deserialize = `${bodyFormat}::from_${bodyFormat}`;
1342
+ use.add('azure_core', bodyFormat);
1298
1343
  body += `${indent.get()}let res: ${helpers.getTypeDeclaration(helpers.unwrapType(method.returns.type))} = ${deserialize}(&body)?;\n`;
1299
1344
  body += `${indent.get()}let rsp = RawResponse::from_bytes(status, headers, body).into();\n`;
1300
1345
  body += `${indent.get()}Ok(${helpers.buildMatch(indent, 'res.status()', [{
@@ -1343,8 +1388,9 @@ function getClientSupplementalEndpointParamValue(param) {
1343
1388
  }
1344
1389
  }
1345
1390
  /**
1346
- * contains the code to use when populating a header/path/query value
1347
- * from a parameter of that type.
1391
+ * returns the code to use when populating a header/path/query value
1392
+ * from a parameter of that type. this will include any borrowing of
1393
+ * the parameter (or its calculated result) as required.
1348
1394
  *
1349
1395
  * if the param's type is a String, then the return value is simply the
1350
1396
  * param's name. the non-String cases require some kind of conversion.
@@ -1354,9 +1400,10 @@ function getClientSupplementalEndpointParamValue(param) {
1354
1400
  * @param use the use statement builder currently in scope
1355
1401
  * @param param the param for which to get the value
1356
1402
  * @param fromSelf applicable for client params. when true, the prefix "self." is included
1403
+ * @param neverBorrow indicates that the borrowing calculation should be skipped
1357
1404
  * @returns the code to use for the param's value
1358
1405
  */
1359
- function getHeaderPathQueryParamValue(use, param, fromSelf) {
1406
+ function getHeaderPathQueryParamValue(use, param, fromSelf, neverBorrow) {
1360
1407
  let paramName = param.name;
1361
1408
  // when fromSelf is false we assume that there's a local with the same name.
1362
1409
  // e.g. in pageable methods where we need to clone the params so they can be
@@ -1371,64 +1418,102 @@ function getHeaderPathQueryParamValue(use, param, fromSelf) {
1371
1418
  }
1372
1419
  return encoding;
1373
1420
  };
1374
- const encodeDateTime = function (type, param) {
1421
+ const encodeDateTime = function (type, paramName) {
1375
1422
  const encoding = helpers.getDateTimeEncodingMethod(type.encoding, 'to', use);
1376
1423
  switch (type.encoding) {
1377
1424
  case 'rfc3339':
1378
1425
  case 'rfc7231': {
1379
- return `${encoding}(&${param})`;
1426
+ return `${encoding}(${param.type.kind === 'ref' ? '' : '&'}${paramName})`;
1380
1427
  }
1381
1428
  case 'unix_time':
1382
- return `${param}.${encoding}.to_string()`;
1429
+ return `${paramName}.${encoding}.to_string()`;
1383
1430
  }
1384
1431
  };
1432
+ // this will contain the final text for
1433
+ // retrieving the param value. this will include
1434
+ // any required conversions and/or borrowing
1435
+ let paramValue;
1436
+ // contains the result from calculating if the
1437
+ // param requires borrowing.
1438
+ let mustBorrow = true;
1385
1439
  const paramType = helpers.unwrapType(param.type);
1386
1440
  if (param.kind === 'headerCollection' || param.kind === 'queryCollection') {
1387
1441
  if (param.format === 'multi') {
1388
1442
  throw new CodegenError('InternalError', 'multi should have been handled outside getHeaderPathQueryParamValue');
1389
1443
  }
1390
1444
  else if (paramType.kind === 'String' || paramType.kind === 'str') {
1391
- return `${paramName}.join("${getCollectionDelimiter(param.format)}")`;
1445
+ paramValue = `${paramName}.join("${getCollectionDelimiter(param.format)}")`;
1392
1446
  }
1393
- // convert the items to strings
1394
- let strConv;
1447
+ else {
1448
+ // convert the items to strings
1449
+ let strConv;
1450
+ switch (paramType.kind) {
1451
+ case 'encodedBytes':
1452
+ strConv = encodeBytes(paramType);
1453
+ break;
1454
+ case 'offsetDateTime':
1455
+ strConv = `|i| ${encodeDateTime(paramType, 'i')}`;
1456
+ break;
1457
+ default:
1458
+ strConv = '|i| i.to_string()';
1459
+ }
1460
+ paramValue = `${param.name}.iter().map(${strConv}).collect::<Vec<String>>().join("${getCollectionDelimiter(param.format)}")`;
1461
+ }
1462
+ }
1463
+ else {
1395
1464
  switch (paramType.kind) {
1465
+ case 'String':
1466
+ paramValue = paramName;
1467
+ // if the param is on the client, or it's optional, then we must borrow
1468
+ mustBorrow = (param.location === 'client' && fromSelf) || param.optional;
1469
+ break;
1470
+ case 'str':
1471
+ paramValue = paramName;
1472
+ // str is already borrowed
1473
+ mustBorrow = false;
1474
+ break;
1475
+ case 'decimal':
1476
+ paramValue = `${param.name}.to_string()`;
1477
+ break;
1396
1478
  case 'encodedBytes':
1397
- strConv = encodeBytes(paramType);
1479
+ paramValue = encodeBytes(paramType, paramName);
1480
+ break;
1481
+ case 'enum':
1482
+ case 'scalar':
1483
+ if (paramType.kind === 'enum' && (param.kind === 'pathScalar' || param.kind === 'queryScalar')) {
1484
+ // append_pair and path.replace() want a reference to the string
1485
+ // TODO: https://github.com/Azure/typespec-rust/issues/25
1486
+ paramValue = `${paramName}.as_ref()`;
1487
+ // as_ref() elides the need to borrow
1488
+ mustBorrow = false;
1489
+ }
1490
+ else {
1491
+ paramValue = `${paramName}.to_string()`;
1492
+ }
1493
+ break;
1494
+ case 'enumValue':
1495
+ paramValue = `${paramType.type.name}::${paramType.name}.to_string()`;
1496
+ break;
1497
+ case 'literal':
1498
+ paramValue = `"${paramType.value}"`;
1398
1499
  break;
1399
1500
  case 'offsetDateTime':
1400
- strConv = `|i| ${encodeDateTime(paramType, 'i')}`;
1501
+ paramValue = encodeDateTime(paramType, param.name);
1401
1502
  break;
1402
1503
  default:
1403
- strConv = '|i| i.to_string()';
1504
+ throw new CodegenError('InternalError', `unhandled ${param.kind} param type kind ${paramType.kind}`);
1404
1505
  }
1405
- return `${param.name}.iter().map(${strConv}).collect::<Vec<String>>().join("${getCollectionDelimiter(param.format)}")`;
1406
1506
  }
1407
- switch (paramType.kind) {
1408
- case 'String':
1409
- case 'str':
1410
- return paramName;
1411
- case 'decimal':
1412
- return `${param.name}.to_string()`;
1413
- case 'encodedBytes':
1414
- return encodeBytes(paramType, paramName);
1415
- case 'enum':
1416
- case 'scalar':
1417
- if (paramType.kind === 'enum' && (param.kind === 'pathScalar' || param.kind === 'queryScalar')) {
1418
- // append_pair and path.replace() want a reference to the string
1419
- // TODO: https://github.com/Azure/typespec-rust/issues/25
1420
- return `${paramName}.as_ref()`;
1421
- }
1422
- return `${paramName}.to_string()`;
1423
- case 'enumValue':
1424
- return `${paramType.type.name}::${paramType.name}.to_string()`;
1425
- case 'literal':
1426
- return `"${paramType.value}"`;
1427
- case 'offsetDateTime':
1428
- return encodeDateTime(paramType, param.name);
1429
- default:
1430
- throw new CodegenError('InternalError', `unhandled ${param.kind} param type kind ${paramType.kind}`);
1507
+ switch (param.kind) {
1508
+ case 'headerCollection':
1509
+ case 'headerHashMap':
1510
+ case 'headerScalar':
1511
+ // for non-copyable params (e.g. String), we need to borrow them if they're on the
1512
+ // client or we're in a closure and the param is required (header params are always owned)
1513
+ mustBorrow = nonCopyableType(param.type) && (param.location === 'client' || (!fromSelf && !param.optional));
1514
+ break;
1431
1515
  }
1516
+ return `${mustBorrow && !neverBorrow ? '&' : ''}${paramValue}`;
1432
1517
  }
1433
1518
  /**
1434
1519
  * returns the delimiter character for the provided format type
@@ -1448,7 +1533,7 @@ function getCollectionDelimiter(format) {
1448
1533
  return '\t';
1449
1534
  }
1450
1535
  }
1451
- /** returns true if the type isn't copyable thus needs to be cloned */
1536
+ /** returns true if the type isn't copyable thus needs to be cloned or borrowed */
1452
1537
  function nonCopyableType(type) {
1453
1538
  const unwrappedType = shared.unwrapOption(type);
1454
1539
  switch (unwrappedType.kind) {