@azure-tools/typespec-rust 0.1.0 → 0.3.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 (75) hide show
  1. package/dist/src/codegen/cargotoml.d.ts +6 -0
  2. package/dist/src/codegen/cargotoml.d.ts.map +1 -1
  3. package/dist/src/codegen/cargotoml.js +6 -1
  4. package/dist/src/codegen/cargotoml.js.map +1 -1
  5. package/dist/src/codegen/clients.d.ts +19 -2
  6. package/dist/src/codegen/clients.d.ts.map +1 -1
  7. package/dist/src/codegen/clients.js +489 -250
  8. package/dist/src/codegen/clients.js.map +1 -1
  9. package/dist/src/codegen/codeGenerator.d.ts +29 -6
  10. package/dist/src/codegen/codeGenerator.d.ts.map +1 -1
  11. package/dist/src/codegen/codeGenerator.js +64 -25
  12. package/dist/src/codegen/codeGenerator.js.map +1 -1
  13. package/dist/src/codegen/context.d.ts +41 -0
  14. package/dist/src/codegen/context.d.ts.map +1 -1
  15. package/dist/src/codegen/context.js +94 -15
  16. package/dist/src/codegen/context.js.map +1 -1
  17. package/dist/src/codegen/enums.d.ts +9 -1
  18. package/dist/src/codegen/enums.d.ts.map +1 -1
  19. package/dist/src/codegen/enums.js +34 -20
  20. package/dist/src/codegen/enums.js.map +1 -1
  21. package/dist/src/codegen/helpers.d.ts +116 -2
  22. package/dist/src/codegen/helpers.d.ts.map +1 -1
  23. package/dist/src/codegen/helpers.js +148 -18
  24. package/dist/src/codegen/helpers.js.map +1 -1
  25. package/dist/src/codegen/lib.d.ts +7 -1
  26. package/dist/src/codegen/lib.d.ts.map +1 -1
  27. package/dist/src/codegen/lib.js +10 -5
  28. package/dist/src/codegen/lib.js.map +1 -1
  29. package/dist/src/codegen/mod.d.ts +6 -1
  30. package/dist/src/codegen/mod.d.ts.map +1 -1
  31. package/dist/src/codegen/mod.js +9 -10
  32. package/dist/src/codegen/mod.js.map +1 -1
  33. package/dist/src/codegen/models.d.ts +20 -1
  34. package/dist/src/codegen/models.d.ts.map +1 -1
  35. package/dist/src/codegen/models.js +250 -13
  36. package/dist/src/codegen/models.js.map +1 -1
  37. package/dist/src/codegen/use.d.ts +32 -0
  38. package/dist/src/codegen/use.d.ts.map +1 -1
  39. package/dist/src/codegen/use.js +42 -15
  40. package/dist/src/codegen/use.js.map +1 -1
  41. package/dist/src/codemodel/client.d.ts +150 -11
  42. package/dist/src/codemodel/client.d.ts.map +1 -1
  43. package/dist/src/codemodel/client.js +60 -17
  44. package/dist/src/codemodel/client.js.map +1 -1
  45. package/dist/src/codemodel/crate.d.ts +33 -0
  46. package/dist/src/codemodel/crate.d.ts.map +1 -1
  47. package/dist/src/codemodel/crate.js +17 -0
  48. package/dist/src/codemodel/crate.js.map +1 -1
  49. package/dist/src/codemodel/method.d.ts +17 -0
  50. package/dist/src/codemodel/method.d.ts.map +1 -1
  51. package/dist/src/codemodel/types.d.ts +152 -24
  52. package/dist/src/codemodel/types.d.ts.map +1 -1
  53. package/dist/src/codemodel/types.js +39 -25
  54. package/dist/src/codemodel/types.js.map +1 -1
  55. package/dist/src/emitter.d.ts +5 -0
  56. package/dist/src/emitter.d.ts.map +1 -1
  57. package/dist/src/emitter.js +36 -23
  58. package/dist/src/emitter.js.map +1 -1
  59. package/dist/src/lib.d.ts +2 -0
  60. package/dist/src/lib.d.ts.map +1 -1
  61. package/dist/src/lib.js +6 -1
  62. package/dist/src/lib.js.map +1 -1
  63. package/dist/src/tcgcadapter/adapter.d.ts +73 -1
  64. package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
  65. package/dist/src/tcgcadapter/adapter.js +342 -81
  66. package/dist/src/tcgcadapter/adapter.js.map +1 -1
  67. package/dist/src/tcgcadapter/helpers.d.ts +18 -0
  68. package/dist/src/tcgcadapter/helpers.d.ts.map +1 -1
  69. package/dist/src/tcgcadapter/helpers.js +23 -2
  70. package/dist/src/tcgcadapter/helpers.js.map +1 -1
  71. package/dist/src/tcgcadapter/naming.d.ts +8 -0
  72. package/dist/src/tcgcadapter/naming.d.ts.map +1 -1
  73. package/dist/src/tcgcadapter/naming.js +8 -5
  74. package/dist/src/tcgcadapter/naming.js.map +1 -1
  75. package/package.json +21 -23
