typespec-rust-emitter 0.10.3 → 0.10.5
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 +15 -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 +39 -7
- package/test/hello.test.ts +48 -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,21 @@ 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:
|
|
416
|
+
"axum::response::sse::Sse<std::pin::Pin<Box<dyn futures::stream::Stream<Item = Result<axum::response::sse::Event, std::convert::Infallible>> + Send>>>",
|
|
417
|
+
bodyDescription: "Server-Sent Events stream",
|
|
418
|
+
isSse: true,
|
|
419
|
+
});
|
|
420
|
+
return responses;
|
|
421
|
+
}
|
|
410
422
|
for (const [propName, prop] of model.properties) {
|
|
411
423
|
if (propName === "body") {
|
|
412
424
|
const { type: rustType } = getRustTypeForProperty(
|
|
@@ -446,9 +458,20 @@ function getBodyFromResponse(
|
|
|
446
458
|
variant: { type: Type },
|
|
447
459
|
program: Program,
|
|
448
460
|
anonymousEnums: Map<string, AnonymousStringLiteralUnion>,
|
|
449
|
-
): {
|
|
461
|
+
): {
|
|
462
|
+
type: string | undefined;
|
|
463
|
+
description: string | undefined;
|
|
464
|
+
isSse?: boolean;
|
|
465
|
+
} {
|
|
450
466
|
if (variant.type.kind === "Model") {
|
|
451
467
|
const model = variant.type as Model;
|
|
468
|
+
if (model.name === "SSEStream") {
|
|
469
|
+
return {
|
|
470
|
+
type: "axum::response::sse::Sse<std::pin::Pin<Box<dyn futures::stream::Stream<Item = Result<axum::response::sse::Event, std::convert::Infallible>> + Send>>>",
|
|
471
|
+
description: "Server-Sent Events stream",
|
|
472
|
+
isSse: true,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
452
475
|
for (const [_propName, prop] of model.properties) {
|
|
453
476
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
454
477
|
const decorators = (prop as any).decorators;
|
|
@@ -712,11 +735,14 @@ function generateResponseEnums(
|
|
|
712
735
|
const variantName = getStatusVariantName(resp.statusCode);
|
|
713
736
|
if (!resp.bodyType) {
|
|
714
737
|
variants.push(` ${variantName},`);
|
|
738
|
+
} else if (resp.isSse) {
|
|
739
|
+
variants.push(` ${variantName}(${resp.bodyType}),`);
|
|
715
740
|
} else {
|
|
716
741
|
variants.push(` ${variantName}(Json<${resp.bodyType}>),`);
|
|
717
742
|
}
|
|
718
743
|
}
|
|
719
|
-
parts.push(
|
|
744
|
+
parts.push(`#[allow(clippy::type_complexity)]
|
|
745
|
+
pub enum ${responseName} {
|
|
720
746
|
${variants.join("\n")}
|
|
721
747
|
}
|
|
722
748
|
`);
|
|
@@ -732,6 +758,10 @@ ${variants.join("\n")}
|
|
|
732
758
|
parts.push(
|
|
733
759
|
` ${responseName}::${variantName} => ${statusCodeStr}.into_response(),`,
|
|
734
760
|
);
|
|
761
|
+
} else if (resp.isSse) {
|
|
762
|
+
parts.push(
|
|
763
|
+
` ${responseName}::${variantName}(body) => body.into_response(),`,
|
|
764
|
+
);
|
|
735
765
|
} else {
|
|
736
766
|
parts.push(
|
|
737
767
|
` ${responseName}::${variantName}(body) => (${statusCodeStr}, body).into_response(),`,
|
|
@@ -1058,7 +1088,9 @@ function getRustTypeForProperty(
|
|
|
1058
1088
|
};
|
|
1059
1089
|
}
|
|
1060
1090
|
const values = variants.map((v) => (v.type as StringLiteral).value);
|
|
1061
|
-
const
|
|
1091
|
+
const sanitized = values.map((v) => v.replace(/_/g, ""));
|
|
1092
|
+
const firstTwo = sanitized.slice(0, 2).map(toPascalCase).join("");
|
|
1093
|
+
const enumName = `Enum${firstTwo}${sanitized.length}`;
|
|
1062
1094
|
if (!anonymousEnums.has(enumName)) {
|
|
1063
1095
|
anonymousEnums.set(enumName, {
|
|
1064
1096
|
enumName,
|
|
@@ -1211,12 +1243,12 @@ function emitStringLiteralUnion(union: Union): string {
|
|
|
1211
1243
|
const variants = Array.from(union.variants.values());
|
|
1212
1244
|
|
|
1213
1245
|
parts.push(
|
|
1214
|
-
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum ${name} {`,
|
|
1246
|
+
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\n#[allow(clippy::enum_variant_names)]\npub enum ${name} {`,
|
|
1215
1247
|
);
|
|
1216
1248
|
|
|
1217
1249
|
for (let i = 0; i < variants.length; i++) {
|
|
1218
1250
|
const literalType = variants[i].type as StringLiteral;
|
|
1219
|
-
const variantName =
|
|
1251
|
+
const variantName = toPascalCase(literalType.value);
|
|
1220
1252
|
const serdeValue = literalType.value;
|
|
1221
1253
|
if (i === 0) {
|
|
1222
1254
|
parts.push(` #[default]`);
|
|
@@ -1547,11 +1579,11 @@ export async function $onEmit(
|
|
|
1547
1579
|
for (const [enumName, anonEnum] of anonymousEnums) {
|
|
1548
1580
|
const parts: string[] = [];
|
|
1549
1581
|
parts.push(
|
|
1550
|
-
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\npub enum ${enumName} {`,
|
|
1582
|
+
`#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]\n#[allow(clippy::enum_variant_names)]\npub enum ${enumName} {`,
|
|
1551
1583
|
);
|
|
1552
1584
|
for (let i = 0; i < anonEnum.variants.length; i++) {
|
|
1553
1585
|
const literal = anonEnum.variants[i];
|
|
1554
|
-
const variantName =
|
|
1586
|
+
const variantName = toPascalCase(literal.value);
|
|
1555
1587
|
if (i === 0) {
|
|
1556
1588
|
parts.push(` #[default]`);
|
|
1557
1589
|
}
|
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,41 @@ 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(
|
|
439
|
+
server.includes(
|
|
440
|
+
"axum::response::sse::Sse<std::pin::Pin<Box<dyn futures::stream::Stream<Item = Result<axum::response::sse::Event, std::convert::Infallible>> + Send>>>",
|
|
441
|
+
),
|
|
442
|
+
true,
|
|
443
|
+
);
|
|
444
|
+
strictEqual(
|
|
445
|
+
server.includes(
|
|
446
|
+
"EventsStreamResponse::Ok(body) => body.into_response(),",
|
|
447
|
+
),
|
|
448
|
+
true,
|
|
449
|
+
);
|
|
450
|
+
});
|
|
403
451
|
});
|
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(
|