@azure-tools/typespec-rust 0.36.0 → 0.37.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 (76) hide show
  1. package/dist/src/codegen/clients.d.ts +4 -4
  2. package/dist/src/codegen/clients.d.ts.map +1 -1
  3. package/dist/src/codegen/clients.js +167 -67
  4. package/dist/src/codegen/clients.js.map +1 -1
  5. package/dist/src/codegen/codeGenerator.d.ts +6 -0
  6. package/dist/src/codegen/codeGenerator.d.ts.map +1 -1
  7. package/dist/src/codegen/codeGenerator.js +112 -32
  8. package/dist/src/codegen/codeGenerator.js.map +1 -1
  9. package/dist/src/codegen/context.d.ts +10 -0
  10. package/dist/src/codegen/context.d.ts.map +1 -1
  11. package/dist/src/codegen/context.js +57 -15
  12. package/dist/src/codegen/context.js.map +1 -1
  13. package/dist/src/codegen/docTests.d.ts +1 -2
  14. package/dist/src/codegen/docTests.d.ts.map +1 -1
  15. package/dist/src/codegen/docTests.js +13 -3
  16. package/dist/src/codegen/docTests.js.map +1 -1
  17. package/dist/src/codegen/enums.d.ts +4 -4
  18. package/dist/src/codegen/enums.d.ts.map +1 -1
  19. package/dist/src/codegen/enums.js +21 -21
  20. package/dist/src/codegen/enums.js.map +1 -1
  21. package/dist/src/codegen/headerTraits.d.ts +2 -2
  22. package/dist/src/codegen/headerTraits.d.ts.map +1 -1
  23. package/dist/src/codegen/headerTraits.js +17 -9
  24. package/dist/src/codegen/headerTraits.js.map +1 -1
  25. package/dist/src/codegen/helpers.d.ts +12 -3
  26. package/dist/src/codegen/helpers.d.ts.map +1 -1
  27. package/dist/src/codegen/helpers.js +19 -13
  28. package/dist/src/codegen/helpers.js.map +1 -1
  29. package/dist/src/codegen/lib.d.ts +3 -1
  30. package/dist/src/codegen/lib.d.ts.map +1 -1
  31. package/dist/src/codegen/lib.js +5 -1
  32. package/dist/src/codegen/lib.js.map +1 -1
  33. package/dist/src/codegen/mod.d.ts +9 -2
  34. package/dist/src/codegen/mod.d.ts.map +1 -1
  35. package/dist/src/codegen/mod.js +25 -9
  36. package/dist/src/codegen/mod.js.map +1 -1
  37. package/dist/src/codegen/models.d.ts +4 -4
  38. package/dist/src/codegen/models.d.ts.map +1 -1
  39. package/dist/src/codegen/models.js +76 -30
  40. package/dist/src/codegen/models.js.map +1 -1
  41. package/dist/src/codegen/unions.d.ts +4 -4
  42. package/dist/src/codegen/unions.d.ts.map +1 -1
  43. package/dist/src/codegen/unions.js +71 -45
  44. package/dist/src/codegen/unions.js.map +1 -1
  45. package/dist/src/codegen/use.d.ts +4 -2
  46. package/dist/src/codegen/use.d.ts.map +1 -1
  47. package/dist/src/codegen/use.js +22 -26
  48. package/dist/src/codegen/use.js.map +1 -1
  49. package/dist/src/codemodel/client.d.ts +30 -15
  50. package/dist/src/codemodel/client.d.ts.map +1 -1
  51. package/dist/src/codemodel/client.js +11 -7
  52. package/dist/src/codemodel/client.js.map +1 -1
  53. package/dist/src/codemodel/crate.d.ts +42 -12
  54. package/dist/src/codemodel/crate.d.ts.map +1 -1
  55. package/dist/src/codemodel/crate.js +21 -46
  56. package/dist/src/codemodel/crate.js.map +1 -1
  57. package/dist/src/codemodel/types.d.ts +60 -34
  58. package/dist/src/codemodel/types.d.ts.map +1 -1
  59. package/dist/src/codemodel/types.js +26 -10
  60. package/dist/src/codemodel/types.js.map +1 -1
  61. package/dist/src/emitter.d.ts.map +1 -1
  62. package/dist/src/emitter.js +40 -12
  63. package/dist/src/emitter.js.map +1 -1
  64. package/dist/src/lib.d.ts +2 -0
  65. package/dist/src/lib.d.ts.map +1 -1
  66. package/dist/src/lib.js +6 -0
  67. package/dist/src/lib.js.map +1 -1
  68. package/dist/src/tcgcadapter/adapter.d.ts +75 -13
  69. package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
  70. package/dist/src/tcgcadapter/adapter.js +655 -173
  71. package/dist/src/tcgcadapter/adapter.js.map +1 -1
  72. package/dist/src/utils/utils.d.ts +7 -4
  73. package/dist/src/utils/utils.d.ts.map +1 -1
  74. package/dist/src/utils/utils.js +25 -23
  75. package/dist/src/utils/utils.js.map +1 -1
  76. package/package.json +17 -17
@@ -2,15 +2,15 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- // cspell: ignore addl requiredness responseheader subclients lropaging
5
+ // cspell: ignore addl clientgenerator requiredness resourcemanager responseheader subclients lropaging
6
6
  import * as tsp from '@typespec/compiler';
7
+ import * as http from '@typespec/http';
7
8
  import * as helpers from './helpers.js';
8
9
  import * as naming from './naming.js';
9
10
  import * as utils from '../utils/utils.js';
10
11
  import * as tcgc from '@azure-tools/typespec-client-generator-core';
11
12
  import * as rust from '../codemodel/index.js';
12
13
  import { FinalStateValue } from "@azure-tools/typespec-azure-core";
13
- import { NoTarget } from "@typespec/compiler";
14
14
  /**
15
15
  * AdapterError is thrown when the emitter fails to convert part of the tcgc code
16
16
  * model to the emitter code model. this could be due to the emitter not supporting
@@ -58,11 +58,32 @@ export class Adapter {
58
58
  return new Adapter(ctx, context.options);
59
59
  }
60
60
  constructor(ctx, options) {
61
+ this.rootNamespace = '';
61
62
  this.types = new Map();
62
63
  this.clientMethodParams = new Map();
63
64
  this.fieldsMap = new Map();
64
65
  this.ctx = ctx;
65
66
  this.options = options;
67
+ // this and adjacent code was taken from
68
+ // https://github.com/microsoft/typespec/blob/c0f464728f60fe3672204dd7f2907ea4c047dfcb/packages/http-client-python/emitter/src/utils.ts#L274
69
+ if (this.ctx.sdkPackage.clients.length > 0) {
70
+ this.rootNamespace = this.ctx.sdkPackage.clients[0].namespace;
71
+ }
72
+ else if (this.ctx.sdkPackage.models.length > 0) {
73
+ const result = this.ctx.sdkPackage.models
74
+ .map((model) => model.namespace)
75
+ .filter((namespace) => !isLibNamespace(namespace));
76
+ if (result.length > 0) {
77
+ result.sort();
78
+ this.rootNamespace = result[0];
79
+ }
80
+ }
81
+ else if (this.ctx.sdkPackage.namespaces.length > 0) {
82
+ this.rootNamespace = this.ctx.sdkPackage.namespaces[0].fullName;
83
+ }
84
+ if (this.rootNamespace === '') {
85
+ throw new AdapterError('UnsupportedTsp', 'unable to determine root namespace');
86
+ }
66
87
  let serviceType = 'data-plane';
67
88
  if (this.ctx.arm === true) {
68
89
  serviceType = 'azure-arm';
@@ -70,17 +91,9 @@ export class Adapter {
70
91
  this.crate = new rust.Crate(this.options['crate-name'], this.options['crate-version'], serviceType);
71
92
  }
72
93
  /** performs all the steps to convert tcgc to a crate */
73
- tcgcToCrate(context) {
74
- this.adaptTypes(context);
94
+ tcgcToCrate() {
95
+ this.adaptTypes();
75
96
  this.adaptClients();
76
- // marker models don't require serde so exclude them from the check
77
- if (this.crate.enums.length > 0 || this.crate.models.some(e => e.kind === 'model')) {
78
- this.crate.addDependency(new rust.CrateDependency('serde'));
79
- }
80
- if (this.crate.clients.length > 0 || this.crate.enums.length > 0 || this.crate.models.length > 0 || this.crate.unions.length > 0) {
81
- this.crate.addDependency(new rust.CrateDependency('azure_core'));
82
- }
83
- this.crate.sortContent();
84
97
  return this.crate;
85
98
  }
86
99
  /** converts tcgc docs to formatted rust.Docs */
@@ -96,21 +109,78 @@ export class Adapter {
96
109
  description: doc,
97
110
  };
98
111
  }
