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