@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.
- 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 +489 -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 +36 -23
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/lib.d.ts +2 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +6 -1
- 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 +342 -81
- 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.
|
|
53
|
-
//
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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(
|
|
499
|
+
rustMethod = new rust.AsyncMethod(methodName, rustClient, pub, methodOptions, httpMethod, httpPath);
|
|
426
500
|
break;
|
|
427
501
|
case 'paging':
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
let
|
|
484
|
-
for (const httpResp of method.operation.responses
|
|
485
|
-
if (
|
|
486
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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 (
|
|
492
|
-
throw new Error(`
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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
|