typespec-rust-emitter 0.6.0 → 0.8.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.
@@ -2,9 +2,11 @@
2
2
 
3
3
  use super::types::*;
4
4
  use async_trait::async_trait;
5
- use axum::{http::StatusCode, Json};
6
5
  use axum::extract::Path;
7
6
  use axum::Extension;
7
+ use axum::http::StatusCode;
8
+ use axum::response::IntoResponse;
9
+ use axum::Json;
8
10
  use eyre::Result;
9
11
 
10
12
  #[async_trait]
@@ -51,7 +53,7 @@ pub trait Server: Send + Sync {
51
53
  }
52
54
  pub enum GroupsListResponse {
53
55
  Ok(Json<Vec<GroupStatistics>>),
54
- Unauthorized,
56
+ Unauthorized(Json<ApiError>),
55
57
  }
56
58
 
57
59
  impl IntoResponse for GroupsListResponse {
@@ -59,15 +61,15 @@ impl IntoResponse for GroupsListResponse {
59
61
  match self {
60
62
 
61
63
  GroupsListResponse::Ok(body) => (StatusCode::OK, body).into_response(),
62
- GroupsListResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
64
+ GroupsListResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
63
65
  }
64
66
  }
65
67
  }
66
68
 
67
69
  pub enum GroupsCreateResponse {
68
70
  Created(Json<Group>),
69
- BadRequest,
70
- Unauthorized,
71
+ BadRequest(Json<ValidationError>),
72
+ Unauthorized(Json<ApiError>),
71
73
  }
72
74
 
73
75
  impl IntoResponse for GroupsCreateResponse {
@@ -75,16 +77,16 @@ impl IntoResponse for GroupsCreateResponse {
75
77
  match self {
76
78
 
77
79
  GroupsCreateResponse::Created(body) => (StatusCode::CREATED, body).into_response(),
78
- GroupsCreateResponse::BadRequest => StatusCode::BAD_REQUEST.into_response(),
79
- GroupsCreateResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
80
+ GroupsCreateResponse::BadRequest(body) => (StatusCode::BAD_REQUEST, body).into_response(),
81
+ GroupsCreateResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
80
82
  }
81
83
  }
82
84
  }
83
85
 
84
86
  pub enum GroupsGetByIdResponse {
85
87
  Ok(Json<GroupStatistics>),
86
- Unauthorized,
87
- NotFound,
88
+ Unauthorized(Json<ApiError>),
89
+ NotFound(Json<NotFoundError>),
88
90
  }
89
91
 
90
92
  impl IntoResponse for GroupsGetByIdResponse {
@@ -92,17 +94,17 @@ impl IntoResponse for GroupsGetByIdResponse {
92
94
  match self {
93
95
 
94
96
  GroupsGetByIdResponse::Ok(body) => (StatusCode::OK, body).into_response(),
95
- GroupsGetByIdResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
96
- GroupsGetByIdResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
97
+ GroupsGetByIdResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
98
+ GroupsGetByIdResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
97
99
  }
98
100
  }
99
101
  }
100
102
 
101
103
  pub enum GroupsUpdateResponse {
102
104
  Ok(Json<Group>),
103
- BadRequest,
104
- Unauthorized,
105
- NotFound,
105
+ BadRequest(Json<ValidationError>),
106
+ Unauthorized(Json<ApiError>),
107
+ NotFound(Json<NotFoundError>),
106
108
  }
107
109
 
108
110
  impl IntoResponse for GroupsUpdateResponse {
@@ -110,17 +112,17 @@ impl IntoResponse for GroupsUpdateResponse {
110
112
  match self {
111
113
 
112
114
  GroupsUpdateResponse::Ok(body) => (StatusCode::OK, body).into_response(),
113
- GroupsUpdateResponse::BadRequest => StatusCode::BAD_REQUEST.into_response(),
114
- GroupsUpdateResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
115
- GroupsUpdateResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
115
+ GroupsUpdateResponse::BadRequest(body) => (StatusCode::BAD_REQUEST, body).into_response(),
116
+ GroupsUpdateResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
117
+ GroupsUpdateResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
116
118
  }
117
119
  }
118
120
  }
