typespec-rust-emitter 0.12.0 → 0.13.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/AGENTS.md +82 -80
- package/CHANGELOG.md +17 -0
- package/dist/src/decorators/cache_control.d.ts +6 -0
- package/dist/src/decorators/cache_control.js +9 -0
- package/dist/src/decorators/cache_control.js.map +1 -0
- package/dist/src/decorators/etag_cache.d.ts +6 -0
- package/dist/src/decorators/etag_cache.js +9 -0
- package/dist/src/decorators/etag_cache.js.map +1 -0
- package/dist/src/decorators/index.d.ts +6 -0
- package/dist/src/decorators/index.js +7 -0
- package/dist/src/decorators/index.js.map +1 -0
- package/dist/src/decorators/rust_attr.d.ts +3 -0
- package/dist/src/decorators/rust_attr.js +45 -0
- package/dist/src/decorators/rust_attr.js.map +1 -0
- package/dist/src/decorators/rust_derive.d.ts +3 -0
- package/dist/src/decorators/rust_derive.js +39 -0
- package/dist/src/decorators/rust_derive.js.map +1 -0
- package/dist/src/decorators/rust_impl.d.ts +2 -0
- package/dist/src/decorators/rust_impl.js +19 -0
- package/dist/src/decorators/rust_impl.js.map +1 -0
- package/dist/src/decorators/rust_self.d.ts +3 -0
- package/dist/src/decorators/rust_self.js +35 -0
- package/dist/src/decorators/rust_self.js.map +1 -0
- package/dist/src/emitter.d.ts +2 -11
- package/dist/src/emitter.js +7 -1282
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/formatter/index.d.ts +2 -0
- package/dist/src/formatter/index.js +3 -0
- package/dist/src/formatter/index.js.map +1 -0
- package/dist/src/formatter/mappings.d.ts +4 -0
- package/dist/src/formatter/mappings.js +68 -0
- package/dist/src/formatter/mappings.js.map +1 -0
- package/dist/src/formatter/strings.d.ts +4 -0
- package/dist/src/formatter/strings.js +32 -0
- package/dist/src/formatter/strings.js.map +1 -0
- package/dist/src/generator/etag_router.d.ts +30 -0
- package/dist/src/generator/etag_router.js +123 -0
- package/dist/src/generator/etag_router.js.map +1 -0
- package/dist/src/generator/index.d.ts +5 -0
- package/dist/src/generator/index.js +6 -0
- package/dist/src/generator/index.js.map +1 -0
- package/dist/src/generator/response_enums.d.ts +6 -0
- package/dist/src/generator/response_enums.js +58 -0
- package/dist/src/generator/response_enums.js.map +1 -0
- package/dist/src/generator/router.d.ts +7 -0
- package/dist/src/generator/router.js +227 -0
- package/dist/src/generator/router.js.map +1 -0
- package/dist/src/generator/server_trait.d.ts +6 -0
- package/dist/src/generator/server_trait.js +97 -0
- package/dist/src/generator/server_trait.js.map +1 -0
- package/dist/src/generator/types_file.d.ts +11 -0
- package/dist/src/generator/types_file.js +209 -0
- package/dist/src/generator/types_file.js.map +1 -0
- 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/src/lib.js +1 -1
- package/dist/src/lib.js.map +1 -1
- package/dist/src/models/index.d.ts +2 -0
- package/dist/src/models/index.js +3 -0
- package/dist/src/models/index.js.map +1 -0
- package/dist/src/models/keys.d.ts +6 -0
- package/dist/src/models/keys.js +8 -0
- package/dist/src/models/keys.js.map +1 -0
- package/dist/src/models/types.d.ts +45 -0
- package/dist/src/models/types.js +2 -0
- package/dist/src/models/types.js.map +1 -0
- package/dist/src/parser/decorators.d.ts +18 -0
- package/dist/src/parser/decorators.js +28 -0
- package/dist/src/parser/decorators.js.map +1 -0
- package/dist/src/parser/index.d.ts +6 -0
- package/dist/src/parser/index.js +7 -0
- package/dist/src/parser/index.js.map +1 -0
- package/dist/src/parser/operations.d.ts +13 -0
- package/dist/src/parser/operations.js +127 -0
- package/dist/src/parser/operations.js.map +1 -0
- package/dist/src/parser/parameters.d.ts +5 -0
- package/dist/src/parser/parameters.js +98 -0
- package/dist/src/parser/parameters.js.map +1 -0
- package/dist/src/parser/responses.d.ts +13 -0
- package/dist/src/parser/responses.js +132 -0
- package/dist/src/parser/responses.js.map +1 -0
- package/dist/src/parser/routes.d.ts +4 -0
- package/dist/src/parser/routes.js +36 -0
- package/dist/src/parser/routes.js.map +1 -0
- package/dist/src/parser/types.d.ts +9 -0
- package/dist/src/parser/types.js +157 -0
- package/dist/src/parser/types.js.map +1 -0
- package/dist/test/etag_cache.test.d.ts +1 -0
- package/dist/test/etag_cache.test.js +62 -0
- package/dist/test/etag_cache.test.js.map +1 -0
- package/dist/test/test-host.d.ts +11 -0
- package/dist/test/test-host.js +28 -0
- package/dist/test/test-host.js.map +1 -1
- package/example/main.tsp +27 -1
- package/example/output-rust/Cargo.lock +48 -0
- package/example/output-rust/Cargo.toml +1 -0
- package/example/output-rust/src/generated/server.rs +122 -11
- package/example/output-rust/src/generated/types.rs +6 -0
- package/example/output-rust/src/main.rs +60 -27
- package/justfile +31 -2
- package/package.json +1 -1
- package/scripts/update-golden.js +36 -0
- package/src/decorators/cache_control.ts +14 -0
- package/src/decorators/etag_cache.ts +14 -0
- package/src/decorators/index.ts +6 -0
- package/src/decorators/rust_attr.ts +61 -0
- package/src/decorators/rust_derive.ts +55 -0
- package/src/decorators/rust_impl.ts +29 -0
- package/src/decorators/rust_self.ts +42 -0
- package/src/emitter.ts +18 -1654
- package/src/formatter/index.ts +2 -0
- package/src/formatter/mappings.ts +70 -0
- package/src/formatter/strings.ts +33 -0
- package/src/generator/etag_router.ts +147 -0
- package/src/generator/index.ts +5 -0
- package/src/generator/response_enums.ts +76 -0
- package/src/generator/router.ts +280 -0
- package/src/generator/server_trait.ts +134 -0
- package/src/generator/types_file.ts +297 -0
- package/src/index.ts +3 -1
- package/src/lib.ts +1 -1
- package/src/lib.tsp +3 -1
- package/src/models/index.ts +2 -0
- package/src/models/keys.ts +7 -0
- package/src/models/types.ts +54 -0
- package/src/parser/decorators.ts +34 -0
- package/src/parser/index.ts +6 -0
- package/src/parser/operations.ts +158 -0
- package/src/parser/parameters.ts +117 -0
- package/src/parser/responses.ts +170 -0
- package/src/parser/routes.ts +47 -0
- package/src/parser/types.ts +215 -0
- package/test/etag_cache.test.ts +69 -0
- package/test/golden/etag_cache/server.rs +109 -0
- package/test/golden/etag_cache/spec.tsp +20 -0
- package/test/golden/etag_cache/types.rs +13 -0
- package/test/test-host.ts +43 -0
|
@@ -2,20 +2,43 @@ mod generated;
|
|
|
2
2
|
|
|
3
3
|
use async_trait::async_trait;
|
|
4
4
|
use axum::Json;
|
|
5
|
+
use axum::extract::FromRef;
|
|
5
6
|
use axum::response::IntoResponse;
|
|
6
7
|
use axum::response::sse::{Event, KeepAlive, Sse};
|
|
7
8
|
use generated::server::{
|
|
8
|
-
ConsumingConsumeAndDeleteResponse, ConsumingUploadResponse,
|
|
9
|
-
|
|
10
|
-
Server,
|
|
9
|
+
ArticlesGetArticleResponse, ConsumingConsumeAndDeleteResponse, ConsumingUploadResponse,
|
|
10
|
+
EtagCache, EventsAccountsEventsResponse, ItemsCreateItemResponse, ItemsGetItemResponse,
|
|
11
|
+
ItemsListResponse, ItemsUpdateItemResponse, PetsListResponse, Server,
|
|
11
12
|
};
|
|
13
|
+
use std::collections::HashMap;
|
|
12
14
|
use std::convert::Infallible;
|
|
15
|
+
use std::sync::{Arc, Mutex};
|
|
13
16
|
use std::time::Duration;
|
|
14
17
|
use tokio::time::interval;
|
|
15
|
-
use tokio_stream::{StreamExt as _
|
|
18
|
+
use tokio_stream::{wrappers::IntervalStream, StreamExt as _};
|
|
19
|
+
|
|
20
|
+
#[derive(Clone, Default)]
|
|
21
|
+
struct InMemoryCache(Arc<Mutex<HashMap<String, String>>>);
|
|
22
|
+
|
|
23
|
+
impl EtagCache for InMemoryCache {
|
|
24
|
+
fn get(&self, key: &str) -> Option<String> {
|
|
25
|
+
self.0.lock().unwrap().get(key).cloned()
|
|
26
|
+
}
|
|
27
|
+
fn set(&self, key: &str, etag: &str) {
|
|
28
|
+
self.0.lock().unwrap().insert(key.to_owned(), etag.to_owned());
|
|
29
|
+
}
|
|
30
|
+
}
|
|
16
31
|
|
|
17
32
|
#[derive(Clone)]
|
|
18
|
-
struct AppState
|
|
33
|
+
struct AppState {
|
|
34
|
+
cache: InMemoryCache,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl FromRef<AppState> for InMemoryCache {
|
|
38
|
+
fn from_ref(state: &AppState) -> Self {
|
|
39
|
+
state.cache.clone()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
#[async_trait]
|
|
21
44
|
impl Server for AppState {
|
|
@@ -44,6 +67,13 @@ impl Server for AppState {
|
|
|
44
67
|
])))
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
async fn items_list(&self) -> eyre::Result<ItemsListResponse> {
|
|
71
|
+
Ok(ItemsListResponse::Ok(Json(vec![generated::types::Item {
|
|
72
|
+
name: "list-item".to_string(),
|
|
73
|
+
value: 123,
|
|
74
|
+
}])))
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
async fn items_get_item(&self, _id: String) -> eyre::Result<ItemsGetItemResponse> {
|
|
48
78
|
Ok(ItemsGetItemResponse::Ok(Json(generated::types::Item {
|
|
49
79
|
name: "test".to_string(),
|
|
@@ -72,6 +102,21 @@ impl Server for AppState {
|
|
|
72
102
|
})))
|
|
73
103
|
}
|
|
74
104
|
|
|
105
|
+
async fn articles_get_article<C: EtagCache + Send + Sync>(
|
|
106
|
+
&self,
|
|
107
|
+
cache: &C,
|
|
108
|
+
id: String,
|
|
109
|
+
) -> eyre::Result<ArticlesGetArticleResponse> {
|
|
110
|
+
let etag = format!("\"article-etag-{}\"", id);
|
|
111
|
+
cache.set(&format!("articles_get_article:{}", id), &etag);
|
|
112
|
+
Ok(ArticlesGetArticleResponse::Ok(Json(
|
|
113
|
+
generated::types::Article {
|
|
114
|
+
id,
|
|
115
|
+
title: "Example Article".to_string(),
|
|
116
|
+
},
|
|
117
|
+
)))
|
|
118
|
+
}
|
|
119
|
+
|
|
75
120
|
async fn consuming_consume_and_delete(
|
|
76
121
|
self,
|
|
77
122
|
_id: String,
|
|
@@ -90,7 +135,10 @@ impl Server for AppState {
|
|
|
90
135
|
|
|
91
136
|
#[tokio::main]
|
|
92
137
|
async fn main() {
|
|
93
|
-
let
|
|
138
|
+
let state = AppState {
|
|
139
|
+
cache: InMemoryCache::default(),
|
|
140
|
+
};
|
|
141
|
+
let app = generated::server::create_router(state.clone(), state.cache.clone(), |r| r);
|
|
94
142
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
95
143
|
println!("Server running on {}", listener.local_addr().unwrap());
|
|
96
144
|
axum::serve(listener, app).await.unwrap();
|
|
@@ -105,26 +153,24 @@ mod tests {
|
|
|
105
153
|
|
|
106
154
|
#[tokio::test]
|
|
107
155
|
async fn test_sse_endpoint() {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let app = generated::server::create_router(
|
|
156
|
+
let state = AppState {
|
|
157
|
+
cache: InMemoryCache::default(),
|
|
158
|
+
};
|
|
159
|
+
let app = generated::server::create_router(state.clone(), state.cache.clone(), |r| r);
|
|
112
160
|
|
|
113
161
|
let req = Request::builder()
|
|
114
|
-
.uri("/events/123")
|
|
115
|
-
// Wait, actually I should check if axum matches {accountId} or if it fails.
|
|
162
|
+
.uri("/events/123")
|
|
116
163
|
.body(axum::body::Body::empty())
|
|
117
164
|
.unwrap();
|
|
118
165
|
|
|
119
166
|
let response = app.oneshot(req).await.unwrap();
|
|
120
167
|
|
|
121
|
-
// If axum doesn't match, we will get 404. Let's handle both.
|
|
122
168
|
if response.status() == 404 {
|
|
123
169
|
let req2 = Request::builder()
|
|
124
170
|
.uri("/events/{accountId}")
|
|
125
171
|
.body(axum::body::Body::empty())
|
|
126
172
|
.unwrap();
|
|
127
|
-
let app = generated::server::create_router(
|
|
173
|
+
let app = generated::server::create_router(state.clone(), state.cache.clone(), |r| r);
|
|
128
174
|
let response2 = app.oneshot(req2).await.unwrap();
|
|
129
175
|
assert_eq!(response2.status(), 200);
|
|
130
176
|
return;
|
|
@@ -136,7 +182,6 @@ mod tests {
|
|
|
136
182
|
"text/event-stream"
|
|
137
183
|
);
|
|
138
184
|
|
|
139
|
-
// Read body frames sequentially
|
|
140
185
|
let mut body = response.into_body();
|
|
141
186
|
let mut timestamps = Vec::new();
|
|
142
187
|
|
|
@@ -149,18 +194,6 @@ mod tests {
|
|
|
149
194
|
}
|
|
150
195
|
}
|
|
151
196
|
|
|
152
|
-
// We should have received 3 events
|
|
153
197
|
assert_eq!(timestamps.len(), 3);
|
|
154
|
-
|
|
155
|
-
// Verify delay between events (approx 100ms)
|
|
156
|
-
for i in 1..timestamps.len() {
|
|
157
|
-
let diff = timestamps[i].duration_since(timestamps[i - 1]);
|
|
158
|
-
// Give it some slack for CI / slow execution, but ensure it's not instantaneous
|
|
159
|
-
assert!(
|
|
160
|
-
diff.as_millis() >= 80,
|
|
161
|
-
"Events were too fast! Diff: {}ms",
|
|
162
|
-
diff.as_millis()
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
198
|
}
|
|
166
199
|
}
|
package/justfile
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
set dotenv-load := true
|
|
2
2
|
|
|
3
|
+
# ─── Build & Test ────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
# Build TypeScript and run all Jest/node:test tests.
|
|
6
|
+
test:
|
|
7
|
+
npm run build && npm test
|
|
8
|
+
|
|
9
|
+
# Lint and format source.
|
|
10
|
+
lint:
|
|
11
|
+
npm run lint && npm run format:check
|
|
12
|
+
|
|
13
|
+
# ─── Rust Validation ─────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
# Re-compile the example TypeSpec spec and validate the generated Rust code.
|
|
3
16
|
check-rust:
|
|
4
17
|
cd example && tsp compile . && cd output-rust && cargo check && cargo clippy
|
|
5
18
|
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
# ─── Combined ────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
# Run everything: TypeScript tests + Rust check. Use this before a release.
|
|
22
|
+
check-all: test check-rust
|
|
23
|
+
@echo "✓ All checks passed."
|
|
24
|
+
|
|
25
|
+
# ─── Golden File Helpers ─────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
# Re-generate and overwrite the golden file for a given feature.
|
|
28
|
+
# Usage: just update-golden etag_cache
|
|
29
|
+
update-golden feature:
|
|
30
|
+
node scripts/update-golden.js {{feature}}
|
|
31
|
+
|
|
32
|
+
# ─── Release ─────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
# Publish to npm. Run check-all first.
|
|
35
|
+
publish: check-all
|
|
36
|
+
npm publish
|
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { emit } from '../dist/test/test-host.js';
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const feature = process.argv[2];
|
|
7
|
+
if (!feature) {
|
|
8
|
+
console.error('Usage: node scripts/update-golden.js <feature>');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const specPath = resolve(process.cwd(), `test/golden/${feature}/spec.tsp`);
|
|
13
|
+
const spec = readFileSync(specPath, 'utf8');
|
|
14
|
+
|
|
15
|
+
console.log(`Emitting spec for ${feature}...`);
|
|
16
|
+
const out = await emit(spec);
|
|
17
|
+
|
|
18
|
+
if (out['types.rs']) {
|
|
19
|
+
const p = resolve(process.cwd(), `test/golden/${feature}/types.rs`);
|
|
20
|
+
writeFileSync(p, out['types.rs']);
|
|
21
|
+
console.log(`Updated types.rs`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (out['server.rs']) {
|
|
25
|
+
const p = resolve(process.cwd(), `test/golden/${feature}/server.rs`);
|
|
26
|
+
writeFileSync(p, out['server.rs']);
|
|
27
|
+
console.log(`Updated server.rs`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`Golden files updated for: ${feature}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main().catch(err => {
|
|
34
|
+
console.error(err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DecoratorContext, Operation } from "@typespec/compiler";
|
|
2
|
+
import { cacheControlKey } from "../models/keys.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @cacheControl decorator implementation.
|
|
6
|
+
* Stores the cache control value on the operation.
|
|
7
|
+
*/
|
|
8
|
+
export function $cacheControl(
|
|
9
|
+
context: DecoratorContext,
|
|
10
|
+
target: Operation,
|
|
11
|
+
value: string,
|
|
12
|
+
) {
|
|
13
|
+
context.program.stateMap(cacheControlKey).set(target, value);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DecoratorContext, Operation } from "@typespec/compiler";
|
|
2
|
+
import { etagCacheKey } from "../models/keys.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @etagCache decorator implementation.
|
|
6
|
+
* Stores the optional etagKey on the operation.
|
|
7
|
+
*/
|
|
8
|
+
export function $etagCache(
|
|
9
|
+
context: DecoratorContext,
|
|
10
|
+
target: Operation,
|
|
11
|
+
etagKey?: string,
|
|
12
|
+
) {
|
|
13
|
+
context.program.stateMap(etagCacheKey).set(target, etagKey ?? true);
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DecoratorContext,
|
|
3
|
+
getNamespaceFullName,
|
|
4
|
+
Namespace,
|
|
5
|
+
Type,
|
|
6
|
+
} from "@typespec/compiler";
|
|
7
|
+
import { rustAttrKey } from "../models/keys.js";
|
|
8
|
+
import { RustAttrInfo } from "../models/types.js";
|
|
9
|
+
|
|
10
|
+
export function $rustAttr(
|
|
11
|
+
context: DecoratorContext,
|
|
12
|
+
target: Type,
|
|
13
|
+
attr: string,
|
|
14
|
+
) {
|
|
15
|
+
if (
|
|
16
|
+
target.kind !== "Model" &&
|
|
17
|
+
target.kind !== "Enum" &&
|
|
18
|
+
target.kind !== "ModelProperty"
|
|
19
|
+
) {
|
|
20
|
+
context.program.reportDiagnostic({
|
|
21
|
+
code: "rust-attr-invalid-target",
|
|
22
|
+
message: `@rustAttr can only be applied to models, enums, and model properties`,
|
|
23
|
+
severity: "error",
|
|
24
|
+
target: context.decoratorTarget,
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let ns: Namespace | undefined;
|
|
30
|
+
if (target.kind === "Model") {
|
|
31
|
+
ns = target.namespace;
|
|
32
|
+
} else if (target.kind === "Enum") {
|
|
33
|
+
ns = target.namespace;
|
|
34
|
+
} else if (target.kind === "ModelProperty") {
|
|
35
|
+
ns = target.model?.namespace;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const nsFullName = ns ? getNamespaceFullName(ns) : "";
|
|
39
|
+
if (!nsFullName.startsWith("TypeSpec")) {
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
const info = (target as any)[rustAttrKey] as RustAttrInfo | undefined;
|
|
42
|
+
if (info) {
|
|
43
|
+
if (!info.attrs.includes(attr)) {
|
|
44
|
+
info.attrs.push(attr);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
(target as any)[rustAttrKey] = { attrs: [attr] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function $rustAttrs(
|
|
54
|
+
context: DecoratorContext,
|
|
55
|
+
target: Type,
|
|
56
|
+
...attrs: string[]
|
|
57
|
+
) {
|
|
58
|
+
for (const attr of attrs) {
|
|
59
|
+
$rustAttr(context, target, attr);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DecoratorContext,
|
|
3
|
+
getNamespaceFullName,
|
|
4
|
+
Type,
|
|
5
|
+
} from "@typespec/compiler";
|
|
6
|
+
import { rustDeriveKey } from "../models/keys.js";
|
|
7
|
+
import { RustDeriveInfo } from "../models/types.js";
|
|
8
|
+
|
|
9
|
+
export function $rustDerive(
|
|
10
|
+
context: DecoratorContext,
|
|
11
|
+
target: Type,
|
|
12
|
+
derive: string,
|
|
13
|
+
) {
|
|
14
|
+
if (target.kind !== "Model" && target.kind !== "Enum") {
|
|
15
|
+
context.program.reportDiagnostic({
|
|
16
|
+
code: "rust-derive-invalid-target",
|
|
17
|
+
message: `@rustDerive can only be applied to models and enums`,
|
|
18
|
+
severity: "error",
|
|
19
|
+
target: context.decoratorTarget,
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ns =
|
|
25
|
+
target.kind === "Model"
|
|
26
|
+
? target.namespace
|
|
27
|
+
? getNamespaceFullName(target.namespace)
|
|
28
|
+
: ""
|
|
29
|
+
: target.namespace
|
|
30
|
+
? getNamespaceFullName(target.namespace)
|
|
31
|
+
: "";
|
|
32
|
+
|
|
33
|
+
if (!ns.startsWith("TypeSpec")) {
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
const info = (target as any)[rustDeriveKey] as RustDeriveInfo | undefined;
|
|
36
|
+
if (info) {
|
|
37
|
+
if (!info.derives.includes(derive)) {
|
|
38
|
+
info.derives.push(derive);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
(target as any)[rustDeriveKey] = { derives: [derive] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function $rustDerives(
|
|
48
|
+
context: DecoratorContext,
|
|
49
|
+
target: Type,
|
|
50
|
+
...derives: string[]
|
|
51
|
+
) {
|
|
52
|
+
for (const derive of derives) {
|
|
53
|
+
$rustDerive(context, target, derive);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DecoratorContext,
|
|
3
|
+
getNamespaceFullName,
|
|
4
|
+
Type,
|
|
5
|
+
} from "@typespec/compiler";
|
|
6
|
+
import { rustImplKey } from "../models/keys.js";
|
|
7
|
+
|
|
8
|
+
export function $rustImpl(
|
|
9
|
+
context: DecoratorContext,
|
|
10
|
+
target: Type,
|
|
11
|
+
impl: string,
|
|
12
|
+
) {
|
|
13
|
+
if (target.kind !== "Model") {
|
|
14
|
+
context.program.reportDiagnostic({
|
|
15
|
+
code: "rust-impl-invalid-target",
|
|
16
|
+
message: `@rustImpl can only be applied to models`,
|
|
17
|
+
severity: "error",
|
|
18
|
+
target: context.decoratorTarget,
|
|
19
|
+
});
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ns = target.namespace ? getNamespaceFullName(target.namespace) : "";
|
|
24
|
+
|
|
25
|
+
if (!ns.startsWith("TypeSpec")) {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
(target as any)[rustImplKey] = { impl: impl };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DecoratorContext,
|
|
3
|
+
getNamespaceFullName,
|
|
4
|
+
Type,
|
|
5
|
+
} from "@typespec/compiler";
|
|
6
|
+
import { rustSelfReceiverKey } from "../models/keys.js";
|
|
7
|
+
|
|
8
|
+
export function $rustMut(context: DecoratorContext, target: Type) {
|
|
9
|
+
if (target.kind !== "Operation") {
|
|
10
|
+
context.program.reportDiagnostic({
|
|
11
|
+
code: "rust-mut-invalid-target",
|
|
12
|
+
message: `@rustMut can only be applied to operations`,
|
|
13
|
+
severity: "error",
|
|
14
|
+
target: context.decoratorTarget,
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ns = target.namespace ? getNamespaceFullName(target.namespace) : "";
|
|
20
|
+
if (!ns.startsWith("TypeSpec")) {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
(target as any)[rustSelfReceiverKey] = "&mut self";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function $rustOwn(context: DecoratorContext, target: Type) {
|
|
27
|
+
if (target.kind !== "Operation") {
|
|
28
|
+
context.program.reportDiagnostic({
|
|
29
|
+
code: "rust-own-invalid-target",
|
|
30
|
+
message: `@rustOwn can only be applied to operations`,
|
|
31
|
+
severity: "error",
|
|
32
|
+
target: context.decoratorTarget,
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ns = target.namespace ? getNamespaceFullName(target.namespace) : "";
|
|
38
|
+
if (!ns.startsWith("TypeSpec")) {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
(target as any)[rustSelfReceiverKey] = "self";
|
|
41
|
+
}
|
|
42
|
+
}
|