@azure-tools/typespec-rust 0.24.1 → 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 (34) hide show
  1. package/dist/src/codegen/clients.d.ts.map +1 -1
  2. package/dist/src/codegen/clients.js +184 -117
  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/models.js +6 -5
  15. package/dist/src/codegen/models.js.map +1 -1
  16. package/dist/src/codemodel/client.d.ts +10 -8
  17. package/dist/src/codemodel/client.d.ts.map +1 -1
  18. package/dist/src/codemodel/client.js +1 -0
  19. package/dist/src/codemodel/client.js.map +1 -1
  20. package/dist/src/codemodel/crate.d.ts.map +1 -1
  21. package/dist/src/codemodel/crate.js +5 -2
  22. package/dist/src/codemodel/crate.js.map +1 -1
  23. package/dist/src/codemodel/types.d.ts +2 -11
  24. package/dist/src/codemodel/types.d.ts.map +1 -1
  25. package/dist/src/codemodel/types.js +0 -7
  26. package/dist/src/codemodel/types.js.map +1 -1
  27. package/dist/src/shared/shared.d.ts.map +1 -1
  28. package/dist/src/shared/shared.js +4 -1
  29. package/dist/src/shared/shared.js.map +1 -1
  30. package/dist/src/tcgcadapter/adapter.d.ts +9 -1
  31. package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
  32. package/dist/src/tcgcadapter/adapter.js +111 -72
  33. package/dist/src/tcgcadapter/adapter.js.map +1 -1
  34. package/package.json +8 -8
