typespec-rust-emitter 0.5.0 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typespec-rust-emitter",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "TypeSpec emitter that generates idiomatic Rust types and structs",
5
5
  "keywords": [
6
6
  "typespec",
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: any): string {
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(decorator: any, index: number = 0): string {
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;
@@ -385,7 +418,20 @@ function getBodyFromResponse(
385
418
  if (variant.type.kind === "Model") {
386
419
  const model = variant.type as Model;
387
420
  for (const [propName, prop] of model.properties) {
388
- if (propName === "body") {
421
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
422
+ const decorators = (prop as any).decorators;
423
+ let isBody = false;
424
+ if (decorators) {
425
+ for (const key of Object.keys(decorators)) {
426
+ const decorator = decorators[key];
427
+ const name = getDecoratorName(decorator);
428
+ if (name === "body" || name === "bodyRoot") {
429
+ isBody = true;
430
+ break;
431
+ }
432
+ }
433
+ }
434
+ if (isBody) {
389
435
  const { type: rustType } = getRustTypeForProperty(
390
436
  prop.type,
391
437
  program,
@@ -408,9 +454,10 @@ function getAllOperations(
408
454
  namespace(ns: Namespace) {
409
455
  const route = getRoute(program, ns);
410
456
  if (!route) return;
411
- if (!seenNamespaces.has(ns.name)) {
412
- seenNamespaces.add(ns.name);
413
- namespaceOps.set(ns.name, { ns, ops: [] });
457
+ const fullName = getNamespaceFullName(ns);
458
+ if (!seenNamespaces.has(fullName)) {
459
+ seenNamespaces.add(fullName);
460
+ namespaceOps.set(fullName, { ns, ops: [] });
414
461
  }
415
462
  },
416
463
  operation(op: Operation) {
@@ -418,11 +465,12 @@ function getAllOperations(
418
465
  if (!method) return;
419
466
  const ns = op.namespace;
420
467
  if (!ns) return;
421
- if (!seenNamespaces.has(ns.name)) {
422
- seenNamespaces.add(ns.name);
423
- namespaceOps.set(ns.name, { ns, ops: [] });
468
+ const fullName = getNamespaceFullName(ns);
469
+ if (!seenNamespaces.has(fullName)) {
470
+ seenNamespaces.add(fullName);
471
+ namespaceOps.set(fullName, { ns, ops: [] });
424
472
  }
425
- const entry = namespaceOps.get(ns.name);
473
+ const entry = namespaceOps.get(fullName);
426
474
  if (entry) {
427
475
  entry.ops.push(op);
428
476
  }
@@ -670,7 +718,7 @@ function generateRouter(
670
718
  const usedMethods = new Set<string>();
671
719
 
672
720
  for (const group of namespaceGroups) {
673
- const nsRoute = getRoute(program, group.namespace);
721
+ const nsRoute = getFullRoute(program, group.namespace);
674
722
  if (!nsRoute) continue;
675
723
 
676
724
  const nsName = toPascalCase(
@@ -739,7 +787,7 @@ function generateRouter(
739
787
  const serverCall = `service.${traitFnName}(${serverArgsStr}).await`;
740
788
 
741
789
  // All handlers use <S> generics, Claims is now an associated type
742
- let handlerCode = `pub async fn ${handlerFnName}_handler<S>(
790
+ const handlerCode = `pub async fn ${handlerFnName}_handler<S>(
743
791
  axum::extract::State(service): axum::extract::State<S>,
744
792
  ${extractorLines.join("\n")}
745
793
  ) -> impl axum::response::IntoResponse
@@ -760,7 +808,7 @@ where
760
808
 
761
809
  handlers.push(handlerCode);
762
810
 
763
- let routePath = `"${opInfo.path}"`;
811
+ const routePath = `"${opInfo.path}"`;
764
812
  let routeStmt = "";
765
813
  if (isProtected) {
766
814
  routeStmt = `.route(${routePath}, ${method}(${handlerFnName}_handler::<S>))`;
@@ -796,14 +844,6 @@ ${routerBody}
796
844
  return parts.join("\n");
797
845
  }
798
846
 
799
- function hasOnlyPathParams(
800
- hasPath: boolean,
801
- hasQuery: boolean,
802
- hasBody: boolean,
803
- ): boolean {
804
- return hasPath && !hasQuery && !hasBody;
805
- }
806
-
807
847
  function buildRouterBody(
808
848
  publicRoutes: string[],
809
849
  protectedRoutes: string[],
@@ -1144,6 +1184,7 @@ function emitModel(
1144
1184
  "serde::Deserialize",
1145
1185
  ];
1146
1186
 
1187
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1147
1188
  const customDerives = (model as any)[rustDeriveKey] as
1148
1189
  | RustDeriveInfo
1149
1190
  | undefined;
@@ -1161,6 +1202,7 @@ function emitModel(
1161
1202
  parts.push('#[error("{code}: {message}")]');
1162
1203
  }
1163
1204
 
1205
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1164
1206
  const customAttrs = (model as any)[rustAttrKey] as RustAttrInfo | undefined;
1165
1207
  if (customAttrs) {
1166
1208
  for (const attr of customAttrs.attrs) {
@@ -1245,6 +1287,7 @@ function emitEnum(enumType: Enum): string {
1245
1287
  "serde::Deserialize",
1246
1288
  ];
1247
1289
 
1290
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1248
1291
  const customDerives = (enumType as any)[rustDeriveKey] as
1249
1292
  | RustDeriveInfo
1250
1293
  | undefined;
@@ -1253,6 +1296,7 @@ function emitEnum(enumType: Enum): string {
1253
1296
  allDerives.push(...customDerives.derives);
1254
1297
  }
1255
1298
 
1299
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1256
1300
  const customAttrs = (enumType as any)[rustAttrKey] as
1257
1301
  | RustAttrInfo
1258
1302
  | undefined;
@@ -1477,7 +1521,7 @@ export async function $onEmit(
1477
1521
 
1478
1522
  const namespaceGroups = getAllOperations(context.program);
1479
1523
 
1480
- const outputContent = content.join("\n");
1524
+ const outputContent = "#![allow(unused)]\n\n" + content.join("\n") + "\n";
1481
1525
 
1482
1526
  await emitFile(context.program, {
1483
1527
  path: resolvePath(context.emitterOutputDir, "types.rs"),
@@ -1501,7 +1545,10 @@ export async function $onEmit(
1501
1545
  anonymousEnums,
1502
1546
  );
1503
1547
 
1504
- const serverContent = [serverTrait, responseEnums, router].join("\n");
1548
+ const serverContent =
1549
+ "#![allow(unused)]\n\n" +
1550
+ [serverTrait, responseEnums, router].join("\n") +
1551
+ "\n";
1505
1552
 
1506
1553
  await emitFile(context.program, {
1507
1554
  path: resolvePath(context.emitterOutputDir, "server.rs"),