From ff52ec2272c136b75dae7eff0b9b604bd08efbf7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 9 May 2026 23:07:35 -0400 Subject: [PATCH] =?UTF-8?q?test(server):=20apply=20simplify=20pass=20?= =?UTF-8?q?=E2=80=94=20drop=20redundant=20Effect.gen,=20prose=20comments,?= =?UTF-8?q?=20and=20assertion=20casts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../httpapi/middleware/schema-error.ts | 37 +++++-------------- .../server/httpapi-schema-error-body.test.ts | 21 ++++------- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts index 4f8fd23252..e5a4314d0f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts @@ -5,38 +5,21 @@ import * as Log from "@opencode-ai/core/util/log" const log = Log.create({ service: "server" }) -// Effect's default Respondable for HttpApiSchemaError returns 400 with an -// empty body. That gives the renderer / SDK / curl no information about -// what was actually rejected (Body field, Query param, etc.). PR #26457 -// previously tried `{data:{}, errors:[], success:false}` and broke a -// plugin (#26546) — root cause was the SDK throwing raw POJOs instead -// of Errors, which has since been fixed by `wrapClientError`. -// -// We use the same shape every other 4xx/5xx in the API already uses — -// NamedError serialization (`{name, data}`). The SDK's `wrapClientError` -// extracts `.data.message` automatically, so plugins that already handle -// 404 NotFoundError bodies handle this with no changes. +// Default Respondable returns an empty 400 body. Match the NamedError shape +// used by other 4xx/5xx so the SDK's `wrapClientError` extracts `.data.message`. export class SchemaErrorMiddleware extends HttpApiMiddleware.Service()( "@opencode/HttpApiSchemaError", ) {} export const schemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform( SchemaErrorMiddleware, - (error) => - Effect.gen(function* () { - log.warn("schema rejection", { - kind: error.kind, - reason: error.cause.message, - }) - return HttpServerResponse.jsonUnsafe( - { - name: "BadRequest", - data: { - message: error.cause.message, - kind: error.kind, - }, - }, + (error) => { + log.warn("schema rejection", { kind: error.kind, reason: error.cause.message }) + return Effect.succeed( + HttpServerResponse.jsonUnsafe( + { name: "BadRequest", data: { message: error.cause.message, kind: error.kind } }, { status: 400 }, - ) - }), + ), + ) + }, ) diff --git a/packages/opencode/test/server/httpapi-schema-error-body.test.ts b/packages/opencode/test/server/httpapi-schema-error-body.test.ts index f280e165f4..b28e06f73e 100644 --- a/packages/opencode/test/server/httpapi-schema-error-body.test.ts +++ b/packages/opencode/test/server/httpapi-schema-error-body.test.ts @@ -1,9 +1,3 @@ -/** - * Regression: a schema rejection used to come back as `400` with an empty - * body, leaving the renderer / SDK / curl with no way to tell which field - * failed. The schemaErrorLayer now returns a NamedError-shaped JSON body - * (same shape as 404 NotFoundError) so callers see the actual reason. - */ import { afterEach, describe, expect } from "bun:test" import { Effect } from "effect" import { Server } from "../../src/server/server" @@ -26,9 +20,6 @@ describe("schema-rejection wire shape", () => { ).pipe( Effect.flatMap((tmp) => Effect.gen(function* () { - // POST /sync/history with `aggregate: -1` is an invalid Body shape - // (aggregate is a NamedString) and triggers the framework's - // HttpApiSchemaError on the Body kind. const res = yield* Effect.promise(async () => Server.Default().app.request(SyncPaths.history, { method: "POST", @@ -39,11 +30,13 @@ describe("schema-rejection wire shape", () => { const body = yield* Effect.promise(async () => res.text()) expect(res.status).toBe(400) expect(res.headers.get("content-type") ?? "").toContain("application/json") - const parsed = JSON.parse(body) as { name?: string; data?: { message?: string; kind?: string } } - expect(parsed.name).toBe("BadRequest") - expect(typeof parsed.data?.message).toBe("string") - expect(parsed.data?.message?.length ?? 0).toBeGreaterThan(0) - expect(parsed.data?.kind).toMatch(/^(Body|Payload)$/) + const parsed = JSON.parse(body) + expect(parsed).toMatchObject({ + name: "BadRequest", + data: { kind: expect.stringMatching(/^(Body|Payload)$/) }, + }) + expect(parsed.data.message).toEqual(expect.any(String)) + expect(parsed.data.message.length).toBeGreaterThan(0) }), ), ),