@@ -1 +1 @@
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"}
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,CAkRxE"}
@@ -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.
@@ -645,7 +657,7 @@ function getParamValueHelper(indent, param, inClosure, setter) {
645
657
  if (param.optional && param.type.kind !== 'literal') {
646
658
  // optional params are in the unwrapped options local var
647
659
  const op = indent.get() + helpers.buildIfBlock(indent, {
648
- condition: `let Some(${param.name}) = ${inClosure ? '&' : ''}options.${param.name}`,
660
+ condition: `let Some(${param.name}) = ${inClosure && nonCopyableType(param.type) ? '&' : ''}options.${param.name}`,
649
661
  body: setter,
650
662
  });
651
663
  return op + '\n';
@@ -659,45 +671,37 @@ function getParamValueHelper(indent, param, inClosure, setter) {
659
671
  * @param use the use statement builder currently in scope
660
672
  * @param method the method for which we're building the body
661
673
  * @param paramGroups the param groups for the provided method
662
- * @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
663
675
  * @returns the URL construction code
664
676
  */
665
- function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
677
+ function constructUrl(indent, use, method, paramGroups, urlVarName) {
666
678
  // for paths that contain query parameters, we must set the query params separately.
667
679
  // including them in the call to set_path() causes the chars to be path-escaped.
668
680
  const pathChunks = method.httpPath.split('?');
669
681
  if (pathChunks.length > 2) {
670
682
  throw new CodegenError('InternalError', 'too many HTTP path chunks');
671
683
  }
672
- /** returns & if the param needs to be borrowed (which is the majority of cases), else the empty string */
673
- const borrowOrNot = function (param) {
674
- // for string-based enums we call .as_ref() which elides the need to borrow.
675
- // for numeric-based enums the borrow will be necessary.
676
- // TODO: https://github.com/Azure/typespec-rust/issues/25
677
- if (param.type.kind !== 'enum' && (param.type.kind !== 'ref' || param.type.type.kind === 'encodedBytes' || param.type.type.kind === 'slice')) {
678
- return '&';
679
- }
680
- return '';
681
- };
682
684
  let body = '';
683
685
  // if path is just "/" no need to set it again, we're already there
684
686
  if (pathChunks[0] !== '/') {
687
+ use.add('azure_core::http', 'UrlExt');
685
688
  let path = `"${pathChunks[0]}"`;
686
689
  if (paramGroups.path.length === 0) {
687
690
  // no path params, just a static path
688
- body += `${indent.get()}${urlVarName} = ${urlVarName}.join(${path})?;\n`;
691
+ body += `${indent.get()}${urlVarName}.append_path(${path});\n`;
689
692
  }
690
693
  else if (paramGroups.path.length === 1 && pathChunks[0] === `{${paramGroups.path[0].segment}}`) {
691
694
  // for a single path param (i.e. "{foo}") we can directly join the path param's value
692
695
  const pathParam = paramGroups.path[0];
693
- 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`;
694
697
  }
695
698
  else {
696
699
  // we have path params that need to have their segments replaced with the param values
697
- 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`;
698
702
  for (const pathParam of paramGroups.path) {
699
703
  let wrapSortedVec = (s) => s;
700
- let paramExpression = getHeaderPathQueryParamValue(use, pathParam, true);
704
+ let paramExpression;
701
705
  if (pathParam.kind === 'pathHashMap') {
702
706
  wrapSortedVec = (s) => `${indent.get()}{`
703
707
  + `${indent.push().get()}let mut ${pathParam.name}_vec = ${pathParam.name}.iter().collect::<Vec<_>>();\n`
@@ -748,6 +752,8 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
748
752
  }
749
753
  }
750
754
  else {
755
+ // skip borrowing by default as we borrow from format!()
756
+ paramExpression = getHeaderPathQueryParamValue(use, pathParam, true, true);
751
757
  switch (pathParam.style) {
752
758
  case 'path':
753
759
  paramExpression = `&format!("/{${paramExpression}}")`;
@@ -759,24 +765,25 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
759
765
  paramExpression = `&format!(";${pathParam.name}={${paramExpression}}")`;
760
766
  break;
761
767
  default:
762
- paramExpression = `${borrowOrNot(pathParam)}${paramExpression}`;
768
+ // use default borrowing calculation logic
769
+ paramExpression = getHeaderPathQueryParamValue(use, pathParam, true, false);
763
770
  }
764
771
  }
765
772
  if (pathParam.optional) {
766
- body += `${indent.get()}path = ${helpers.buildMatch(indent, `options.${pathParam.name}`, [{
773
+ body += `${indent.get()}${pathVarName} = ${helpers.buildMatch(indent, `options.${pathParam.name}`, [{
767
774
  pattern: `Some(${pathParam.name})`,
768
- body: (indent) => wrapSortedVec(`${indent.get()}path.replace("{${pathParam.segment}}", ${paramExpression})\n`),
775
+ body: (indent) => wrapSortedVec(`${indent.get()}${pathVarName}.replace("{${pathParam.segment}}", ${paramExpression})\n`),
769
776
  }, {
770
777
  pattern: `None`,
771
- body: (indent) => `${indent.get()}path.replace("{${pathParam.segment}}", "")\n`,
778
+ body: (indent) => `${indent.get()}${pathVarName}.replace("{${pathParam.segment}}", "")\n`,
772
779
  }])};\n`;
773
780
  }
774
781
  else {
775
- body += wrapSortedVec(`${indent.get()}path = path.replace("{${pathParam.segment}}", ${paramExpression});\n`);
782
+ body += wrapSortedVec(`${indent.get()}${pathVarName} = ${pathVarName}.replace("{${pathParam.segment}}", ${paramExpression});\n`);
776
783
  }
777
784
  }
778
- path = '&path';
779
- body += `${indent.get()}${urlVarName} = ${urlVarName}.join(${path})?;\n`;
785
+ path = `&${pathVarName}`;
786
+ body += `${indent.get()}${urlVarName}.append_path(${path});\n`;
780
787
  }
781
788
  }
782
789
  if (pathChunks.length === 2) {
@@ -830,9 +837,7 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
830
837
  }
831
838
  else {
832
839
  body += getParamValueHelper(indent, queryParam, false, () => {
833
- // for enums we call .as_ref() which elides the need to borrow
834
- const borrow = queryParam.type.kind === 'enum' ? '' : borrowOrNot(queryParam);
835
- 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`;
836
841
  });
837
842
  }
838
843
  }
@@ -848,7 +853,7 @@ function constructUrl(indent, use, method, paramGroups, urlVarName = 'url') {
848
853
  * @param inClosure indicates if the request is being constructed within a closure (e.g. pageable methods)
849
854
  * @returns the code which sets HTTP headers for the request
850
855
  */
851
- function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
856
+ function applyHeaderParams(indent, use, method, paramGroups, inClosure, requestVarName) {
852
857
  var _a, _b, _c;
853
858
  let body = '';
854
859
  for (const headerParam of paramGroups.header) {
@@ -868,7 +873,7 @@ function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
868
873
  ]) + ';\n';
869
874
  body += indent.get() + helpers.buildIfBlock(indent, {
870
875
  condition: `let Some(${headerParam.name}) = ${headerParam.name}`,
871
- 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`,
872
877
  }) + '\n';
873
878
  continue;
874
879
  }
@@ -880,15 +885,12 @@ function applyHeaderParams(indent, use, method, paramGroups, inClosure) {
880
885
  }
881
886
  body += getParamValueHelper(indent, headerParam, inClosure, () => {
882
887
  if (headerParam.kind === 'headerHashMap') {
883
- let setter = `for (k, v) in &${headerParam.name} {\n`;
884
- 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`;
885
890
  setter += `${indent.pop().get()}}\n`;
886
891
  return setter;
887
892
  }
888
- // for non-copyable params (e.g. String), we need to borrow them if they're on the
889
- // client or we're in a closure and the param is required (header params are always owned)
890
- const borrow = nonCopyableType(headerParam.type) && (headerParam.location === 'client' || (inClosure && !headerParam.optional)) ? '&' : '';
891
- 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`;
892
894
  });
893
895
  }
894
896
  return body;
@@ -907,13 +909,17 @@ function isOptionalContentTypeHeader(headerParam) {
907
909
  * @param method the method for which we're building the body
908
910
  * @param paramGroups the param groups for the provided method
909
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
910
913
  * @param cloneUrl indicates if url should be cloned when it is passed to initialize request
911
914
  * @param forceMut indicates whether request should get declared as 'mut' regardless of whether there are any headers to set
912
915
  * @returns the request construction code
913
916
  */
914
- function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl = false, forceMut = true) {
915
- let body = `${indent.get()}let ${(forceMut || paramGroups.header.length > 0) ? 'mut ' : ''}request = Request::new(url${cloneUrl ? '.clone()' : ''}, Method::${codegen.capitalize(method.httpMethod)});\n`;
916
- 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);
917
923
  let optionalContentTypeParam;
918
924
  for (const headerParam of paramGroups.header) {
919
925
  // if the content-type header is optional, we need to emit it inside the "if let Some(body)" clause below.
@@ -926,9 +932,9 @@ function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl
926
932
  body += getParamValueHelper(indent, bodyParam, inClosure, () => {
927
933
  let bodyParamContent = '';
928
934
  if (optionalContentTypeParam) {
929
- 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`;
930
936
  }
931
- bodyParamContent += `${indent.get()}request.set_body(${bodyParam.name}${inClosure ? '.clone()' : ''});\n`;
937
+ bodyParamContent += `${indent.get()}${requestVarName}.set_body(${bodyParam.name}${inClosure ? '.clone()' : ''});\n`;
932
938
  return bodyParamContent;
933
939
  });
934
940
  }
@@ -958,9 +964,12 @@ function constructRequest(indent, use, method, paramGroups, inClosure, cloneUrl
958
964
  body += `${indent.get()}${initializer},\n`;
959
965
  }
960
966
  body += `${indent.pop().get()}}.try_into()?;\n`;
961
- body += `${indent.get()}request.set_body(body);\n`;
967
+ body += `${indent.get()}${requestVarName}.set_body(body);\n`;
962
968
  }
963
- return body;
969
+ return {
970
+ requestVarName: requestVarName,
971
+ content: body
972
+ };
964
973
  }
