@azure-tools/typespec-rust 0.1.0 → 0.2.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 +483 -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 +20 -20
  58. package/dist/src/emitter.js.map +1 -1
  59. package/dist/src/lib.d.ts +1 -0
  60. package/dist/src/lib.d.ts.map +1 -1
  61. package/dist/src/lib.js +1 -0
  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 +338 -71
  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.Error) === tcgc.UsageFlags.Error || 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);
@@ -233,6 +293,7 @@ export class Adapter {
233
293
  throw new Error(`unhandled tcgc type ${type.kind}`);
234
294
  }
235
295
  }
296
+ /** returns the Rust unit type */
236
297
  getUnitType() {
237
298
  const typeKey = 'rust-unit';
238
299
  let unitType = this.types.get(typeKey);
@@ -243,6 +304,12 @@ export class Adapter {
243
304
  this.types.set(typeKey, unitType);
244
305
  return unitType;
245
306
  }
307
+ /**
308
+ * converts a tcgc constant to a Rust literal
309
+ *
310
+ * @param constType the constant to convert
311
+ * @returns a Rust literal
312
+ */
246
313
  getLiteral(constType) {
247
314
  const literalKey = `literal-${constType.value}`;
248
315
  let literalType = this.types.get(literalKey);
@@ -253,7 +320,7 @@ export class Adapter {
253
320
  this.types.set(literalKey, literalType);
254
321
  return literalType;
255
322
  }
256
- // converts all tcgc clients and their methods into Rust clients/methods
323
+ /** converts all tcgc clients and their methods into Rust clients/methods */
257
324
  adaptClients() {
258
325
  for (const client of this.ctx.sdkPackage.clients) {
259
326
  if (client.methods.length === 0) {
@@ -266,8 +333,14 @@ export class Adapter {
266
333
  }
267
334
  }
268
335
  }
269
- // recursively adapts a client and its methods.
270
- // this simplifies the case for hierarchical clients.
336
+ /**
337
+ * recursively converts a client and its methods.
338
+ * this simplifies the case for hierarchical clients.
339
+ *
340
+ * @param client the tcgc client to recursively convert
341
+ * @param parent contains the parent Rust client when converting a child client
342
+ * @returns a Rust client
343
+ */
271
344
  recursiveAdaptClient(client, parent) {
272
345
  let clientName = client.name;
273
346
  if (parent) {
@@ -287,8 +360,7 @@ export class Adapter {
287
360
  // strip off the Client suffix from the parent client name
288
361
  clientName = parent.name.substring(0, parent.name.length - 6) + clientName;
289
362
  }
290
- // only add the Client suffix to instantiable clients
291
- if (!clientName.match(/Client$/) && !parent) {
363
+ if (!clientName.match(/Client$/)) {
292
364
  clientName += 'Client';
293
365
  }
294
366
  const rustClient = new rust.Client(clientName);
@@ -298,7 +370,7 @@ export class Adapter {
298
370
  // anything other than public means non-instantiable client
299
371
  if (client.initialization.access === 'public') {
300
372
  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'));
373
+ const clientOptionsField = new rust.StructField('client_options', true, new rust.ExternalType(this.crate, 'azure_core', 'ClientOptions'));
302
374
  clientOptionsField.defaultValue = 'ClientOptions::default()';
303
375
  clientOptionsStruct.fields.push(clientOptionsField);
304
376
  rustClient.constructable = new rust.ClientConstruction(new rust.ClientOptions(clientOptionsStruct));
@@ -346,14 +418,14 @@ export class Adapter {
346
418
  break;
347
419
  case 'union':
348
420
  // TODO: https://github.com/Azure/typespec-rust/issues/57
349
- throw new Error('credential unions NYI');
421
+ continue;
350
422
  }
351
423
  break;
352
424
  case 'endpoint':
353
425
  // for Rust, we always require a complete endpoint param, templated
354
426
  // endpoints, e.g. https://{something}.contoso.com isn't supported.
355
427
  // 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())));
428
+ ctorParams.push(new rust.ClientParameter(param.name, new rust.StringSlice(), true));
357
429
  rustClient.fields.push(new rust.ClientParameter(param.name, new rust.Url(this.crate)));
358
430
  break;
359
431
  case 'method': {
@@ -370,7 +442,7 @@ export class Adapter {
370
442
  const paramName = snakeCaseName(param.name);
371
443
  rustClient.fields.push(new rust.ClientParameter(paramName, new rust.StringType()));
372
444
  // client-side default value makes the param optional
373
- const apiVersionField = new rust.StructField(paramName, false, new rust.StringType());
445
+ const apiVersionField = new rust.StructField(paramName, true, new rust.StringType());
374
446
  apiVersionField.defaultValue = `String::from("${param.clientDefaultValue}")`;
375
447
  clientOptionsStruct.fields.push(apiVersionField);
376
448
  break;
@@ -400,7 +472,7 @@ export class Adapter {
400
472
  for (const method of client.methods) {
401
473
  if (method.kind === 'clientaccessor') {
402
474
  const subClient = this.recursiveAdaptClient(method.response, rustClient);
403
- rustClient.methods.push(new rust.ClientAccessor(`get_${snakeCaseName(subClient.name)}_client`, rustClient, subClient));
475
+ rustClient.methods.push(new rust.ClientAccessor(`get_${snakeCaseName(subClient.name)}`, rustClient, subClient));
404
476
  }
405
477
  else {
406
478
  this.adaptMethod(method, rustClient);
@@ -409,7 +481,12 @@ export class Adapter {
409
481
  this.crate.clients.push(rustClient);
410
482
  return rustClient;
411
483
  }
412
- // converts method into a rust.Method for the specified rust.Client
484
+ /**
485
+ * converts a tcgc method to a Rust method for the specified client
486
+ *
487
+ * @param method the tcgc method to convert
488
+ * @param rustClient the client to which the method belongs
489
+ */
413
490
  adaptMethod(method, rustClient) {
414
491
  let rustMethod;
415
492
  const optionsLifetime = new rust.Lifetime('a');
@@ -417,16 +494,31 @@ export class Adapter {
417
494
  methodOptionsStruct.lifetime = optionsLifetime;
418
495
  const clientMethodOptions = new rust.ExternalType(this.crate, 'azure_core', 'ClientMethodOptions');
419
496
  clientMethodOptions.lifetime = optionsLifetime;
420
- methodOptionsStruct.fields.push(new rust.StructField('method_options', false, clientMethodOptions));
497
+ methodOptionsStruct.fields.push(new rust.StructField('method_options', true, clientMethodOptions));
498
+ const methodName = naming.getEscapedReservedName(snakeCaseName(method.name), 'fn');
499
+ const pub = method.access === 'public';
500
+ const methodOptions = new rust.MethodOptions(methodOptionsStruct);
421
501
  const httpMethod = method.operation.verb;
422
502
  const httpPath = method.operation.path;
423
503
  switch (method.kind) {
424
504
  case 'basic':
425
- rustMethod = new rust.AsyncMethod(naming.getEscapedReservedName(snakeCaseName(method.name), 'fn'), rustClient, isPub(method.access), new rust.MethodOptions(methodOptionsStruct, false), httpMethod, httpPath);
505
+ rustMethod = new rust.AsyncMethod(methodName, rustClient, pub, methodOptions, httpMethod, httpPath);
426
506
  break;
427
507
  case 'paging':
428
- // TODO: https://github.com/Azure/typespec-rust/issues/60
429
- return;
508
+ rustMethod = new rust.PageableMethod(methodName, rustClient, pub, methodOptions, httpMethod, httpPath);
509
+ if (method.nextLinkOperation) {
510
+ // TODO: https://github.com/Azure/autorest.rust/issues/103
511
+ throw new Error('next page operation NYI');
512
+ }
513
+ else if (method.nextLinkPath) {
514
+ // this is the name of the field in the response type that contains the next link URL
515
+ if (method.nextLinkPath.indexOf('.') > 0) {
516
+ // TODO: https://github.com/Azure/autorest.rust/issues/102
517
+ throw new Error('nested next link path NYI');
518
+ }
519
+ rustMethod.strategy = new rust.PageableStrategyNextLink(naming.getEscapedReservedName(snakeCaseName(method.nextLinkPath), 'prop'));
520
+ }
521
+ break;
430
522
  default:
431
523
  throw new Error(`method kind ${method.kind} NYI`);
432
524
  }
@@ -434,7 +526,7 @@ export class Adapter {
434
526
  rustMethod.docs.description = method.doc;
435
527
  rustClient.methods.push(rustMethod);
436
528
  // stuff all of the operation parameters into one array for easy traversal
437
- let allOpParams = new Array();
529
+ const allOpParams = new Array();
438
530
  allOpParams.push(...method.operation.parameters);
439
531
  if (method.operation.bodyParam) {
440
532
  allOpParams.push(method.operation.bodyParam);
@@ -453,8 +545,13 @@ export class Adapter {
453
545
  throw new Error(`didn't find operation parameter for method ${method.name} parameter ${param.name}`);
454
546
  }
455
547
  let adaptedParam;
456
- if (opParam.kind === 'body' && opParam.type.kind === 'model' && opParam.type.kind !== param.type.kind) {
457
- throw new Error('spread params NYI');
548
+ // for spread params there are two cases we need to consider.
549
+ // if the method param's type doesn't match the op param's type then it's a spread param
550
+ // - e.g. method param's type is string/int/etc which is a field in the op param's body type
551
+ // 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
552
+ // - e.g. op param is an intersection of multiple model types, and each model type is exposed as a discrete param
553
+ if (opParam.kind === 'body' && opParam.type.kind === 'model' && (opParam.type.kind !== param.type.kind || opParam.correspondingMethodParams.length > 1)) {
554
+ adaptedParam = this.adaptMethodSpreadParameter(param, this.adaptBodyFormat(opParam.defaultContentType), opParam.type);
458
555
  }
459
556
  else {
460
557
  adaptedParam = this.adaptMethodParameter(opParam);
@@ -462,10 +559,24 @@ export class Adapter {
462
559
  adaptedParam.docs.summary = param.summary;
463
560
  adaptedParam.docs.description = param.doc;
464
561
  rustMethod.params.push(adaptedParam);
465
- // remove the opParam we just processed
466
- allOpParams = allOpParams.filter((v) => {
467
- return v !== opParam;
468
- });
562
+ if (adaptedParam.optional) {
563
+ let fieldType;
564
+ if (adaptedParam.kind === 'partialBody') {
565
+ // for partial body params, adaptedParam.type is the model type that's
566
+ // sent in the request. we want the field within the model for this param.
567
+ // NOTE: if the param is optional then the field is optional, thus it's
568
+ // already wrapped in an Option<T> type.
569
+ const field = adaptedParam.type.type.fields.find(f => { return f.name === adaptedParam.name; });
570
+ if (!field) {
571
+ throw new Error(`didn't find spread param field ${adaptedParam.name} in type ${adaptedParam.type.type.name}`);
572
+ }
573
+ fieldType = field.type;
574
+ }
575
+ else {
576
+ fieldType = new rust.Option(adaptedParam.type);
577
+ }
578
+ rustMethod.options.type.fields.push(new rust.StructField(adaptedParam.name, true, fieldType));
579
+ }
469
580
  }
470
581
  // client params aren't included in method.parameters so
471
582
  // look for them in the remaining operation parameters.
@@ -477,27 +588,65 @@ export class Adapter {
477
588
  rustMethod.params.push(adaptedParam);
478
589
  }
479
590
  }
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;
591
+ const getBodyFormat = () => {
592
+ // fetch the body format from the HTTP responses.
593
+ // they should all have the same type so no need to match responses to type.
594
+ let defaultContentType;
595
+ for (const httpResp of method.operation.responses) {
596
+ if (defaultContentType && httpResp.defaultContentType && defaultContentType !== httpResp.defaultContentType) {
597
+ throw new Error(`method ${method.name} has conflicting content types`);
487
598
  }
488
- format = this.adaptSerdeFormat(httpResp.defaultContentType);
489
- break;
599
+ defaultContentType = httpResp.defaultContentType;
600
+ }
601
+ if (!defaultContentType) {
602
+ throw new Error(`unable to determine content type for method ${method.name}`);
603
+ }
604
+ return this.adaptBodyFormat(defaultContentType);
605
+ };
606
+ let returnType;
607
+ if (method.kind === 'paging') {
608
+ // for paged methods, tcgc models method.response.type as an Array<T>.
609
+ // however, we want the synthesized paged response envelope type instead.
610
+ const synthesizedType = method.operation.responses[0].type;
611
+ if (!synthesizedType) {
612
+ throw new Error(`paged method ${method.name} has no synthesized response type`);
613
+ }
614
+ else if (synthesizedType.kind !== 'model') {
615
+ throw new Error(`paged method ${method.name} synthesized response type has unexpected kind ${synthesizedType.kind}`);
616
+ }
617
+ const format = getBodyFormat();
618
+ const synthesizedModel = this.getModel(synthesizedType);
619
+ if (!this.crate.models.includes(synthesizedModel)) {
620
+ this.crate.models.push(synthesizedModel);
490
621
  }
491
- if (!format) {
492
- throw new Error(`didn't find HTTP response for kind ${method.response.type.kind} in method ${method.name}`);
622
+ returnType = new rust.Pager(this.crate, synthesizedModel, format);
623
+ }
624
+ else if (method.response.type) {
625
+ const format = getBodyFormat();
626
+ // until https://github.com/Azure/typespec-azure/issues/614 is resolved
627
+ // we have to look at the format to determine if the response is a streaming
628
+ // binary response. at present, it's represented as a base-64 encoded byte
629
+ // array which is clearly wrong.
630
+ let respType;
631
+ if (format === 'binary') {
632
+ respType = this.getUnitType();
633
+ }
634
+ else {
635
+ respType = this.getType(method.response.type);
493
636
  }
494
- returnType = new rust.Response(this.crate, this.getType(method.response.type), format);
637
+ returnType = new rust.Response(this.crate, respType, format);
495
638
  }
496
639
  else {
497
640
  returnType = new rust.Response(this.crate, this.getUnitType());
498
641
  }
499
642
  rustMethod.returns = new rust.Result(this.crate, returnType);
500
643
  }
644
+ /**
645
+ * converts a tcgc operation parameter into a Rust method parameter
646
+ *
647
+ * @param param the tcgc operation parameter to convert
648
+ * @returns a Rust method parameter
649
+ */
501
650
  adaptMethodParameter(param) {
502
651
  const paramLoc = param.onClient ? 'client' : 'method';
503
652
  // if this is a client method param, check if we've already adapted it
@@ -508,15 +657,11 @@ export class Adapter {
508
657
  }
509
658
  }
510
659
  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
- }
660
+ const paramType = this.getType(param.type);
516
661
  let adaptedParam;
517
662
  switch (param.kind) {
518
663
  case 'body': {
519
- adaptedParam = new rust.BodyParameter(paramName, paramLoc, new rust.RequestContent(this.crate, paramType, this.adaptSerdeFormat(param.defaultContentType)));
664
+ adaptedParam = new rust.BodyParameter(paramName, paramLoc, param.optional, new rust.RequestContent(this.crate, paramType, this.adaptBodyFormat(param.defaultContentType)));
520
665
  break;
521
666
  }
522
667
  case 'header':
@@ -538,14 +683,20 @@ export class Adapter {
538
683
  default:
539
684
  throw new Error(`unexpected format ${param.collectionFormat} for HeaderCollectionParameter`);
540
685
  }
541
- adaptedParam = new rust.HeaderCollectionParameter(paramName, param.serializedName, paramLoc, paramType, format);
686
+ adaptedParam = new rust.HeaderCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, format);
687
+ }
688
+ else if (param.serializedName === 'x-ms-meta') {
689
+ if (paramType.kind !== 'hashmap') {
690
+ throw new Error(`unexpected kind ${paramType.kind} for header ${param.serializedName}`);
691
+ }
692
+ adaptedParam = new rust.HeaderHashMapParameter(paramName, param.serializedName, paramLoc, param.optional, paramType);
542
693
  }
543
694
  else {
544
- adaptedParam = new rust.HeaderParameter(paramName, param.serializedName, paramLoc, paramType);
695
+ adaptedParam = new rust.HeaderParameter(paramName, param.serializedName, paramLoc, param.optional, paramType);
545
696
  }
546
697
  break;
547
698
  case 'path':
548
- adaptedParam = new rust.PathParameter(paramName, param.serializedName, paramLoc, paramType, param.urlEncode);
699
+ adaptedParam = new rust.PathParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, param.urlEncode);
549
700
  break;
550
701
  case 'query':
551
702
  if (param.collectionFormat) {
@@ -554,11 +705,11 @@ export class Adapter {
554
705
  throw new Error(`unexpected kind ${paramType.kind} for QueryCollectionParameter`);
555
706
  }
556
707
  // 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);
708
+ adaptedParam = new rust.QueryCollectionParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true, format);
558
709
  }
559
710
  else {
560
711
  // TODO: hard-coded encoding setting, https://github.com/Azure/typespec-azure/issues/1314
561
- adaptedParam = new rust.QueryParameter(paramName, param.serializedName, paramLoc, paramType, true);
712
+ adaptedParam = new rust.QueryParameter(paramName, param.serializedName, paramLoc, param.optional, paramType, true);
562
713
  }
563
714
  break;
564
715
  }
@@ -569,22 +720,86 @@ export class Adapter {
569
720
  }
570
721
  return adaptedParam;
571
722
  }
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}`);
723
+ /**
724
+ * converts a tcgc spread parameter into a Rust partial body parameter.
725
+ *
726
+ * @param param the tcgc method parameter to convert
727
+ * @param format the wire format for the underlying body type
728
+ * @param opParamType the tcgc model to which the spread parameter belongs
729
+ * @returns a Rust partial body parameter
730
+ */
731
+ adaptMethodSpreadParameter(param, format, opParamType) {
732
+ switch (format) {
733
+ case 'binary':
734
+ throw new Error('binary spread NYI');
735
+ case 'json':
736
+ case 'xml': {
737
+ // find the corresponding field within the model so we can get its index
738
+ let serializedName;
739
+ for (const property of opParamType.properties) {
740
+ if (property.name === param.name) {
741
+ serializedName = property.serializedName;
742
+ break;
743
+ }
744
+ }
745
+ if (serializedName === undefined) {
746
+ throw new Error(`didn't find body model property for spread parameter ${param.name}`);
747
+ }
748
+ const paramLoc = 'method';
749
+ const paramType = this.getType(opParamType);
750
+ if (paramType.kind !== 'model') {
751
+ throw new Error(`unexpected kind ${paramType.kind} for spread body param`);
752
+ }
753
+ const paramName = naming.getEscapedReservedName(snakeCaseName(param.name), 'param');
754
+ return new rust.PartialBodyParameter(paramName, paramLoc, param.optional, serializedName, new rust.RequestContent(this.crate, paramType, format));
755
+ }
579
756
  }
580
757
  }
758
+ /**
759
+ * converts a Content-Type header value into a Rust body format
760
+ *
761
+ * @param contentType the value of the Content-Type header
762
+ * @returns a Rust body format
763
+ */
764
+ adaptBodyFormat(contentType) {
765
+ // we only recognize/support JSON, text, and XML content types, so assume anything else is binary
766
+ // NOTE: we check XML before text in order to support text/xml
767
+ let bodyFormat = 'binary';
768
+ if (contentType.match(/json/i)) {
769
+ this.crate.addDependency(new rust.CrateDependency('serde_json'));
770
+ bodyFormat = 'json';
771
+ }
772
+ else if (contentType.match(/xml/i)) {
773
+ bodyFormat = 'xml';
774
+ }
775
+ else if (contentType.match(/text/i)) {
776
+ throw new Error(`unhandled contentType ${contentType}`);
777
+ }
778
+ return bodyFormat;
779
+ }
581
780
  }
582
- function isPub(access) {
583
- return access !== 'internal';
584
- }
781
+ /**
782
+ * snake-cases the provided name
783
+ *
784
+ * @param name the name to snake-case
785
+ * @returns name in snake-case format
786
+ */
585
787
  function snakeCaseName(name) {
586
788
  return codegen.deconstruct(name).join('_');
587
789
  }
790
+ /**
791
+ * recursively creates a map key from the specified type.
792
+ * this is idempotent so providing the same type will create
793
+ * the same key.
794
+ *
795
+ * obj is recursively unwrapped, and each layer is used to construct
796
+ * the key. e.g. if obj is a HashMap<String, Vec<i32>> this would
797
+ * unwrap to dict-array-int32.
798
+ *
799
+ * @param root the starting value for the key
800
+ * @param obj the type for which to create the key
801
+ * @returns a string containing the complete map key
802
+ */
588
803
  function recursiveKeyName(root, obj) {
589
804
  switch (obj.kind) {
590
805
  case 'array':
@@ -614,4 +829,56 @@ function recursiveKeyName(root, obj) {
614
829
  return `${root}-${obj.kind}`;
615
830
  }
616
831
  }
832
+ /**
833
+ * returns the XML-specific name based on the provided decorators
834
+ *
835
+ * @param decorators the decorators to enumerate
836
+ * @returns the XML-specific name or undefined if there isn't one
837
+ */
838
+ function getXMLName(decorators) {
839
+ if (decorators.length === 0) {
840
+ return undefined;
841
+ }
842
+ for (const decorator of decorators) {
843
+ switch (decorator.name) {
844
+ case 'TypeSpec.@encodedName':
845
+ if (decorator.arguments['mimeType'] === 'application/xml') {
846
+ return decorator.arguments['name'];
847
+ }
848
+ break;
849
+ case 'TypeSpec.Xml.@name':
850
+ return decorator.arguments['name'];
851
+ }
852
+ }
853
+ return undefined;
854
+ }
855
+ /**
856
+ * returns the XML-specific kind for field based on the provided decorators
857
+ *
858
+ * @param decorators the decorators to enumerate
859
+ * @param field the Rust model field to which the kind will apply
860
+ * @returns the XML-specific field kind or undefined if there isn't one
861
+ */
862
+ function getXMLKind(decorators, field) {
863
+ if (decorators.length === 0) {
864
+ return undefined;
865
+ }
866
+ for (const decorator of decorators) {
867
+ switch (decorator.name) {
868
+ case 'TypeSpec.Xml.@attribute':
869
+ return 'attribute';
870
+ case 'TypeSpec.Xml.@unwrapped': {
871
+ const fieldType = helpers.unwrapOption(field.type);
872
+ switch (fieldType.kind) {
873
+ case 'vector':
874
+ return 'unwrappedList';
875
+ case 'String':
876
+ // an unwrapped string means it's text
877
+ return 'text';
878
+ }
879
+ }
880
+ }
881
+ }
882
+ return undefined;
883
+ }
617
884
  //# sourceMappingURL=adapter.js.map