typespec-rust-emitter 0.2.0 → 0.4.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 +94 -0
- package/QWEN.md +107 -31
- package/README.md +61 -13
- package/dist/src/emitter.d.ts +2 -0
- package/dist/src/emitter.js +181 -116
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/test/hello.test.js +78 -0
- package/dist/test/hello.test.js.map +1 -1
- package/example/lib/learning/models.tsp +2 -0
- package/example/output-rust/Cargo.lock +22 -0
- package/example/output-rust/Cargo.toml +1 -0
- package/example/output-rust/src/generated/server.rs +58 -156
- package/example/output-rust/src/generated/types.rs +2 -1
- package/example/package-lock.json +1 -2
- package/package.json +1 -1
- package/src/emitter.ts +218 -143
- package/src/index.ts +1 -1
- package/src/lib.tsp +4 -2
- package/test/hello.test.ts +100 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,100 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-03-27
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Breaking**: Server trait methods now accept individual parameters instead of request structs
|
|
13
|
+
- `async fn subjects_create(&self, claims: Self::Claims, request: SubjectsCreateRequest)` → `async fn subjects_create(&self, claims: Self::Claims, group_id: i64, body: CreateSubjectBody)`
|
|
14
|
+
- Path parameters extracted with `Path<T>` extractor
|
|
15
|
+
- Body extracted with `Json<T>` extractor
|
|
16
|
+
- Request structs (`SubjectsCreateRequest`, etc.) are no longer generated
|
|
17
|
+
- **Breaking**: Extractor order in handlers follows axum conventions
|
|
18
|
+
- Order: `State` → `Extension` → `Path` → `Json`
|
|
19
|
+
- Fixes axum 0.8 `Handler` trait inference issues
|
|
20
|
+
- Removed unused `Query` extractor import from generated code
|
|
21
|
+
- Fixed decorator detection to use `getDecoratorName()` instead of direct property access
|
|
22
|
+
|
|
23
|
+
### Benefits
|
|
24
|
+
|
|
25
|
+
- Cleaner server trait API - no boilerplate request structs
|
|
26
|
+
- Direct parameter access in handler implementations
|
|
27
|
+
- Follows axum best practices for extractor ordering
|
|
28
|
+
- Better IDE autocomplete for handler parameters
|
|
29
|
+
|
|
30
|
+
### Example
|
|
31
|
+
|
|
32
|
+
**TypeSpec:**
|
|
33
|
+
|
|
34
|
+
```typespec
|
|
35
|
+
@post
|
|
36
|
+
@route("/groups/{groupId}/subjects")
|
|
37
|
+
op create(@path groupId: int64, @body body: CreateSubjectBody): Subject;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Before (v0.3.0):**
|
|
41
|
+
|
|
42
|
+
```rust
|
|
43
|
+
// Server trait
|
|
44
|
+
async fn subjects_create(&self, claims: Self::Claims, request: SubjectsCreateRequest) -> Result<...>;
|
|
45
|
+
|
|
46
|
+
// Handler
|
|
47
|
+
axum::extract::Query(query): axum::extract::Query<SubjectsCreateRequest>
|
|
48
|
+
let result = service.subjects_create(claims, query).await;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**After (v0.4.0):**
|
|
52
|
+
|
|
53
|
+
```rust
|
|
54
|
+
// Server trait
|
|
55
|
+
async fn subjects_create(&self, claims: Self::Claims, group_id: i64, body: CreateSubjectBody) -> Result<...>;
|
|
56
|
+
|
|
57
|
+
// Handler
|
|
58
|
+
Extension(claims): Extension<S::Claims>,
|
|
59
|
+
Path(group_id): Path<i64>,
|
|
60
|
+
Json(payload): Json<CreateSubjectBody>
|
|
61
|
+
let result = service.subjects_create(claims, group_id, payload).await;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## [0.3.0] - 2026-03-27
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- `@rustAttr` and `@rustAttrs` decorators for adding custom Rust attributes to models and enums
|
|
69
|
+
- Example: `@rustAttr("sqlx(type_name = \"user\")")` generates `#[sqlx(type_name = "user")]`
|
|
70
|
+
- Support for `@rustDerive` and `@rustDerives` on enums (previously only models)
|
|
71
|
+
- Enables sqlx and other derive macros on enum types
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- Custom attributes are now rendered after `#[derive(...)]` for proper Rust syntax
|
|
76
|
+
- Updated example to demonstrate `@rustDerive` + `@rustAttr` combination on enums
|
|
77
|
+
|
|
78
|
+
### Example
|
|
79
|
+
|
|
80
|
+
```typespec
|
|
81
|
+
@rustDerive("sqlx::Type")
|
|
82
|
+
@rustAttr("sqlx(type_name = \"study_status\")")
|
|
83
|
+
enum StudyStatus {
|
|
84
|
+
Starting,
|
|
85
|
+
Paused,
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Generates:
|
|
90
|
+
|
|
91
|
+
```rust
|
|
92
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, sqlx::Type)]
|
|
93
|
+
#[sqlx(type_name = "study_status")]
|
|
94
|
+
pub enum StudyStatus {
|
|
95
|
+
#[serde(rename = "Starting")]
|
|
96
|
+
Starting,
|
|
97
|
+
#[serde(rename = "Paused")]
|
|
98
|
+
Paused,
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
8
102
|
## [0.2.0] - 2026-03-27
|
|
9
103
|
|
|
10
104
|
### Changed
|
package/QWEN.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
- **Type**: TypeScript/Node.js project (TypeSpec emitter)
|
|
8
8
|
- **Output**: Generates Rust structs, enums, server traits with serde, thiserror, axum support
|
|
9
|
-
- **Main Entry**: `src/emitter.ts`
|
|
10
|
-
- **Package**: `typespec-rust-emitter` v0.
|
|
9
|
+
- **Main Entry**: `src/emitter.ts` - core codegen logic
|
|
10
|
+
- **Package**: `typespec-rust-emitter` v0.3.0
|
|
11
11
|
|
|
12
12
|
## Directory Structure
|
|
13
13
|
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
typespec-emitter/
|
|
16
16
|
├── src/
|
|
17
17
|
│ ├── emitter.ts # MAIN: TypeSpec→Rust code generation
|
|
18
|
-
│ ├── index.ts # Exports ($onEmit, $rustDerive, $rustDerives)
|
|
18
|
+
│ ├── index.ts # Exports ($onEmit, $rustDerive, $rustDerives, $rustAttr, $rustAttrs)
|
|
19
19
|
│ ├── lib.ts # TypeSpec library definition
|
|
20
20
|
│ ├── lib.tsp # Decorator declarations
|
|
21
21
|
│ └── testing/ # Test utilities
|
|
22
22
|
├── test/
|
|
23
|
-
│ ├── hello.test.ts # Unit tests
|
|
23
|
+
│ ├── hello.test.ts # Unit tests
|
|
24
24
|
│ └── test-host.ts # Test infrastructure (emit() helper)
|
|
25
25
|
├── example/
|
|
26
26
|
│ ├── main.tsp # Demo TypeSpec entry
|
|
@@ -37,6 +37,7 @@ typespec-emitter/
|
|
|
37
37
|
## Build & Run Commands
|
|
38
38
|
|
|
39
39
|
### Primary Workflow (Run After Every Change)
|
|
40
|
+
|
|
40
41
|
```bash
|
|
41
42
|
just build # Compile TypeScript (npm run build)
|
|
42
43
|
just test # Run unit tests (npm test)
|
|
@@ -45,6 +46,7 @@ just check-rust # Verify Rust compiles (cargo check)
|
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
### Individual Commands
|
|
49
|
+
|
|
48
50
|
```bash
|
|
49
51
|
# Development
|
|
50
52
|
npm run build # tsc compile
|
|
@@ -61,47 +63,52 @@ just publish # build + npm publish
|
|
|
61
63
|
|
|
62
64
|
## Type Mappings (TypeSpec → Rust)
|
|
63
65
|
|
|
64
|
-
| TypeSpec
|
|
65
|
-
|
|
66
|
-
| `string`
|
|
67
|
-
| `int8/16/32/64`
|
|
68
|
-
| `uint8/16/32/64` | `u8/u16/u32/u64`
|
|
69
|
-
| `float32/64`
|
|
70
|
-
| `boolean`
|
|
71
|
-
| `bytes`
|
|
72
|
-
| `T[]`
|
|
73
|
-
| `Record<T>`
|
|
74
|
-
| `T \| null`
|
|
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>` |
|
|
75
77
|
|
|
76
78
|
### Format Mappings
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
| `"
|
|
81
|
-
| `"
|
|
79
|
+
|
|
80
|
+
| `@format()` | Rust Type |
|
|
81
|
+
| ------------- | ----------------------- |
|
|
82
|
+
| `"uuid"` | `uuid::Uuid` |
|
|
83
|
+
| `"date"` | `chrono::NaiveDate` |
|
|
84
|
+
| `"time"` | `chrono::NaiveTime` |
|
|
82
85
|
| `"date-time"` | `chrono::DateTime<Utc>` |
|
|
83
86
|
|
|
84
87
|
## Decorators
|
|
85
88
|
|
|
86
|
-
| Decorator
|
|
87
|
-
|
|
88
|
-
| `@error`
|
|
89
|
-
| `@pattern("regex")`
|
|
90
|
-
| `@rustDerive("...")`
|
|
91
|
-
| `@rustDerives("...", "...")` | Adds multiple custom derives |
|
|
92
|
-
| `@
|
|
93
|
-
| `@
|
|
94
|
-
| `@
|
|
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 |
|
|
95
100
|
|
|
96
101
|
## Output Files
|
|
97
102
|
|
|
98
103
|
### types.rs
|
|
104
|
+
|
|
99
105
|
- All models → `pub struct` with serde derives
|
|
100
106
|
- Enums → `pub enum` with serde rename
|
|
101
107
|
- Unions → `Option<T>` for nullable, or sum types
|
|
102
108
|
- Scalars → `pub type` or `pub struct` (if pattern validation)
|
|
103
109
|
|
|
104
110
|
### server.rs
|
|
111
|
+
|
|
105
112
|
- `Server` trait with `type Claims` associated type
|
|
106
113
|
- Request structs per operation
|
|
107
114
|
- Response enums with status code variants
|
|
@@ -110,6 +117,7 @@ just publish # build + npm publish
|
|
|
110
117
|
## Key Implementation Details
|
|
111
118
|
|
|
112
119
|
### emitter.ts Architecture
|
|
120
|
+
|
|
113
121
|
1. `navigateProgram()` - Walks TypeSpec AST
|
|
114
122
|
2. Collects: models, enums, unions, scalars, operations
|
|
115
123
|
3. Processes decorators (`@error`, `@pattern`, `@rustDerive`)
|
|
@@ -117,15 +125,19 @@ just publish # build + npm publish
|
|
|
117
125
|
5. Emits files to configured output directory
|
|
118
126
|
|
|
119
127
|
### Server Trait Generation
|
|
128
|
+
|
|
120
129
|
- Generates `Server` trait with `type Claims: Send + Sync + 'static` associated type
|
|
121
|
-
- Handler functions
|
|
130
|
+
- Handler functions accept individual parameters (path params, body) directly
|
|
131
|
+
- Path params extracted with `Path<T>`
|
|
132
|
+
- Body extracted with `Json<T>`
|
|
122
133
|
- Protected routes (`@useAuth`) receive `claims: Self::Claims` via `Extension<Self::Claims>`
|
|
123
|
-
-
|
|
134
|
+
- Extractor order: `State` → `Extension` → `Path` → `Json`
|
|
124
135
|
- `create_router<S, M>(service: S, middleware: M)` accepts middleware function
|
|
125
136
|
- Middleware wraps protected routes: `middleware(protected)`
|
|
126
137
|
- Router merges public/protected, applies `.with_state(service)` once at end
|
|
127
138
|
|
|
128
139
|
### Test Pattern
|
|
140
|
+
|
|
129
141
|
```typescript
|
|
130
142
|
import { emit } from "./test-host.js";
|
|
131
143
|
const results = await emit(`model User { name: string; }`);
|
|
@@ -136,15 +148,18 @@ strictEqual(output.includes("pub struct User"), true);
|
|
|
136
148
|
## Dependencies
|
|
137
149
|
|
|
138
150
|
### Peer (Required)
|
|
151
|
+
|
|
139
152
|
- `@typespec/compiler` >=1.0.0
|
|
140
153
|
- `@typespec/emitter-framework` ^0.17.0
|
|
141
154
|
|
|
142
155
|
### Dev
|
|
156
|
+
|
|
143
157
|
- `typescript` ^5.3.3
|
|
144
158
|
- `eslint` ^9.15.0
|
|
145
159
|
- `prettier` ^3.3.3
|
|
146
160
|
|
|
147
161
|
### Generated Rust (example/output-rust/Cargo.toml)
|
|
162
|
+
|
|
148
163
|
```toml
|
|
149
164
|
serde = { version = "1.0", features = ["derive"] }
|
|
150
165
|
thiserror = "2.0"
|
|
@@ -161,6 +176,7 @@ tokio = { version = "1.50", features = ["full"] }
|
|
|
161
176
|
## Configuration Files
|
|
162
177
|
|
|
163
178
|
### tspconfig.yaml
|
|
179
|
+
|
|
164
180
|
```yaml
|
|
165
181
|
emit:
|
|
166
182
|
- "@typespec/openapi3"
|
|
@@ -171,6 +187,7 @@ options:
|
|
|
171
187
|
```
|
|
172
188
|
|
|
173
189
|
### tsconfig.json
|
|
190
|
+
|
|
174
191
|
- Target: ES2022
|
|
175
192
|
- Module: NodeNext
|
|
176
193
|
- Strict: true
|
|
@@ -194,9 +211,67 @@ options:
|
|
|
194
211
|
|
|
195
212
|
## Recent Changes
|
|
196
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
|
+
|
|
197
271
|
### 2026-03-27: v0.2.0 - Server Trait Refactoring
|
|
198
272
|
|
|
199
273
|
**Changes**:
|
|
274
|
+
|
|
200
275
|
- `pub trait Server<Claims>` → `pub trait Server` with `type Claims` associated type
|
|
201
276
|
- Removed generated `Claims` struct from `types.rs` (users define their own)
|
|
202
277
|
- `create_router<S, M>(service: S, middleware: M)` now accepts middleware function
|
|
@@ -204,6 +279,7 @@ options:
|
|
|
204
279
|
- Single `.with_state(service)` call at end of router creation
|
|
205
280
|
|
|
206
281
|
**Benefits**:
|
|
282
|
+
|
|
207
283
|
- Cleaner API - Claims type defined by user, not generated
|
|
208
284
|
- Flexible middleware - any function `FnOnce(Router<S>) -> Router<S>`
|
|
209
285
|
- No duplicate `.with_state(service.clone())` calls
|
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@ A TypeSpec emitter that generates idiomatic Rust types and structs from TypeSpec
|
|
|
18
18
|
- **Inheritance**: Supports model inheritance with `getAllProperties()`
|
|
19
19
|
- **Error Models**: Generates `thiserror::Error` derive with `#[error(...)]` attributes
|
|
20
20
|
- **Pattern Validation**: Supports `@pattern` decorators with `TryFrom<String>` validation
|
|
21
|
-
- **Custom Derives**: Add arbitrary Rust derive macros via `@rustDerive` decorator
|
|
21
|
+
- **Custom Derives**: Add arbitrary Rust derive macros via `@rustDerive` decorator (models & enums)
|
|
22
|
+
- **Custom Attributes**: Add arbitrary Rust attributes via `@rustAttr` decorator (models & enums)
|
|
22
23
|
- **Server Trait**: Generates axum server trait and router from HTTP operations
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
@@ -178,6 +179,44 @@ pub struct GroupStatistics {
|
|
|
178
179
|
}
|
|
179
180
|
```
|
|
180
181
|
|
|
182
|
+
### Custom Attributes
|
|
183
|
+
|
|
184
|
+
```typespec
|
|
185
|
+
import "typespec-emitter";
|
|
186
|
+
|
|
187
|
+
@rustAttr("sqlx(type_name = \"user\")")
|
|
188
|
+
model User {
|
|
189
|
+
name: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@rustDerive("sqlx::Type")
|
|
193
|
+
@rustAttr("sqlx(type_name = \"study_status\")")
|
|
194
|
+
enum StudyStatus {
|
|
195
|
+
Starting,
|
|
196
|
+
Paused,
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Generates:
|
|
201
|
+
|
|
202
|
+
```rust
|
|
203
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
204
|
+
#[sqlx(type_name = "user")]
|
|
205
|
+
pub struct User {
|
|
206
|
+
#[serde(rename = "name")]
|
|
207
|
+
pub name: String,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, sqlx::Type)]
|
|
211
|
+
#[sqlx(type_name = "study_status")]
|
|
212
|
+
pub enum StudyStatus {
|
|
213
|
+
#[serde(rename = "Starting")]
|
|
214
|
+
Starting,
|
|
215
|
+
#[serde(rename = "Paused")]
|
|
216
|
+
Paused,
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
181
220
|
## Server Trait Generation
|
|
182
221
|
|
|
183
222
|
The emitter can generate a complete axum server trait and router from TypeSpec HTTP operations.
|
|
@@ -209,9 +248,9 @@ namespace Groups {
|
|
|
209
248
|
pub trait Server: Send + Sync {
|
|
210
249
|
type Claims: Send + Sync + 'static;
|
|
211
250
|
|
|
212
|
-
async fn groups_list(&self
|
|
213
|
-
async fn groups_create(&self, claims: Self::Claims,
|
|
214
|
-
async fn groups_get_by_id(&self,
|
|
251
|
+
async fn groups_list(&self) -> Result<GroupsListResponse>;
|
|
252
|
+
async fn groups_create(&self, claims: Self::Claims, body: CreateGroupBody) -> Result<GroupsCreateResponse>;
|
|
253
|
+
async fn groups_get_by_id(&self, id: i64) -> Result<GroupsGetByIdResponse>;
|
|
215
254
|
}
|
|
216
255
|
```
|
|
217
256
|
|
|
@@ -231,10 +270,7 @@ pub struct MyServer {
|
|
|
231
270
|
impl Server for MyServer {
|
|
232
271
|
type Claims = Claims; // Your custom claims type
|
|
233
272
|
|
|
234
|
-
async fn groups_list(
|
|
235
|
-
&self,
|
|
236
|
-
_request: GroupsListRequest,
|
|
237
|
-
) -> eyre::Result<GroupsListResponse> {
|
|
273
|
+
async fn groups_list(&self) -> eyre::Result<GroupsListResponse> {
|
|
238
274
|
let groups = sqlx::query_as::<_, Group>("SELECT * FROM groups")
|
|
239
275
|
.fetch_all(&self.state.db)
|
|
240
276
|
.await?;
|
|
@@ -244,19 +280,29 @@ impl Server for MyServer {
|
|
|
244
280
|
async fn groups_create(
|
|
245
281
|
&self,
|
|
246
282
|
claims: Self::Claims,
|
|
247
|
-
|
|
283
|
+
body: CreateGroupBody,
|
|
248
284
|
) -> eyre::Result<GroupsCreateResponse> {
|
|
249
|
-
//
|
|
285
|
+
// Direct access to body fields
|
|
250
286
|
let group = sqlx::query_as::<_, Group>(
|
|
251
287
|
"INSERT INTO groups (name, owner_id) VALUES ($1, $2) RETURNING *"
|
|
252
288
|
)
|
|
253
|
-
.bind(&
|
|
289
|
+
.bind(&body.name)
|
|
254
290
|
.bind(&claims.sub)
|
|
255
291
|
.fetch_one(&self.state.db)
|
|
256
292
|
.await?;
|
|
257
293
|
Ok(GroupsCreateResponse::Created(Json(group)))
|
|
258
294
|
}
|
|
259
295
|
|
|
296
|
+
async fn groups_get_by_id(&self, id: i64) -> eyre::Result<GroupsGetByIdResponse> {
|
|
297
|
+
// Direct access to path parameter
|
|
298
|
+
let group = sqlx::query_as::<_, Group>("SELECT * FROM groups WHERE id = $1")
|
|
299
|
+
.bind(id)
|
|
300
|
+
.fetch_optional(&self.state.db)
|
|
301
|
+
.await?
|
|
302
|
+
.ok_or_else(|| GroupsGetByIdResponse::NotFound)?;
|
|
303
|
+
Ok(GroupsGetByIdResponse::Ok(Json(group)))
|
|
304
|
+
}
|
|
305
|
+
|
|
260
306
|
// ... implement other methods
|
|
261
307
|
}
|
|
262
308
|
```
|
|
@@ -337,8 +383,10 @@ Public routes do not receive claims and are not wrapped with auth middleware.
|
|
|
337
383
|
| ---------------------------- | ----------------------------------------------------- |
|
|
338
384
|
| `@error` | Adds `thiserror::Error` derive with error message |
|
|
339
385
|
| `@pattern("regex")` | Generates `TryFrom<String>` with regex validation |
|
|
340
|
-
| `@rustDerive("...")` | Adds a custom derive macro (
|
|
341
|
-
| `@rustDerives("...", "...")` | Adds multiple custom derive macros
|
|
386
|
+
| `@rustDerive("...")` | Adds a custom derive macro (models & enums) |
|
|
387
|
+
| `@rustDerives("...", "...")` | Adds multiple custom derive macros (models & enums) |
|
|
388
|
+
| `@rustAttr("...")` | Adds a custom Rust attribute (models & enums) |
|
|
389
|
+
| `@rustAttrs("...", "...")` | Adds multiple custom Rust attributes (models & enums) |
|
|
342
390
|
| `@doc("...")` | Generates `///` doc comments |
|
|
343
391
|
| `@useAuth(AuthType)` | Marks operation as protected, adds `Claims` parameter |
|
|
344
392
|
|
package/dist/src/emitter.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export interface RustEmitterOptions {
|
|
|
4
4
|
}
|
|
5
5
|
export declare function $rustDerive(context: DecoratorContext, target: Type, derive: string): void;
|
|
6
6
|
export declare function $rustDerives(context: DecoratorContext, target: Type, ...derives: string[]): void;
|
|
7
|
+
export declare function $rustAttr(context: DecoratorContext, target: Type, attr: string): void;
|
|
8
|
+
export declare function $rustAttrs(context: DecoratorContext, target: Type, ...attrs: string[]): void;
|
|
7
9
|
export declare function $onEmit(context: EmitContext<RustEmitterOptions>, _options?: RustEmitterOptions): Promise<void>;
|