965
974
  /**
966
975
  * Returns 'mut ' if the Url local var needs to be mutable, else the empty string.
@@ -1037,13 +1046,15 @@ function emitEmptyPathParamCheck(indent, param) {
1037
1046
  */
1038
1047
  function getAsyncMethodBody(indent, use, client, method) {
1039
1048
  use.add('azure_core::http', 'Method', 'Request');
1049
+ const urlVarName = helpers.getUniqueVarName(method.params, ['url', 'url_var']);
1040
1050
  const paramGroups = getMethodParamGroup(method);
1041
1051
  let body = checkEmptyRequiredPathParams(indent, paramGroups.path);
1042
1052
  body += 'let options = options.unwrap_or_default();\n';
1043
1053
  body += `${indent.get()}let ctx = options.method_options.context.to_borrowed();\n`;
1044
- body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}url = self.${getEndpointFieldName(client)}.clone();\n`;
1045
- body += constructUrl(indent, use, method, paramGroups);
1046
- 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;
1047
1058
  let pipelineMethod;
1048
1059
  switch (method.returns.type.kind) {
1049
1060
  case 'asyncResponse':
@@ -1053,7 +1064,7 @@ function getAsyncMethodBody(indent, use, client, method) {
1053
1064
  pipelineMethod = 'send';
1054
1065
  break;
1055
1066
  }
1056
- 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`;
1057
1068
  body += `${indent.get()}Ok(rsp.into())\n`;
