typespec-rust-emitter 0.7.0 → 0.9.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/.qwen/settings.json +3 -2
- package/AGENTS.md +167 -0
- package/CHANGELOG.md +66 -0
- package/README.md +18 -10
- package/dist/src/emitter.js +51 -24
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/testing/index.js +1 -1
- package/dist/src/testing/index.js.map +1 -1
- package/dist/test/hello.test.js +2 -2
- package/dist/test/hello.test.js.map +1 -1
- package/example/output-rust/src/generated/server.rs +3 -2
- package/example/output-rust/src/generated/types.rs +50 -23
- package/example/package-lock.json +1 -1
- package/justfile +1 -1
- package/package.json +1 -1
- package/src/emitter.ts +55 -34
- package/src/testing/index.ts +9 -5
- package/test/hello.test.ts +2 -2
- package/DEV.md +0 -81
- package/QWEN.md +0 -292
package/test/hello.test.ts
CHANGED
|
@@ -179,7 +179,7 @@ describe("Rust emitter", () => {
|
|
|
179
179
|
const output = results["types.rs"];
|
|
180
180
|
strictEqual(
|
|
181
181
|
output.includes(
|
|
182
|
-
"#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, strum::Display)]",
|
|
182
|
+
"#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize, strum::Display)]",
|
|
183
183
|
),
|
|
184
184
|
true,
|
|
185
185
|
);
|
|
@@ -198,7 +198,7 @@ describe("Rust emitter", () => {
|
|
|
198
198
|
const output = results["types.rs"];
|
|
199
199
|
strictEqual(
|
|
200
200
|
output.includes(
|
|
201
|
-
"#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, strum::Display, strum::EnumString)]",
|
|
201
|
+
"#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize, strum::Display, strum::EnumString)]",
|
|
202
202
|
),
|
|
203
203
|
true,
|
|
204
204
|
);
|
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
|
package/QWEN.md
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
# QWEN.md - Project Context
|
|
2
|
-
|
|
3
|
-
## Project Overview
|
|
4
|
-
|
|
5
|
-
**TypeSpec Rust Emitter** - A TypeScript-based code generator that converts TypeSpec specifications into idiomatic Rust code.
|
|
6
|
-
|
|
7
|
-
- **Type**: TypeScript/Node.js project (TypeSpec emitter)
|
|
8
|
-
- **Output**: Generates Rust structs, enums, server traits with serde, thiserror, axum support
|
|
9
|
-
- **Main Entry**: `src/emitter.ts` - core codegen logic
|
|
10
|
-
- **Package**: `typespec-rust-emitter` v0.3.0
|
|
11
|
-
|
|
12
|
-
## Directory Structure
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
typespec-emitter/
|
|
16
|
-
├── src/
|
|
17
|
-
│ ├── emitter.ts # MAIN: TypeSpec→Rust code generation
|
|
18
|
-
│ ├── index.ts # Exports ($onEmit, $rustDerive, $rustDerives, $rustAttr, $rustAttrs)
|
|
19
|
-
│ ├── lib.ts # TypeSpec library definition
|
|
20
|
-
│ ├── lib.tsp # Decorator declarations
|
|
21
|
-
│ └── testing/ # Test utilities
|
|
22
|
-
├── test/
|
|
23
|
-
│ ├── hello.test.ts # Unit tests
|
|
24
|
-
│ └── test-host.ts # Test infrastructure (emit() helper)
|
|
25
|
-
├── example/
|
|
26
|
-
│ ├── main.tsp # Demo TypeSpec entry
|
|
27
|
-
│ ├── lib/learning/ # Demo models & operations
|
|
28
|
-
│ ├── tspconfig.yaml # Emitter configuration
|
|
29
|
-
│ └── output-rust/ # Generated Rust crate
|
|
30
|
-
├── oas3-gen/ # Reference template repo
|
|
31
|
-
├── dist/ # Compiled TypeScript output
|
|
32
|
-
├── package.json # Dependencies & scripts
|
|
33
|
-
├── tsconfig.json # TypeScript config (strict mode)
|
|
34
|
-
└── justfile # Build automation
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Build & Run Commands
|
|
38
|
-
|
|
39
|
-
### Primary Workflow (Run After Every Change)
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
just build # Compile TypeScript (npm run build)
|
|
43
|
-
just test # Run unit tests (npm test)
|
|
44
|
-
just compile # Generate Rust code from example
|
|
45
|
-
just check-rust # Verify Rust compiles (cargo check)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Individual Commands
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
# Development
|
|
52
|
-
npm run build # tsc compile
|
|
53
|
-
npm run watch # Watch mode
|
|
54
|
-
npm run lint # ESLint check
|
|
55
|
-
npm run lint:fix # Auto-fix lint
|
|
56
|
-
npm run format # Prettier format
|
|
57
|
-
npm run format:check
|
|
58
|
-
|
|
59
|
-
# Full pipeline
|
|
60
|
-
just install # npm install (root + example)
|
|
61
|
-
just publish # build + npm publish
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Type Mappings (TypeSpec → Rust)
|
|
65
|
-
|
|
66
|
-
| TypeSpec | Rust |
|
|
67
|
-
| ---------------- | -------------------- |
|
|
68
|
-
| `string` | `String` |
|
|
69
|
-
| `int8/16/32/64` | `i8/i16/i32/i64` |
|
|
70
|
-
| `uint8/16/32/64` | `u8/u16/u32/u64` |
|
|
71
|
-
| `float32/64` | `f32/f64` |
|
|
72
|
-
| `boolean` | `bool` |
|
|
73
|
-
| `bytes` | `Vec<u8>` |
|
|
74
|
-
| `T[]` | `Vec<T>` |
|
|
75
|
-
| `Record<T>` | `HashMap<String, T>` |
|
|
76
|
-
| `T \| null` | `Option<T>` |
|
|
77
|
-
|
|
78
|
-
### Format Mappings
|
|
79
|
-
|
|
80
|
-
| `@format()` | Rust Type |
|
|
81
|
-
| ------------- | ----------------------- |
|
|
82
|
-
| `"uuid"` | `uuid::Uuid` |
|
|
83
|
-
| `"date"` | `chrono::NaiveDate` |
|
|
84
|
-
| `"time"` | `chrono::NaiveTime` |
|
|
85
|
-
| `"date-time"` | `chrono::DateTime<Utc>` |
|
|
86
|
-
|
|
87
|
-
## Decorators
|
|
88
|
-
|
|
89
|
-
| Decorator | Effect |
|
|
90
|
-
| ---------------------------- | ---------------------------------------------------------------------------- |
|
|
91
|
-
| `@error` | Adds `thiserror::Error` derive + `#[error("{code}: {message}")]` |
|
|
92
|
-
| `@pattern("regex")` | Generates `TryFrom<String>` with regex validation |
|
|
93
|
-
| `@rustDerive("...")` | Adds custom derive (models & enums, e.g., `sqlx::FromRow`, `sqlx::Type`) |
|
|
94
|
-
| `@rustDerives("...", "...")` | Adds multiple custom derives (models & enums) |
|
|
95
|
-
| `@rustAttr("...")` | Adds custom Rust attribute (models & enums, e.g., `sqlx(type_name = "...")`) |
|
|
96
|
-
| `@rustAttrs("...", "...")` | Adds multiple custom attributes (models & enums) |
|
|
97
|
-
| `@doc("...")` | Generates `///` doc comments |
|
|
98
|
-
| `@useAuth(BearerAuth)` | Marks operation as protected (adds `Claims` param) |
|
|
99
|
-
| `@maxLength(n)` | Documentation only |
|
|
100
|
-
|
|
101
|
-
## Output Files
|
|
102
|
-
|
|
103
|
-
### types.rs
|
|
104
|
-
|
|
105
|
-
- All models → `pub struct` with serde derives
|
|
106
|
-
- Enums → `pub enum` with serde rename
|
|
107
|
-
- Unions → `Option<T>` for nullable, or sum types
|
|
108
|
-
- Scalars → `pub type` or `pub struct` (if pattern validation)
|
|
109
|
-
|
|
110
|
-
### server.rs
|
|
111
|
-
|
|
112
|
-
- `Server` trait with `type Claims` associated type
|
|
113
|
-
- Request structs per operation
|
|
114
|
-
- Response enums with status code variants
|
|
115
|
-
- `create_router<S, M>(service: S, middleware: M)` function with axum routes
|
|
116
|
-
|
|
117
|
-
## Key Implementation Details
|
|
118
|
-
|
|
119
|
-
### emitter.ts Architecture
|
|
120
|
-
|
|
121
|
-
1. `navigateProgram()` - Walks TypeSpec AST
|
|
122
|
-
2. Collects: models, enums, unions, scalars, operations
|
|
123
|
-
3. Processes decorators (`@error`, `@pattern`, `@rustDerive`)
|
|
124
|
-
4. Generates Rust via string templates
|
|
125
|
-
5. Emits files to configured output directory
|
|
126
|
-
|
|
127
|
-
### Server Trait Generation
|
|
128
|
-
|
|
129
|
-
- Generates `Server` trait with `type Claims: Send + Sync + 'static` associated type
|
|
130
|
-
- Handler functions accept individual parameters (path params, body) directly
|
|
131
|
-
- Path params extracted with `Path<T>`
|
|
132
|
-
- Body extracted with `Json<T>`
|
|
133
|
-
- Protected routes (`@useAuth`) receive `claims: Self::Claims` via `Extension<Self::Claims>`
|
|
134
|
-
- Extractor order: `State` → `Extension` → `Path` → `Json`
|
|
135
|
-
- `create_router<S, M>(service: S, middleware: M)` accepts middleware function
|
|
136
|
-
- Middleware wraps protected routes: `middleware(protected)`
|
|
137
|
-
- Router merges public/protected, applies `.with_state(service)` once at end
|
|
138
|
-
|
|
139
|
-
### Test Pattern
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
import { emit } from "./test-host.js";
|
|
143
|
-
const results = await emit(`model User { name: string; }`);
|
|
144
|
-
const output = results["types.rs"];
|
|
145
|
-
strictEqual(output.includes("pub struct User"), true);
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Dependencies
|
|
149
|
-
|
|
150
|
-
### Peer (Required)
|
|
151
|
-
|
|
152
|
-
- `@typespec/compiler` >=1.0.0
|
|
153
|
-
- `@typespec/emitter-framework` ^0.17.0
|
|
154
|
-
|
|
155
|
-
### Dev
|
|
156
|
-
|
|
157
|
-
- `typescript` ^5.3.3
|
|
158
|
-
- `eslint` ^9.15.0
|
|
159
|
-
- `prettier` ^3.3.3
|
|
160
|
-
|
|
161
|
-
### Generated Rust (example/output-rust/Cargo.toml)
|
|
162
|
-
|
|
163
|
-
```toml
|
|
164
|
-
serde = { version = "1.0", features = ["derive"] }
|
|
165
|
-
thiserror = "2.0"
|
|
166
|
-
uuid = { version = "1.23", features = ["serde"] }
|
|
167
|
-
chrono = { version = "0.4", features = ["serde"] }
|
|
168
|
-
regex = "1.12"
|
|
169
|
-
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "derive"] }
|
|
170
|
-
axum = "0.8"
|
|
171
|
-
eyre = "0.6"
|
|
172
|
-
async-trait = "0.1"
|
|
173
|
-
tokio = { version = "1.50", features = ["full"] }
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Configuration Files
|
|
177
|
-
|
|
178
|
-
### tspconfig.yaml
|
|
179
|
-
|
|
180
|
-
```yaml
|
|
181
|
-
emit:
|
|
182
|
-
- "@typespec/openapi3"
|
|
183
|
-
- "typespec-rust-emitter"
|
|
184
|
-
options:
|
|
185
|
-
"typespec-rust-emitter":
|
|
186
|
-
emitter-output-dir: "{output-dir}/../output-rust/src/generated"
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### tsconfig.json
|
|
190
|
-
|
|
191
|
-
- Target: ES2022
|
|
192
|
-
- Module: NodeNext
|
|
193
|
-
- Strict: true
|
|
194
|
-
- Root/Out: `.` → `dist`
|
|
195
|
-
|
|
196
|
-
## Development Conventions
|
|
197
|
-
|
|
198
|
-
1. **TypeScript**: Strict mode, ES2022, NodeNext modules
|
|
199
|
-
2. **Testing**: Node.js built-in test runner (`node:test`)
|
|
200
|
-
3. **Formatting**: Prettier (yaml config)
|
|
201
|
-
4. **Linting**: ESLint + typescript-eslint
|
|
202
|
-
5. **Code Style**: Match existing patterns in `src/emitter.ts`
|
|
203
|
-
|
|
204
|
-
## Rules
|
|
205
|
-
|
|
206
|
-
1. Always run full cycle after changes: `build → test → compile → check-rust`
|
|
207
|
-
2. Add tests for new features in `test/hello.test.ts`
|
|
208
|
-
3. Generated Rust must compile (verify with `just check-rust`)
|
|
209
|
-
4. Reference `oas3-gen/` for advanced codegen patterns
|
|
210
|
-
5. No implicit any (strict TypeScript)
|
|
211
|
-
|
|
212
|
-
## Recent Changes
|
|
213
|
-
|
|
214
|
-
### 2026-03-27: v0.4.0 - Individual Parameters Instead of Request Structs
|
|
215
|
-
|
|
216
|
-
**Breaking Changes**:
|
|
217
|
-
|
|
218
|
-
- Server trait methods now accept individual parameters instead of request structs
|
|
219
|
-
- `async fn subjects_create(&self, claims: Self::Claims, request: SubjectsCreateRequest)` → `async fn subjects_create(&self, claims: Self::Claims, group_id: i64, body: CreateSubjectBody)`
|
|
220
|
-
- Request structs (`SubjectsCreateRequest`, etc.) are no longer generated
|
|
221
|
-
- Handler extractor order follows axum conventions: `State` → `Extension` → `Path` → `Json`
|
|
222
|
-
|
|
223
|
-
**Benefits**:
|
|
224
|
-
|
|
225
|
-
- Cleaner server trait API - no boilerplate request structs
|
|
226
|
-
- Direct parameter access in handler implementations
|
|
227
|
-
- Follows axum best practices for extractor ordering
|
|
228
|
-
- Better IDE autocomplete for handler parameters
|
|
229
|
-
|
|
230
|
-
**Example**:
|
|
231
|
-
|
|
232
|
-
```typespec
|
|
233
|
-
@post
|
|
234
|
-
@route("/groups/{groupId}/subjects")
|
|
235
|
-
op create(@path groupId: int64, @body body: CreateSubjectBody): Subject;
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
```rust
|
|
239
|
-
// Server trait
|
|
240
|
-
async fn subjects_create(&self, claims: Self::Claims, group_id: i64, body: CreateSubjectBody) -> Result<...>;
|
|
241
|
-
|
|
242
|
-
// Handler
|
|
243
|
-
Extension(claims): Extension<S::Claims>,
|
|
244
|
-
Path(group_id): Path<i64>,
|
|
245
|
-
Json(payload): Json<CreateSubjectBody>
|
|
246
|
-
let result = service.subjects_create(claims, group_id, payload).await;
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### 2026-03-27: v0.3.0 - Custom Attributes & Enum Derives
|
|
250
|
-
|
|
251
|
-
**New Features**:
|
|
252
|
-
|
|
253
|
-
- `@rustAttr` / `@rustAttrs` decorators for custom Rust attributes (models & enums)
|
|
254
|
-
- `@rustDerive` / `@rustDerives` now work on enums (previously models only)
|
|
255
|
-
- Attributes render after `#[derive(...)]` for proper Rust syntax
|
|
256
|
-
|
|
257
|
-
**Example**:
|
|
258
|
-
|
|
259
|
-
```typespec
|
|
260
|
-
@rustDerive("sqlx::Type")
|
|
261
|
-
@rustAttr("sqlx(type_name = \"study_status\")")
|
|
262
|
-
enum StudyStatus { Starting, Paused }
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
```rust
|
|
266
|
-
#[derive(..., sqlx::Type)]
|
|
267
|
-
#[sqlx(type_name = "study_status")]
|
|
268
|
-
pub enum StudyStatus { ... }
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### 2026-03-27: v0.2.0 - Server Trait Refactoring
|
|
272
|
-
|
|
273
|
-
**Changes**:
|
|
274
|
-
|
|
275
|
-
- `pub trait Server<Claims>` → `pub trait Server` with `type Claims` associated type
|
|
276
|
-
- Removed generated `Claims` struct from `types.rs` (users define their own)
|
|
277
|
-
- `create_router<S, M>(service: S, middleware: M)` now accepts middleware function
|
|
278
|
-
- Middleware wraps protected routes: `middleware(protected)`
|
|
279
|
-
- Single `.with_state(service)` call at end of router creation
|
|
280
|
-
|
|
281
|
-
**Benefits**:
|
|
282
|
-
|
|
283
|
-
- Cleaner API - Claims type defined by user, not generated
|
|
284
|
-
- Flexible middleware - any function `FnOnce(Router<S>) -> Router<S>`
|
|
285
|
-
- No duplicate `.with_state(service.clone())` calls
|
|
286
|
-
|
|
287
|
-
## TODO
|
|
288
|
-
|
|
289
|
-
- [ ] More server trait test coverage
|
|
290
|
-
- [ ] Integration tests with Rust compilation
|
|
291
|
-
- [ ] Expand scalar format mappings (more chrono types)
|
|
292
|
-
- [ ] Support additional HTTP decorators
|