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/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
- ): { type: string | undefined; description: string | undefined } {
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(`pub enum ${responseName} {
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 enumName = `Enum_${values.slice(0, 2).join("_")}_${values.length}`;
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 = toRustVariantName(literalType.value);
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 = toRustVariantName(literal.value);
1586
+ const variantName = toPascalCase(literal.value);
1555
1587
  if (i === 0) {
1556
1588
  parts.push(` #[default]`);
1557
1589
  }
@@ -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: ["@typespec/http", "typespec-rust-emitter"],
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(