typespec-rust-emitter 0.4.0 → 0.6.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/CHANGELOG.md +66 -0
- package/README.md +9 -4
- package/dist/src/emitter.js +47 -19
- package/dist/src/emitter.js.map +1 -1
- package/example/lib/learning/operations.tsp +27 -13
- package/example/output-rust/src/generated/server.rs +59 -56
- package/example/output-rust/src/generated/types.rs +15 -12
- package/example/package-lock.json +1 -1
- package/package.json +1 -1
- package/src/emitter.ts +61 -26
package/src/emitter.ts
CHANGED
|
@@ -65,12 +65,14 @@ export function $rustDerive(
|
|
|
65
65
|
: "";
|
|
66
66
|
|
|
67
67
|
if (!ns.startsWith("TypeSpec")) {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
69
|
const info = (target as any)[rustDeriveKey] as RustDeriveInfo | undefined;
|
|
69
70
|
if (info) {
|
|
70
71
|
if (!info.derives.includes(derive)) {
|
|
71
72
|
info.derives.push(derive);
|
|
72
73
|
}
|
|
73
74
|
} else {
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
76
|
(target as any)[rustDeriveKey] = { derives: [derive] };
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -111,12 +113,14 @@ export function $rustAttr(
|
|
|
111
113
|
: "";
|
|
112
114
|
|
|
113
115
|
if (!ns.startsWith("TypeSpec")) {
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
117
|
const info = (target as any)[rustAttrKey] as RustAttrInfo | undefined;
|
|
115
118
|
if (info) {
|
|
116
119
|
if (!info.attrs.includes(attr)) {
|
|
117
120
|
info.attrs.push(attr);
|
|
118
121
|
}
|
|
119
122
|
} else {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
120
124
|
(target as any)[rustAttrKey] = { attrs: [attr] };
|
|
121
125
|
}
|
|
122
126
|
}
|
|
@@ -160,7 +164,9 @@ interface ResponseInfo {
|
|
|
160
164
|
bodyDescription: string | undefined;
|
|
161
165
|
}
|
|
162
166
|
|
|
163
|
-
function getDecoratorName(decorator:
|
|
167
|
+
function getDecoratorName(decorator: {
|
|
168
|
+
node?: { target?: { sv?: string; kind?: string } };
|
|
169
|
+
}): string {
|
|
164
170
|
if (!decorator) return "";
|
|
165
171
|
if (typeof decorator !== "object") return "";
|
|
166
172
|
|
|
@@ -177,7 +183,13 @@ function getDecoratorName(decorator: any): string {
|
|
|
177
183
|
return "";
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
function getDecoratorArgValue(
|
|
186
|
+
function getDecoratorArgValue(
|
|
187
|
+
decorator: {
|
|
188
|
+
args?: { jsValue?: unknown; value?: unknown }[];
|
|
189
|
+
arguments?: { jsValue?: unknown; value?: unknown }[];
|
|
190
|
+
},
|
|
191
|
+
index: number = 0,
|
|
192
|
+
): string {
|
|
181
193
|
if (!decorator) return "";
|
|
182
194
|
const args = decorator.args || decorator.arguments;
|
|
183
195
|
if (!args) return "";
|
|
@@ -191,6 +203,7 @@ function getHttpMethod(
|
|
|
191
203
|
_program: Program,
|
|
192
204
|
operation: Operation,
|
|
193
205
|
): HttpMethod | undefined {
|
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
207
|
const decorators = (operation as any).decorators;
|
|
195
208
|
if (!decorators) return undefined;
|
|
196
209
|
|
|
@@ -208,6 +221,7 @@ function getHttpMethod(
|
|
|
208
221
|
}
|
|
209
222
|
|
|
210
223
|
function getRoute(_program: Program, target: Namespace | Operation): string {
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
211
225
|
const decorators = (target as any).decorators;
|
|
212
226
|
if (!decorators) return "";
|
|
213
227
|
|
|
@@ -221,7 +235,23 @@ function getRoute(_program: Program, target: Namespace | Operation): string {
|
|
|
221
235
|
return "";
|
|
222
236
|
}
|
|
223
237
|
|
|
238
|
+
function getFullRoute(program: Program, ns: Namespace): string {
|
|
239
|
+
const routes: string[] = [];
|
|
240
|
+
let current: Namespace | undefined = ns;
|
|
241
|
+
|
|
242
|
+
while (current) {
|
|
243
|
+
const route = getRoute(program, current);
|
|
244
|
+
if (route) {
|
|
245
|
+
routes.unshift(route);
|
|
246
|
+
}
|
|
247
|
+
current = current.namespace;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return routes.join("");
|
|
251
|
+
}
|
|
252
|
+
|
|
224
253
|
function hasAuthDecorator(operation: Operation): boolean {
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
225
255
|
const decorators = (operation as any).decorators;
|
|
226
256
|
if (!decorators) return false;
|
|
227
257
|
|
|
@@ -244,6 +274,7 @@ function getOperationParameters(
|
|
|
244
274
|
const model = operation.parameters;
|
|
245
275
|
|
|
246
276
|
for (const [propName, prop] of model.properties) {
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
278
|
const decorators = (prop as any).decorators;
|
|
248
279
|
|
|
249
280
|
// Skip body parameters - they are handled separately
|
|
@@ -308,6 +339,7 @@ function getOperationParameters(
|
|
|
308
339
|
|
|
309
340
|
function getOperationBody(operation: Operation): ModelProperty | undefined {
|
|
310
341
|
for (const [_propName, prop] of operation.parameters.properties) {
|
|
342
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
311
343
|
const decorators = (prop as any).decorators;
|
|
312
344
|
if (!decorators) continue;
|
|
313
345
|
|
|
@@ -367,6 +399,7 @@ function getStatusCode(variant: { type: Type }): number {
|
|
|
367
399
|
const model = variant.type as Model;
|
|
368
400
|
for (const [propName, prop] of model.properties) {
|
|
369
401
|
if (propName === "statusCode") {
|
|
402
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
370
403
|
const typeAny = prop.type as any;
|
|
371
404
|
if (typeAny.value !== undefined) {
|
|
372
405
|
return typeAny.value as number;
|
|
@@ -408,9 +441,10 @@ function getAllOperations(
|
|
|
408
441
|
namespace(ns: Namespace) {
|
|
409
442
|
const route = getRoute(program, ns);
|
|
410
443
|
if (!route) return;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
444
|
+
const fullName = getNamespaceFullName(ns);
|
|
445
|
+
if (!seenNamespaces.has(fullName)) {
|
|
446
|
+
seenNamespaces.add(fullName);
|
|
447
|
+
namespaceOps.set(fullName, { ns, ops: [] });
|
|
414
448
|
}
|
|
415
449
|
},
|
|
416
450
|
operation(op: Operation) {
|
|
@@ -418,11 +452,12 @@ function getAllOperations(
|
|
|
418
452
|
if (!method) return;
|
|
419
453
|
const ns = op.namespace;
|
|
420
454
|
if (!ns) return;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
455
|
+
const fullName = getNamespaceFullName(ns);
|
|
456
|
+
if (!seenNamespaces.has(fullName)) {
|
|
457
|
+
seenNamespaces.add(fullName);
|
|
458
|
+
namespaceOps.set(fullName, { ns, ops: [] });
|
|
424
459
|
}
|
|
425
|
-
const entry = namespaceOps.get(
|
|
460
|
+
const entry = namespaceOps.get(fullName);
|
|
426
461
|
if (entry) {
|
|
427
462
|
entry.ops.push(op);
|
|
428
463
|
}
|
|
@@ -670,7 +705,7 @@ function generateRouter(
|
|
|
670
705
|
const usedMethods = new Set<string>();
|
|
671
706
|
|
|
672
707
|
for (const group of namespaceGroups) {
|
|
673
|
-
const nsRoute =
|
|
708
|
+
const nsRoute = getFullRoute(program, group.namespace);
|
|
674
709
|
if (!nsRoute) continue;
|
|
675
710
|
|
|
676
711
|
const nsName = toPascalCase(
|
|
@@ -739,7 +774,7 @@ function generateRouter(
|
|
|
739
774
|
const serverCall = `service.${traitFnName}(${serverArgsStr}).await`;
|
|
740
775
|
|
|
741
776
|
// All handlers use <S> generics, Claims is now an associated type
|
|
742
|
-
|
|
777
|
+
const handlerCode = `pub async fn ${handlerFnName}_handler<S>(
|
|
743
778
|
axum::extract::State(service): axum::extract::State<S>,
|
|
744
779
|
${extractorLines.join("\n")}
|
|
745
780
|
) -> impl axum::response::IntoResponse
|
|
@@ -760,7 +795,7 @@ where
|
|
|
760
795
|
|
|
761
796
|
handlers.push(handlerCode);
|
|
762
797
|
|
|
763
|
-
|
|
798
|
+
const routePath = `"${opInfo.path}"`;
|
|
764
799
|
let routeStmt = "";
|
|
765
800
|
if (isProtected) {
|
|
766
801
|
routeStmt = `.route(${routePath}, ${method}(${handlerFnName}_handler::<S>))`;
|
|
@@ -796,14 +831,6 @@ ${routerBody}
|
|
|
796
831
|
return parts.join("\n");
|
|
797
832
|
}
|
|
798
833
|
|
|
799
|
-
function hasOnlyPathParams(
|
|
800
|
-
hasPath: boolean,
|
|
801
|
-
hasQuery: boolean,
|
|
802
|
-
hasBody: boolean,
|
|
803
|
-
): boolean {
|
|
804
|
-
return hasPath && !hasQuery && !hasBody;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
834
|
function buildRouterBody(
|
|
808
835
|
publicRoutes: string[],
|
|
809
836
|
protectedRoutes: string[],
|
|
@@ -844,10 +871,11 @@ const scalarToRust: Record<string, string> = {
|
|
|
844
871
|
float64: "f64",
|
|
845
872
|
boolean: "bool",
|
|
846
873
|
bytes: "Vec<u8>",
|
|
847
|
-
plainDate: "
|
|
848
|
-
plainTime: "
|
|
849
|
-
utcDateTime: "
|
|
850
|
-
offsetDateTime: "
|
|
874
|
+
plainDate: "chrono::NaiveDate",
|
|
875
|
+
plainTime: "chrono::NaiveTime",
|
|
876
|
+
utcDateTime: "chrono::DateTime<chrono::Utc>",
|
|
877
|
+
offsetDateTime: "chrono::DateTime<chrono::FixedOffset>",
|
|
878
|
+
plainDateTime: "chrono::NaiveDateTime",
|
|
851
879
|
duration: "String",
|
|
852
880
|
numeric: "f64",
|
|
853
881
|
integer: "i64",
|
|
@@ -1143,6 +1171,7 @@ function emitModel(
|
|
|
1143
1171
|
"serde::Deserialize",
|
|
1144
1172
|
];
|
|
1145
1173
|
|
|
1174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1146
1175
|
const customDerives = (model as any)[rustDeriveKey] as
|
|
1147
1176
|
| RustDeriveInfo
|
|
1148
1177
|
| undefined;
|
|
@@ -1160,6 +1189,7 @@ function emitModel(
|
|
|
1160
1189
|
parts.push('#[error("{code}: {message}")]');
|
|
1161
1190
|
}
|
|
1162
1191
|
|
|
1192
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1163
1193
|
const customAttrs = (model as any)[rustAttrKey] as RustAttrInfo | undefined;
|
|
1164
1194
|
if (customAttrs) {
|
|
1165
1195
|
for (const attr of customAttrs.attrs) {
|
|
@@ -1244,6 +1274,7 @@ function emitEnum(enumType: Enum): string {
|
|
|
1244
1274
|
"serde::Deserialize",
|
|
1245
1275
|
];
|
|
1246
1276
|
|
|
1277
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1247
1278
|
const customDerives = (enumType as any)[rustDeriveKey] as
|
|
1248
1279
|
| RustDeriveInfo
|
|
1249
1280
|
| undefined;
|
|
@@ -1252,6 +1283,7 @@ function emitEnum(enumType: Enum): string {
|
|
|
1252
1283
|
allDerives.push(...customDerives.derives);
|
|
1253
1284
|
}
|
|
1254
1285
|
|
|
1286
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1255
1287
|
const customAttrs = (enumType as any)[rustAttrKey] as
|
|
1256
1288
|
| RustAttrInfo
|
|
1257
1289
|
| undefined;
|
|
@@ -1476,7 +1508,7 @@ export async function $onEmit(
|
|
|
1476
1508
|
|
|
1477
1509
|
const namespaceGroups = getAllOperations(context.program);
|
|
1478
1510
|
|
|
1479
|
-
const outputContent = content.join("\n");
|
|
1511
|
+
const outputContent = "#![allow(unused)]\n\n" + content.join("\n") + "\n";
|
|
1480
1512
|
|
|
1481
1513
|
await emitFile(context.program, {
|
|
1482
1514
|
path: resolvePath(context.emitterOutputDir, "types.rs"),
|
|
@@ -1500,7 +1532,10 @@ export async function $onEmit(
|
|
|
1500
1532
|
anonymousEnums,
|
|
1501
1533
|
);
|
|
1502
1534
|
|
|
1503
|
-
const serverContent =
|
|
1535
|
+
const serverContent =
|
|
1536
|
+
"#![allow(unused)]\n\n" +
|
|
1537
|
+
[serverTrait, responseEnums, router].join("\n") +
|
|
1538
|
+
"\n";
|
|
1504
1539
|
|
|
1505
1540
|
await emitFile(context.program, {
|
|
1506
1541
|
path: resolvePath(context.emitterOutputDir, "server.rs"),
|