112
+ /**
113
+ * adapts the specified namespace to a hierarchy of Rust modules
114
+ *
115
+ * @param namespace the namespace for which to return the package
116
+ * @returns the leaf module in the namespace
117
+ */
118
+ adaptNamespace(namespace) {
119
+ // some example namespaces
120
+ // foo
121
+ // foo.bar
122
+ // foo.bar.baz
123
+ //
124
+ // if the namespace is empty or it's not under the root namespace or the
125
+ // root's parent namespace then it belongs to a core library, so redirect
126
+ // its content to the root namespace.
127
+ // when the root's parent IS a known library namespace (e.g. Azure.ResourceManager),
128
+ // sibling namespaces (like CommonTypes) also get redirected to root since they
129
+ // contain library types, not service types.
130
+ const nsLower = namespace.toLowerCase();
131
+ const rootLower = this.rootNamespace.toLowerCase();
132
+ const rootParentLower = rootLower.includes('.')
133
+ ? rootLower.substring(0, rootLower.lastIndexOf('.'))
134
+ : '';
135
+ const isUnderRoot = nsLower === rootLower || nsLower.startsWith(rootLower + '.');
136
+ const isParent = rootParentLower !== '' && nsLower === rootParentLower;
137
+ const isSiblingOfRoot = rootParentLower !== '' && !isUnderRoot && nsLower.startsWith(rootParentLower + '.');
138
+ const isParentLibrary = LIB_NAMESPACE.includes(rootParentLower);
139
+ if (namespace === '' || isParent || (!isUnderRoot && (!isSiblingOfRoot || isParentLibrary))) {
140
+ namespace = this.rootNamespace;
141
+ }
142
+ // trim off the root namespace. if the result is the empty
143
+ // string it means the contents goes into the crate's root.
144
+ // also handle sibling namespaces: if the root namespace is Foo.Bar
145
+ // and we encounter Foo.Baz, treat Baz as a child module by trimming
146
+ // the shared parent (Foo) prefix.
147
+ if (namespace.toLowerCase().startsWith(this.rootNamespace.toLowerCase())) {
148
+ namespace = namespace.substring(this.rootNamespace.length + 1);
149
+ }
150
+ else {
151
+ const rootParent = this.rootNamespace.includes('.')
152
+ ? this.rootNamespace.substring(0, this.rootNamespace.lastIndexOf('.'))
153
+ : '';
154
+ if (rootParent !== '' && namespace.toLowerCase().startsWith(rootParent.toLowerCase() + '.')) {
155
+ namespace = namespace.substring(rootParent.length + 1);
156
+ }
157
+ }
158
+ const namespaces = namespace.split('.').filter(Boolean);
159
+ let cur = this.crate;
160
+ for (const namespace of namespaces) {
161
+ const modName = utils.snakeCaseName(namespace);
162
+ let subMod = cur.subModules.find((each) => each.name === modName);
163
+ if (!subMod) {
164
+ subMod = new rust.SubModule(modName, cur);
165
+ cur.subModules.push(subMod);
166
+ }
167
+ cur = subMod;
168
+ }
169
+ return cur;
170
+ }
99
171
  /** converts all tcgc types to their Rust type equivalent */
