typespec-rust-emitter 0.11.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 +83 -79
- package/CHANGELOG.md +60 -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 -1252
- 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/hello.test.js +23 -0
- package/dist/test/hello.test.js.map +1 -1
- 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 +48 -1
- package/example/output-rust/Cargo.lock +75 -0
- package/example/output-rust/Cargo.toml +2 -1
- package/example/output-rust/src/generated/server.rs +163 -12
- package/example/output-rust/src/generated/types.rs +8 -0
- package/example/output-rust/src/main.rs +75 -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 -1623
- 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/hello.test.ts +24 -0
- package/test/test-host.ts +43 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Operation, Program, Namespace } from "@typespec/compiler";
|
|
2
|
+
import { AnonymousStringLiteralUnion } from "../models/types.js";
|
|
3
|
+
import { formatDoc, toPascalCase, toRustIdent } from "../formatter/strings.js";
|
|
4
|
+
import {
|
|
5
|
+
emitOperationInfo,
|
|
6
|
+
hasAuthDecorator,
|
|
7
|
+
getSelfReceiver,
|
|
8
|
+
hasEtagCache,
|
|
9
|
+
} from "../parser/operations.js";
|
|
10
|
+
import { getRustTypeForProperty } from "../parser/types.js";
|
|
11
|
+
import { hasMultipartBody } from "../parser/parameters.js";
|
|
12
|
+
import { generateEtagCacheTrait } from "./etag_router.js";
|
|
13
|
+
|
|
14
|
+
export function generateServerTrait(
|
|
15
|
+
program: Program,
|
|
16
|
+
namespaceGroups: { namespace: Namespace; operations: Operation[] }[],
|
|
17
|
+
anonymousEnums: Map<string, AnonymousStringLiteralUnion>,
|
|
18
|
+
): string {
|
|
19
|
+
const parts: string[] = [];
|
|
20
|
+
|
|
21
|
+
parts.push(`use super::types::*;
|
|
22
|
+
use async_trait::async_trait;
|
|
23
|
+
use axum::extract::Path;
|
|
24
|
+
use axum::Extension;
|
|
25
|
+
use axum::http::StatusCode;
|
|
26
|
+
use axum::response::IntoResponse;
|
|
27
|
+
use axum::Json;
|
|
28
|
+
use eyre::Result;
|
|
29
|
+
|
|
30
|
+
`);
|
|
31
|
+
|
|
32
|
+
if (namespaceGroups.some((g) => g.operations.some((o) => hasEtagCache(program, o)))) {
|
|
33
|
+
parts.push(generateEtagCacheTrait());
|
|
34
|
+
parts.push("\n");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
parts.push(`#[async_trait]
|
|
38
|
+
pub trait Server: Send + Sync {
|
|
39
|
+
type Claims: Send + Sync + 'static;
|
|
40
|
+
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
for (const group of namespaceGroups) {
|
|
44
|
+
const nsName = toPascalCase(
|
|
45
|
+
group.namespace.name.replace(/[^a-zA-Z0-9_]/g, "_"),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
for (const op of group.operations) {
|
|
49
|
+
const opInfo = emitOperationInfo(program, op, "", anonymousEnums);
|
|
50
|
+
if (!opInfo) continue;
|
|
51
|
+
|
|
52
|
+
if (opInfo.doc) {
|
|
53
|
+
parts.push(` ${formatDoc(opInfo.doc)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const responseName = `${nsName}${toPascalCase(opInfo.name)}Response`;
|
|
57
|
+
const fnName = toRustIdent(`${nsName}_${opInfo.name}`);
|
|
58
|
+
const isProtected = hasAuthDecorator(op);
|
|
59
|
+
|
|
60
|
+
// Build parameter list for the trait method
|
|
61
|
+
const paramParts: string[] = [];
|
|
62
|
+
|
|
63
|
+
// Add path parameters
|
|
64
|
+
for (const param of opInfo.parameters) {
|
|
65
|
+
if (param.location === "path") {
|
|
66
|
+
paramParts.push(`${param.rustName}: ${param.rustType}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Add query parameters
|
|
71
|
+
for (const param of opInfo.parameters) {
|
|
72
|
+
if (param.location === "query") {
|
|
73
|
+
paramParts.push(`${param.rustName}: ${param.rustType}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add body parameter
|
|
78
|
+
if (opInfo.body) {
|
|
79
|
+
if (hasMultipartBody(op)) {
|
|
80
|
+
paramParts.push(`body: Multipart`);
|
|
81
|
+
} else {
|
|
82
|
+
const bodyType = getRustTypeForProperty(
|
|
83
|
+
opInfo.body.type,
|
|
84
|
+
program,
|
|
85
|
+
anonymousEnums,
|
|
86
|
+
);
|
|
87
|
+
paramParts.push(`body: ${bodyType.type}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const paramsStr = paramParts.join(", ");
|
|
92
|
+
const selfReceiver = getSelfReceiver(op);
|
|
93
|
+
|
|
94
|
+
if (hasEtagCache(program, op)) {
|
|
95
|
+
const paramList = paramsStr ? `cache: &C, ${paramsStr}` : `cache: &C`;
|
|
96
|
+
if (isProtected) {
|
|
97
|
+
parts.push(
|
|
98
|
+
` async fn ${fnName}<C: EtagCache + Send + Sync>(\n ${selfReceiver}, claims: Self::Claims, ${paramList}\n ) -> Result<${responseName}>;`,
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
parts.push(
|
|
102
|
+
` async fn ${fnName}<C: EtagCache + Send + Sync>(\n ${selfReceiver}, ${paramList}\n ) -> Result<${responseName}>;`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
if (isProtected) {
|
|
107
|
+
if (paramsStr) {
|
|
108
|
+
parts.push(
|
|
109
|
+
` async fn ${fnName}(${selfReceiver}, claims: Self::Claims, ${paramsStr}) -> Result<${responseName}>;`,
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
parts.push(
|
|
113
|
+
` async fn ${fnName}(${selfReceiver}, claims: Self::Claims) -> Result<${responseName}>;`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
if (paramsStr) {
|
|
118
|
+
parts.push(
|
|
119
|
+
` async fn ${fnName}(${selfReceiver}, ${paramsStr}) -> Result<${responseName}>;`,
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
parts.push(
|
|
123
|
+
` async fn ${fnName}(${selfReceiver}) -> Result<${responseName}>;`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
parts.push("}");
|
|
132
|
+
|
|
133
|
+
return parts.join("\n");
|
|
134
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Enum,
|
|
3
|
+
getDoc,
|
|
4
|
+
getFormat,
|
|
5
|
+
getPattern,
|
|
6
|
+
Model,
|
|
7
|
+
ModelProperty,
|
|
8
|
+
Program,
|
|
9
|
+
Scalar,
|
|
10
|
+
StringLiteral,
|
|
11
|
+
Union,
|
|
12
|
+
} from "@typespec/compiler";
|
|
13
|
+
import {
|
|
14
|
+
AnonymousStringLiteralUnion,
|
|
15
|
+
RustAttrInfo,
|
|
16
|
+
RustDeriveInfo,
|
|
17
|
+
RustImplInfo,
|
|
18
|
+
} from "../models/types.js";
|
|
19
|
+
import { rustAttrKey, rustDeriveKey, rustImplKey } from "../models/keys.js";
|
|
20
|
+
import { formatToRust, scalarToRust } from "../formatter/mappings.js";
|
|
21
|
+
import {
|
|
22
|
+
formatDoc,
|
|
23
|
+
toPascalCase,
|
|
24
|
+
toRustIdent,
|
|
25
|
+
toRustVariantName,
|
|
26
|
+
} from "../formatter/strings.js";
|
|
27
|
+
import { getRustTypeForProperty } from "../parser/types.js";
|
|
28
|
+
|
|
29
|
+
export function getAllProperties(
|
|
30
|
+
model: Model,
|
|
31
|
+
_program: Program,
|
|
32
|
+
): Map<string, ModelProperty> {
|
|
33
|
+
const props = new Map<string, ModelProperty>();
|
|
34
|
+
|
|
35
|
+
if (model.baseModel) {
|
|
36
|
+
const baseProps = getAllProperties(model.baseModel, _program);
|
|
37
|
+
for (const [key, value] of baseProps) {
|
|
38
|
+
props.set(key, value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [key, value] of model.properties) {
|
|
43
|
+
props.set(key, value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return props;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function emitModel(
|
|
50
|
+
model: Model,
|
|
51
|
+
program: Program,
|
|
52
|
+
anonymousEnums: Map<string, AnonymousStringLiteralUnion>,
|
|
53
|
+
): string {
|
|
54
|
+
const parts: string[] = [];
|
|
55
|
+
const name = toPascalCase(model.name);
|
|
56
|
+
const allProps = getAllProperties(model, program);
|
|
57
|
+
|
|
58
|
+
const deriveAttrs = [
|
|
59
|
+
"Debug",
|
|
60
|
+
"Clone",
|
|
61
|
+
"serde::Serialize",
|
|
62
|
+
"serde::Deserialize",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
const customDerives = (model as any)[rustDeriveKey] as
|
|
67
|
+
| RustDeriveInfo
|
|
68
|
+
| undefined;
|
|
69
|
+
if (customDerives) {
|
|
70
|
+
deriveAttrs.push(...customDerives.derives);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
parts.push(`#[derive(${deriveAttrs.join(", ")})]`);
|
|
74
|
+
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
+
const customAttrs = (model as any)[rustAttrKey] as RustAttrInfo | undefined;
|
|
77
|
+
if (customAttrs) {
|
|
78
|
+
for (const attr of customAttrs.attrs) {
|
|
79
|
+
parts.push(`#[${attr}]`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (allProps.size > 0) {
|
|
84
|
+
const fields: string[] = [];
|
|
85
|
+
for (const [propName, prop] of allProps) {
|
|
86
|
+
const doc = getDoc(program, prop);
|
|
87
|
+
const { type: rustType } = getRustTypeForProperty(
|
|
88
|
+
prop.type,
|
|
89
|
+
program,
|
|
90
|
+
anonymousEnums,
|
|
91
|
+
);
|
|
92
|
+
const optional = prop.optional ? true : false;
|
|
93
|
+
const fieldName = toRustIdent(propName);
|
|
94
|
+
const serdeRename =
|
|
95
|
+
propName !== fieldName ? `#[serde(rename = "${propName}")]` : "";
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
const propAttrs = (prop as any)[rustAttrKey] as RustAttrInfo | undefined;
|
|
99
|
+
|
|
100
|
+
if (doc) {
|
|
101
|
+
fields.push(` ${formatDoc(doc)}`);
|
|
102
|
+
}
|
|
103
|
+
if (propAttrs) {
|
|
104
|
+
for (const attr of propAttrs.attrs) {
|
|
105
|
+
fields.push(` #[${attr}]`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (serdeRename) {
|
|
109
|
+
fields.push(` ${serdeRename}`);
|
|
110
|
+
}
|
|
111
|
+
if (optional) {
|
|
112
|
+
fields.push(` #[serde(skip_serializing_if = "Option::is_none")]`);
|
|
113
|
+
}
|
|
114
|
+
fields.push(
|
|
115
|
+
` pub ${fieldName}: ${optional ? `Option<${rustType}>` : rustType},`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
parts.push(`pub struct ${name} {
|
|
119
|
+
${fields.join("\n")}
|
|
120
|
+
}`);
|
|
121
|
+
} else {
|
|
122
|
+
parts.push(`pub struct ${name}`);
|
|
123
|
+
parts.push("(());");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
127
|
+
const customImpl = (model as any)[rustImplKey] as RustImplInfo | undefined;
|
|
128
|
+
if (customImpl) {
|
|
129
|
+
parts.push(`
|
|
130
|
+
${customImpl.impl}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return parts.join("\n");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function emitEnum(enumType: Enum): string {
|
|
137
|
+
const parts: string[] = [];
|
|
138
|
+
const name = toPascalCase(enumType.name);
|
|
139
|
+
const members = Array.from(enumType.members.values());
|
|
140
|
+
const isString = members.every(
|
|
141
|
+
(m) => m.value === undefined || typeof m.value === "string",
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const baseDerives = [
|
|
145
|
+
"Debug",
|
|
146
|
+
"Clone",
|
|
147
|
+
"Copy",
|
|
148
|
+
"PartialEq",
|
|
149
|
+
"Eq",
|
|
150
|
+
"Hash",
|
|
151
|
+
"Default",
|
|
152
|
+
"serde::Serialize",
|
|
153
|
+
"serde::Deserialize",
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
+
const customDerives = (enumType as any)[rustDeriveKey] as
|
|
158
|
+
| RustDeriveInfo
|
|
159
|
+
| undefined;
|
|
160
|
+
const allDerives = [...baseDerives];
|
|
161
|
+
if (customDerives) {
|
|
162
|
+
allDerives.push(...customDerives.derives);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
+
const customAttrs = (enumType as any)[rustAttrKey] as
|
|
167
|
+
| RustAttrInfo
|
|
168
|
+
| undefined;
|
|
169
|
+
const attrLines: string[] = [];
|
|
170
|
+
if (customAttrs) {
|
|
171
|
+
for (const attr of customAttrs.attrs) {
|
|
172
|
+
attrLines.push(`#[${attr}]`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (isString) {
|
|
177
|
+
parts.push(`#[derive(${allDerives.join(", ")})]`);
|
|
178
|
+
if (attrLines.length > 0) {
|
|
179
|
+
parts.push(...attrLines);
|
|
180
|
+
}
|
|
181
|
+
parts.push(`pub enum ${name} {`);
|
|
182
|
+
for (let i = 0; i < members.length; i++) {
|
|
183
|
+
const variantName = toRustVariantName(members[i].name);
|
|
184
|
+
const serdeValue = members[i].value ?? members[i].name;
|
|
185
|
+
if (i === 0) {
|
|
186
|
+
parts.push(` #[default]`);
|
|
187
|
+
}
|
|
188
|
+
parts.push(` #[serde(rename = "${serdeValue}")]`);
|
|
189
|
+
parts.push(` ${variantName},`);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
parts.push(`#[derive(${allDerives.join(", ")})]`);
|
|
193
|
+
if (attrLines.length > 0) {
|
|
194
|
+
parts.push(...attrLines);
|
|
195
|
+
}
|
|
196
|
+
parts.push(`pub enum ${name} {`);
|
|
197
|
+
for (let i = 0; i < members.length; i++) {
|
|
198
|
+
const variantName = toRustVariantName(members[i].name);
|
|
199
|
+
const enumValue = members[i].value ?? 0;
|
|
200
|
+
if (i === 0) {
|
|
201
|
+
parts.push(` #[default]`);
|
|
202
|
+
}
|
|
203
|
+
parts.push(` ${variantName} = ${enumValue},`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
parts.push("}");
|
|
207
|
+
return parts.join("\n");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function emitUnion(union: Union, program: Program): string {
|
|
211
|
+
const parts: string[] = [];
|
|
212
|
+
const name = toPascalCase(union.name ?? "Union");
|
|
213
|
+
|
|
214
|
+
const variants = Array.from(union.variants.values());
|
|
215
|
+
if (variants.length === 0) {
|
|
216
|
+
parts.push(
|
|
217
|
+
`#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub enum ${name} {}\nimpl ${name} {\n pub fn new() -> Self { unreachable!() }\n}`,
|
|
218
|
+
);
|
|
219
|
+
return parts.join("\n");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
parts.push(
|
|
223
|
+
`#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n#[serde(untagged)]\npub enum ${name} {`,
|
|
224
|
+
);
|
|
225
|
+
for (let i = 0; i < variants.length; i++) {
|
|
226
|
+
const variant = variants[i];
|
|
227
|
+
const variantName = `Variant${i + 1}`;
|
|
228
|
+
const { type: rustType } = getRustTypeForProperty(
|
|
229
|
+
variant.type,
|
|
230
|
+
program,
|
|
231
|
+
new Map(),
|
|
232
|
+
);
|
|
233
|
+
parts.push(` ${variantName}(${rustType}),`);
|
|
234
|
+
}
|
|
235
|
+
parts.push("}");
|
|
236
|
+
|
|
237
|
+
return parts.join("\n");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function emitScalar(
|
|
241
|
+
scalar: Scalar,
|
|
242
|
+
program: Program,
|
|
243
|
+
): { typeDef: string; impls: string[] } {
|
|
244
|
+
const format = getFormat(program, scalar);
|
|
245
|
+
const pattern = getPattern(program, scalar);
|
|
246
|
+
const structName = toPascalCase(scalar.name);
|
|
247
|
+
const impls: string[] = [];
|
|
248
|
+
|
|
249
|
+
if (format && formatToRust[format]) {
|
|
250
|
+
return {
|
|
251
|
+
typeDef: `pub type ${structName} = ${formatToRust[format]};`,
|
|
252
|
+
impls: [],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (pattern) {
|
|
257
|
+
const rustType = scalarToRust[scalar.name] ?? "String";
|
|
258
|
+
impls.push(
|
|
259
|
+
`\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}`,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
typeDef: `#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub struct ${structName}(pub ${rustType});`,
|
|
264
|
+
impls,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const rustType = scalarToRust[scalar.name] ?? "serde_json::Value";
|
|
269
|
+
return {
|
|
270
|
+
typeDef: `pub type ${structName} = ${rustType};`,
|
|
271
|
+
impls: [],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function emitStringLiteralUnion(union: Union): string {
|
|
276
|
+
const parts: string[] = [];
|
|
277
|
+
const name = toPascalCase(union.name ?? "Value");
|
|
278
|
+
const variants = Array.from(union.variants.values());
|
|
279
|
+
|
|
280
|
+
parts.push(
|
|
281
|
+
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\n#[allow(clippy::enum_variant_names)]\npub enum ${name} {`,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < variants.length; i++) {
|
|
285
|
+
const literalType = variants[i].type as StringLiteral;
|
|
286
|
+
const variantName = toPascalCase(literalType.value);
|
|
287
|
+
const serdeValue = literalType.value;
|
|
288
|
+
if (i === 0) {
|
|
289
|
+
parts.push(` #[default]`);
|
|
290
|
+
}
|
|
291
|
+
parts.push(` #[serde(rename = "${serdeValue}")]`);
|
|
292
|
+
parts.push(` ${variantName},`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
parts.push("}");
|
|
296
|
+
return parts.join("\n");
|
|
297
|
+
}
|
package/src/index.ts
CHANGED
package/src/lib.ts
CHANGED
package/src/lib.tsp
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../dist/src/
|
|
1
|
+
import "../dist/src/index.js";
|
|
2
2
|
|
|
3
3
|
using TypeSpec.Reflection;
|
|
4
4
|
|
|
@@ -9,3 +9,5 @@ extern dec rustAttrs(target: Model | Enum | ModelProperty, ...attrs: valueof str
|
|
|
9
9
|
extern dec rustImpl(target: Model, impl: valueof string);
|
|
10
10
|
extern dec rustMut(target: Operation);
|
|
11
11
|
extern dec rustOwn(target: Operation);
|
|
12
|
+
extern dec etagCache(target: Operation, etagKey?: valueof string);
|
|
13
|
+
extern dec cacheControl(target: Operation, value: valueof string);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// All Symbol keys that decorators store on TypeSpec Type objects.
|
|
2
|
+
export const rustDeriveKey = Symbol("rustDerive");
|
|
3
|
+
export const rustAttrKey = Symbol("rustAttr");
|
|
4
|
+
export const rustImplKey = Symbol("rustImpl");
|
|
5
|
+
export const rustSelfReceiverKey = Symbol("rustSelfReceiver");
|
|
6
|
+
export const etagCacheKey = Symbol("etagCache");
|
|
7
|
+
export const cacheControlKey = Symbol("cacheControl");
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ModelProperty, StringLiteral } from "@typespec/compiler";
|
|
2
|
+
|
|
3
|
+
export interface RustEmitterOptions {
|
|
4
|
+
moduleName?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type SelfReceiver = "&self" | "&mut self" | "self";
|
|
8
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
|
9
|
+
|
|
10
|
+
export interface RustDeriveInfo {
|
|
11
|
+
derives: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RustAttrInfo {
|
|
15
|
+
attrs: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RustImplInfo {
|
|
19
|
+
impl: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ParameterInfo {
|
|
23
|
+
name: string;
|
|
24
|
+
rustName: string;
|
|
25
|
+
rustType: string;
|
|
26
|
+
location: "path" | "query" | "header" | "cookie";
|
|
27
|
+
required: boolean;
|
|
28
|
+
optional?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ResponseInfo {
|
|
32
|
+
statusCode: number;
|
|
33
|
+
bodyType: string | undefined;
|
|
34
|
+
bodyDescription: string | undefined;
|
|
35
|
+
isSse?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OperationInfo {
|
|
39
|
+
name: string;
|
|
40
|
+
method: HttpMethod;
|
|
41
|
+
path: string;
|
|
42
|
+
tags: string[];
|
|
43
|
+
parameters: ParameterInfo[];
|
|
44
|
+
body: ModelProperty | undefined;
|
|
45
|
+
responses: ResponseInfo[];
|
|
46
|
+
doc: string | undefined;
|
|
47
|
+
etagKey?: string;
|
|
48
|
+
cacheControl?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AnonymousStringLiteralUnion {
|
|
52
|
+
enumName: string;
|
|
53
|
+
variants: StringLiteral[];
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function getDecoratorName(decorator: {
|
|
2
|
+
node?: { target?: { sv?: string; kind?: string } };
|
|
3
|
+
}): string {
|
|
4
|
+
if (!decorator) return "";
|
|
5
|
+
if (typeof decorator !== "object") return "";
|
|
6
|
+
|
|
7
|
+
if (decorator.node?.target?.sv) {
|
|
8
|
+
return decorator.node.target.sv;
|
|
9
|
+
}
|
|
10
|
+
if (
|
|
11
|
+
decorator.node?.target?.kind === "Identifier" &&
|
|
12
|
+
decorator.node?.target?.sv
|
|
13
|
+
) {
|
|
14
|
+
return decorator.node.target.sv;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getDecoratorArgValue(
|
|
21
|
+
decorator: {
|
|
22
|
+
args?: { jsValue?: unknown; value?: unknown }[];
|
|
23
|
+
arguments?: { jsValue?: unknown; value?: unknown }[];
|
|
24
|
+
},
|
|
25
|
+
index: number = 0,
|
|
26
|
+
): string {
|
|
27
|
+
if (!decorator) return "";
|
|
28
|
+
const args = decorator.args || decorator.arguments;
|
|
29
|
+
if (!args) return "";
|
|
30
|
+
const arg = args[index];
|
|
31
|
+
if (arg?.jsValue !== undefined) return String(arg.jsValue);
|
|
32
|
+
if (arg?.value !== undefined) return String(arg.value);
|
|
33
|
+
return "";
|
|
34
|
+
}
|