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/.qwen/settings.json
CHANGED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# TypeSpec Rust Emitter - Agent Guidelines
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
TypeSpec emitter that generates idiomatic Rust code (structs, enums, server traits) from TypeSpec specifications.
|
|
6
|
+
|
|
7
|
+
## Build, Lint, and Test Commands
|
|
8
|
+
|
|
9
|
+
### Standard Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Build TypeScript
|
|
13
|
+
npm run build # or: just build
|
|
14
|
+
|
|
15
|
+
# Run tests
|
|
16
|
+
npm test # or: just test
|
|
17
|
+
node --test 'dist/test/**/*.test.js'
|
|
18
|
+
|
|
19
|
+
# Run a single test file
|
|
20
|
+
node --test 'dist/test/hello.test.js'
|
|
21
|
+
|
|
22
|
+
# Run a specific test
|
|
23
|
+
node --test 'dist/test/hello.test.js' --test-name-pattern="emits model"
|
|
24
|
+
|
|
25
|
+
# Lint
|
|
26
|
+
npm run lint # Check linting
|
|
27
|
+
npm run lint:fix # Auto-fix linting issues
|
|
28
|
+
|
|
29
|
+
# Format code
|
|
30
|
+
npm run format # Format all files
|
|
31
|
+
npm run format:check # Check formatting without changes
|
|
32
|
+
|
|
33
|
+
# Compile TypeSpec to Rust
|
|
34
|
+
just compile # Runs: cd example && tsp compile .
|
|
35
|
+
|
|
36
|
+
# Verify generated Rust compiles
|
|
37
|
+
just check-rust # Runs: cd example/output-rust && cargo check
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Full Development Cycle
|
|
41
|
+
|
|
42
|
+
After every change, run this sequence:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
just build && npm test && just compile && just check-rust
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Project Structure
|
|
49
|
+
|
|
50
|
+
| Path | Purpose |
|
|
51
|
+
| ------------------------------------ | -------------------------------------------------------- |
|
|
52
|
+
| `src/emitter.ts` | Main emitter logic (~1500 lines) - TypeSpec→Rust codegen |
|
|
53
|
+
| `src/lib.tsp` | Decorator declarations (`@rustDerive`, `@rustDerives`) |
|
|
54
|
+
| `src/lib.ts` | Decorator implementations |
|
|
55
|
+
| `src/index.ts` | Public API exports |
|
|
56
|
+
| `src/testing/index.ts` | Test utilities |
|
|
57
|
+
| `test/hello.test.ts` | Unit tests using `emit()` helper |
|
|
58
|
+
| `test/test-host.ts` | Test host setup |
|
|
59
|
+
| `example/lib/` | Demo TypeSpec models & operations |
|
|
60
|
+
| `example/output-rust/src/generated/` | Generated Rust output |
|
|
61
|
+
|
|
62
|
+
## Code Style
|
|
63
|
+
|
|
64
|
+
### TypeScript
|
|
65
|
+
|
|
66
|
+
- **Strict mode enabled** - No implicit `any`
|
|
67
|
+
- **No `any` types** - Use `unknown` or proper generics instead
|
|
68
|
+
- **Explicit return types** on public functions
|
|
69
|
+
- **Interfaces over type aliases** for object shapes
|
|
70
|
+
|
|
71
|
+
### Imports
|
|
72
|
+
|
|
73
|
+
- Named imports from `@typespec/compiler` first, then external packages
|
|
74
|
+
- Group imports: 1) `@typespec/*`, 2) external, 3) internal
|
|
75
|
+
- Use `import type { X }` for type-only imports when appropriate
|
|
76
|
+
|
|
77
|
+
### Formatting (Prettier)
|
|
78
|
+
|
|
79
|
+
Configuration in `prettierrc.yaml`:
|
|
80
|
+
|
|
81
|
+
- `printWidth: 120`
|
|
82
|
+
- `trailingComma: "all"`
|
|
83
|
+
- `arrowParens: always`
|
|
84
|
+
- `endOfLine: lf`
|
|
85
|
+
|
|
86
|
+
Use `npm run format` before committing.
|
|
87
|
+
|
|
88
|
+
### Naming Conventions
|
|
89
|
+
|
|
90
|
+
| Element | Convention | Example |
|
|
91
|
+
| --------------------------- | ---------- | -------------------- |
|
|
92
|
+
| TypeScript functions | camelCase | `$rustDerive` |
|
|
93
|
+
| TypeScript types/interfaces | PascalCase | `RustEmitterOptions` |
|
|
94
|
+
| Rust types | PascalCase | `pub struct User` |
|
|
95
|
+
| Rust functions/methods | snake_case | `get_user_by_id` |
|
|
96
|
+
| Internal symbols | camelCase | `rustDeriveKey` |
|
|
97
|
+
|
|
98
|
+
### Type Mappings (TypeSpec → Rust)
|
|
99
|
+
|
|
100
|
+
| TypeSpec | Rust |
|
|
101
|
+
| ---------------------- | ----------------------- |
|
|
102
|
+
| `string` | `String` |
|
|
103
|
+
| `int32/64` | `i32/i64` |
|
|
104
|
+
| `float32/64` | `f32/f64` |
|
|
105
|
+
| `boolean` | `bool` |
|
|
106
|
+
| `T \| null` | `Option<T>` |
|
|
107
|
+
| `T[]` | `Vec<T>` |
|
|
108
|
+
| `Record<T>` | `HashMap<String, T>` |
|
|
109
|
+
| `@format("uuid")` | `uuid::Uuid` |
|
|
110
|
+
| `@format("date-time")` | `chrono::DateTime<Utc>` |
|
|
111
|
+
|
|
112
|
+
## Error Handling
|
|
113
|
+
|
|
114
|
+
- Use `context.program.reportDiagnostic()` for errors and warnings
|
|
115
|
+
- Diagnostic codes should use kebab-case: `code: "rust-derive-invalid-target"`
|
|
116
|
+
- Always specify `severity: "error"` or `"warning"`
|
|
117
|
+
- Include `target: context.decoratorTarget` for location info
|
|
118
|
+
|
|
119
|
+
## Testing Pattern
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { emit } from "./test-host.js";
|
|
123
|
+
|
|
124
|
+
const results = await emit(`model User { name: string; }`);
|
|
125
|
+
const output = results["types.rs"];
|
|
126
|
+
strictEqual(output.includes("pub struct User"), true);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Server Trait Generation Rules
|
|
130
|
+
|
|
131
|
+
1. Generates `Server<Claims>` trait with async methods per operation
|
|
132
|
+
2. Handler functions: `pub async fn {op}_handler<S, Claims>(...)`
|
|
133
|
+
3. All handlers use `<S, Claims>` generics (even public routes)
|
|
134
|
+
4. Protected routes (`@useAuth`) receive `claims: Claims` parameter
|
|
135
|
+
5. Router splits public/protected routes, merges at end
|
|
136
|
+
6. Public routes include `Claims: Send + Sync + Clone + 'static` bound
|
|
137
|
+
|
|
138
|
+
## Architecture
|
|
139
|
+
|
|
140
|
+
1. `navigateProgram()` walks TypeSpec AST
|
|
141
|
+
2. Collects models, enums, operations
|
|
142
|
+
3. Processes decorators
|
|
143
|
+
4. Generates Rust code via string templates
|
|
144
|
+
5. Emits to `example/output-rust/src/generated/`
|
|
145
|
+
|
|
146
|
+
## Linting
|
|
147
|
+
|
|
148
|
+
ESLint config (`eslint.config.js`):
|
|
149
|
+
|
|
150
|
+
- Uses `typescript-eslint` recommended rules
|
|
151
|
+
- Ignores `dist/**` and `.temp/**`
|
|
152
|
+
- Unused variables: warn with `_` prefix exception
|
|
153
|
+
|
|
154
|
+
## Commit Message Style
|
|
155
|
+
|
|
156
|
+
- Use clear, descriptive titles
|
|
157
|
+
- Focus on "why" rather than "what"
|
|
158
|
+
- Keep concise (1-2 sentences)
|
|
159
|
+
- Example: "Fix handler generics for public routes without auth"
|
|
160
|
+
|
|
161
|
+
## Checklist Before PR
|
|
162
|
+
|
|
163
|
+
- [ ] Run full cycle: `just build && npm test && just compile && just check-rust`
|
|
164
|
+
- [ ] Add tests for new features
|
|
165
|
+
- [ ] Run `npm run lint` and `npm run format`
|
|
166
|
+
- [ ] Generated Rust must compile with `cargo check`
|
|
167
|
+
- [ ] No TypeScript errors
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,72 @@ 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.9.0] - 2026-03-31
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Error `IntoResponse` now uses dynamic status code from `code` field**: Instead of mapping status codes by error type name, errors now use `StatusCode::from_str(&self.code)` to determine the HTTP status code at runtime
|
|
13
|
+
- This allows defining any error type with custom codes without changing the emitter
|
|
14
|
+
- Falls back to `INTERNAL_SERVER_ERROR` if the code is not a valid HTTP status string
|
|
15
|
+
|
|
16
|
+
### Example
|
|
17
|
+
|
|
18
|
+
```rust
|
|
19
|
+
// Error model with any code
|
|
20
|
+
model CustomError {
|
|
21
|
+
code: string; // e.g., "NOT_FOUND", "BAD_REQUEST", "MY_CUSTOM_CODE"
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl IntoResponse for CustomError {
|
|
26
|
+
fn into_response(self) -> axum::response::Response {
|
|
27
|
+
(
|
|
28
|
+
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
29
|
+
Json(self),
|
|
30
|
+
)
|
|
31
|
+
.into_response()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **Clippy warnings**: Enums and newtype structs with pattern validation now use `#[derive(Default)]` with `#[default]` attribute instead of manual `impl Default`, eliminating `clippy::derivable_impls` warnings
|
|
39
|
+
|
|
40
|
+
### Internal
|
|
41
|
+
|
|
42
|
+
- Added `use std::str::FromStr;` import to generated types.rs
|
|
43
|
+
- Removed unused `getHttpStatusCodeForError()` function
|
|
44
|
+
|
|
45
|
+
## [0.8.0] - 2026-03-30
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **axum IntoResponse implementation for error types**: Error models (`ApiError`, `NotFoundError`, `ValidationError`, `ConflictError`) now implement `axum::response::IntoResponse`
|
|
50
|
+
- Error types can be returned directly from axum handlers
|
|
51
|
+
- Status code mapping based on error name suffix:
|
|
52
|
+
- `*NotFoundError` → `404 NOT_FOUND`
|
|
53
|
+
- `*ValidationError` → `400 BAD_REQUEST`
|
|
54
|
+
- `*ConflictError` → `409 CONFLICT`
|
|
55
|
+
- Default → `500 INTERNAL_SERVER_ERROR`
|
|
56
|
+
|
|
57
|
+
### Example
|
|
58
|
+
|
|
59
|
+
```rust
|
|
60
|
+
// Generated in types.rs
|
|
61
|
+
impl IntoResponse for NotFoundError {
|
|
62
|
+
fn into_response(self) -> axum::response::Response {
|
|
63
|
+
(StatusCode::NOT_FOUND, Json(self)).into_response()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// In a handler, error types can be returned directly
|
|
68
|
+
async fn get_item_handler<S>(/* ... */) -> impl IntoResponse {
|
|
69
|
+
// ...
|
|
70
|
+
NotFoundError { code: "NOT_FOUND".to_string(), message: "Item not found".to_string() }
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
8
74
|
## [0.7.0] - 2026-03-30
|
|
9
75
|
|
|
10
76
|
### Fixed
|
package/README.md
CHANGED
|
@@ -12,11 +12,11 @@ A TypeSpec emitter that generates idiomatic Rust types and structs from TypeSpec
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- **Models**: Converts TypeSpec models to Rust structs with serde derive macros
|
|
15
|
-
- **Enums**: Supports both string and integer enums
|
|
15
|
+
- **Enums**: Supports both string and integer enums with `Default` derive
|
|
16
16
|
- **Unions**: Handles nullable types (`T | null` → `Option<T>`) and string literal unions
|
|
17
17
|
- **Scalars**: Maps TypeSpec scalars to Rust equivalents with `@format` support
|
|
18
18
|
- **Inheritance**: Supports model inheritance with `getAllProperties()`
|
|
19
|
-
- **Error Models**: Generates `thiserror::Error` derive with `#[error(...)]` attributes
|
|
19
|
+
- **Error Models**: Generates `thiserror::Error` derive with `#[error(...)]` attributes and `IntoResponse` impl for axum
|
|
20
20
|
- **Pattern Validation**: Supports `@pattern` decorators with `TryFrom<String>` validation
|
|
21
21
|
- **Custom Derives**: Add arbitrary Rust derive macros via `@rustDerive` decorator (models & enums)
|
|
22
22
|
- **Custom Attributes**: Add arbitrary Rust attributes via `@rustAttr` decorator (models & enums)
|
|
@@ -78,8 +78,9 @@ enum Status {
|
|
|
78
78
|
Generates:
|
|
79
79
|
|
|
80
80
|
```rust
|
|
81
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
81
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
|
|
82
82
|
pub enum Status {
|
|
83
|
+
#[default]
|
|
83
84
|
#[serde(rename = "active")]
|
|
84
85
|
Active,
|
|
85
86
|
#[serde(rename = "inactive")]
|
|
@@ -87,12 +88,6 @@ pub enum Status {
|
|
|
87
88
|
#[serde(rename = "pending")]
|
|
88
89
|
Pending,
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
-
impl Default for Status {
|
|
92
|
-
fn default() -> Self {
|
|
93
|
-
Status::Active
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
91
|
```
|
|
97
92
|
|
|
98
93
|
### Error Model
|
|
@@ -116,8 +111,20 @@ pub struct ApiError {
|
|
|
116
111
|
#[serde(rename = "message")]
|
|
117
112
|
pub message: String,
|
|
118
113
|
}
|
|
114
|
+
|
|
115
|
+
impl IntoResponse for ApiError {
|
|
116
|
+
fn into_response(self) -> axum::response::Response {
|
|
117
|
+
(
|
|
118
|
+
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
119
|
+
Json(self),
|
|
120
|
+
)
|
|
121
|
+
.into_response()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
119
124
|
```
|
|
120
125
|
|
|
126
|
+
Error codes are parsed dynamically, so any valid HTTP status code string (e.g., `"NOT_FOUND"`, `"BAD_REQUEST"`, `"418 I'M_A_TEAPOT"`) will work.
|
|
127
|
+
|
|
121
128
|
### UUID Format
|
|
122
129
|
|
|
123
130
|
```typespec
|
|
@@ -207,9 +214,10 @@ pub struct User {
|
|
|
207
214
|
pub name: String,
|
|
208
215
|
}
|
|
209
216
|
|
|
210
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, sqlx::Type)]
|
|
217
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize, sqlx::Type)]
|
|
211
218
|
#[sqlx(type_name = "study_status")]
|
|
212
219
|
pub enum StudyStatus {
|
|
220
|
+
#[default]
|
|
213
221
|
#[serde(rename = "Starting")]
|
|
214
222
|
Starting,
|
|
215
223
|
#[serde(rename = "Paused")]
|
package/dist/src/emitter.js
CHANGED
|
@@ -287,7 +287,7 @@ function getStatusCode(variant) {
|
|
|
287
287
|
function getBodyFromResponse(variant, program, anonymousEnums) {
|
|
288
288
|
if (variant.type.kind === "Model") {
|
|
289
289
|
const model = variant.type;
|
|
290
|
-
for (const [
|
|
290
|
+
for (const [_propName, prop] of model.properties) {
|
|
291
291
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
292
292
|
const decorators = prop.decorators;
|
|
293
293
|
let isBody = false;
|
|
@@ -414,9 +414,11 @@ function generateServerTrait(program, namespaceGroups, anonymousEnums) {
|
|
|
414
414
|
const parts = [];
|
|
415
415
|
parts.push(`use super::types::*;
|
|
416
416
|
use async_trait::async_trait;
|
|
417
|
-
use axum::{http::StatusCode, Json};
|
|
418
417
|
use axum::extract::Path;
|
|
419
418
|
use axum::Extension;
|
|
419
|
+
use axum::http::StatusCode;
|
|
420
|
+
use axum::response::IntoResponse;
|
|
421
|
+
use axum::Json;
|
|
420
422
|
use eyre::Result;
|
|
421
423
|
|
|
422
424
|
#[async_trait]
|
|
@@ -610,8 +612,7 @@ where
|
|
|
610
612
|
const methodImports = Array.from(usedMethods).sort().join(", ");
|
|
611
613
|
const routerBody = buildRouterBody(publicRoutes, protectedRoutes);
|
|
612
614
|
const parts = [];
|
|
613
|
-
parts.push(`use axum::
|
|
614
|
-
use axum::routing::{${methodImports}};
|
|
615
|
+
parts.push(`use axum::routing::{${methodImports}};
|
|
615
616
|
use axum::Router;
|
|
616
617
|
|
|
617
618
|
`);
|
|
@@ -870,17 +871,19 @@ function emitStringLiteralUnion(union) {
|
|
|
870
871
|
const parts = [];
|
|
871
872
|
const name = toPascalCase(union.name ?? "Value");
|
|
872
873
|
const variants = Array.from(union.variants.values());
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
874
|
+
const defaultVariant = toRustVariantName(variants[0]?.type ? variants[0].type.value : "");
|
|
875
|
+
parts.push(`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum ${name} {`);
|
|
876
|
+
for (let i = 0; i < variants.length; i++) {
|
|
877
|
+
const literalType = variants[i].type;
|
|
876
878
|
const variantName = toRustVariantName(literalType.value);
|
|
877
879
|
const serdeValue = literalType.value;
|
|
880
|
+
if (i === 0) {
|
|
881
|
+
parts.push(` #[default]`);
|
|
882
|
+
}
|
|
878
883
|
parts.push(` #[serde(rename = "${serdeValue}")]`);
|
|
879
884
|
parts.push(` ${variantName},`);
|
|
880
885
|
}
|
|
881
886
|
parts.push("}");
|
|
882
|
-
const defaultVariant = toRustVariantName(variants[0]?.type ? variants[0].type.value : "");
|
|
883
|
-
parts.push(`\n\nimpl Default for ${name} {\n fn default() -> Self {\n ${name}::${defaultVariant}\n }\n}`);
|
|
884
887
|
return parts.join("\n");
|
|
885
888
|
}
|
|
886
889
|
function emitModel(model, program, anonymousEnums) {
|
|
@@ -940,6 +943,18 @@ ${fields.join("\n")}
|
|
|
940
943
|
parts.push(`pub struct ${name}`);
|
|
941
944
|
parts.push("(());");
|
|
942
945
|
}
|
|
946
|
+
if (isError && allProps.size > 0) {
|
|
947
|
+
parts.push(`
|
|
948
|
+
impl IntoResponse for ${name} {
|
|
949
|
+
fn into_response(self) -> axum::response::Response {
|
|
950
|
+
(
|
|
951
|
+
StatusCode::from_str(&self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
|
952
|
+
Json(self),
|
|
953
|
+
)
|
|
954
|
+
.into_response()
|
|
955
|
+
}
|
|
956
|
+
}`);
|
|
957
|
+
}
|
|
943
958
|
return parts.join("\n");
|
|
944
959
|
}
|
|
945
960
|
function getAllProperties(model, _program) {
|
|
@@ -967,6 +982,7 @@ function emitEnum(enumType) {
|
|
|
967
982
|
"PartialEq",
|
|
968
983
|
"Eq",
|
|
969
984
|
"Hash",
|
|
985
|
+
"Default",
|
|
970
986
|
"serde::Serialize",
|
|
971
987
|
"serde::Deserialize",
|
|
972
988
|
];
|
|
@@ -990,9 +1006,12 @@ function emitEnum(enumType) {
|
|
|
990
1006
|
parts.push(...attrLines);
|
|
991
1007
|
}
|
|
992
1008
|
parts.push(`pub enum ${name} {`);
|
|
993
|
-
for (
|
|
994
|
-
const variantName = toRustVariantName(
|
|
995
|
-
const serdeValue =
|
|
1009
|
+
for (let i = 0; i < members.length; i++) {
|
|
1010
|
+
const variantName = toRustVariantName(members[i].name);
|
|
1011
|
+
const serdeValue = members[i].value ?? members[i].name;
|
|
1012
|
+
if (i === 0) {
|
|
1013
|
+
parts.push(` #[default]`);
|
|
1014
|
+
}
|
|
996
1015
|
parts.push(` #[serde(rename = "${serdeValue}")]`);
|
|
997
1016
|
parts.push(` ${variantName},`);
|
|
998
1017
|
}
|
|
@@ -1003,15 +1022,16 @@ function emitEnum(enumType) {
|
|
|
1003
1022
|
parts.push(...attrLines);
|
|
1004
1023
|
}
|
|
1005
1024
|
parts.push(`pub enum ${name} {`);
|
|
1006
|
-
for (
|
|
1007
|
-
const variantName = toRustVariantName(
|
|
1008
|
-
const enumValue =
|
|
1025
|
+
for (let i = 0; i < members.length; i++) {
|
|
1026
|
+
const variantName = toRustVariantName(members[i].name);
|
|
1027
|
+
const enumValue = members[i].value ?? 0;
|
|
1028
|
+
if (i === 0) {
|
|
1029
|
+
parts.push(` #[default]`);
|
|
1030
|
+
}
|
|
1009
1031
|
parts.push(` ${variantName} = ${enumValue},`);
|
|
1010
1032
|
}
|
|
1011
1033
|
}
|
|
1012
1034
|
parts.push("}");
|
|
1013
|
-
const defaultVariant = toRustVariantName(members[0]?.name ?? "");
|
|
1014
|
-
parts.push(`\n\nimpl Default for ${name} {\n fn default() -> Self {\n ${name}::${defaultVariant}\n }\n}`);
|
|
1015
1035
|
return parts.join("\n");
|
|
1016
1036
|
}
|
|
1017
1037
|
function emitUnion(union, program) {
|
|
@@ -1046,9 +1066,8 @@ function emitScalar(scalar, program) {
|
|
|
1046
1066
|
if (pattern) {
|
|
1047
1067
|
const rustType = scalarToRust[scalar.name] ?? "String";
|
|
1048
1068
|
impls.push(`\nimpl TryFrom<String> for ${structName} {\n type Error = String;\n\n fn try_from(value: String) -> Result<Self, Self::Error> {\n let re = regex::Regex::new(r"${pattern}").unwrap();\n if re.is_match(&value) { Ok(Self(value)) } else { Err(format!("Invalid value: {}", value)) }\n }\n}`);
|
|
1049
|
-
impls.push(`\nimpl Default for ${structName} {\n fn default() -> Self {\n Self(String::new())\n }\n}`);
|
|
1050
1069
|
return {
|
|
1051
|
-
typeDef: `#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub struct ${structName}(pub ${rustType});`,
|
|
1070
|
+
typeDef: `#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub struct ${structName}(pub ${rustType});`,
|
|
1052
1071
|
impls,
|
|
1053
1072
|
};
|
|
1054
1073
|
}
|
|
@@ -1112,15 +1131,17 @@ export async function $onEmit(context, _options) {
|
|
|
1112
1131
|
}
|
|
1113
1132
|
for (const [enumName, anonEnum] of anonymousEnums) {
|
|
1114
1133
|
const parts = [];
|
|
1115
|
-
parts.push(`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum ${enumName} {`);
|
|
1116
|
-
for (
|
|
1134
|
+
parts.push(`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum ${enumName} {`);
|
|
1135
|
+
for (let i = 0; i < anonEnum.variants.length; i++) {
|
|
1136
|
+
const literal = anonEnum.variants[i];
|
|
1117
1137
|
const variantName = toRustVariantName(literal.value);
|
|
1138
|
+
if (i === 0) {
|
|
1139
|
+
parts.push(` #[default]`);
|
|
1140
|
+
}
|
|
1118
1141
|
parts.push(` #[serde(rename = "${literal.value}")]`);
|
|
1119
1142
|
parts.push(` ${variantName},`);
|
|
1120
1143
|
}
|
|
1121
1144
|
parts.push("}");
|
|
1122
|
-
const defaultVariant = toRustVariantName(anonEnum.variants[0]?.value ?? "");
|
|
1123
|
-
parts.push(`\n\nimpl Default for ${enumName} {\n fn default() -> Self {\n ${enumName}::${defaultVariant}\n }\n}`);
|
|
1124
1145
|
content.push(parts.join("\n"));
|
|
1125
1146
|
content.push("");
|
|
1126
1147
|
}
|
|
@@ -1145,7 +1166,13 @@ export async function $onEmit(context, _options) {
|
|
|
1145
1166
|
content.push("");
|
|
1146
1167
|
}
|
|
1147
1168
|
const namespaceGroups = getAllOperations(context.program);
|
|
1148
|
-
const outputContent = "#![allow(unused)]\n\n" +
|
|
1169
|
+
const outputContent = "#![allow(unused)]\n\n" +
|
|
1170
|
+
"use std::str::FromStr;\n" +
|
|
1171
|
+
"use axum::http::StatusCode;\n" +
|
|
1172
|
+
"use axum::response::IntoResponse;\n" +
|
|
1173
|
+
"use axum::Json;\n\n" +
|
|
1174
|
+
content.join("\n") +
|
|
1175
|
+
"\n";
|
|
1149
1176
|
await emitFile(context.program, {
|
|
1150
1177
|
path: resolvePath(context.emitterOutputDir, "types.rs"),
|
|
1151
1178
|
content: outputContent,
|