119
121
 
120
122
  pub enum GroupsDeleteResponse {
121
123
  NoContent,
122
- Unauthorized,
123
- NotFound,
124
+ Unauthorized(Json<ApiError>),
125
+ NotFound(Json<NotFoundError>),
124
126
  }
125
127
 
126
128
  impl IntoResponse for GroupsDeleteResponse {
@@ -128,16 +130,16 @@ impl IntoResponse for GroupsDeleteResponse {
128
130
  match self {
129
131
 
130
132
  GroupsDeleteResponse::NoContent => StatusCode::NO_CONTENT.into_response(),
131
- GroupsDeleteResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
132
- GroupsDeleteResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
133
+ GroupsDeleteResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
134
+ GroupsDeleteResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
133
135
  }
134
136
  }
135
137
  }
136
138
 
137
139
  pub enum SubjectsListResponse {
138
140
  Ok(Json<Vec<SubjectStatistics>>),
139
- Unauthorized,
140
- NotFound,
141
+ Unauthorized(Json<ApiError>),
142
+ NotFound(Json<NotFoundError>),
141
143
  }
142
144
 
143
145
  impl IntoResponse for SubjectsListResponse {
@@ -145,17 +147,17 @@ impl IntoResponse for SubjectsListResponse {
145
147
  match self {
146
148
 
147
149
  SubjectsListResponse::Ok(body) => (StatusCode::OK, body).into_response(),
148
- SubjectsListResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
149
- SubjectsListResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
150
+ SubjectsListResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
151
+ SubjectsListResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
150
152
  }
151
153
  }
152
154
  }
153
155
 
154
156
  pub enum SubjectsCreateResponse {
155
157
  Created(Json<Subject>),
156
- BadRequest,
157
- Unauthorized,
158
- NotFound,
158
+ BadRequest(Json<ValidationError>),
159
+ Unauthorized(Json<ApiError>),
160
+ NotFound(Json<NotFoundError>),
159
161
  }
160
162
 
161
163
  impl IntoResponse for SubjectsCreateResponse {
@@ -163,17 +165,17 @@ impl IntoResponse for SubjectsCreateResponse {
163
165
  match self {
164
166
 
165
167
  SubjectsCreateResponse::Created(body) => (StatusCode::CREATED, body).into_response(),
166
- SubjectsCreateResponse::BadRequest => StatusCode::BAD_REQUEST.into_response(),
167
- SubjectsCreateResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
168
- SubjectsCreateResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
168
+ SubjectsCreateResponse::BadRequest(body) => (StatusCode::BAD_REQUEST, body).into_response(),
169
+ SubjectsCreateResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
170
+ SubjectsCreateResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
169
171
  }
170
172
  }
171
173
  }
172
174
 
173
175
  pub enum SubjectsGetByIdResponse {
174
176
  Ok(Json<SubjectStatistics>),
175
- Unauthorized,
176
- NotFound,
177
+ Unauthorized(Json<ApiError>),
178
+ NotFound(Json<NotFoundError>),
177
179
  }
178
180
 
179
181
  impl IntoResponse for SubjectsGetByIdResponse {
@@ -181,17 +183,17 @@ impl IntoResponse for SubjectsGetByIdResponse {
181
183
  match self {
182
184
 
183
185
  SubjectsGetByIdResponse::Ok(body) => (StatusCode::OK, body).into_response(),
184
- SubjectsGetByIdResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
185
- SubjectsGetByIdResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
186
+ SubjectsGetByIdResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
187
+ SubjectsGetByIdResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
186
188
  }
187
189
  }
188
190
  }
189
191
 
190
192
  pub enum SubjectsUpdateResponse {
191
193
  Ok(Json<Subject>),
192
- BadRequest,
193
- Unauthorized,
194
- NotFound,
194
+ BadRequest(Json<ValidationError>),
195
+ Unauthorized(Json<ApiError>),
196
+ NotFound(Json<NotFoundError>),
195
197
  }
196
198
 