@@ -8,10 +8,21 @@ import * as helpers from './helpers.js';
8
8
  import * as naming from './naming.js';
9
9
  import * as tcgc from '@azure-tools/typespec-client-generator-core';
10
10
  import * as rust from '../codemodel/index.js';
11
- // Adapter converts the tcgc code model to a Rust Crate
11
+ /** Adapter converts the tcgc code model to a Rust Crate */
12
12
  export class Adapter {
13
+ /**
14
+ * Creates an Adapter for the specified EmitContext.
15
+ *
16
+ * @param context the compiler context from which to create the Adapter
17
+ * @returns
18
+ */
13
19
  static async create(context) {
14
- const ctx = await tcgc.createSdkContext(context);
20
+ // @encodedName can be used in XML scenarios, it is effectively the
21
+ // same as TypeSpec.Xml.@name. however, it's filtered out by default
22
+ // so we need to add it to the allow list of decorators
23
+ const ctx = await tcgc.createSdkContext(context, '@azure-tools/typespec-rust', {
24
+ additionalDecorators: ['TypeSpec\\.@encodedName'],
25
+ });
15
26
  return new Adapter(ctx, context.options);
16
27
  }
17
28
  constructor(ctx, options) {
@@ -24,20 +35,38 @@ export class Adapter {
24
35
  }
25
36
  this.crate = new rust.Crate(options['crate-name'], options['crate-version'], serviceType);
26
37
  }
27
- // performs all the steps to convert tcgc to a crate
38
+ /** performs all the steps to convert tcgc to a crate */
28
39
  tcgcToCrate() {
29
40
  this.adaptTypes();
30
41
  this.adaptClients();
31
42
  if (this.crate.enums.length > 0 || this.crate.models.length > 0) {
32
43
  this.crate.addDependency(new rust.CrateDependency('serde'));
33
44
  }
45
+ if (this.crate.enums.length > 0 || this.crate.models.length > 0) {
46
+ this.crate.addDependency(new rust.CrateDependency('typespec_client_core'));
47
+ }
34
48
  if (this.crate.clients.length > 0) {
35
49
  this.crate.addDependency(new rust.CrateDependency('async-std'));
36
50
  }
51
+ // TODO: remove once https://github.com/Azure/typespec-rust/issues/22 is fixed
52
+ for (const client of this.crate.clients) {
53
+ let done = false;
54
+ for (const method of client.methods) {
55
+ if (method.kind === 'pageable') {
56
+ this.crate.addDependency(new rust.CrateDependency('futures'));
57
+ done = true;
58
+ break;
59
+ }
60
+ }
61
+ if (done) {
62
+ break;
63
+ }
64
+ }
65
+ // end TODO
37
66
  this.crate.sortContent();
38
67
  return this.crate;
39
68
  }
40
- // converts all tcgc types to their Rust type equivalent
69
+ /** converts all tcgc types to their Rust type equivalent */
41
70
  adaptTypes() {
42
71
  for (const sdkEnum of this.ctx.sdkPackage.enums) {
43
72
  if ((sdkEnum.usage & tcgc.UsageFlags.ApiVersionEnum) === tcgc.UsageFlags.ApiVersionEnum) {
@@ -49,16 +78,20 @@ export class Adapter {
49
78
  this.crate.enums.push(rustEnum);
50
79
  }
51
80
  for (const model of this.ctx.sdkPackage.models) {
52
- if (model.usage === tcgc.UsageFlags.Error) {
53
- // if the model is purely used for errors then
54
- // skip it as we use azure_core::Error.
81
+ if ((model.usage & tcgc.UsageFlags.Exception) === tcgc.UsageFlags.Exception && (model.usage & tcgc.UsageFlags.Input) === 0 && (model.usage & tcgc.UsageFlags.Output) === 0 || tcgc.isAzureCoreModel(model)) {
82
+ // skip error and core types as we use their azure_core equivalents
55
83
  continue;
56
84
  }
57
85
  const rustModel = this.getModel(model);
58
86
  this.crate.models.push(rustModel);
59
87
  }
60
88
  }
61
- // converts a tcgc enum to a Rust enum
89
+ /**
90
+ * converts a tcgc enum to a Rust enum
91
+ *
92
+ * @param sdkEnum the tcgc enum to convert
93
+ * @returns a Rust enum
94
+ */
62
95
  getEnum(sdkEnum) {
63
96
  const enumName = codegen.capitalize(sdkEnum.name);
64
97
  let rustEnum = this.types.get(enumName);
@@ -71,11 +104,16 @@ export class Adapter {
71
104
  const rustEnumValue = new rust.EnumValue(helpers.fixUpEnumValueName(value.name), value.value);
72
105
  values.push(rustEnumValue);
73
106
  }
74
- rustEnum = new rust.Enum(enumName, isPub(sdkEnum.access), values, !sdkEnum.isFixed);
107
+ rustEnum = new rust.Enum(enumName, sdkEnum.access === 'public', values, !sdkEnum.isFixed);
75
108
  this.types.set(enumName, rustEnum);
76
109
  return rustEnum;
77
110
  }
78
- // converts a tcgc model to a Rust model
111
+ /**
112
+ * converts a tcgc model to a Rust model
113
+ *
114
+ * @param model the tcgc model to convert
115
+ * @returns a Rust model
116
+ */
79
117
  getModel(model) {
80
118
  if (model.name.length === 0) {
81
119
  throw new Error('unnamed model'); // TODO: this might no longer be an issue
@@ -85,29 +123,51 @@ export class Adapter {
85
123
  if (rustModel) {
86
124
  return rustModel;
87
125
  }
88
- rustModel = new rust.Model(modelName, isPub(model.access));
126
+ rustModel = new rust.Model(modelName, model.access === 'internal');
89
127
  rustModel.docs.summary = model.summary;
90
128
  rustModel.docs.description = model.doc;
129
+ rustModel.xmlName = getXMLName(model.decorators);
91
130
  this.types.set(modelName, rustModel);
92
131
  for (const property of model.properties) {
93
132
  if (property.kind !== 'property') {
133
+ if (property.type.kind === 'constant') {
134
+ // typical case is content-type header.
135
+ // we don't need to emit this as a field so skip it.
136
+ continue;
137
+ }
94
138
  // TODO: https://github.com/Azure/typespec-rust/issues/96
95
139
  throw new Error(`model property kind ${property.kind} NYI`);
96
140
  }
97
- const structField = this.getModelField(property);
141
+ const structField = this.getModelField(property, !rustModel.internal);
98
142
  rustModel.fields.push(structField);
99
143
  }
100
144
  return rustModel;
101
145
  }
102
- // converts a tcgc model property to a model field
103
- getModelField(property) {
104
- const fieldType = new rust.Option(this.getType(property.type), false);
146
+ /**
147
+ * converts a tcgc model property to a model field
148
+ *
149
+ * @param property the tcgc model property to convert
150
+ * @param isPubMod indicates if the model is public
151
+ * @returns a Rust model field
152
+ */
153
+ getModelField(property, isPubMod) {
154
+ let fieldType = this.getType(property.type);
155
+ // for public models each field is always an Option<T>
156
+ if (isPubMod || property.optional) {
157
+ fieldType = new rust.Option(fieldType);
158
+ }
105
159
  const modelField = new rust.ModelField(naming.getEscapedReservedName(snakeCaseName(property.name), 'prop'), property.serializedName, true, fieldType);
106
160
  modelField.docs.summary = property.summary;
107
161
  modelField.docs.description = property.doc;
162
+ const xmlName = getXMLName(property.decorators);
163
+ if (xmlName) {
164
+ // use the XML name when specified
165
+ modelField.serde = xmlName;
166
+ }
167
+ modelField.xmlKind = getXMLKind(property.decorators, modelField);
108
168
  return modelField;
109
169
  }
110
- // converts a tcgc type to a Rust type
170
+ /** converts a tcgc type to a Rust type */
111
171
  getType(type) {
112
172
  const getScalar = (kind) => {
113
173
  let scalar = this.types.get(kind);
@@ -192,7 +252,8 @@ export class Adapter {
192
252
  case 'model':
193
253
  return this.getModel(type);
194
254
  case 'endpoint':
195
- case 'string': {
255
+ case 'string':
256
+ case 'url': {
196
257
  let stringType = this.types.get(type.kind);
197
258
  if (stringType) {
198
259
  return stringType;
@@ -210,15 +271,6 @@ export class Adapter {
210
271
  this.types.set(type.kind, anyType);
211
272
  return anyType;
212
273
  }
213
- case 'url': {
214
- let urlType = this.types.get(type.kind);
215
- if (urlType) {
216
- return urlType;
217
- }
218
- urlType = new rust.Url(this.crate);
219
- this.types.set(type.kind, urlType);
220
- return urlType;
221
- }
222
274
  case 'utcDateTime': {
223
275
  const keyName = `${type.kind}-${type.encode}`;
224
276
  let timeType = this.types.get(keyName);
@@ -233,6 +285,7 @@ export class Adapter {
233
285
  throw new Error(`unhandled tcgc type ${type.kind}`);
234
286
  }
235
287
  }
288
+ /** returns the Rust unit type */
236
289
  getUnitType() {
237
290
  const typeKey = 'rust-unit';
238
291
  let unitType = this.types.get(typeKey);
@@ -243,6 +296,12 @@ export class Adapter {
243
296
  this.types.set(typeKey, unitType);
244
297
  return unitType;
245
298
  }
299
+ /**
300
+ * converts a tcgc constant to a Rust literal
301
+ *
302
+ * @param constType the constant to convert
303
+ * @returns a Rust literal
304
+ */
246
305
  getLiteral(constType) {
247
306
  const literalKey = `literal-${constType.value}`;
248
307
  let literalType = this.types.get(literalKey);
@@ -253,7 +312,7 @@ export class Adapter {
253
312
  this.types.set(literalKey, literalType);
254
313
  return literalType;
255
314
  }
256
- // converts all tcgc clients and their methods into Rust clients/methods
315
+ /** converts all tcgc clients and their methods into Rust clients/methods */
257
316
  adaptClients() {
258
317
  for (const client of this.ctx.sdkPackage.clients) {
259
318
  if (client.methods.length === 0) {
@@ -266,8 +325,14 @@ export class Adapter {
266
325
  }
267
326
  }
268
327
  }
269
- // recursively adapts a client and its methods.
270
- // this simplifies the case for hierarchical clients.
328
+ /**
329
+ * recursively converts a client and its methods.
330
+ * this simplifies the case for hierarchical clients.
331
+ *
332
+ * @param client the tcgc client to recursively convert
333
+ * @param parent contains the parent Rust client when converting a child client
334
+ * @returns a Rust client
335
+ */
271
336
  recursiveAdaptClient(client, parent) {
272
337
  let clientName = client.name;
273
338
  if (parent) {
@@ -287,8 +352,7 @@ export class Adapter {
287
352
  // strip off the Client suffix from the parent client name
288
353
  clientName = parent.name.substring(0, parent.name.length - 6) + clientName;
289
354
  }
290
- // only add the Client suffix to instantiable clients
291
- if (!clientName.match(/Client$/) && !parent) {
355
+ if (!clientName.match(/Client$/)) {
292
356
  clientName += 'Client';
293
357
  }
294
358
  const rustClient = new rust.Client(clientName);
@@ -298,7 +362,7 @@ export class Adapter {
298
362
  // anything other than public means non-instantiable client
299
363
  if (client.initialization.access === 'public') {
300
364
  const clientOptionsStruct = new rust.Struct(`${rustClient.name}Options`, true);
301
- const clientOptionsField = new rust.StructField('client_options', false, new rust.ExternalType(this.crate, 'azure_core', 'ClientOptions'));
365
+ const clientOptionsField = new rust.StructField('client_options', true, new rust.ExternalType(this.crate, 'azure_core', 'ClientOptions'));
302
366
  clientOptionsField.defaultValue = 'ClientOptions::default()';
303
367
  clientOptionsStruct.fields.push(clientOptionsField);
304
368
  rustClient.constructable = new rust.ClientConstruction(new rust.ClientOptions(clientOptionsStruct));
@@ -346,14 +410,14 @@ export class Adapter {
346
410
  break;
347
411
  case 'union':
348
412
  // TODO: https://github.com/Azure/typespec-rust/issues/57
349
- throw new Error('credential unions NYI');
413
+ continue;
350
414
  }
351
415
  break;
352
416
  case 'endpoint':
353
417
  // for Rust, we always require a complete endpoint param, templated
354
418
  // endpoints, e.g. https://{something}.contoso.com isn't supported.
355
419
  // note that the types of the param and the field are slightly different
356
- ctorParams.push(new rust.ClientParameter(param.name, new rust.ImplTrait('AsRef', new rust.StringSlice())));
420
+ ctorParams.push(new rust.ClientParameter(param.name, new rust.StringSlice(), true));
357
421
  rustClient.fields.push(new rust.ClientParameter(param.name, new rust.Url(this.crate)));
358
422
  break;
359
423
  case 'method': {
@@ -370,7 +434,7 @@ export class Adapter {
370
434
  const paramName = snakeCaseName(param.name);
371
435
  rustClient.fields.push(new rust.ClientParameter(paramName, new rust.StringType()));
372
436
  // client-side default value makes the param optional
373
- const apiVersionField = new rust.StructField(paramName, false, new rust.StringType());
437
+ const apiVersionField = new rust.StructField(paramName, true, new rust.StringType());
374
438
  apiVersionField.defaultValue = `String::from("${param.clientDefaultValue}")`;
375
439
  clientOptionsStruct.fields.push(apiVersionField);
376
440
  break;
@@ -400,7 +464,9 @@ export class Adapter {
400
464
  for (const method of client.methods) {
401
465
  if (method.kind === 'clientaccessor') {
402
466
  const subClient = this.recursiveAdaptClient(method.response, rustClient);
403
- rustClient.methods.push(new rust.ClientAccessor(`get_${snakeCaseName(subClient.name)}_client`, rustClient, subClient));
467
+ const clientAccessor = new rust.ClientAccessor(`get_${snakeCaseName(subClient.name)}`, rustClient, subClient);
468
+ clientAccessor.docs.summary = `Returns a new instance of ${subClient.name}.`;
469
+ rustClient.methods.push(clientAccessor);
404
470
  }
405
471
  else {
406
472
  this.adaptMethod(method, rustClient);
@@ -409,7 +475,12 @@ export class Adapter {
409
475
  this.crate.clients.push(rustClient);
410
476
  return rustClient;
411
477
  }
412
- // converts method into a rust.Method for the specified rust.Client
478
+ /**
479
+ * converts a tcgc method to a Rust method for the specified client
480
+ *
481
+ * @param method the tcgc method to convert
482
+ * @param rustClient the client to which the method belongs
483
+ */
413
484
  adaptMethod(method, rustClient) {
414
485
  let rustMethod;
415
486
  const optionsLifetime = new rust.Lifetime('a');
@@ -417,16 +488,31 @@ export class Adapter {
417
488
  methodOptionsStruct.lifetime = optionsLifetime;
418
489
  const clientMethodOptions = new rust.ExternalType(this.crate, 'azure_core', 'ClientMethodOptions');
419
490
  clientMethodOptions.lifetime = optionsLifetime;
420
- methodOptionsStruct.fields.push(new rust.StructField('method_options', false, clientMethodOptions));
491
+ methodOptionsStruct.fields.push(new rust.StructField('method_options', true, clientMethodOptions));
492
+ const methodName = naming.getEscapedReservedName(snakeCaseName(method.name), 'fn');
493
+ const pub = method.access === 'public';
494
+ const methodOptions = new rust.MethodOptions(methodOptionsStruct);
421
495
  const httpMethod = method.operation.verb;
422
496
  const httpPath = method.operation.path;
423
497
  switch (method.kind) {
424
498
  case 'basic':
425
- rustMethod = new rust.AsyncMethod(naming.getEscapedReservedName(snakeCaseName(method.name), 'fn'), rustClient, isPub(method.access), new rust.MethodOptions(methodOptionsStruct, false), httpMethod, httpPath);
499
+ rustMethod = new rust.AsyncMethod(methodName, rustClient, pub, methodOptions, httpMethod, httpPath);
426
500
  break;
427
501
  case 'paging':
428
- // TODO: https://github.com/Azure/typespec-rust/issues/60
429
- return;
502
+ rustMethod = new rust.PageableMethod(methodName, rustClient, pub, methodOptions, httpMethod, httpPath);
503
+ if (method.nextLinkOperation) {
504
+ // TODO: https://github.com/Azure/autorest.rust/issues/103
505
+ throw new Error('next page operation NYI');
506
+ }
507
+ else if (method.nextLinkPath) {
508
+ // this is the name of the field in the response type that contains the next link URL
509
+ if (method.nextLinkPath.indexOf('.') > 0) {
510
+ // TODO: https://github.com/Azure/autorest.rust/issues/102
511
+ throw new Error('nested next link path NYI');
512
+ }
513
+ rustMethod.strategy = new rust.PageableStrategyNextLink(naming.getEscapedReservedName(snakeCaseName(method.nextLinkPath), 'prop'));
514
+ }
515
+ break;
430
516
  default:
431
517
  throw new Error(`method kind ${method.kind} NYI`);
432
518
  }
@@ -434,7 +520,7 @@ export class Adapter {
434
520
  rustMethod.docs.description = method.doc;
435
521
  rustClient.methods.push(rustMethod);
436
522
  // stuff all of the operation parameters into one array for easy traversal
437
- let allOpParams = new Array();
523
+ const allOpParams = new Array();
438
524
  allOpParams.push(...method.operation.parameters);
439
525
  if (method.operation.bodyParam) {
440
526
  allOpParams.push(method.operation.bodyParam);
@@ -453,8 +539,13 @@ export class Adapter {
453
539
  throw new Error(`didn't find operation parameter for method ${method.name} parameter ${param.name}`);
454
540
  }
455
541
  let adaptedParam;
456
- if (opParam.kind === 'body' && opParam.type.kind === 'model' && opParam.type.kind !== param.type.kind) {
457
- throw new Error('spread params NYI');
542
+ // for spread params there are two cases we need to consider.
543
+ // if the method param's type doesn't match the op param's type then it's a spread param
544
+ // - e.g. method param's type is string/int/etc which is a field in the op param's body type
545
+ // 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
546
+ // - e.g. op param is an intersection of multiple model types, and each model type is exposed as a discrete param
547
+ if (opParam.kind === 'body' && opParam.type.kind === 'model' && (opParam.type.kind !== param.type.kind || opParam.correspondingMethodParams.length > 1)) {
548
+ adaptedParam = this.adaptMethodSpreadParameter(param, this.adaptBodyFormat(opParam.defaultContentType), opParam.type);
458
549
  }
459
550
  else {
460
551
  adaptedParam = this.adaptMethodParameter(opParam);
@@ -462,10 +553,24 @@ export class Adapter {
462
553
  adaptedParam.docs.summary = param.summary;
463
554
  adaptedParam.docs.description = param.doc;
464
555
  rustMethod.params.push(adaptedParam);
465
- // remove the opParam we just processed
466
- allOpParams = allOpParams.filter((v) => {
467
- return v !== opParam;
468
- });
556
+ if (adaptedParam.optional) {
557
+ let fieldType;
558
+ if (adaptedParam.kind === 'partialBody') {
559
+ // for partial body params, adaptedParam.type is the model type that's
560
+ // sent in the request. we want the field within the model for this param.
561
+ // NOTE: if the param is optional then the field is optional, thus it's
562
+ // already wrapped in an Option<T> type.
563
+ const field = adaptedParam.type.type.fields.find(f => { return f.name === adaptedParam.name; });
564
+ if (!field) {
565
+ throw new Error(`didn't find spread param field ${adaptedParam.name} in type ${adaptedParam.type.type.name}`);
566
+ }
567
+ fieldType = field.type;
568
+ }
569
+ else {
570
+ fieldType = new rust.Option(adaptedParam.type);
571
+ }
572
+ rustMethod.options.type.fields.push(new rust.StructField(adaptedParam.name, true, fieldType));
573
+ }
469
574
  }
470
575
  // client params aren't included in method.parameters so
471
576
  // look for them in the remaining operation parameters.
@@ -477,27 +582,65 @@ export class Adapter {
477
582
  rustMethod.params.push(adaptedParam);
478
583
  }
479
584
  }
480
- let returnType;
481
- if (method.response.type) {
482
- // search the HTTP responses for the corresponding type so we can determine the wire format
483
- let format;
484
- for (const httpResp of method.operation.responses.values()) {
485
- if (!httpResp.type || !httpResp.defaultContentType || httpResp.type.kind !== method.response.type.kind) {
486
- continue;
585
+ const getBodyFormat = () => {
586
+ // fetch the body format from the HTTP responses.
587
+ // they should all have the same type so no need to match responses to type.
588
+ let defaultContentType;
589
+ for (const httpResp of method.operation.responses) {
590
+ if (defaultContentType && httpResp.defaultContentType && defaultContentType !== httpResp.defaultContentType) {
591
+ throw new Error(`method ${method.name} has conflicting content types`);
487
592
  }
488
- format = this.adaptSerdeFormat(httpResp.defaultContentType);
489
- break;
593
+ defaultContentType = httpResp.defaultContentType;
594
+ }
595
+ if (!defaultContentType) {
596
+ throw new Error(`unable to determine content type for method ${method.name}`);
597
+ }
598
+ return this.adaptBodyFormat(defaultContentType);
599
+ };
600
+ let returnType;
601
+ if (method.kind === 'paging') {
602
+ // for paged methods, tcgc models method.response.type as an Array<T>.
603
+ // however, we want the synthesized paged response envelope type instead.
604
+ const synthesizedType = method.operation.responses[0].type;
605
+ if (!synthesizedType) {
606
+ throw new Error(`paged method ${method.name} has no synthesized response type`);
490
607
  }
491
- if (!format) {
492
- throw new Error(`didn't find HTTP response for kind ${method.response.type.kind} in method ${method.name}`);
608
+ else if (synthesizedType.kind !== 'model') {
609
+ throw new Error(`paged method ${method.name} synthesized response type has unexpected kind ${synthesizedType.kind}`);
493
610
  }
494
- returnType = new rust.Response(this.crate, this.getType(method.response.type), format);
611
+ const format = getBodyFormat();
612
+ const synthesizedModel = this.getModel(synthesizedType);
613
+ if (!this.crate.models.includes(synthesizedModel)) {
614
+ this.crate.models.push(synthesizedModel);
615
+ }
616
+ returnType = new rust.Pager(this.crate, synthesizedModel, format);
617
+ }
618
+ else if (method.response.type) {
619
+ const format = getBodyFormat();
620
+ // until https://github.com/Azure/typespec-azure/issues/614 is resolved
621
+ // we have to look at the format to determine if the response is a streaming
622
+ // binary response. at present, it's represented as a base-64 encoded byte
623
+ // array which is clearly wrong.
624
+ let respType;
625
+ if (format === 'binary') {
626
+ respType = this.getUnitType();
627
+ }
628
+ else {
629
+ respType = this.getType(method.response.type);
630
+ }
631
+ returnType = new rust.Response(this.crate, respType, format);
495
632
  }
496
633
  else {
497
634
  returnType = new rust.Response(this.crate, this.getUnitType());
498
635
  }
499
636
  rustMethod.returns = new rust.Result(this.crate, returnType);
500
637
  }
638
+ /**
639
+ * converts a tcgc operation parameter into a Rust method parameter
640
+ *
641
+ * @param param the tcgc operation parameter to convert
642
+ * @returns a Rust method parameter
643
+ */
501
644
  adaptMethodParameter(param) {
502
645
  const paramLoc = param.onClient ? 'client' : 'method';
503
646
  // if this is a client method param, check if we've already adapted it
@@ -508,15 +651,11 @@ export class Adapter {
508
651
  }
509
652
  }
510
653
  const paramName = naming.getEscapedReservedName(snakeCaseName(param.name), 'param');
511
- let paramType = this.getType(param.type);
512
- if (paramLoc === 'method' && paramType.kind === 'String') {
513
- // for Strings, we define these as "impl Into<String>" so that passing a str will just work
514
- paramType = new rust.ImplTrait('Into', paramType);
515
- }
654
+ const paramType = this.getType(param.type);
516
655
  let adaptedParam;
517
656
  switch (param.kind) {
518
657
  case 'body': {
519
- adaptedParam = new rust.BodyParameter(paramName, paramLoc, new rust.RequestContent(this.crate, paramType, this.adaptSerdeFormat(param.defaultContentType)));
658
+ adaptedParam = new rust.BodyParameter(paramName, paramLoc, param.optional, new rust.RequestContent(this.crate, paramType, this.adaptBodyFormat(param.defaultContentType)));
520
659
  break;
521
660
  }
522
661
  case 'header':
@@ -538,14 +677,20 @@ export class Adapter {
538
677
  default:
539
678
  throw new Error(`unexpected format ${param.collectionFormat} for HeaderCollectionParameter`);
540
679
  }
541
- adaptedParam = new rust.HeaderCollectionParameter(paramName, param.serializedName, paramLoc, paramType, format);
680
+ adaptedParam = new rust.HeaderCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, format);
681
+ }
682
+ else if (param.serializedName === 'x-ms-meta') {
683
+ if (paramType.kind !== 'hashmap') {
684
+ throw new Error(`unexpected kind ${paramType.kind} for header ${param.serializedName}`);
685
+ }
686
+ adaptedParam = new rust.HeaderHashMapParameter(paramName, param.serializedName, paramLoc, param.optional, paramType);
542
687
  }
543
688
  else {
544
- adaptedParam = new rust.HeaderParameter(paramName, param.serializedName, paramLoc, paramType);
689
+ adaptedParam = new rust.HeaderParameter(paramName, param.serializedName, paramLoc, param.optional, paramType);
545
690
  }
546
691
  break;
547
692
  case 'path':
548
- adaptedParam = new rust.PathParameter(paramName, param.serializedName, paramLoc, paramType, param.urlEncode);
693
+ adaptedParam = new rust.PathParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, param.urlEncode);
549
694
  break;
550
695
  case 'query':
551
696
  if (param.collectionFormat) {
@@ -554,11 +699,11 @@ export class Adapter {
554
699
  throw new Error(`unexpected kind ${paramType.kind} for QueryCollectionParameter`);
555
700
  }
556
701
  // TODO: hard-coded encoding setting, https://github.com/Azure/typespec-azure/issues/1314
557
- adaptedParam = new rust.QueryCollectionParameter(paramName, param.serializedName, paramLoc, paramType, true, format);
702
+ adaptedParam = new rust.QueryCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true, format);
558
703
  }
559
704
  else {
560
705
  // TODO: hard-coded encoding setting, https://github.com/Azure/typespec-azure/issues/1314
561
- adaptedParam = new rust.QueryParameter(paramName, param.serializedName, paramLoc, paramType, true);
706
+ adaptedParam = new rust.QueryParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true);
562
707
  }
563
708
  break;
564
709
  }
@@ -569,22 +714,86 @@ export class Adapter {
569
714
  }
570
715
  return adaptedParam;
571
716
  }
572
- adaptSerdeFormat(contentType) {
573
- switch (contentType) {
574
- case 'application/json':
575
- this.crate.addDependency(new rust.CrateDependency('serde_json'));
576
- return 'json';
577
- default:
578
- throw new Error(`unhandled contentType ${contentType}`);
717
+ /**
718
+ * converts a tcgc spread parameter into a Rust partial body parameter.
719
+ *
720
+ * @param param the tcgc method parameter to convert
721
+ * @param format the wire format for the underlying body type
722
+ * @param opParamType the tcgc model to which the spread parameter belongs
723
+ * @returns a Rust partial body parameter
724
+ */
725
+ adaptMethodSpreadParameter(param, format, opParamType) {
726
+ switch (format) {
727
+ case 'binary':
728
+ throw new Error('binary spread NYI');
729
+ case 'json':
730
+ case 'xml': {
731
+ // find the corresponding field within the model so we can get its index
732
+ let serializedName;
733
+ for (const property of opParamType.properties) {
734
+ if (property.name === param.name) {
735
+ serializedName = property.serializedName;
736
+ break;
737
+ }
738
+ }
739
+ if (serializedName === undefined) {
740
+ throw new Error(`didn't find body model property for spread parameter ${param.name}`);
741
+ }
742
+ const paramLoc = 'method';
743
+ const paramType = this.getType(opParamType);
744
+ if (paramType.kind !== 'model') {
745
+ throw new Error(`unexpected kind ${paramType.kind} for spread body param`);
746
+ }
747
+ const paramName = naming.getEscapedReservedName(snakeCaseName(param.name), 'param');
748
+ return new rust.PartialBodyParameter(paramName, paramLoc, param.optional, serializedName, new rust.RequestContent(this.crate, paramType, format));
749
+ }
579
750
  }
580
751
  }
752
+ /**
753
+ * converts a Content-Type header value into a Rust body format
754
+ *
755
+ * @param contentType the value of the Content-Type header
756
+ * @returns a Rust body format
757
+ */
758
+ adaptBodyFormat(contentType) {
759
+ // we only recognize/support JSON, text, and XML content types, so assume anything else is binary
760
+ // NOTE: we check XML before text in order to support text/xml
761
+ let bodyFormat = 'binary';
762
+ if (contentType.match(/json/i)) {
763
+ this.crate.addDependency(new rust.CrateDependency('serde_json'));
764
+ bodyFormat = 'json';
765
+ }
766
+ else if (contentType.match(/xml/i)) {
767
+ bodyFormat = 'xml';
768
+ }
769
+ else if (contentType.match(/text/i)) {
770
+ throw new Error(`unhandled contentType ${contentType}`);
771
+ }
772
+ return bodyFormat;
773
+ }
581
774
  }
582
- function isPub(access) {
583
- return access !== 'internal';
584
- }
775
+ /**
776
+ * snake-cases the provided name
777
+ *
778
+ * @param name the name to snake-case
779
+ * @returns name in snake-case format
780
+ */
585
781
  function snakeCaseName(name) {
586
782
  return codegen.deconstruct(name).join('_');
587
783
  }
784
+ /**
785
+ * recursively creates a map key from the specified type.
786
+ * this is idempotent so providing the same type will create
787
+ * the same key.
788
+ *
789
+ * obj is recursively unwrapped, and each layer is used to construct
790
+ * the key. e.g. if obj is a HashMap<String, Vec<i32>> this would
791
+ * unwrap to dict-array-int32.
792
+ *
793
+ * @param root the starting value for the key
794
+ * @param obj the type for which to create the key
795
+ * @returns a string containing the complete map key
796
+ */
588
797
  function recursiveKeyName(root, obj) {
589
798
  switch (obj.kind) {
590
799
  case 'array':
@@ -614,4 +823,56 @@ function recursiveKeyName(root, obj) {
614
823
  return `${root}-${obj.kind}`;
615
824
  }
616
825
  }
826
+ /**
827
+ * returns the XML-specific name based on the provided decorators
828
+ *
829
+ * @param decorators the decorators to enumerate
830
+ * @returns the XML-specific name or undefined if there isn't one
831
+ */
832
+ function getXMLName(decorators) {
833
+ if (decorators.length === 0) {
834
+ return undefined;
835
+ }
836
+ for (const decorator of decorators) {
837
+ switch (decorator.name) {
838
+ case 'TypeSpec.@encodedName':
839
+ if (decorator.arguments['mimeType'] === 'application/xml') {
840
+ return decorator.arguments['name'];
841
+ }
842
+ break;
843
+ case 'TypeSpec.Xml.@name':
844
+ return decorator.arguments['name'];
845
+ }
846
+ }
847
+ return undefined;
848
+ }
849
+ /**
850
+ * returns the XML-specific kind for field based on the provided decorators
851
+ *
852
+ * @param decorators the decorators to enumerate
853
+ * @param field the Rust model field to which the kind will apply
854
+ * @returns the XML-specific field kind or undefined if there isn't one
855
+ */
856
+ function getXMLKind(decorators, field) {
857
+ if (decorators.length === 0) {
858
+ return undefined;
859
+ }
860
+ for (const decorator of decorators) {
861
+ switch (decorator.name) {
862
+ case 'TypeSpec.Xml.@attribute':
863
+ return 'attribute';
864
+ case 'TypeSpec.Xml.@unwrapped': {
865
+ const fieldType = helpers.unwrapOption(field.type);
866
+ switch (fieldType.kind) {
867
+ case 'vector':
868
+ return 'unwrappedList';
869
+ case 'String':
870
+ // an unwrapped string means it's text
871
+ return 'text';
872
+ }
873
+ }
874
+ }
875
+ }
876
+ return undefined;
877
+ }
617
878
  //# sourceMappingURL=adapter.js.map