1058
1069
  return body;
1059
1070
  }
@@ -1073,7 +1084,7 @@ function getPageableMethodBody(indent, use, client, method) {
1073
1084
  use.addForType(method.returns.type);
1074
1085
  use.addForType(helpers.unwrapType(method.returns.type));
1075
1086
  const paramGroups = getMethodParamGroup(method);
1076
- const urlVar = method.strategy ? 'first_url' : 'url';
1087
+ const urlVar = method.strategy ? 'first_url' : helpers.getUniqueVarName(method.params, ['url', 'url_var']);
1077
1088
  let body = checkEmptyRequiredPathParams(indent, paramGroups.path);
1078
1089
  body += 'let options = options.unwrap_or_default().into_owned();\n';
1079
1090
  body += `${indent.get()}let pipeline = self.pipeline.clone();\n`;
@@ -1082,9 +1093,13 @@ function getPageableMethodBody(indent, use, client, method) {
1082
1093
  // passed to constructRequest. we only need to
1083
1094
  // clone it for the non-continuation case.
1084
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;
1085
1100
  if (method.strategy) {
1086
1101
  if (paramGroups.apiVersion) {
1087
- body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true)}.clone();\n`;
1102
+ body += `${indent.get()}let ${paramGroups.apiVersion.name} = ${getHeaderPathQueryParamValue(use, paramGroups.apiVersion, true, true)}.clone();\n`;
1088
1103
  }
1089
1104
  switch (method.strategy.kind) {
1090
1105
  case 'continuationToken': {
@@ -1113,33 +1128,47 @@ function getPageableMethodBody(indent, use, client, method) {
1113
1128
  }
1114
1129
  })}\n`;
1115
1130
  }
1131
+ srcUrlVar = 'url';
1116
1132
  break;
1117
1133
  }
1118
1134
  case 'nextLink': {
1119
1135
  const nextLinkName = method.strategy.nextLinkPath[method.strategy.nextLinkPath.length - 1].name;
1136
+ const reinjectedParams = method.strategy.reinjectedParams;
1120
1137
  body += `${indent.get()}Ok(${method.returns.type.name}::from_callback(move |${nextLinkName}: PagerState<Url>| {\n`;
1121
1138
  body += `${indent.push().get()}let url = ` + helpers.buildMatch(indent, nextLinkName, [{
1122
1139
  pattern: `PagerState::More(${nextLinkName})`,
1123
1140
  body: (indent) => {
1141
+ const cloneNextLink = `${indent.get()}let mut ${nextLinkName} = ${nextLinkName}.clone();\n`;
1142
+ let content = '';
1124
1143
  if (paramGroups.apiVersion && paramGroups.apiVersion.kind === 'queryScalar') {
1125
1144
  const apiVersionKey = `"${paramGroups.apiVersion.key}"`;
1126
1145
  // there are no APIs to set/update an existing query parameter.
1127
1146
  // so, we filter the existing query params to remove the api-version
1128
1147
  // query param. we then add back the filtered set and then add the
1129
1148
  // api-version as specified on the client.
1130
- let setApiVerBody = `${indent.get()}let qp = ${nextLinkName}.query_pairs().filter(|(name, _)| name.ne(${apiVersionKey}));\n`;
1131
- setApiVerBody += `${indent.get()}let mut ${nextLinkName} = ${nextLinkName}.clone();\n`;
1132
- setApiVerBody += `${indent.get()}${nextLinkName}.query_pairs_mut().clear().extend_pairs(qp).append_pair(${apiVersionKey}, &${paramGroups.apiVersion.name});\n`;
1133
- setApiVerBody += `${indent.get()}${nextLinkName}\n`;
1134
- 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;
1135
1156
  }
1136
- 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;
1137
1165
  }
1138
1166
  }, {
1139
1167
  pattern: 'PagerState::Initial',
1140
1168
  body: (indent) => `${indent.get()}${urlVar}.clone()\n`
1141
1169
  }]);
1142
1170
  body += ';\n';
1171
+ srcUrlVar = 'url';
1143
1172
  break;
1144
1173
  }