197
199
  impl IntoResponse for SubjectsUpdateResponse {
@@ -199,17 +201,17 @@ impl IntoResponse for SubjectsUpdateResponse {
199
201
  match self {
200
202
 
201
203
  SubjectsUpdateResponse::Ok(body) => (StatusCode::OK, body).into_response(),
202
- SubjectsUpdateResponse::BadRequest => StatusCode::BAD_REQUEST.into_response(),
203
- SubjectsUpdateResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
204
- SubjectsUpdateResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
204
+ SubjectsUpdateResponse::BadRequest(body) => (StatusCode::BAD_REQUEST, body).into_response(),
205
+ SubjectsUpdateResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
206
+ SubjectsUpdateResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
205
207
  }
206
208
  }
207
209
  }
208
210
 
209
211
  pub enum SubjectsDeleteResponse {
210
212
  NoContent,
211
- Unauthorized,
212
- NotFound,
213
+ Unauthorized(Json<ApiError>),
214
+ NotFound(Json<NotFoundError>),
213
215
  }
214
216
 
215
217
  impl IntoResponse for SubjectsDeleteResponse {
@@ -217,17 +219,17 @@ impl IntoResponse for SubjectsDeleteResponse {
217
219
  match self {
218
220
 
219
221
  SubjectsDeleteResponse::NoContent => StatusCode::NO_CONTENT.into_response(),
220
- SubjectsDeleteResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
221
- SubjectsDeleteResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
222
+ SubjectsDeleteResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
223
+ SubjectsDeleteResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
222
224
  }
223
225
  }
224
226
  }
225
227
 
226
228
  pub enum SessionsStartResponse {
227
229
  Created(Json<SessionOpenedResponse>),
228
- Unauthorized,
229
- NotFound,
230
- Conflict,
230
+ Unauthorized(Json<ApiError>),
231
+ NotFound(Json<NotFoundError>),
232
+ Conflict(Json<ConflictError>),
231
233
  }
232
234
 
233
235
  impl IntoResponse for SessionsStartResponse {
@@ -235,18 +237,18 @@ impl IntoResponse for SessionsStartResponse {
235
237
  match self {
236
238
 
237
239
  SessionsStartResponse::Created(body) => (StatusCode::CREATED, body).into_response(),
238
- SessionsStartResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
239
- SessionsStartResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
240
- SessionsStartResponse::Conflict => StatusCode::CONFLICT.into_response(),
240
+ SessionsStartResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
241
+ SessionsStartResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
242
+ SessionsStartResponse::Conflict(body) => (StatusCode::CONFLICT, body).into_response(),
241
243
  }
242
244
  }
243
245
  }
244
246
 
245
247
  pub enum SessionsPauseResponse {
246
248
  Ok(Json<SessionClosedResponse>),
247
- Unauthorized,
248
- NotFound,
249
- Conflict,
249
+ Unauthorized(Json<ApiError>),
250
+ NotFound(Json<NotFoundError>),
251
+ Conflict(Json<ConflictError>),
250
252
  }
251
253
 
252
254
  impl IntoResponse for SessionsPauseResponse {
@@ -254,18 +256,18 @@ impl IntoResponse for SessionsPauseResponse {
254
256
  match self {
255
257
 
256
258
  SessionsPauseResponse::Ok(body) => (StatusCode::OK, body).into_response(),
257
- SessionsPauseResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
258
- SessionsPauseResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
259
- SessionsPauseResponse::Conflict => StatusCode::CONFLICT.into_response(),
259
+ SessionsPauseResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
260
+ SessionsPauseResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
261
+ SessionsPauseResponse::Conflict(body) => (StatusCode::CONFLICT, body).into_response(),
260
262
  }
261
263
  }
262
264
  }
263
265
 
264
266
  pub enum SessionsResumeResponse {
265
267
  Ok(Json<SessionOpenedResponse>),
266
- Unauthorized,
267
- NotFound,
268
- Conflict,
268
+ Unauthorized(Json<ApiError>),
269
+ NotFound(Json<NotFoundError>),
270
+ Conflict(Json<ConflictError>),
269
271
  }
270
272
 
