@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.
- package/dist/src/codegen/cargotoml.d.ts +6 -0
- package/dist/src/codegen/cargotoml.d.ts.map +1 -1
- package/dist/src/codegen/cargotoml.js +6 -1
- package/dist/src/codegen/cargotoml.js.map +1 -1
- package/dist/src/codegen/clients.d.ts +19 -2
- package/dist/src/codegen/clients.d.ts.map +1 -1
- package/dist/src/codegen/clients.js +483 -250
- package/dist/src/codegen/clients.js.map +1 -1
- package/dist/src/codegen/codeGenerator.d.ts +29 -6
- package/dist/src/codegen/codeGenerator.d.ts.map +1 -1
- package/dist/src/codegen/codeGenerator.js +64 -25
- package/dist/src/codegen/codeGenerator.js.map +1 -1
- package/dist/src/codegen/context.d.ts +41 -0
- package/dist/src/codegen/context.d.ts.map +1 -1
- package/dist/src/codegen/context.js +94 -15
- package/dist/src/codegen/context.js.map +1 -1
- package/dist/src/codegen/enums.d.ts +9 -1
- package/dist/src/codegen/enums.d.ts.map +1 -1
- package/dist/src/codegen/enums.js +34 -20
- package/dist/src/codegen/enums.js.map +1 -1
- package/dist/src/codegen/helpers.d.ts +116 -2
- package/dist/src/codegen/helpers.d.ts.map +1 -1
- package/dist/src/codegen/helpers.js +148 -18
- package/dist/src/codegen/helpers.js.map +1 -1
- package/dist/src/codegen/lib.d.ts +7 -1
- package/dist/src/codegen/lib.d.ts.map +1 -1
- package/dist/src/codegen/lib.js +10 -5
- package/dist/src/codegen/lib.js.map +1 -1
- package/dist/src/codegen/mod.d.ts +6 -1
- package/dist/src/codegen/mod.d.ts.map +1 -1
- package/dist/src/codegen/mod.js +9 -10
- package/dist/src/codegen/mod.js.map +1 -1
- package/dist/src/codegen/models.d.ts +20 -1
- package/dist/src/codegen/models.d.ts.map +1 -1
- package/dist/src/codegen/models.js +250 -13
- package/dist/src/codegen/models.js.map +1 -1
- package/dist/src/codegen/use.d.ts +32 -0
- package/dist/src/codegen/use.d.ts.map +1 -1
- package/dist/src/codegen/use.js +42 -15
- package/dist/src/codegen/use.js.map +1 -1
- package/dist/src/codemodel/client.d.ts +150 -11
- package/dist/src/codemodel/client.d.ts.map +1 -1
- package/dist/src/codemodel/client.js +60 -17
- package/dist/src/codemodel/client.js.map +1 -1
- package/dist/src/codemodel/crate.d.ts +33 -0
- package/dist/src/codemodel/crate.d.ts.map +1 -1
- package/dist/src/codemodel/crate.js +17 -0
- package/dist/src/codemodel/crate.js.map +1 -1
- package/dist/src/codemodel/method.d.ts +17 -0
- package/dist/src/codemodel/method.d.ts.map +1 -1
- package/dist/src/codemodel/types.d.ts +152 -24
- package/dist/src/codemodel/types.d.ts.map +1 -1
- package/dist/src/codemodel/types.js +39 -25
- package/dist/src/codemodel/types.js.map +1 -1
- package/dist/src/emitter.d.ts +5 -0
- package/dist/src/emitter.d.ts.map +1 -1
- package/dist/src/emitter.js +20 -20
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/lib.d.ts +1 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +1 -0
- package/dist/src/lib.js.map +1 -1
- package/dist/src/tcgcadapter/adapter.d.ts +73 -1
- package/dist/src/tcgcadapter/adapter.d.ts.map +1 -1
- package/dist/src/tcgcadapter/adapter.js +338 -71
- package/dist/src/tcgcadapter/adapter.js.map +1 -1
- package/dist/src/tcgcadapter/helpers.d.ts +18 -0
- package/dist/src/tcgcadapter/helpers.d.ts.map +1 -1
- package/dist/src/tcgcadapter/helpers.js +23 -2
- package/dist/src/tcgcadapter/helpers.js.map +1 -1
- package/dist/src/tcgcadapter/naming.d.ts +8 -0
- package/dist/src/tcgcadapter/naming.d.ts.map +1 -1
- package/dist/src/tcgcadapter/naming.js +8 -5
- package/dist/src/tcgcadapter/naming.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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)}
|
|
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
|
-
|
|
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',
|
|
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(
|
|
505
|
+
rustMethod = new rust.AsyncMethod(methodName, rustClient, pub, methodOptions, httpMethod, httpPath);
|
|
426
506
|
break;
|
|
427
507
|
case 'paging':
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
let
|
|
484
|
-
for (const httpResp of method.operation.responses
|
|
485
|
-
if (
|
|
486
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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
|