diff --git a/packages/http-server-js/src/http/server/index.ts b/packages/http-server-js/src/http/server/index.ts index a2e1154f8b5..9eca8ed4316 100644 --- a/packages/http-server-js/src/http/server/index.ts +++ b/packages/http-server-js/src/http/server/index.ts @@ -580,6 +580,25 @@ function* emitResultProcessingForType( } } + if (target.kind === "Scalar" || isValueLiteralType(target)) { + const serializationRequired = + target.kind === "Scalar" && isSerializationRequired(ctx, module, target, "application/json"); + + if (target.kind === "Scalar") { + requireSerialization(ctx, target, "application/json"); + } + + yield `${names.ctx}.response.setHeader("content-type", "application/json");`; + + if (serializationRequired) { + yield `${names.ctx}.response.end(globalThis.JSON.stringify(${transposeExpressionToJson(ctx, target, names.result, module)}));`; + } else { + yield `${names.ctx}.response.end(globalThis.JSON.stringify(${names.result}));`; + } + + return; + } + if (target.kind !== "Model") { throw new UnimplementedError(`result processing for type kind '${target.kind}'`); } diff --git a/packages/http-server-js/test/scalar.test.ts b/packages/http-server-js/test/scalar.test.ts index 274cd5cb3cd..b3c786dadf6 100644 --- a/packages/http-server-js/test/scalar.test.ts +++ b/packages/http-server-js/test/scalar.test.ts @@ -1,15 +1,22 @@ -import { ModelProperty, NoTarget, Scalar } from "@typespec/compiler"; -import { BasicTestRunner, createTestRunner } from "@typespec/compiler/testing"; +import { ModelProperty, NoTarget, Scalar, resolvePath } from "@typespec/compiler"; +import { BasicTestRunner, createTestRunner, createTester } from "@typespec/compiler/testing"; import { deepStrictEqual, strictEqual } from "assert"; -import { beforeEach, describe, it } from "vitest"; +import { beforeEach, describe, expect, it } from "vitest"; import { getJsScalar } from "../src/common/scalar.js"; -import { createPathCursor, JsContext, Module } from "../src/ctx.js"; +import { JsContext, Module, createPathCursor } from "../src/ctx.js"; import { module as dateTimeModule } from "../generated-defs/helpers/datetime.js"; import { module as temporalHelpersModule } from "../generated-defs/helpers/temporal/native.js"; import { module as temporalPolyfillHelpersModule } from "../generated-defs/helpers/temporal/polyfill.js"; import { JsEmitterOptions } from "../src/lib.js"; +const HttpServerEmitterTester = createTester(resolvePath(import.meta.dirname, ".."), { + libraries: ["@typespec/http", "@typespec/http-server-js"], +}) + .import("@typespec/http") + .using("Http") + .emit("@typespec/http-server-js"); + describe("scalar", () => { let runner: BasicTestRunner; @@ -186,6 +193,22 @@ describe("scalar", () => { ); }); + it("emits result processing for bare scalar responses", async () => { + const { outputs } = await HttpServerEmitterTester.compile(` + @service(#{ title: "Example" }) + @route("/") + namespace Example { + @get op read(): string; + } + `); + + const serverRaw = outputs["src/generated/http/operations/server-raw.ts"]; + + expect(serverRaw).toBeDefined(); + expect(serverRaw).toContain('response.setHeader("content-type", "application/json");'); + expect(serverRaw).toMatch(/response\.end\(globalThis\.JSON\.stringify\(__result_\d+\)\);/); + }); + describe("date/time/duration types", () => { describe("mode: temporal", () => { const options: JsEmitterOptions = {