271
273
  impl IntoResponse for SessionsResumeResponse {
@@ -273,18 +275,18 @@ impl IntoResponse for SessionsResumeResponse {
273
275
  match self {
274
276
 
275
277
  SessionsResumeResponse::Ok(body) => (StatusCode::OK, body).into_response(),
276
- SessionsResumeResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
277
- SessionsResumeResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
278
- SessionsResumeResponse::Conflict => StatusCode::CONFLICT.into_response(),
278
+ SessionsResumeResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
279
+ SessionsResumeResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
280
+ SessionsResumeResponse::Conflict(body) => (StatusCode::CONFLICT, body).into_response(),
279
281
  }
280
282
  }
281
283
  }
282
284
 
283
285
  pub enum SessionsStopResponse {
284
286
  Ok(Json<SessionClosedResponse>),
285
- Unauthorized,
286
- NotFound,
287
- Conflict,
287
+ Unauthorized(Json<ApiError>),
288
+ NotFound(Json<NotFoundError>),
289
+ Conflict(Json<ConflictError>),
288
290
  }
289
291
 
290
292
  impl IntoResponse for SessionsStopResponse {
@@ -292,14 +294,13 @@ impl IntoResponse for SessionsStopResponse {
292
294
  match self {
293
295
 
294
296
  SessionsStopResponse::Ok(body) => (StatusCode::OK, body).into_response(),
295
- SessionsStopResponse::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
296
- SessionsStopResponse::NotFound => StatusCode::NOT_FOUND.into_response(),
297
- SessionsStopResponse::Conflict => StatusCode::CONFLICT.into_response(),
297
+ SessionsStopResponse::Unauthorized(body) => (StatusCode::UNAUTHORIZED, body).into_response(),
298
+ SessionsStopResponse::NotFound(body) => (StatusCode::NOT_FOUND, body).into_response(),
299
+ SessionsStopResponse::Conflict(body) => (StatusCode::CONFLICT, body).into_response(),
298
300
  }
299
301
  }
300
302
  }
301
303
 
302
- use axum::response::IntoResponse;
303
304
  use axum::routing::{delete, get, patch, post};
304
305
  use axum::Router;
305
306
 
@@ -1,5 +1,9 @@
1
1
  #![allow(unused)]
2
2
 
3
+ use axum::http::StatusCode;
4
+ use axum::response::IntoResponse;
5
+ use axum::Json;
6
+
3
7
  #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
4
8
  #[error("{code}: {message}")]
