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/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;
@@ -408,9 +441,10 @@ function getAllOperations(
408
441
  namespace(ns: Namespace) {
409
442
  const route = getRoute(program, ns);
410
443
  if (!route) return;
411
- if (!seenNamespaces.has(ns.name)) {
412
- seenNamespaces.add(ns.name);
413
- namespaceOps.set(ns.name, { ns, ops: [] });
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
- if (!seenNamespaces.has(ns.name)) {
422
- seenNamespaces.add(ns.name);
423
- namespaceOps.set(ns.name, { ns, ops: [] });
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(ns.name);
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 = getRoute(program, group.namespace);
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
- let handlerCode = `pub async fn ${handlerFnName}_handler<S>(
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
- let routePath = `"${opInfo.path}"`;
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: "String",
848
- plainTime: "String",
849
- utcDateTime: "String",
850
- offsetDateTime: "String",
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 = [serverTrait, responseEnums, router].join("\n");
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"),