typespec-rust-emitter 0.10.3 → 0.10.6
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 +65 -0
- package/CHANGELOG.md +21 -0
- package/dist/src/emitter.js +32 -6
- package/dist/src/emitter.js.map +1 -1
- package/dist/test/hello.test.js +37 -0
- package/dist/test/hello.test.js.map +1 -1
- package/dist/test/test-host.js +6 -1
- package/dist/test/test-host.js.map +1 -1
- package/example/lib/learning/models.tsp +38 -0
- package/example/lib/learning/operations.tsp +14 -0
- package/example/output-rust/Cargo.lock +29 -0
- package/example/output-rust/Cargo.toml +1 -0
- package/example/output-rust/src/generated/server.rs +51 -0
- package/example/output-rust/src/generated/types.rs +86 -0
- package/example/package-lock.json +170 -127
- package/example/package.json +3 -1
- package/example/tspconfig.yaml +1 -1
- package/package.json +3 -1
- package/src/emitter.ts +38 -7
- package/test/hello.test.ts +43 -0
- package/test/test-host.ts +6 -1
package/src/emitter.ts
CHANGED
|
@@ -194,6 +194,7 @@ interface ResponseInfo {
|
|
|
194
194
|
statusCode: number;
|
|
195
195
|
bodyType: string | undefined;
|
|
196
196
|
bodyDescription: string | undefined;
|
|
197
|
+
isSse?: boolean;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
function getDecoratorName(decorator: {
|
|
@@ -403,10 +404,20 @@ function getOperationResponses(
|
|
|
403
404
|
statusCode,
|
|
404
405
|
bodyType: bodyInfo.type,
|
|
405
406
|
bodyDescription: bodyInfo.description,
|
|
407
|
+
isSse: bodyInfo.isSse,
|
|
406
408
|
});
|
|
407
409
|
}
|
|
408
410
|
} else if (returnType.kind === "Model") {
|
|
409
411
|
const model = returnType as Model;
|
|
412
|
+
if (model.name === "SSEStream") {
|
|
413
|
+
responses.push({
|
|
414
|
+
statusCode: 200,
|
|
415
|
+
bodyType: "axum::response::Response",
|
|
416
|
+
bodyDescription: "Server-Sent Events stream",
|
|
417
|
+
isSse: true,
|
|
418
|
+
});
|
|
419
|
+
return responses;
|
|
420
|
+
}
|
|
410
421
|
for (const [propName, prop] of model.properties) {
|
|
411
422
|
if (propName === "body") {
|
|
412
423
|
const { type: rustType } = getRustTypeForProperty(
|
|
@@ -446,9 +457,20 @@ function getBodyFromResponse(
|
|
|
446
457
|
variant: { type: Type },
|
|
447
458
|
program: Program,
|
|
448
459
|
anonymousEnums: Map<string, AnonymousStringLiteralUnion>,
|
|
449
|
-
): {
|
|
460
|
+
): {
|
|
461
|
+
type: string | undefined;
|
|
462
|
+
description: string | undefined;
|
|
463
|
+
isSse?: boolean;
|
|
464
|
+
} {
|
|
450
465
|
if (variant.type.kind === "Model") {
|
|
451
466
|
const model = variant.type as Model;
|
|
467
|
+
if (model.name === "SSEStream") {
|
|
468
|
+
return {
|
|
469
|
+
type: "axum::response::Response",
|
|
470
|
+
description: "Server-Sent Events stream",
|
|
471
|
+
isSse: true,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
452
474
|
for (const [_propName, prop] of model.properties) {
|
|
453
475
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
454
476
|
const decorators = (prop as any).decorators;
|
|
@@ -712,11 +734,14 @@ function generateResponseEnums(
|
|
|
712
734
|
const variantName = getStatusVariantName(resp.statusCode);
|
|
713
735
|
if (!resp.bodyType) {
|
|
714
736
|
variants.push(` ${variantName},`);
|
|
737
|
+
} else if (resp.isSse) {
|
|
738
|
+
variants.push(` ${variantName}(${resp.bodyType}),`);
|
|
715
739
|
} else {
|
|
716
740
|
variants.push(` ${variantName}(Json<${resp.bodyType}>),`);
|
|
717
741
|
}
|
|
718
742
|
}
|
|
719
|
-
parts.push(
|
|
743
|
+
parts.push(`#[allow(clippy::type_complexity)]
|
|
744
|
+
pub enum ${responseName} {
|
|
720
745
|
${variants.join("\n")}
|
|
721
746
|
}
|
|
722
747
|
`);
|
|
@@ -732,6 +757,10 @@ ${variants.join("\n")}
|
|
|
732
757
|
parts.push(
|
|
733
758
|
` ${responseName}::${variantName} => ${statusCodeStr}.into_response(),`,
|
|
734
759
|
);
|
|
760
|
+
} else if (resp.isSse) {
|
|
761
|
+
parts.push(
|
|
762
|
+
` ${responseName}::${variantName}(body) => body.into_response(),`,
|
|
763
|
+
);
|
|
735
764
|
} else {
|
|
736
765
|
parts.push(
|
|
737
766
|
` ${responseName}::${variantName}(body) => (${statusCodeStr}, body).into_response(),`,
|
|
@@ -1058,7 +1087,9 @@ function getRustTypeForProperty(
|
|
|
1058
1087
|
};
|
|
1059
1088
|
}
|
|
1060
1089
|
const values = variants.map((v) => (v.type as StringLiteral).value);
|
|
1061
|
-
const
|
|
1090
|
+
const sanitized = values.map((v) => v.replace(/_/g, ""));
|
|
1091
|
+
const firstTwo = sanitized.slice(0, 2).map(toPascalCase).join("");
|
|
1092
|
+
const enumName = `Enum${firstTwo}${sanitized.length}`;
|
|
1062
1093
|
if (!anonymousEnums.has(enumName)) {
|
|
1063
1094
|
anonymousEnums.set(enumName, {
|
|
1064
1095
|
enumName,
|
|
@@ -1211,12 +1242,12 @@ function emitStringLiteralUnion(union: Union): string {
|
|
|
1211
1242
|
const variants = Array.from(union.variants.values());
|
|
1212
1243
|
|
|
1213
1244
|
parts.push(
|
|
1214
|
-
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum ${name} {`,
|
|
1245
|
+
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\n#[allow(clippy::enum_variant_names)]\npub enum ${name} {`,
|
|
1215
1246
|
);
|
|
1216
1247
|
|
|
1217
1248
|
for (let i = 0; i < variants.length; i++) {
|
|
1218
1249
|
const literalType = variants[i].type as StringLiteral;
|
|
1219
|
-
const variantName =
|
|
1250
|
+
const variantName = toPascalCase(literalType.value);
|
|
1220
1251
|
const serdeValue = literalType.value;
|
|
1221
1252
|
if (i === 0) {
|
|
1222
1253
|
parts.push(` #[default]`);
|
|
@@ -1547,11 +1578,11 @@ export async function $onEmit(
|
|
|
1547
1578
|
for (const [enumName, anonEnum] of anonymousEnums) {
|
|
1548
1579
|
const parts: string[] = [];
|
|
1549
1580
|
parts.push(
|
|
1550
|
-
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum ${enumName} {`,
|
|
1581
|
+
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\n#[allow(clippy::enum_variant_names)]\npub enum ${enumName} {`,
|
|
1551
1582
|
);
|
|
1552
1583
|
for (let i = 0; i < anonEnum.variants.length; i++) {
|
|
1553
1584
|
const literal = anonEnum.variants[i];
|
|
1554
|
-
const variantName =
|
|
1585
|
+
const variantName = toPascalCase(literal.value);
|
|
1555
1586
|
if (i === 0) {
|
|
1556
1587
|
parts.push(` #[default]`);
|
|
1557
1588
|
}
|
package/test/hello.test.ts
CHANGED
|
@@ -50,6 +50,17 @@ describe("Rust emitter", () => {
|
|
|
50
50
|
strictEqual(output.includes("Pending,"), true);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
it("emits anonymous string enum without underscores", async () => {
|
|
54
|
+
const results = await emit(`
|
|
55
|
+
model Event {
|
|
56
|
+
eventType: "event_one" | "event_two" | "event_three",
|
|
57
|
+
}
|
|
58
|
+
`);
|
|
59
|
+
const output = results["types.rs"];
|
|
60
|
+
const badNameContainsUnderscore = output.includes("Enum_event_one_");
|
|
61
|
+
strictEqual(badNameContainsUnderscore, false);
|
|
62
|
+
});
|
|
63
|
+
|
|
53
64
|
it("emits integer enum", async () => {
|
|
54
65
|
const results = await emit(`
|
|
55
66
|
enum Priority {
|
|
@@ -400,4 +411,36 @@ describe("Rust emitter", () => {
|
|
|
400
411
|
strictEqual(server.includes("params.year"), true);
|
|
401
412
|
strictEqual(server.includes("params.month"), true);
|
|
402
413
|
});
|
|
414
|
+
it("emits SSEStream properly", async () => {
|
|
415
|
+
const results = await emit(`
|
|
416
|
+
import "@typespec/http";
|
|
417
|
+
import "@typespec/sse";
|
|
418
|
+
import "@typespec/events";
|
|
419
|
+
using TypeSpec.Http;
|
|
420
|
+
using TypeSpec.SSE;
|
|
421
|
+
|
|
422
|
+
model TestEventData {
|
|
423
|
+
data: string;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@TypeSpec.Events.events
|
|
427
|
+
union TestEvent {
|
|
428
|
+
data: TestEventData
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@route("/events")
|
|
432
|
+
namespace Events {
|
|
433
|
+
@get
|
|
434
|
+
op stream(): SSEStream<TestEvent>;
|
|
435
|
+
}
|
|
436
|
+
`);
|
|
437
|
+
const server = results["server.rs"];
|
|
438
|
+
strictEqual(server.includes("axum::response::Response"), true);
|
|
439
|
+
strictEqual(
|
|
440
|
+
server.includes(
|
|
441
|
+
"EventsStreamResponse::Ok(body) => body.into_response(),",
|
|
442
|
+
),
|
|
443
|
+
true,
|
|
444
|
+
);
|
|
445
|
+
});
|
|
403
446
|
});
|
package/test/test-host.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { expectDiagnosticEmpty } from "@typespec/compiler/testing";
|
|
|
3
3
|
import { createTester } from "@typespec/compiler/testing";
|
|
4
4
|
|
|
5
5
|
export const Tester = createTester(resolvePath(import.meta.dirname, "../.."), {
|
|
6
|
-
libraries: [
|
|
6
|
+
libraries: [
|
|
7
|
+
"@typespec/http",
|
|
8
|
+
"@typespec/sse",
|
|
9
|
+
"@typespec/events",
|
|
10
|
+
"typespec-rust-emitter",
|
|
11
|
+
],
|
|
7
12
|
}).emit("typespec-rust-emitter");
|
|
8
13
|
|
|
9
14
|
export async function emitWithDiagnostics(
|