5
9
  pub struct ApiError {
@@ -9,6 +13,12 @@ pub struct ApiError {
9
13
  pub message: String,
10
14
  }
11
15
 
16
+ impl IntoResponse for ApiError {
17
+ fn into_response(self) -> axum::response::Response {
18
+ (StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
19
+ }
20
+ }
21
+
12
22
  #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
13
23
  #[error("{code}: {message}")]
14
24
  pub struct NotFoundError {
@@ -17,6 +27,12 @@ pub struct NotFoundError {
17
27
  pub message: String,
18
28
  }
19
29
 
30
+ impl IntoResponse for NotFoundError {
31
+ fn into_response(self) -> axum::response::Response {
32
+ (StatusCode::NOT_FOUND, Json(self)).into_response()
33
+ }
34
+ }
35
+
20
36
  #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
21
37
  #[error("{code}: {message}")]
22
38
  pub struct ValidationError {
@@ -25,6 +41,12 @@ pub struct ValidationError {
25
41
  pub message: String,
26
42
  }
27
43
 
44
+ impl IntoResponse for ValidationError {
45
+ fn into_response(self) -> axum::response::Response {
46
+ (StatusCode::BAD_REQUEST, Json(self)).into_response()
47
+ }
48
+ }
49
+
28
50
  #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
29
51
  #[error("{code}: {message}")]
30
52
  pub struct ConflictError {
@@ -33,6 +55,12 @@ pub struct ConflictError {
33
55
  pub message: String,
34
56
  }
35
57
 
58
+ impl IntoResponse for ConflictError {
59
+ fn into_response(self) -> axum::response::Response {
60
+ (StatusCode::CONFLICT, Json(self)).into_response()
61
+ }
62
+ }
63
+
36
64
  #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
37
65
  pub struct DurationStats {
38
66
  #[serde(rename = "durationTotal")]
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "..": {
20
- "version": "0.6.0",
20
+ "version": "0.8.0",
21
21
  "license": "MIT",
22
22
  "devDependencies": {
23
23
  "@types/node": "latest",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typespec-rust-emitter",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "TypeSpec emitter that generates idiomatic Rust types and structs",
5
5
  "keywords": [
6
6
  "typespec",
package/src/emitter.ts CHANGED
@@ -417,8 +417,21 @@ function getBodyFromResponse(
417
417
  ): { type: string | undefined; description: string | undefined } {
418
418
  if (variant.type.kind === "Model") {
419
419
  const model = variant.type as Model;
420
- for (const [propName, prop] of model.properties) {
421
- if (propName === "body") {
420
+ for (const [_propName, prop] of model.properties) {
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) {
422
435
  const { type: rustType } = getRustTypeForProperty(
423
436
  prop.type,
424
437
  program,
@@ -545,6 +558,13 @@ function getHttpStatusCode(statusCode: number): string {
545
558
  return statusCodes[statusCode] || `StatusCode::from_u16(${statusCode})`;
546
559
  }
547
560
 
561
+ function getHttpStatusCodeForError(errorName: string): string {
562
+ if (errorName.endsWith("NotFoundError")) return "NOT_FOUND";
563
+ if (errorName.endsWith("ValidationError")) return "BAD_REQUEST";
564
+ if (errorName.endsWith("ConflictError")) return "CONFLICT";
565
+ return "INTERNAL_SERVER_ERROR";
566
+ }
567
+
548
568
  function generateServerTrait(
549
569
  program: Program,
550
570
  namespaceGroups: { namespace: Namespace; operations: Operation[] }[],
@@ -554,9 +574,11 @@ function generateServerTrait(
554
574
 
555
575
  parts.push(`use super::types::*;
556
576
  use async_trait::async_trait;
557
- use axum::{http::StatusCode, Json};
558
577
  use axum::extract::Path;
559
578
  use axum::Extension;
579
+ use axum::http::StatusCode;
580
+ use axum::response::IntoResponse;
581
+ use axum::Json;
560
582
  use eyre::Result;
561
583
 
562
584
  #[async_trait]
@@ -812,8 +834,7 @@ where
812
834
  const routerBody = buildRouterBody(publicRoutes, protectedRoutes);
813
835
 
814
836
  const parts: string[] = [];
815
- parts.push(`use axum::response::IntoResponse;
816
- use axum::routing::{${methodImports}};
837
+ parts.push(`use axum::routing::{${methodImports}};
817
838
  use axum::Router;
818
839
 
819
840
  `);
@@ -1232,6 +1253,16 @@ ${fields.join("\n")}
1232
1253
  parts.push("(());");
1233
1254
  }
1234
1255
 
1256
+ if (isError && allProps.size > 0) {
1257
+ const statusCode = getHttpStatusCodeForError(name);
1258
+ parts.push(`
1259
+ impl IntoResponse for ${name} {
1260
+ fn into_response(self) -> axum::response::Response {
1261
+ (StatusCode::${statusCode}, Json(self)).into_response()
1262
+ }
1263
+ }`);
1264
+ }
1265
+
1235
1266
  return parts.join("\n");
1236
1267
  }
1237
1268
 
@@ -1508,7 +1539,13 @@ export async function $onEmit(
1508
1539
 
1509
1540
  const namespaceGroups = getAllOperations(context.program);
1510
1541
 
1511
- const outputContent = "#![allow(unused)]\n\n" + content.join("\n") + "\n";
1542
+ const outputContent =
1543
+ "#![allow(unused)]\n\n" +
1544
+ "use axum::http::StatusCode;\n" +
1545
+ "use axum::response::IntoResponse;\n" +
1546
+ "use axum::Json;\n\n" +
1547
+ content.join("\n") +
1548
+ "\n";
1512
1549
 
1513
1550
  await emitFile(context.program, {
1514
1551
  path: resolvePath(context.emitterOutputDir, "types.rs"),
@@ -1,8 +1,12 @@
1
1
  import { resolvePath } from "@typespec/compiler";
2
- import { createTestLibrary, TypeSpecTestLibrary } from "@typespec/compiler/testing";
2
+ import {
3
+ createTestLibrary,
4
+ TypeSpecTestLibrary,
5
+ } from "@typespec/compiler/testing";
3
6
  import { fileURLToPath } from "url";
4
7
 
5
- export const TypespecEmitterTestLibrary: TypeSpecTestLibrary = createTestLibrary({
6
- name: "typespec-emitter",
7
- packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
8
- });
8
+ export const TypespecEmitterTestLibrary: TypeSpecTestLibrary =
9
+ createTestLibrary({
10
+ name: "typespec-emitter",
11
+ packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
12
+ });
package/DEV.md DELETED
@@ -1,81 +0,0 @@
1
- # TypeSpec Rust Emitter - LLM Context
2
-
3
- ## Purpose
4
-
5
- TypeSpec emitter that generates idiomatic Rust code (structs, enums, server traits) from TypeSpec specifications.
6
-
7
- ## Key Paths
8
-
9
- | Path | Purpose |
10
- | ------------------------------------ | ------------------------------------------------------- |
11
- | `src/emitter.ts` | Main emitter logic (1464 lines) - TypeSpec→Rust codegen |
12
- | `src/lib.tsp` | Decorator declarations (`@rustDerive`, `@rustDerives`) |
13
- | `test/hello.test.ts` | Unit tests using `emit()` helper |
14
- | `example/lib/learning/` | Demo TypeSpec models & operations |
15
- | `example/output-rust/src/generated/` | Generated Rust output |
16
- | `oas3-gen/crates/oas3-gen/` | Reference template for codegen patterns |
17
-
18
- ## Commands (Run After Every Change)
19
-
20
- ```bash
21
- just build # npm run build - Compile TypeScript
22
- just test # npm test - Run unit tests
23
- just compile # tsp compile example → generates Rust code
24
- just check-rust # cargo check - Verify Rust compiles
25
- ```
26
-
27
- ## Type Mappings
28
-
29
- | TypeSpec | Rust |
30
- | ---------------------- | ----------------------- |
31
- | `string` | `String` |
32
- | `int32/64` | `i32/i64` |
33
- | `T \| null` | `Option<T>` |
34
- | `T[]` | `Vec<T>` |
35
- | `Record<T>` | `HashMap<String, T>` |
36
- | `@format("uuid")` | `uuid::Uuid` |
37
- | `@format("date-time")` | `chrono::DateTime<Utc>` |
38
-
39
- ## Architecture
40
-
41
- 1. `navigateProgram()` walks TypeSpec AST
42
- 2. Collects models, enums, operations
43
- 3. Processes decorators
44
- 4. Generates Rust code via string templates
45
- 5. Emits to `example/output-rust/src/generated/`
46
-
47
- ## Server Trait Generation
48
-
49
- - Generates `Server<Claims>` trait with async methods per operation
50
- - Handler functions: `pub async fn {op}_handler<S, Claims>(...)`
51
- - All handlers use `<S, Claims>` generics (even public routes)
52
- - Protected routes (`@useAuth`) receive `claims: Claims` parameter
53
- - Router splits public/protected routes, merges at end
54
-
55
- ## Test Pattern
56
-
57
- ```typescript
58
- import { emit } from "./test-host.js";
59
- const results = await emit(`model User { name: string; }`);
60
- const output = results["types.rs"];
61
- strictEqual(output.includes("pub struct User"), true);
62
- ```
63
-
64
- ## Recent Fix (2026-03-27)
65
-
66
- All handler functions now use `<S, Claims>` generics uniformly. Public routes include `Claims: Send + Sync + Clone + 'static` bound even without using Claims, because Server trait requires it.
67
-
68
- ## Rules
69
-
70
- 1. Always run full cycle: `build → test → compile → check-rust`
71
- 2. TypeScript strict mode enabled - no implicit any
72
- 3. Match existing code patterns in `src/emitter.ts`
73
- 4. Add tests for new features
74
- 5. Generated Rust must compile
75
-
76
- ## Todo
77
-
78
- - [ ] More server trait test coverage
79
- - [ ] Integration tests with Rust compilation
80
- - [ ] Expand scalar format mappings
81
- - [ ] Full working Rust server example