1145
1174
  }
@@ -1149,6 +1178,7 @@ function getPageableMethodBody(indent, use, client, method) {
1149
1178
  body += `${indent.get()}Ok(Pager::from_callback(move |_: PagerState<Url>| {\n`;
1150
1179
  indent.push();
1151
1180
  cloneUrl = true;
1181
+ srcUrlVar = urlVar;
1152
1182
  }
1153
1183
  // Pipeline::send() returns a RawResponse, so no reason to declare the type if not something else.
1154
1184
  let rspType = '';
@@ -1161,17 +1191,18 @@ function getPageableMethodBody(indent, use, client, method) {
1161
1191
  rspType = `: ${rspTypeDecl}`;
1162
1192
  rspInto = '.into()';
1163
1193
  }
1164
- body += constructRequest(indent, use, method, paramGroups, true, cloneUrl);
1194
+ const requestResult = constructRequest(indent, use, method, paramGroups, true, srcUrlVar, cloneUrl);
1195
+ body += requestResult.content;
1165
1196
  body += `${indent.get()}let ctx = options.method_options.context.clone();\n`;
1166
1197
  body += `${indent.get()}let pipeline = pipeline.clone();\n`;
1167
1198
  body += `${indent.get()}async move {\n`;
1168
- 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`;
1169
1200
  // check if we need to extract the next link field from the response model
1170
1201
  if (method.strategy && (method.strategy.kind === 'nextLink' || method.strategy.responseToken.kind === 'nextLink')) {
1171
1202
  const bodyFormat = helpers.convertResponseFormat(method.returns.type.type.format);
1172
1203
  use.add('azure_core', bodyFormat, 'http::RawResponse');
1173
1204
  body += `${indent.get()}let (status, headers, body) = rsp.deconstruct();\n`;
1174
- const deserialize = bodyFormat === 'json' ? 'json::from_json' : 'xml::read_xml';
1205
+ const deserialize = `${bodyFormat}::from_${bodyFormat}`;
1175
1206
  body += `${indent.get()}let res: ${helpers.getTypeDeclaration(helpers.unwrapType(method.returns.type))} = ${deserialize}(&body)?;\n`;
1176
1207
  body += `${indent.get()}let rsp = RawResponse::from_bytes(status, headers, body).into();\n`;
1177
1208
  }
@@ -1260,17 +1291,19 @@ function getLroMethodBody(indent, use, client, method) {
1260
1291
  use.addForType(method.returns.type);
1261
1292
  use.addForType(helpers.unwrapType(method.returns.type));
1262
1293
  const paramGroups = getMethodParamGroup(method);
1263
- const urlVar = 'url';
1294
+ const urlVar = helpers.getUniqueVarName(method.params, ['url', 'url_var']);
1264
1295
  let body = 'let options = options.unwrap_or_default().into_owned();\n';
1265
1296
  body += `${indent.get()}let pipeline = self.pipeline.clone();\n`;
1266
1297
  body += `${indent.get()}let ${urlVarNeedsMut(paramGroups, method)}${urlVar} = self.${getEndpointFieldName(client)}.clone();\n`;
1267
1298
  body += constructUrl(indent, use, method, paramGroups, urlVar);
1268
1299
  if (paramGroups.apiVersion) {
1269
- 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`;
1270
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);
1271
1304
  body += `${indent.get()}Ok(${method.returns.type.name}::from_callback(\n`;