100
- adaptTypes(context) {
172
+ adaptTypes() {
173
+ let needsCoreAndSerde = false;
101
174
  for (const sdkUnion of this.ctx.sdkPackage.unions.filter(u => u.kind === 'union')) {
102
175
  if (!sdkUnion.discriminatedOptions) {
103
- // When https://github.com/microsoft/typespec/issues/9749 is fixed, we can return to throwing an exception here.
104
- context.program.reportDiagnostic({
105
- code: 'UnsupportedTsp',
106
- severity: 'warning',
107
- message: `Non-discriminated unions ('${sdkUnion.name}') are not supported. No type will be generated.`,
108
- target: NoTarget,
109
- });
176
+ // getNonDiscriminatedUnion() self-registers the type into the module and caches it
177
+ this.getNonDiscriminatedUnion(sdkUnion);
178
+ needsCoreAndSerde = true;
110
179
  continue;
111
180
  }
112
181
  const rustUnion = this.getDiscriminatedUnion(sdkUnion);
113
- this.crate.unions.push(rustUnion);
182
+ this.adaptNamespace(sdkUnion.namespace).unions.push(rustUnion);
183
+ needsCoreAndSerde = true;
114
184
  }
115
185
  for (const sdkEnum of this.ctx.sdkPackage.enums) {
116
186
  if ((sdkEnum.usage & tcgc.UsageFlags.ApiVersionEnum) === tcgc.UsageFlags.ApiVersionEnum) {
@@ -123,17 +193,49 @@ export class Adapter {
123
193
  }
124
194
  else {
125
195
  const rustEnum = this.getEnum(sdkEnum);
126
- this.crate.enums.push(rustEnum);
127
- }
196
+ this.adaptNamespace(sdkEnum.namespace).enums.push(rustEnum);
197
+ needsCoreAndSerde = true;
198
+ }
199
+ }
200
+ let terminalErrorModelNames = new Set();
201
+ if (this.options['emit-error-traits']) {
202
+ // Typespec would flag any model that is used in the error model with the Exception flag,
203
+ // even if that model is not directly used in any method, but is used as one of the terminal error type fields.
204
+ // We need to find these terminal error models, and ignore all others, because when we need to implement TryFrom trait,
205
+ // there is no need to implement it for every Exception model - we just need it for the terminal ones.
206
+ const getTerminalErrorModelNames = function (clients, visitedClientNames = new Set()) {
207
+ var _a;
208
+ const terminalErrorModelNames = new Set();
209
+ for (const client of clients) {
210
+ if (visitedClientNames.has(client.name)) {
211
+ continue;
212
+ }
213
+ visitedClientNames.add(client.name);
214
+ for (const errorModelName of client.methods.flatMap(mt => mt.operation.exceptions).filter(e => { var _a; return ((_a = e.type) === null || _a === void 0 ? void 0 : _a.kind) === 'model'; }).map(md => md.type.name)) {
215
+ terminalErrorModelNames.add(errorModelName);
216
+ }
217
+ for (const errorModelName of getTerminalErrorModelNames((_a = client.children) !== null && _a !== void 0 ? _a : [], visitedClientNames).values()) {
218
+ terminalErrorModelNames.add(errorModelName);
219
+ }
220
+ }
221
+ return terminalErrorModelNames;
222
+ };
223
+ terminalErrorModelNames = getTerminalErrorModelNames(this.ctx.sdkPackage.clients);
128
224
  }
129
225
  const processedTypes = new Set();
130
226
  for (const model of this.ctx.sdkPackage.models) {
131
- if ((model.usage & tcgc.UsageFlags.Input) === 0 && (model.usage & tcgc.UsageFlags.Output) === 0 && (model.usage & tcgc.UsageFlags.Spread) === 0) {
227
+ if ((model.usage & (tcgc.UsageFlags.Input | tcgc.UsageFlags.Output | tcgc.UsageFlags.Spread | tcgc.UsageFlags.Exception)) === 0) {
132
228
  // skip types without input and output usage. this will include core
133
229
  // types unless they're explicitly referenced (e.g. a model property).
134
230
  // we keep the models for spread params as we internally use them.
231
+ // We also emit exception (error) types.
232
+ continue;
233
+ }
234
+ // Skip the default Azure core error models.
235
+ if (tcgc.isAzureCoreModel(model)) {
135
236
  continue;
136
237
  }
238
+ needsCoreAndSerde = true;
137
239
  // TODO: workaround for https://github.com/Azure/typespec-azure/issues/3614
138
240
  if (processedTypes.has(model.name)) {
139
241
  continue;
@@ -144,7 +246,7 @@ export class Adapter {
144
246
  // END workaround
145
247
  if (model.discriminatedSubtypes) {
146
248
  const rustUnion = this.getDiscriminatedUnion(model);
147
- this.crate.unions.push(rustUnion);
249
+ this.adaptNamespace(model.namespace).unions.push(rustUnion);
148
250
  // we don't want to add the base type to the array
149
251
  // of models to emit as we hijack its name to be used
150
252
  // for the associated tagged enum type.
@@ -155,9 +257,16 @@ export class Adapter {
155
257
  }
156
258
  else {
157
259
  const rustModel = this.getModel(model);
158
- this.crate.models.push(rustModel);
260
+ if (terminalErrorModelNames.has(model.name)) {
261
+ rustModel.flags |= rust.ModelFlags.Error;
262
+ }
263
+ this.adaptNamespace(model.namespace).models.push(rustModel);
159
264
  }
160
265
  }
266
+ if (needsCoreAndSerde) {
267
+ this.crate.addDependency(new rust.CrateDependency('azure_core'));
268
+ this.crate.addDependency(new rust.CrateDependency('serde'));
269
+ }
161
270
  }
162
271
  /**
163
272
  * converts a tcgc enum to a Rust enum
@@ -195,7 +304,7 @@ export class Adapter {
195
304
  default:
196
305
  throw new AdapterError('UnsupportedTsp', `unsupported enum underlying type ${sdkEnum.valueType.kind}`, (_a = sdkEnum.__raw) === null || _a === void 0 ? void 0 : _a.node);
197
306
  }
198
- rustEnum = new rust.Enum(enumName, adaptAccessFlags(sdkEnum.access), !sdkEnum.isFixed, enumType);
307
+ rustEnum = new rust.Enum(enumName, adaptAccessFlags(sdkEnum.access), !sdkEnum.isFixed, enumType, this.adaptNamespace(sdkEnum.namespace));
199
308
  rustEnum.docs = this.adaptDocs(sdkEnum.summary, sdkEnum.doc);
200
309
  this.types.set(enumName, rustEnum);
201
310
  // the first pass is to detect any enum values that coalesce into duplicate entries
@@ -306,11 +415,10 @@ export class Adapter {
306
415
  }
307
416
  // include error and LRO polling types as output types
308
417
  if ((model.usage & tcgc.UsageFlags.Output) === tcgc.UsageFlags.Output ||
309
- (model.usage & tcgc.UsageFlags.Exception) === tcgc.UsageFlags.Exception ||
310
418
  (model.usage & tcgc.UsageFlags.LroPolling) === tcgc.UsageFlags.LroPolling) {
311
419
  modelFlags |= rust.ModelFlags.Output;
312
420
  }
313
- rustModel = new rust.Model(modelName, model.access === 'internal' ? 'pubCrate' : 'pub', modelFlags);
421
+ rustModel = new rust.Model(modelName, model.access === 'internal' ? 'pubCrate' : 'pub', modelFlags, this.adaptNamespace(model.namespace));
314
422
  rustModel.docs = this.adaptDocs(model.summary, model.doc);
315
423
  rustModel.xmlName = getXMLName(model.decorators);
316
424
  this.types.set(modelName, rustModel);
@@ -413,7 +521,7 @@ export class Adapter {
413
521
  if (!discriminatorProperty) {
414
522
  throw new AdapterError('InternalError', `failed to find discriminator field for type ${src.name}`, (_c = src.__raw) === null || _c === void 0 ? void 0 : _c.node);
415
523
  }
416
- rustUnion = new rust.DiscriminatedUnion(unionName, adaptAccessFlags(src.access), discriminatorProperty.name);
524
+ rustUnion = new rust.DiscriminatedUnion(unionName, adaptAccessFlags(src.access), discriminatorProperty.name, this.adaptNamespace(src.namespace));
417
525
  if (discriminatorProperty.type.kind === 'enum' && discriminatorProperty.type.isFixed) {
418
526
  // when the DU is a fixed enum, it means we don't fall back to the
419
527
  // base type when the discriminator value is unknown or missing.
@@ -439,7 +547,7 @@ export class Adapter {
439
547
  // at us when accessing src.discriminatedOptions
440
548
  throw new AdapterError('InternalError', 'getDiscriminatedUnion called for non-discriminated union', (_e = src.__raw) === null || _e === void 0 ? void 0 : _e.node);
441
549
  }
442
- rustUnion = new rust.DiscriminatedUnion(unionName, adaptAccessFlags(src.access), src.discriminatedOptions.discriminatorPropertyName);
550
+ rustUnion = new rust.DiscriminatedUnion(unionName, adaptAccessFlags(src.access), src.discriminatedOptions.discriminatorPropertyName, this.adaptNamespace(src.namespace));
443
551
  if (src.discriminatedOptions.envelopePropertyName) {
444
552
  rustUnion.unionKind = new rust.DiscriminatedUnionEnvelope(src.discriminatedOptions.envelopePropertyName);
445
553
  }
@@ -483,6 +591,193 @@ export class Adapter {
483
591
  var _a, _b, _c, _d, _e;
484
592
  return property.kind === 'property' ? (_d = (_b = (_a = property.serializationOptions.json) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : (_c = property.serializationOptions.xml) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : (_e = property.serializationOptions.multipart) === null || _e === void 0 ? void 0 : _e.name : undefined;
485
593
  }
594
+ /**
595
+ * converts a non-discriminated tcgc union to either a flat merged Rust Enum (union-of-enums)
596
+ * or a Rust UntaggedUnion (#[serde(untagged)] enum).
597
+ * self-registers the result in the owning module and caches it.
598
+ *
599
+ * @param src the non-discriminated tcgc union
600
+ * @returns a Rust Enum or UntaggedUnion
601
+ */
602
+ getNonDiscriminatedUnion(src) {
603
+ const unionName = src.name.length > 0
604
+ ? utils.deconstruct(src.name).map(each => utils.capitalize(each)).join('')
605
+ : this.synthesizeUnionName(src);
606
+ const keyName = `non-discriminated-union-${unionName}`;
607
+ const cached = this.types.get(keyName);
608
+ if (cached) {
609
+ return cached;
610
+ }
611
+ const result = src.variantTypes.every(v => v.kind === 'enum')
612
+ ? this.getMergedFlatEnum(unionName, src)
613
+ : this.buildUntaggedUnion(unionName, src);
614
+ this.types.set(keyName, result);
615
+ const mod = this.adaptNamespace(src.namespace);
616
+ if (result.kind === 'enum') {
617
+ mod.enums.push(result);
618
+ }
619
+ else {
620
+ mod.unions.push(result);
621
+ }
622
+ return result;
623
+ }
624
+ /**
625
+ * synthesizes a union name for anonymous inline unions by concatenating variant type names
626
+ *
627
+ * @param src the non-discriminated tcgc union
628
+ * @returns a PascalCase name for the union
629
+ */
630
+ synthesizeUnionName(src) {
631
+ return src.variantTypes
632
+ .map(v => {
633
+ switch (v.kind) {
634
+ case 'model':
635
+ case 'enum':
636
+ return utils.deconstruct(v.name).map(each => utils.capitalize(each)).join('');
637
+ case 'string':
638
+ return 'String';
639
+ case 'boolean':
640
+ return 'Boolean';
641
+ case 'int32':
642
+ return 'Int32';
643
+ case 'int64':
644
+ return 'Int64';
645
+ case 'float32':
646
+ return 'Float32';
647
+ case 'float64':
648
+ return 'Float64';
649
+ case 'array':
650
+ return 'Array';
651
+ default:
652
+ return 'Value';
653
+ }
654
+ })
655
+ .join('');
656
+ }
657
+ /**
658
+ * builds a flat merged Rust Enum from a union whose every variant is a TSP enum type.
659
+ * all variant enum values are merged into a single flat enum.
660
+ *
661
+ * @param unionName the PascalCase name for the resulting enum
662
+ * @param src the non-discriminated tcgc union
663
+ * @returns a flat merged Rust Enum
664
+ */
665
+ getMergedFlatEnum(unionName, src) {
666
+ const rustEnum = new rust.Enum(unionName, adaptAccessFlags(src.access), false, 'String', this.adaptNamespace(src.namespace));
667
+ rustEnum.docs = this.adaptDocs(src.summary, src.doc);
668
+ this.types.set(unionName, rustEnum);
669
+ for (const variant of src.variantTypes) {
670
+ const sdkEnum = variant;
671
+ for (const value of sdkEnum.values) {
672
+ const valueName = naming.fixUpEnumValueName(value);
673
+ if (!rustEnum.values.some(v => v.name === valueName)) {
674
+ const rv = new rust.EnumValue(valueName, rustEnum, value.value);
675
+ rv.docs = this.adaptDocs(value.summary, value.doc);
676
+ rustEnum.values.push(rv);
677
+ }
678
+ }
679
+ }
680
+ return rustEnum;
681
+ }
682
+ /**
683
+ * builds an UntaggedUnion (#[serde(untagged)] enum) from a non-discriminated tcgc union
684
+ * whose variants are not all enum types.
685
+ *
686
+ * @param unionName the PascalCase name for the resulting union
687
+ * @param src the non-discriminated tcgc union
688
+ * @returns a Rust UntaggedUnion
689
+ */
690
+ buildUntaggedUnion(unionName, src) {
691
+ const mod = this.adaptNamespace(src.namespace);
692
+ const rustUnion = new rust.UntaggedUnion(unionName, adaptAccessFlags(src.access), mod);
693
+ rustUnion.docs = this.adaptDocs(src.summary, src.doc);
694
+ // sort variants for correct serde untagged disambiguation:
695
+ // bool(0) < int(1) < float(2) < enum(3) < string(4) < array(5) < model(6)
696
+ const sorted = [...src.variantTypes].sort((a, b) => this.getUntaggedVariantOrder(a) - this.getUntaggedVariantOrder(b));
697
+ for (const variant of sorted) {
698
+ const variantName = this.getUntaggedVariantName(variant);
699
+ const variantType = this.typeToWireType(this.getType(variant));
700
+ const rv = new rust.UntaggedUnionVariant(variantName, variantType);
701
+ rustUnion.variants.push(rv);
702
+ }
703
+ return rustUnion;
704
+ }
705
+ /**
706
+ * returns the sort order for a variant in a #[serde(untagged)] enum.
707
+ * more specific types must come before more general ones to ensure correct deserialization.
708
+ *
709
+ * @param variant the tcgc type to get the order for
710
+ * @returns a numeric sort key (lower = earlier)
711
+ */
712
+ getUntaggedVariantOrder(variant) {
713
+ switch (variant.kind) {
714
+ case 'boolean':
715
+ return 0;
716
+ case 'constant':
717
+ return this.getUntaggedVariantOrder(variant.valueType);
718
+ case 'int8':
719
+ case 'int16':
720
+ case 'int32':
721
+ case 'int64':
722
+ case 'safeint':
723
+ return 1;
724
+ case 'float':
725
+ case 'float32':
726
+ case 'float64':
727
+ return 2;
728
+ case 'enum':
729
+ return 3;
730
+ case 'string':
731
+ return 4;
732
+ case 'array':
733
+ return 5;
734
+ case 'model':
735
+ return 6;
736
+ default:
737
+ return 7;
738
+ }
739
+ }
740
+ /**
741
+ * returns the Rust variant name (PascalCase) for a variant in a #[serde(untagged)] enum.
742
+ *
743
+ * @param variant the tcgc type to name
744
+ * @returns a PascalCase Rust variant name
745
+ */
746
+ getUntaggedVariantName(variant) {
747
+ switch (variant.kind) {
748
+ case 'model':
749
+ return utils.deconstruct(variant.name).map(each => utils.capitalize(each)).join('');
750
+ case 'enum':
751
+ return utils.deconstruct(variant.name).map(each => utils.capitalize(each)).join('');
752
+ case 'boolean':
753
+ return 'Boolean';
754
+ case 'int8':
755
+ return 'Int8';
756
+ case 'int16':
757
+ return 'Int16';
758
+ case 'int32':
759
+ return 'Int32';
760
+ case 'int64':
761
+ return 'Int64';
762
+ case 'safeint':
763
+ return 'SafeInt';
764
+ case 'float':
765
+ case 'float32':
766
+ return 'Float32';
767
+ case 'float64':
768
+ return 'Float64';
769
+ case 'string':
770
+ return 'String';
771
+ case 'constant':
772
+ return this.getUntaggedVariantName(variant.valueType);
773
+ case 'array': {
774
+ const elemName = this.getUntaggedVariantName(variant.valueType);
775
+ return `${elemName}Array`;
776
+ }
777
+ default:
778
+ return 'Value';
779
+ }
780
+ }
486
781
  /**
487
782
  * converts a tcgc model property to a model field
488
783
  *
@@ -495,7 +790,7 @@ export class Adapter {
495
790
  getModelField(modelFlags, property, modelVisibility, stack) {
496
791
  var _a, _b, _c, _d, _e;
497
792
  const fieldNeedsBoxing = function (fieldType) {
498
- if (fieldType.kind === 'model' && stack.includes(fieldType)) {
793
+ if (fieldType.kind === 'model' && (stack.includes(fieldType))) {
499
794
  // if the field's type is a model and it's in the type stack then
500
795
  // box it. this is to avoid infinitely recursive type definitions.
501
796
  return true;
@@ -525,6 +820,17 @@ export class Adapter {
525
820
  const serializedName = (_a = this.getSerializedPropertyName(property)) !== null && _a !== void 0 ? _a : property.name;
526
821
  const modelField = new rust.ModelField(naming.getEscapedReservedName(utils.snakeCaseName(property.name), 'prop'), serializedName, modelVisibility, fieldType, property.optional);
527
822
  modelField.docs = this.adaptDocs(property.summary, property.doc);
823
+ // append visibility info as a doc comment when visibility is restricted
824
+ const visibilityStr = formatVisibility(property.visibility);
825
+ if (visibilityStr) {
826
+ if (!modelField.docs.description) {
827
+ modelField.docs.description = '';
828
+ }
829
+ else {
830
+ modelField.docs.description += '\n\n';
831
+ }
832
+ modelField.docs.description += `Operational visibility: ${visibilityStr}`;
833
+ }
528
834
  // if this is a literal, add a doc comment explaining its behavior
529
835
  const unwrappedType = utils.unwrapOption(fieldType);
530
836
  if (unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal') {
@@ -600,7 +906,7 @@ export class Adapter {
600
906
  * @returns the adapted Rust type
601
907
  */
602
908
  getType(type, stack) {
603
- var _a, _b, _c;
909
+ var _a, _b;
604
910
  if (type.external) {
605
911
  return this.getExternalType(type.external);
606
912
  }
@@ -682,6 +988,9 @@ export class Adapter {
682
988
  else if (type.discriminatedSubtypes) {
683
989
  return this.getDiscriminatedUnion(type);
684
990
  }
991
+ else if (tcgc.isAzureCoreModel(type)) {
992
+ return this.getExternalType({ kind: 'externalTypeInfo', identity: 'azure_core::error::ErrorDetail' });
993
+ }
685
994
  return this.getModel(type, stack);
686
995
  case 'endpoint':
687
996
  case 'plainDate':
@@ -735,12 +1044,12 @@ export class Adapter {
735
1044
  }
736
1045
  case 'union': {
737
1046
  if (!type.discriminatedOptions) {
738
- throw new AdapterError('UnsupportedTsp', 'non-discriminated unions are not supported', (_b = type.__raw) === null || _b === void 0 ? void 0 : _b.node);
1047
+ return this.getNonDiscriminatedUnion(type);
739
1048
  }
740
1049
  return this.getDiscriminatedUnion(type);
741
1050
  }
742
1051
  default:
743
- throw new AdapterError('UnsupportedTsp', `unhandled tcgc type ${type.kind}`, (_c = type.__raw) === null || _c === void 0 ? void 0 : _c.node);
1052
+ throw new AdapterError('UnsupportedTsp', `unhandled tcgc type ${type.kind}`, (_b = type.__raw) === null || _b === void 0 ? void 0 : _b.node);
744
1053
  }
745
1054
  }
746
1055
  /** returns a Box type */
@@ -808,11 +1117,12 @@ export class Adapter {
808
1117
  return optionType;
809
1118
  }
810
1119
  /** returns the specified type wrapped in a Ref */
811
- getRefType(type) {
812
- const typeKey = recursiveKeyName('ref', type);
1120
+ getRefType(type, lifetime) {
1121
+ const typeKey = recursiveKeyName(`ref${lifetime ? `-${lifetime.name}` : ''}`, type);
813
1122
  let refType = this.types.get(typeKey);
814
1123
  if (!refType) {
815
1124
  refType = new rust.Ref(type);
1125
+ refType.lifetime = lifetime;
816
1126
  this.types.set(typeKey, refType);
817
1127
  }
818
1128
  return refType;
@@ -961,8 +1271,14 @@ export class Adapter {
961
1271
  }
962
1272
  /** converts all tcgc clients and their methods into Rust clients/methods */
963
1273
  adaptClients() {
1274
+ let needsCore = false;
964
1275
  for (const client of this.ctx.sdkPackage.clients) {
1276
+ // start with instantiable clients and recursively work down
965
1277
  this.recursiveAdaptClient(client);
1278
+ needsCore = true;
1279
+ }
1280
+ if (needsCore) {
1281
+ this.crate.addDependency(new rust.CrateDependency('azure_core'));
966
1282
  }
967
1283
  }
968
1284
  /**
@@ -1012,7 +1328,7 @@ export class Adapter {
1012
1328
  if (!clientName.match(/Client$/)) {
1013
1329
  clientName += 'Client';
1014
1330
  }
1015
- const rustClient = new rust.Client(clientName);
1331
+ const rustClient = new rust.Client(clientName, this.adaptNamespace(client.namespace));
1016
1332
  rustClient.docs = this.adaptDocs(client.summary, client.doc);
1017
1333
  rustClient.parent = parent;
1018
1334
  rustClient.fields.push(new rust.StructField('pipeline', 'pubCrate', new rust.ExternalType(this.crate, 'Pipeline', 'azure_core::http')));
@@ -1217,7 +1533,7 @@ export class Adapter {
1217
1533
  }
1218
1534
  // Set the tracing namespace for tracing based on the client's namespace
1219
1535
  rustClient.languageIndependentName = client.crossLanguageDefinitionId;
1220
- this.crate.clients.push(rustClient);
1536
+ this.adaptNamespace(client.namespace).clients.push(rustClient);
1221
1537
  return rustClient;
1222
1538
  }
1223
1539
  /**
@@ -1266,7 +1582,9 @@ export class Adapter {
1266
1582
  paramField.docs = this.adaptDocs(param.summary, param.doc);
1267
1583
  constructable.options.type.fields.push(paramField);
1268
1584
  if (param.clientDefaultValue) {
1269
- paramField.defaultValue = `String::from("${param.clientDefaultValue}")`;
1585
+ const constName = `DEFAULT_${paramName.toUpperCase()}`;
1586
+ paramField.defaultValue = `String::from(${constName})`;
1587
+ paramField.defaultValueConstant = { name: constName, value: param.clientDefaultValue };
1270
1588
  }
1271
1589
  }
1272
1590
  let adaptedParam;
@@ -1319,7 +1637,7 @@ export class Adapter {
1319
1637
  * @param rustClient the client to which the method belongs
1320
1638
  */
1321
1639
  adaptMethod(method, rustClient) {
1322
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
1640
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
1323
1641
  let srcMethodName = method.name;
1324
1642
  if (method.kind === 'paging' && !srcMethodName.match(/^list/i)) {
1325
1643
  const chunks = utils.deconstruct(srcMethodName);
@@ -1351,7 +1669,7 @@ export class Adapter {
1351
1669
  const optionsLifetime = new rust.Lifetime('a');
1352
1670
  const methodOptionsStruct = new rust.Struct(`${rustClient.name}${utils.pascalCase(srcMethodName, false)}Options`, pub);
1353
1671
  methodOptionsStruct.lifetime = optionsLifetime;
1354
- methodOptionsStruct.docs.summary = `Options to be passed to ${this.asDocLink(`${rustClient.name}::${methodName}()`, `crate::generated::clients::${rustClient.name}::${methodName}()`)}`;
1672
+ methodOptionsStruct.docs.summary = `Options to be passed to ${this.asDocLink(`${rustClient.name}::${methodName}()`, `${utils.buildImportPath(rustClient.module, rustClient.module)}::clients::${rustClient.name}::${methodName}()`)}`;
1355
1673
  let clientMethodOptions;
1356
1674
  switch (method.kind) {
1357
1675
  case 'paging':
@@ -1367,7 +1685,7 @@ export class Adapter {
1367
1685
  const methodOptionsField = new rust.StructField('method_options', pub, clientMethodOptions);
1368
1686
  methodOptionsField.docs.summary = 'Allows customization of the method call.';
1369
1687
  methodOptionsStruct.fields.push(methodOptionsField);
1370
- const methodOptions = new rust.MethodOptions(methodOptionsStruct);
1688
+ const methodOptions = new rust.ParameterGroup('options', new rust.Option(methodOptionsStruct));
1371
1689
  const httpMethod = method.operation.verb;
1372
1690
  let rustMethod;
1373
1691
  switch (method.kind) {
@@ -1418,44 +1736,21 @@ export class Adapter {
1418
1736
  }
1419
1737
  // maps tcgc method header/query params to their Rust method params
1420
1738
  const paramsMap = new Map();
1421
- for (const param of method.parameters) {
1422
- // we need to translate from the method param to its underlying operation param.
1423
- // most params have a one-to-one mapping. however, for spread params, there will
1424
- // be a many-to-one mapping. i.e. multiple params will map to the same underlying
1425
- // operation param. each param corresponds to a field within the operation param.
1426
- const opParam = allOpParams.find((opParam) => {
1427
- return opParam.methodParameterSegments.map((segment) => segment[segment.length - 1]).some((methodParam) => {
1428
- return methodParam.name === param.name;
1429
- });
1430
- });
1431
- if (!opParam) {
1432
- throw new AdapterError('InternalError', `didn't find operation parameter for method ${method.name} parameter ${param.name}`, (_f = param.__raw) === null || _f === void 0 ? void 0 : _f.node);
1433
- }
1434
- if (opParam.kind === 'header' && opParam.serializedName.toLowerCase() === 'x-ms-client-request-id') {
1435
- // x-ms-client-request-id is automatically inserted into requests via
1436
- // a pipeline policy. so we don't want to expose this as an actual param.
1437
- continue;
1438
- }
1439
- let adaptedParam;
1440
- // for spread params there are two cases we need to consider.
1441
- // if the method param's type doesn't match the op param's type then it's a spread param
1442
- // - e.g. method param's type is string/int/etc which is a field in the op param's body type
1443
- // if the method param's type DOES match the op param's type and the op param has multiple corresponding method params, it's a spread param
1444
- // - e.g. op param is an intersection of multiple model types, and each model type is exposed as a discrete param
1445
- if (opParam.kind === 'body' && opParam.type.kind === 'model'
1446
- && (opParam.type.kind !== param.type.kind || opParam.methodParameterSegments.map((segment) => segment[segment.length - 1]).length > 1)) {
1447
- adaptedParam = this.adaptMethodSpreadParameter(param, this.adaptPayloadFormat(opParam.defaultContentType), opParam.type);
1448
- }
1449
- else {
1450
- adaptedParam = this.adaptMethodParameter(opParam, param);
1451
- }
1739
+ /** returns true if the op param should be omitted */
1740
+ const skipOpParam = function (opParam) {
1741
+ // x-ms-client-request-id is automatically inserted into requests via
1742
+ // a pipeline policy. so we don't want to expose this as an actual param.
1743
+ return opParam.kind === 'header' && opParam.serializedName.toLowerCase() === 'x-ms-client-request-id';
1744
+ };
1745
+ /** adds adapted params to the method's param list and optional params to method options */
1746
+ const processAdaptedParam = (adaptedParam, methodParam) => {
1452
1747
  switch (adaptedParam.kind) {
1453
1748
  case 'headerScalar':
1454
1749
  case 'queryScalar':
1455
- paramsMap.set(param, adaptedParam);
1750
+ paramsMap.set(methodParam, adaptedParam);
1456
1751
  break;
1457
1752
  }
1458
- adaptedParam.docs = this.adaptDocs(param.summary, param.doc);
1753
+ adaptedParam.docs = this.adaptDocs(methodParam.summary, methodParam.doc);
1459
1754
  rustMethod.params.push(adaptedParam);
1460
1755
  // we specially handle an optional content-type header to ensure it's omitted
1461
1756
  // from the options bag type. this shows up when the request body is optional.
@@ -1467,9 +1762,9 @@ export class Adapter {
1467
1762
  // sent in the request. we want the field within the model for this param.
1468
1763
  // NOTE: if the param is optional then the field is optional, thus it's
1469
1764
  // already wrapped in an Option<T> type.
1470
- const field = adaptedParam.type.content.type.fields.find(f => { return f.name === adaptedParam.name; });
1765
+ const field = adaptedParam.type.content.fields.find(f => { return f.name === adaptedParam.name; });
1471
1766
  if (!field) {
1472
- throw new AdapterError('InternalError', `didn't find spread param field ${adaptedParam.name} in type ${adaptedParam.type.content.type.name}`);
1767
+ throw new AdapterError('InternalError', `didn't find spread param field ${adaptedParam.name} in type ${adaptedParam.type.content.name}`);
1473
1768
  }
1474
1769
  fieldType = field.type;
1475
1770
  }
@@ -1478,7 +1773,124 @@ export class Adapter {
1478
1773
  }
1479
1774
  const optionsField = new rust.StructField(adaptedParam.name, pub, fieldType);
1480
1775
  optionsField.docs = adaptedParam.docs;
1481
- rustMethod.options.type.fields.push(optionsField);
1776
+ rustMethod.options.type.type.fields.push(optionsField);
1777
+ }
1778
+ };
1779
+ for (const param of method.parameters) {
1780
+ // we need to translate from the method param to its underlying operation param.
1781
+ // most params have a one-to-one mapping. however, for spread params, there will
1782
+ // be a many-to-one mapping. i.e. multiple params will map to the same underlying
1783
+ // operation param. each param corresponds to a field within the operation param.
1784
+ // there are three distinct cases
1785
+ // - one-to-one mapping (header param -> header op param)
1786
+ // - many-to-one mapping (spread params, multiple params map to one op param)
1787
+ // - one-to-many mapping (grouped params, one param maps to multiple op params)
1788
+ // two-phase filter: first try strict match on the last segment element (cases 1 and 2).
1789
+ // if no matches, try matching the first segment element (case 3: parameter group from @@override).
1790
+ // this avoids false case-3 matches when a body model has fields extracted as HTTP params
1791
+ // (e.g. @query fields), where segment[0] is the body param name but it's not a parameter group.
1792
+ let opParams = allOpParams.filter((opParam) => {
1793
+ return opParam.methodParameterSegments.some((segment) => segment[segment.length - 1].name === param.name);
1794
+ });
1795
+ if (opParams.length === 0) {
1796
+ opParams = allOpParams.filter((opParam) => {
1797
+ return opParam.methodParameterSegments.some((segment) => segment[0].name === param.name);
1798
+ });
1799
+ }
1800
+ let adaptedParam;
1801
+ if (opParams.length === 1) {
1802
+ // case 1 or 2
1803
+ const opParam = opParams[0];
1804
+ if (skipOpParam(opParam)) {
1805
+ continue;
1806
+ }
1807
+ // for spread params there are two cases we need to consider.
1808
+ // if the method param's type doesn't match the op param's type then it's a spread param
1809
+ // - e.g. method param's type is string/int/etc which is a field in the op param's body type
1810
+ // if the method param's type DOES match the op param's type and the op param has multiple corresponding method params, it's a spread param
1811
+ // - e.g. op param is an intersection of multiple model types, and each model type is exposed as a discrete param
1812
+ if (opParam.kind === 'body' && opParam.type.kind === 'model'
1813
+ && (opParam.type !== param.type || opParam.methodParameterSegments.map((segment) => segment[segment.length - 1]).length > 1)) {
1814
+ adaptedParam = this.adaptMethodSpreadParameter(param, this.getPayloadFormatType(opParam.type, opParam.defaultContentType), opParam.type);
1815
+ }
1816
+ else {
1817
+ adaptedParam = this.adaptMethodParameter(opParam, param);
1818
+ }
1819
+ processAdaptedParam(adaptedParam, param);
1820
+ }
1821
+ else if (opParams.length > 1) {
1822
+ // case 3 (param group)
1823
+ // Parameter group handling:
1824
+ // - required params stay in the named parameter group
1825
+ // - optional params are moved to the method's options type
1826
+ // - if no required params remain, the param group evaporates
1827
+ let paramGroup;
1828
+ const groupedParams = new Array();
1829
+ const lifetime = new rust.Lifetime('a');
1830
+ let hasRefs = false;
1831
+ for (const opParam of opParams) {
1832
+ if (skipOpParam(opParam)) {
1833
+ continue;
1834
+ }
1835
+ // for grouped params, the segment is [groupMethodParam, fieldProp].
1836
+ // we need the field property to get the correct type for this specific
1837
+ // operation parameter (the opParam's own type might be the group model type).
1838
+ const fieldProp = opParam.methodParameterSegments
1839
+ .flatMap((segment) => segment.length > 1 ? [segment[segment.length - 1]] : [])
1840
+ .find((prop) => prop.name === opParam.name);
1841
+ // pass fieldProp as methodParam so adaptMethodParameter uses the field's type.
1842
+ // cast is safe as we only need .type, .optional, .summary, .doc from it.
1843
+ const groupedParam = this.adaptMethodParameter(opParam, fieldProp, lifetime);
1844
+ const fieldOptional = fieldProp ? fieldProp.optional : opParam.optional;
1845
+ if (!fieldOptional) {
1846
+ if (!paramGroup) {
1847
+ if (param.type.kind !== 'model') {
1848
+ throw new AdapterError('InternalError', `expected model type for parameter group but got ${param.type.kind}`, (_f = param.__raw) === null || _f === void 0 ? void 0 : _f.node);
1849
+ }
1850
+ const groupName = utils.capitalize(param.type.name).replace(/\W/g, '');
1851
+ const paramName = naming.getEscapedReservedName(utils.snakeCaseName(param.name), 'param', reservedParams);
1852
+ // param group's visibility is tied to its matching method
1853
+ const groupStruct = new rust.Struct(groupName, rustMethod.visibility);
1854
+ groupStruct.docs = this.adaptDocs(param.type.summary, param.type.doc);
1855
+ paramGroup = new rust.ParameterGroup(paramName, groupStruct);
1856
+ paramGroup.docs = this.adaptDocs(param.summary, param.doc);
1857
+ }
1858
+ groupedParam.docs = this.adaptDocs(opParam.summary, opParam.doc);
1859
+ groupedParams.push(groupedParam);
1860
+ groupedParam.group = paramGroup;
1861
+ if (groupedParam.type.kind === 'ref') {
1862
+ hasRefs = true;
1863
+ }
1864
+ }
1865
+ else {
1866
+ // optional params within a group are moved to the method's options type
1867
+ processAdaptedParam(groupedParam, param);
1868
+ }
1869
+ }
1870
+ if (paramGroup) {
1871
+ if (hasRefs) {
1872
+ paramGroup.type.lifetime = lifetime;
1873
+ }
1874
+ // push each grouped param individually; they already have .group set
1875
+ for (const gp of groupedParams) {
1876
+ rustMethod.params.push(gp);
1877
+ }
1878
+ // remove the group's model type from the module's models list.
1879
+ // the parameter group struct will be emitted by the client codegen
1880
+ // in method_options.rs. leaving the model in models.rs would create
1881
+ // a duplicate struct definition with incompatible field types.
1882
+ if (param.type.kind === 'model') {
1883
+ const groupModelName = utils.capitalize(param.type.name).replace(/\W/g, '');
1884
+ const ns = this.adaptNamespace(param.type.namespace);
1885
+ const idx = ns.models.findIndex(m => m.kind === 'model' && m.name === groupModelName);
1886
+ if (idx >= 0) {
1887
+ ns.models.splice(idx, 1);
1888
+ }
1889
+ }
1890
+ }
1891
+ }
1892
+ else {
1893
+ throw new AdapterError('InternalError', `didn't find operation parameter for method ${method.name} parameter ${param.name}`, (_g = param.__raw) === null || _g === void 0 ? void 0 : _g.node);
1482
1894
  }
1483
1895
  }
1484
1896
  // client params aren't included in method.parameters so
@@ -1494,6 +1906,7 @@ export class Adapter {
1494
1906
  var _a;
1495
1907
  // fetch the body format from the HTTP responses.
1496
1908
  // they should all have the same type so no need to match responses to type.
1909
+ let responseType;
1497
1910
  let defaultContentType;
1498
1911
  for (const httpResp of method.operation.responses) {
1499
1912
  if (!httpResp.defaultContentType) {
@@ -1505,11 +1918,12 @@ export class Adapter {
1505
1918
  throw new AdapterError('InternalError', `method ${method.name} has conflicting content types`, (_a = method.__raw) === null || _a === void 0 ? void 0 : _a.node);
1506
1919
  }
1507
1920
  defaultContentType = httpResp.defaultContentType;
1921
+ responseType = httpResp.type;
1508
1922
  }
1509
1923
  if (!defaultContentType) {
1510
1924
  return 'NoFormat';
1511
1925
  }
1512
- return this.adaptPayloadFormatType(defaultContentType);
1926
+ return this.getPayloadFormatType(responseType, defaultContentType);
1513
1927
  };
1514
1928
  const getStatusCodes = function (httpOp) {
1515
1929
  const statusCodes = new Array();
@@ -1558,25 +1972,26 @@ export class Adapter {
1558
1972
  }
1559
1973
  const responseFormat = getResponseFormat();
1560
1974
  if (method.kind === 'paging') {
1561
- if (responseFormat === 'NoFormat') {
1562
- throw new AdapterError('InternalError', `paged method ${method.name} unexpected response format ${responseFormat}`, (_g = method.__raw) === null || _g === void 0 ? void 0 : _g.node);
1975
+ if (responseFormat !== 'JsonFormat' && responseFormat !== 'XmlFormat') {
1976
+ throw new AdapterError('InternalError', `paged method ${method.name} unexpected response format ${responseFormat}`, (_h = method.__raw) === null || _h === void 0 ? void 0 : _h.node);
1563
1977
  }
1564
1978
  // for paged methods, tcgc models method.response.type as an Array<T>.
1565
1979
  // however, we want the synthesized paged response envelope type instead.
1566
1980
  const synthesizedType = method.operation.responses[0].type;
1567
1981
  if (!synthesizedType) {
1568
- throw new AdapterError('InternalError', `paged method ${method.name} has no synthesized response type`, (_h = method.__raw) === null || _h === void 0 ? void 0 : _h.node);
1982
+ throw new AdapterError('InternalError', `paged method ${method.name} has no synthesized response type`, (_j = method.__raw) === null || _j === void 0 ? void 0 : _j.node);
1569
1983
  }
1570
1984
  else if (synthesizedType.kind !== 'model') {
1571
- throw new AdapterError('UnsupportedTsp', `paged method ${method.name} synthesized response type has unexpected kind ${synthesizedType.kind}`, (_j = method.__raw) === null || _j === void 0 ? void 0 : _j.node);
1985
+ throw new AdapterError('UnsupportedTsp', `paged method ${method.name} synthesized response type has unexpected kind ${synthesizedType.kind}`, (_k = method.__raw) === null || _k === void 0 ? void 0 : _k.node);
1572
1986
  }
1573
1987
  const synthesizedModel = this.getModel(synthesizedType, new Array());
1574
- if (!this.crate.models.includes(synthesizedModel)) {
1575
- this.crate.models.push(synthesizedModel);
1988
+ const modelNs = this.adaptNamespace(synthesizedType.namespace);
1989
+ if (!modelNs.models.includes(synthesizedModel)) {
1990
+ modelNs.models.push(synthesizedModel);
1576
1991
  }
1577
1992
  // for the pager response type, remove the Option<T> around the Vec<T> for the page items
1578
1993
  if (!method.pagingMetadata.pageItemsSegments) {
1579
- throw new AdapterError('InternalError', `paged method ${method.name} has no pageItemsSegments`, (_k = method.__raw) === null || _k === void 0 ? void 0 : _k.node);
1994
+ throw new AdapterError('InternalError', `paged method ${method.name} has no pageItemsSegments`, (_l = method.__raw) === null || _l === void 0 ? void 0 : _l.node);
1580
1995
  }
1581
1996
  // unwrap all of the segments for the paged response
1582
1997
  let unwrappedCount = 0;
@@ -1587,13 +2002,13 @@ export class Adapter {
1587
2002
  switch (responseFormat) {
1588
2003
  case 'JsonFormat':
1589
2004
  if (segment.kind !== 'property' || !segment.serializationOptions.json) {
1590
- throw new AdapterError('InternalError', `paged method ${method.name} is missing JSON serialization data`, (_l = method.__raw) === null || _l === void 0 ? void 0 : _l.node);
2005
+ throw new AdapterError('InternalError', `paged method ${method.name} is missing JSON serialization data`, (_m = method.__raw) === null || _m === void 0 ? void 0 : _m.node);
1591
2006
  }
1592
2007
  serde = segment.serializationOptions.json.name;
1593
2008
  break;
1594
2009
  case 'XmlFormat':
1595
2010
  if (segment.kind !== 'property' || !segment.serializationOptions.xml) {
1596
- throw new AdapterError('InternalError', `paged method ${method.name} is missing XML serialization data`, (_m = method.__raw) === null || _m === void 0 ? void 0 : _m.node);
2011
+ throw new AdapterError('InternalError', `paged method ${method.name} is missing XML serialization data`, (_o = method.__raw) === null || _o === void 0 ? void 0 : _o.node);
1597
2012
  }
1598
2013
  serde = segment.serializationOptions.xml.name;
1599
2014
  break;
@@ -1619,45 +2034,55 @@ export class Adapter {
1619
2034
  }
1620
2035
  }
1621
2036
  if (unwrappedCount !== method.pagingMetadata.pageItemsSegments.length) {
1622
- throw new AdapterError('InternalError', `failed to unwrap paged items for method ${method.name}`, (_o = method.__raw) === null || _o === void 0 ? void 0 : _o.node);
2037
+ throw new AdapterError('InternalError', `failed to unwrap paged items for method ${method.name}`, (_p = method.__raw) === null || _p === void 0 ? void 0 : _p.node);
1623
2038
  }
1624
2039
  this.crate.addDependency(new rust.CrateDependency('async-trait'));
1625
2040
  // default to nextLink. will update it as required when we have that info
1626
2041
  rustMethod.returns = new rust.Result(this.crate, new rust.Pager(this.crate, new rust.Response(this.crate, synthesizedModel, responseFormat), 'nextLink'));
1627
2042
  }
1628
2043
  else if (method.kind === 'lro') {
1629
- const format = responseFormat === 'NoFormat' ? 'JsonFormat' : responseFormat;
1630
- const statusType = this.typeToWireType(this.getModel(method.lroMetadata.pollingInfo.responseModel, undefined, `${rustClient.name}${utils.pascalCase(rustMethod.name, false)}OperationStatus`));
1631
- if (statusType.kind !== 'model') {
1632
- throw new AdapterError('InternalError', `status type for an LRO method '${method.name}' is not a model`, (_p = method.__raw) === null || _p === void 0 ? void 0 : _p.node);
1633
- }
1634
- const pushModels = (model, crate) => {
1635
- if (crate.models.some(m => m === model)) {
2044
+ const pushModels = (tcgcType, container, rustType = this.getType(tcgcType), ignoreAzureCoreModels = true) => {
2045
+ switch (tcgcType.kind) {
2046
+ case 'array':
2047
+ pushModels(tcgcType.valueType, container);
2048
+ return;
2049
+ case 'model':
2050
+ if (ignoreAzureCoreModels && tcgc.isAzureCoreModel(tcgcType)) {
2051
+ return;
2052
+ }
2053
+ break;
2054
+ default:
2055
+ return;
2056
+ }
2057
+ if (rustType.kind !== 'model' || container.models.some(m => m === rustType) || rustType.module !== rustClient.module) {
2058
+ // model already exists or belongs to a different module, do nothing
1636
2059
  return;
1637
2060
  }
1638
- crate.models.push(model);
1639
- for (const field of model.fields) {
1640
- let fieldType = field.type;
1641
- fieldType = fieldType.kind === 'option' ? fieldType.type : fieldType;
1642
- fieldType = fieldType.kind === 'Vec' ? fieldType.type : fieldType;
1643
- if (fieldType.kind === 'model') {
1644
- pushModels(fieldType, crate);
1645
- }
2061
+ container.models.push(rustType);
2062
+ for (const tcgcField of tcgcType.properties) {
2063
+ pushModels(tcgcField.type, container);
1646
2064
  }
1647
2065
  };
1648
- pushModels(statusType, this.crate);
2066
+ const format = responseFormat === 'JsonFormat' || responseFormat === 'XmlFormat' ? responseFormat : 'JsonFormat';
2067
+ const statusModel = this.getModel(method.lroMetadata.pollingInfo.responseModel, undefined, `${rustClient.name}${utils.pascalCase(rustMethod.name, false)}OperationStatus`);
2068
+ const statusType = this.typeToWireType(statusModel);
2069
+ if (statusType.kind !== 'model') {
2070
+ throw new AdapterError('InternalError', `status type for an LRO method '${method.name}' is not a model`, (_q = method.__raw) === null || _q === void 0 ? void 0 : _q.node);
2071
+ }
2072
+ // the adapted type likely comes from core which means
2073
+ // it will be in the root namespace. since we emit these
2074
+ // per client.operation, set the correct module
2075
+ statusType.module = rustClient.module;
2076
+ pushModels(method.lroMetadata.pollingInfo.responseModel, rustClient.module, statusModel, false);
1649
2077
  const poller = new rust.Poller(this.crate, new rust.Response(this.crate, statusType, format));
1650
- if (((_q = method.response.type) === null || _q === void 0 ? void 0 : _q.kind) === "model") {
1651
- const responseType = this.typeToWireType(this.getModel(method.response.type));
1652
- if (responseType.kind !== 'model') {
1653
- throw new AdapterError('InternalError', `response type for an LRO method '${method.name}' is not a model`, (_r = method.__raw) === null || _r === void 0 ? void 0 : _r.node);
1654
- }
1655
- pushModels(responseType, this.crate);
1656
- poller.resultType = new rust.Response(this.crate, responseType, format);
2078
+ if (method.response.type) {
2079
+ const resultType = this.getType(method.response.type);
2080
+ pushModels(method.response.type, rustClient.module, resultType);
2081
+ poller.resultType = new rust.Response(this.crate, this.typeToWireType(resultType), format);
1657
2082
  }
1658
2083
  rustMethod.returns = new rust.Result(this.crate, poller);
1659
2084
  }
1660
- else if (method.response.type && !(method.response.type.kind === 'bytes' && method.response.type.encode === 'bytes')) {
2085
+ else if (method.response.type && responseFormat !== 'BinaryFormat') {
1661
2086
  const response = new rust.Response(this.crate, this.typeToWireType(this.getType(method.response.type)), responseFormat);
1662
2087
  rustMethod.returns = new rust.Result(this.crate, response);
1663
2088
  }
@@ -1666,9 +2091,9 @@ export class Adapter {
1666
2091
  // we need to return a marker type
1667
2092
  const markerType = new rust.MarkerType(`${rustClient.name}${utils.pascalCase(method.name, false)}Result`, rustMethod.visibility);
1668
2093
  markerType.docs.summary = `Contains results for ${this.asDocLink(`${rustClient.name}::${methodName}()`, `crate::generated::clients::${rustClient.name}::${methodName}()`)}`;
1669
- this.crate.models.push(markerType);
2094
+ rustClient.module.models.push(markerType);
1670
2095
  let resultType;
1671
- if (method.response.type && method.response.type.kind === 'bytes' && method.response.type.encode === 'bytes') {
2096
+ if (responseFormat === 'BinaryFormat') {
1672
2097
  // method returns a streaming binary response with headers
1673
2098
  resultType = new rust.AsyncResponse(this.crate, markerType);
1674
2099
  }
@@ -1677,8 +2102,8 @@ export class Adapter {
1677
2102
  }
1678
2103
  rustMethod.returns = new rust.Result(this.crate, resultType);
1679
2104
  }
1680
- else if (method.response.type && method.response.type.kind === 'bytes' && method.response.type.encode === 'bytes') {
1681
- // bytes encoding indicates a streaming binary response
2105
+ else if (responseFormat === 'BinaryFormat') {
2106
+ // binary format indicates a streaming binary response
1682
2107
  rustMethod.returns = new rust.Result(this.crate, new rust.AsyncResponse(this.crate, this.getUnitType()));
1683
2108
  }
1684
2109
  else {
@@ -1699,13 +2124,13 @@ export class Adapter {
1699
2124
  // can't do this until the method has been completely adapted
1700
2125
  const pageableMethod = rustMethod;
1701
2126
  pageableMethod.strategy = this.adaptPageableMethodStrategy(method, paramsMap, responseHeadersMap);
1702
- if (((_s = pageableMethod.strategy) === null || _s === void 0 ? void 0 : _s.kind) === 'nextLink') {
2127
+ if (((_r = pageableMethod.strategy) === null || _r === void 0 ? void 0 : _r.kind) === 'nextLink') {
1703
2128
  pageableMethod.strategy.reinjectedParams = this.adaptPageableMethodReinjectionParams(method, paramsMap);
1704
2129
  }
1705
- else if (((_t = pageableMethod.strategy) === null || _t === void 0 ? void 0 : _t.kind) === 'continuationToken') {
2130
+ else if (((_s = pageableMethod.strategy) === null || _s === void 0 ? void 0 : _s.kind) === 'continuationToken') {
1706
2131
  // set the continuation type to token on the Pager and the PagerOptions field in the method options
1707
2132
  pageableMethod.returns.type.continuation = 'token';
1708
- for (const field of pageableMethod.options.type.fields) {
2133
+ for (const field of pageableMethod.options.type.type.fields) {
1709
2134
  if (field.type.kind === 'pagerOptions') {
1710
2135
  field.type.continuation = 'token';
1711
2136
  }
@@ -1812,7 +2237,7 @@ export class Adapter {
1812
2237
  const traitName = `${recursiveTypeName(implFor.kind === 'asyncResponse' ? implFor.type : implFor.content)}Headers`;
1813
2238
  // NOTE: the complete doc text will be emitted at codegen time
1814
2239
  const docs = this.asDocLink(`${client.name}::${method.name}()`, `crate::generated::clients::${client.name}::${method.name}()`);
1815
- const responseHeadersTrait = new rust.ResponseHeadersTrait(traitName, implFor, docs, method.visibility);
2240
+ const responseHeadersTrait = new rust.ResponseHeadersTrait(traitName, implFor, docs, method.visibility, client.module);
1816
2241
  responseHeadersTrait.headers.push(...responseHeaders);
1817
2242
  return responseHeadersTrait;
1818
2243
  }
@@ -1940,9 +2365,10 @@ export class Adapter {
1940
2365
  *
1941
2366
  * @param opParam the tcgc operation parameter to convert
1942
2367
  * @param methodParam the tcgc method parameter associated with opParam
2368
+ * @param lifetime optional lifetime to stamp on borrowed ref types (used for grouped params that are struct fields)
1943
2369
  * @returns a Rust method parameter
1944
2370
  */
1945
- adaptMethodParameter(opParam, methodParam) {
2371
+ adaptMethodParameter(opParam, methodParam, lifetime) {
1946
2372
  var _a, _b, _c, _d, _e, _f, _g, _h;
1947
2373
  /**
1948
2374
  * used to create keys for this.clientMethodParams
@@ -1976,9 +2402,9 @@ export class Adapter {
1976
2402
  const paramName = naming.getEscapedReservedName(utils.snakeCaseName(getCorrespondingClientParamName(opParam)), 'param', reservedParams);
1977
2403
  const paramOptional = methodParam ? methodParam.optional : opParam.optional;
1978
2404
  let paramType = this.getType(methodParam ? methodParam.type : opParam.type);
1979
- // for required header/path/query method string params, we might emit them as borrowed types
2405
+ // for required header/path/query method string params, we might emit them as borrowed types.
1980
2406
  if (!paramOptional && paramLoc !== 'client' && (opParam.kind === 'header' || opParam.kind === 'path' || opParam.kind === 'query')) {
1981
- const borrowedType = this.canBorrowMethodParam(paramType, opParam.kind);
2407
+ const borrowedType = this.canBorrowMethodParam(paramType, opParam.kind, lifetime);
1982
2408
  if (borrowedType) {
1983
2409
  paramType = borrowedType;
1984
2410
  }
@@ -1987,15 +2413,17 @@ export class Adapter {
1987
2413
  switch (opParam.kind) {
1988
2414
  case 'body': {
1989
2415
  let requestType;
1990
- if (opParam.type.kind === 'bytes' && opParam.type.encode === 'bytes') {
1991
- // bytes encoding indicates a streaming binary request
2416
+ const requestFormatType = this.getPayloadFormatType(opParam.type, opParam.defaultContentType);
2417
+ if (requestFormatType === 'BinaryFormat') {
2418
+ // binary format indicates a streaming binary request
1992
2419
  requestType = new rust.Bytes(this.crate);
1993
2420
  }
1994
2421
  else {
1995
- requestType = new rust.Payload(this.typeToWireType(paramType), this.adaptPayloadFormat(opParam.defaultContentType));
2422
+ requestType = this.typeToWireType(paramType);
1996
2423
  }
1997
- const requestFormatType = this.adaptPayloadFormatType(opParam.defaultContentType);
1998
- adaptedParam = new rust.BodyParameter(paramName, paramLoc, paramOptional, new rust.RequestContent(this.crate, requestType, requestFormatType));
2424
+ // binary payloads use NoFormat as the body is sent as raw bytes
2425
+ const wireFormat = requestFormatType === 'BinaryFormat' ? 'NoFormat' : requestFormatType;
2426
+ adaptedParam = new rust.BodyParameter(paramName, paramLoc, paramOptional, new rust.RequestContent(this.crate, requestType, wireFormat));
1999
2427
  break;
2000
2428
  }
2001
2429
  case 'cookie':
@@ -2115,17 +2543,18 @@ export class Adapter {
2115
2543
  *
2116
2544
  * @param type the param type to be updated
2117
2545
  * @param kind the kind of param
2546
+ * @param lifetime optional lifetime to stamp on Ref types (used for struct fields)
2118
2547
  * @returns the updated param type or undefined
2119
2548
  */
2120
- canBorrowMethodParam(type, kind) {
2549
+ canBorrowMethodParam(type, kind, lifetime) {
2121
2550
  const recursiveBuildVecStr = (v) => {
2122
2551
  switch (v.kind) {
2123
2552
  case 'encodedBytes':
2124
- return this.getRefType(this.getEncodedBytes(v.encoding, true));
2553
+ return this.getRefType(this.getEncodedBytes(v.encoding, true), lifetime);
2125
2554
  case 'hashmap':
2126
2555
  return this.getHashMap(this.typeToWireType(recursiveBuildVecStr(v.type)));
2127
2556
  case 'String':
2128
- return this.getRefType(this.getStringSlice());
2557
+ return this.getRefType(this.getStringSlice(), lifetime);
2129
2558
  case 'Vec':
2130
2559
  return this.getVec(this.typeToWireType(recursiveBuildVecStr(v.type)));
2131
2560
  default:
@@ -2142,19 +2571,19 @@ export class Adapter {
2142
2571
  case 'String':
2143
2572
  // header String params are always owned
2144
2573
  if (kind !== 'header') {
2145
- return this.getRefType(this.getStringSlice());
2574
+ return this.getRefType(this.getStringSlice(), lifetime);
2146
2575
  }
2147
2576
  break;
2148
2577
  case 'Vec': {
2149
2578
  // if this is an array of string, we ultimately want a slice of &str
2150
2579
  const unwrapped = recursiveUnwrapVec(type);
2151
2580
  if (unwrapped.kind === 'String' || unwrapped.kind === 'encodedBytes') {
2152
- return this.getRefType(this.getSlice(recursiveBuildVecStr(type.type)));
2581
+ return this.getRefType(this.getSlice(recursiveBuildVecStr(type.type)), lifetime);
2153
2582
  }
2154
- return this.getRefType(this.getSlice(type.type));
2583
+ return this.getRefType(this.getSlice(type.type), lifetime);
2155
2584
  }
2156
2585
  case 'encodedBytes':
2157
- return this.getRefType(this.getEncodedBytes(type.encoding, true));
2586
+ return this.getRefType(this.getEncodedBytes(type.encoding, true), lifetime);
2158
2587
  case 'decimal':
2159
2588
  case 'Etag':
2160
2589
  case 'hashmap':
@@ -2164,7 +2593,7 @@ export class Adapter {
2164
2593
  case 'Url':
2165
2594
  // these types all require conversion
2166
2595
  // to String so we don't need to own them
2167
- return this.getRefType(type);
2596
+ return this.getRefType(type, lifetime);
2168
2597
  }
2169
2598
  return undefined;
2170
2599
  }
@@ -2196,6 +2625,7 @@ export class Adapter {
2196
2625
  case 'slice':
2197
2626
  case 'str':
2198
2627
  case 'String':
2628
+ case 'untaggedUnion':
2199
2629
  case 'Url':
2200
2630
  case 'Vec':
2201
2631
  return type;
@@ -2231,46 +2661,41 @@ export class Adapter {
2231
2661
  }
2232
2662
  const paramName = naming.getEscapedReservedName(utils.snakeCaseName(param.name), 'param');
2233
2663
  const paramLoc = 'method';
2234
- const formatType = utils.getPayloadFormatType(format);
2235
- const adaptedParam = new rust.PartialBodyParameter(paramName, paramLoc, param.optional, serializedName, this.getType(param.type), new rust.RequestContent(this.crate, new rust.Payload(payloadType, format), formatType));
2664
+ const adaptedParam = new rust.PartialBodyParameter(paramName, paramLoc, param.optional, serializedName, this.getType(param.type), new rust.RequestContent(this.crate, payloadType, format));
2236
2665
  return adaptedParam;
2237
2666
  }
2238
2667
  /**
2239
- * converts a Content-Type header value into a payload format
2668
+ * determines the payload format type from serializationOptions on the type, falling
2669
+ * back to inspecting the defaultContentType when serializationOptions isn't available.
2240
2670
  *
2241
- * @param contentType the value of the Content-Type header
2242
- * @returns a payload format
2671
+ * @param type the SDK type associated with the payload, if available
2672
+ * @param defaultContentType the value of the Accept or Content-Type header
2673
+ * @returns a payload format type
2243
2674
  */
2244
- adaptPayloadFormat(contentType) {
2245
- // we only recognize/support JSON and XML content types.
2246
- if (contentType.match(/json/i)) {
2247
- return 'json';
2248
- }
2249
- else if (contentType.match(/xml/i)) {
2250
- // XML support is disabled by default
2251
- this.crate.addDependency(new rust.CrateDependency('azure_core', ['xml']));
2252
- return 'xml';
2253
- }
2254
- else if (contentType.match(/text\/plain/i)) {
2255
- return 'text';
2675
+ getPayloadFormatType(type, defaultContentType) {
2676
+ if ((type === null || type === void 0 ? void 0 : type.kind) === 'model') {
2677
+ const opts = type.serializationOptions;
2678
+ if (opts.json) {
2679
+ return 'JsonFormat';
2680
+ }
2681
+ else if (opts.xml) {
2682
+ this.crate.addDependency(new rust.CrateDependency('azure_core', ['xml']));
2683
+ return 'XmlFormat';
2684
+ }
2685
+ else if (opts.binary) {
2686
+ return 'BinaryFormat';
2687
+ }
2256
2688
  }
2257
- else {
2258
- throw new AdapterError('InternalError', `unexpected contentType ${contentType}`);
2689
+ else if ((type === null || type === void 0 ? void 0 : type.kind) === 'bytes' && type.encode === 'bytes') {
2690
+ // fallback: check if it's a bytes type with bytes encoding (binary stream)
2691
+ return 'BinaryFormat';
2259
2692
  }
2260
- }
2261
- /**
2262
- * converts an accept or content-type header value into a payload format type
2263
- *
2264
- * @param contentType the value of the Accept or Content-Type header
2265
- * @returns a response format
2266
- */
2267
- adaptPayloadFormatType(contentType) {
2268
- // we only recognize/support JSON and XML content types.
2269
- // anything else is NoFormat
2270
- if (contentType.match(/json/i)) {
2693
+ // tcgc doesn't yet have serializationOptions on types other
2694
+ // than models. for those cases, fall back to the header value
2695
+ if (defaultContentType.match(/json/i)) {
2271
2696
  return 'JsonFormat';
2272
2697
  }
2273
- else if (contentType.match(/xml/i)) {
2698
+ else if (defaultContentType.match(/xml/i)) {
2274
2699
  // XML support is disabled by default
2275
2700
  this.crate.addDependency(new rust.CrateDependency('azure_core', ['xml']));
2276
2701
  return 'XmlFormat';
@@ -2319,15 +2744,14 @@ function recursiveKeyName(root, type) {
2319
2744
  case 'discriminatedUnion':
2320
2745
  case 'model':
2321
2746
  case 'struct':
2747
+ case 'untaggedUnion':
2322
2748
  return `${root}-${type.kind}-${type.name}`;
2323
2749
  case 'literal':
2324
2750
  return `${recursiveKeyName(`${root}-${type.kind}`, type.valueKind)}-${type.value}`;
2325
- case 'payload':
2326
- return recursiveKeyName(`${root}-${type.kind}-${type.format}`, type.type);
2327
2751
  case 'ref':
2328
- return recursiveKeyName(`${root}-${type.kind}`, type.type);
2752
+ return recursiveKeyName(`${root}-${type.kind}${type.lifetime ? `-${type.lifetime.name}` : ''}`, type.type);
2329
2753
  case 'requestContent':
2330
- return recursiveKeyName(`${root}-${type.kind}`, type.content);
2754
+ return recursiveKeyName(`${root}-${type.kind}-${type.format}`, type.content);
2331
2755
  case 'safeint':
2332
2756
  case 'decimal':
2333
2757
  return `${root}-${type.kind}${type.stringEncoding ? '-string' : ''}`;
@@ -2417,9 +2841,67 @@ function isHttpStatusCodeRange(statusCode) {
2417
2841
  function adaptAccessFlags(access) {
2418
2842
  return access === 'public' ? 'pub' : 'pubCrate';
2419
2843
  }
2844
+ /**
2845
+ * converts an array of Visibility flags to a sorted, human-readable string.
2846
+ * returns undefined if visibility is unrestricted (all flags or undefined).
2847
+ *
2848
+ * @param visibility the visibility flags from TCGC
2849
+ * @returns a formatted string like "Read" or "Create, Update", or undefined if unrestricted
2850
+ */
2851
+ export function formatVisibility(visibility) {
2852
+ if (!visibility || visibility.length === 0) {
2853
+ return undefined;
2854
+ }
2855
+ // combine all flags into a single bitmask
2856
+ let combined = 0;
2857
+ for (const v of visibility) {
2858
+ combined |= v;
2859
+ }
2860
+ // if all lifecycle flags are set, there's no restriction
2861
+ if ((combined & http.Visibility.All) === http.Visibility.All) {
2862
+ return undefined;
2863
+ }
2864
+ // flags are checked in sorted order: Create, Delete, Query, Read, Update
2865
+ const names = [];
2866
+ if (combined & http.Visibility.Create) {
2867
+ names.push('Create');
2868
+ }
2869
+ if (combined & http.Visibility.Delete) {
2870
+ names.push('Delete');
2871
+ }
2872
+ if (combined & http.Visibility.Query) {
2873
+ names.push('Query');
2874
+ }
2875
+ if (combined & http.Visibility.Read) {
2876
+ names.push('Read');
2877
+ }
2878
+ if (combined & http.Visibility.Update) {
2879
+ names.push('Update');
2880
+ }
2881
+ return names.length > 0 ? names.join(', ') : undefined;
2882
+ }
2420
2883
  /** contains reserved param names */
2421
2884
  const reservedParams = new Set([
2422
2885
  // reserved per SDK guidelines
2423
2886
  'options',
2424
2887
  ]);
2888
+ /**
2889
+ * possible namespaces from core libraries.
2890
+ * all types from these namespaces will be placed
2891
+ * into the root namespace.
2892
+ * if more core libs show up, add their namespaces here.
2893
+ */
2894
+ const LIB_NAMESPACE = [
2895
+ "azure.clientgenerator.core",
2896
+ "azure.core",
2897
+ "azure.resourcemanager",
2898
+ "typespec.http",
2899
+ "typespec.rest",
2900
+ "typespec.versioning",
2901
+ ];
2902
+ /** returns true if the given namespace is a known library namespace or a child of one */
2903
+ function isLibNamespace(namespace) {
2904
+ const ns = namespace.toLowerCase();
2905
+ return LIB_NAMESPACE.some((lib) => ns === lib || ns.startsWith(lib + '.'));
2906
+ }
2425
2907
  //# sourceMappingURL=adapter.js.map