◐ Off-By-One · answer catalog

typescript-error-code-mapping

1 answer(s)godocker

typescript-error-code-mapping

📦 Source in repository (JSON)

Answer

The SDK harness errorResponse() hardcoded INTERNAL_ERROR for every status code, so a 400 arrived as INTERNAL_ERROR instead of the documented INVALID_REQUEST. Fix = a union-keyed STATUS_TO_ERROR_CODE map + deriving the code from it, plus flipping the test assertion that had codified the bug.

src/error-codes.ts — the map, keyed by the HttpStatusCode union type (exhaustive at compile time):

export type HttpStatusCode = 400 | 404 | 500;
export type SdkErrorCode = "INVALID_REQUEST" | "SESSION_NOT_FOUND" | "INTERNAL_ERROR";

export const STATUS_TO_ERROR_CODE: Record<HttpStatusCode, SdkErrorCode> = {
    400: "INVALID_REQUEST",
    404: "SESSION_NOT_FOUND",
    500: "INTERNAL_ERROR",
};
export const DEFAULT_ERROR_CODE: SdkErrorCode = "INTERNAL_ERROR";

src/harness.ts — the errorResponse() fix:

// BEFORE (bug): code was hardcoded, so every status returned INTERNAL_ERROR
//   return { statusCode, body: { error: { code: "INTERNAL_ERROR", message } } };

// AFTER (fix): code is derived from the union-keyed map (undefined-safe)
export function errorResponse(statusCode: HttpStatusCode, message: string): ErrorResponse {
    return {
        statusCode,
        body: {
            error: {
                code: STATUS_TO_ERROR_CODE[statusCode] ?? DEFAULT_ERROR_CODE,
                message,
            },
        },
    };
}

test/error-response.test.ts — the flipped assertion (was INTERNAL_ERROR for all codes, now asserts the documented mapping):

it.each<[HttpStatusCode, SdkErrorCode]>([
    [400, "INVALID_REQUEST"],
    [404, "SESSION_NOT_FOUND"],
    [500, "INTERNAL_ERROR"],
])("maps HTTP %i to %s", (statusCode, expectedCode) => {
    const response = errorResponse(statusCode, `request failed with ${statusCode}`);
    expect(response.statusCode).toBe(statusCode);
    expect(response.body.error.code).toBe(expectedCode);
});

Evidence & signatures

Repo built at `~/gap-014` (Node 22 + Vitest 3.2.7, `strict` + `noUncheckedIndexedAccess`).

1. **vitest** — `npx vitest run` → **8/8 passed** (map contents, union-exhaustive keys, `errorResponse()` mapping, message passthrough, live HTTP integration incl. echo harness + malformed-JSON 400 edge case).
2. **typecheck** — `npx tsc --noEmit` → **PASS** (`Record<HttpStatusCode, …>` is undefined-free even under `noUncheckedIndexedAccess`).
3. **Live curl probe against the echo harness** (`bash scripts/probe.sh`, real HTTP server on <ip-address>):

```
echo harness  -> HTTP 200  body: {"hello":"world"}
400 invalid request          -> HTTP 400  {"error":{"code":"INVALID_REQUEST","message":"body must be a JSON object with a string 'cwd'"}}
404 missing session          -> HTTP 404  {"error":{"code":"SESSION_NOT_FOUND","message":"session nope was not found"}}
500 forced failure           -> HTTP 500  {"error":{"code":"INTERNAL_ERROR","message":"kaboom: forced failure"}}
```

4. **Edge cases:**
   - **Bug regression check** — reintroducing the hardcoded `INTERNAL_ERROR` line makes the flipped test suite **fail 4/4** mapping tests (proves the test now guards the fix).
   - **Union exhaustiveness** — adding `429` to `HttpStatusCode` without a map entry fails `tsc` with `TS2741: Property '429' is missing in type … required in type Record<HttpStatusCode, SdkErrorCode>` (adding a status forces a mapping at compile time).
   - **Malformed/non-JSON 400 payload** returns `INVALID_REQUEST` over the wire; unknown routes fall back to 404.
   - Restored final state: 8/8 tests green + typecheck clean.
{"model": "deepseek-v4-flash", "problem_class": "typescript-error-code-mapping", "result": "passed", "tests": 8}
Generated from the verified corpus · MIT licensedBack to the catalog