1272
1305
  body += `${indent.push().get()}move |next_link: PollerState<Url>| {\n`;
1273
- 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', [{
1274
1307
  pattern: `PollerState::More(next_link)`,
1275
1308
  body: (indent) => {
1276
1309
  var _a;
@@ -1286,33 +1319,27 @@ function getLroMethodBody(indent, use, client, method) {
1286
1319
  if (paramGroups.header.length > 1 || (paramGroups.header.length === 1 && !isOptionalContentTypeHeader(paramGroups.header[0]))) {
1287
1320
  mutRequest = 'mut ';
1288
1321
  }
1289
- body += `${indent.get()}let ${mutRequest}request = Request::new(next_link.clone(), Method::Get);\n`;
1290
- body += applyHeaderParams(indent, use, method, paramGroups, true);
1291
- body += `${indent.get()}(request, next_link)\n`;
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`;
1292
1325
  return body;
1293
1326
  },
1294
1327
  }, {
1295
1328
  pattern: 'PollerState::Initial',
1296
1329
  body: (indent) => {
1297
- let body = constructRequest(indent, use, method, paramGroups, true, true, false);
1298
- body += `${indent.get()}(request, url.clone())\n`;
1330
+ let body = initialRequestResult.content;
1331
+ body += `${indent.get()}(${initialRequestResult.requestVarName}, url.clone())\n`;
1299
1332
  return body;
1300
1333
  },
1301
1334
  }])};\n`;
1302
1335
  body += `${indent.get()}let ctx = options.method_options.context.clone();\n`;
1303
1336
  body += `${indent.get()}let pipeline = pipeline.clone();\n`;
1304
1337
  body += `${indent.get()}async move {\n`;
1305
- 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`;
1306
1339
  body += `${indent.get()}let (status, headers, body) = rsp.deconstruct();\n`;
1307
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`;
1308
- let deserialize = '';
1309
- if (bodyFormat === 'json') {
1310
- use.add('azure_core', 'json');
1311
- deserialize = 'json::from_json';
1312
- }
1313
- else {
1314
- deserialize = 'xml::read_xml';
1315
- }
1341
+ const deserialize = `${bodyFormat}::from_${bodyFormat}`;
1342
+ use.add('azure_core', bodyFormat);
1316
1343
  body += `${indent.get()}let res: ${helpers.getTypeDeclaration(helpers.unwrapType(method.returns.type))} = ${deserialize}(&body)?;\n`;
1317
1344
  body += `${indent.get()}let rsp = RawResponse::from_bytes(status, headers, body).into();\n`;
1318
1345
  body += `${indent.get()}Ok(${helpers.buildMatch(indent, 'res.status()', [{
@@ -1361,8 +1388,9 @@ function getClientSupplementalEndpointParamValue(param) {
1361
1388
  }
1362
1389
  }
1363
1390
  /**
1364
- * contains the code to use when populating a header/path/query value
1365
- * 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.
1366
1394
  *
1367
1395
  * if the param's type is a String, then the return value is simply the
1368
1396
  * param's name. the non-String cases require some kind of conversion.
@@ -1372,9 +1400,10 @@ function getClientSupplementalEndpointParamValue(param) {
1372
1400
  * @param use the use statement builder currently in scope
1373
1401
  * @param param the param for which to get the value
1374
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
1375
1404
  * @returns the code to use for the param's value
1376
1405
  */
1377
- function getHeaderPathQueryParamValue(use, param, fromSelf) {
1406
+ function getHeaderPathQueryParamValue(use, param, fromSelf, neverBorrow) {
1378
1407
  let paramName = param.name;
1379
1408
  // when fromSelf is false we assume that there's a local with the same name.
1380
1409
  // e.g. in pageable methods where we need to clone the params so they can be
@@ -1389,64 +1418,102 @@ function getHeaderPathQueryParamValue(use, param, fromSelf) {
1389
1418
  }
1390
1419
  return encoding;
1391
1420
  };
1392
- const encodeDateTime = function (type, param) {
1421
+ const encodeDateTime = function (type, paramName) {
1393
1422
  const encoding = helpers.getDateTimeEncodingMethod(type.encoding, 'to', use);
1394
1423
  switch (type.encoding) {
1395
1424
  case 'rfc3339':
1396
1425
  case 'rfc7231': {
1397
- return `${encoding}(&${param})`;
1426
+ return `${encoding}(${param.type.kind === 'ref' ? '' : '&'}${paramName})`;
1398
1427
  }
1399
1428
  case 'unix_time':
1400
- return `${param}.${encoding}.to_string()`;
1429
+ return `${paramName}.${encoding}.to_string()`;
1401
1430
  }
1402
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;
1403
1439
  const paramType = helpers.unwrapType(param.type);
1404
1440
  if (param.kind === 'headerCollection' || param.kind === 'queryCollection') {
1405
1441
  if (param.format === 'multi') {
1406
1442
  throw new CodegenError('InternalError', 'multi should have been handled outside getHeaderPathQueryParamValue');
1407
1443
  }
1408
1444
  else if (paramType.kind === 'String' || paramType.kind === 'str') {
1409
- return `${paramName}.join("${getCollectionDelimiter(param.format)}")`;
1445
+ paramValue = `${paramName}.join("${getCollectionDelimiter(param.format)}")`;
1410
1446
  }
1411
- // convert the items to strings
1412
- 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 {
1413
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;
1414
1478
  case 'encodedBytes':
1415
- 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}"`;
1416
1499
  break;
1417
1500
  case 'offsetDateTime':
1418
- strConv = `|i| ${encodeDateTime(paramType, 'i')}`;
1501
+ paramValue = encodeDateTime(paramType, param.name);
1419
1502
  break;
1420
1503
  default:
1421
- strConv = '|i| i.to_string()';
1504
+ throw new CodegenError('InternalError', `unhandled ${param.kind} param type kind ${paramType.kind}`);
1422
1505
  }
1423
- return `${param.name}.iter().map(${strConv}).collect::<Vec<String>>().join("${getCollectionDelimiter(param.format)}")`;
1424
1506
  }
1425
- switch (paramType.kind) {
1426
- case 'String':
1427
- case 'str':
1428
- return paramName;
1429
- case 'decimal':
1430
- return `${param.name}.to_string()`;
1431
- case 'encodedBytes':
1432
- return encodeBytes(paramType, paramName);
1433
- case 'enum':
1434
- case 'scalar':
1435
- if (paramType.kind === 'enum' && (param.kind === 'pathScalar' || param.kind === 'queryScalar')) {
1436
- // append_pair and path.replace() want a reference to the string
1437
- // TODO: https://github.com/Azure/typespec-rust/issues/25
1438
- return `${paramName}.as_ref()`;
1439
- }
1440
- return `${paramName}.to_string()`;
1441
- case 'enumValue':
1442
- return `${paramType.type.name}::${paramType.name}.to_string()`;
1443
- case 'literal':
1444
- return `"${paramType.value}"`;
1445
- case 'offsetDateTime':
1446
- return encodeDateTime(paramType, param.name);
1447
- default:
1448
- 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;
1449
1515
  }
1516
+ return `${mustBorrow && !neverBorrow ? '&' : ''}${paramValue}`;
1450
1517
  }
1451
1518
  /**
1452
1519
  * returns the delimiter character for the provided format type
@@ -1466,7 +1533,7 @@ function getCollectionDelimiter(format) {
1466
1533
  return '\t';
1467
1534
  }
1468
1535
  }
1469
- /** 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 */
1470
1537
  function nonCopyableType(type) {
1471
1538
  const unwrappedType = shared.unwrapOption(type);
1472
1539
  switch (unwrappedType.kind) {