File
Blob: src/worker/http.ts
| 1 | import type { Context } from "hono"; |
| 2 | import type { ContentfulStatusCode } from "hono/utils/http-status"; |
| 3 | |
| 4 | import { toCodecIssueDetails } from "@/lib/codec-errors"; |
| 5 | |
| 6 | export class HttpError extends Error { |
| 7 | public readonly status: ContentfulStatusCode; |
| 8 | public readonly code: string; |
| 9 | public readonly details: unknown; |
| 10 | |
| 11 | public constructor(status: ContentfulStatusCode, code: string, message: string, details?: unknown) { |
| 12 | super(message); |
| 13 | this.status = status; |
| 14 | this.code = code; |
| 15 | this.details = details; |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | export const parseJson = async <T>(request: Request, codec: { assertDecode: (value: unknown) => T }): Promise<T> => { |
| 20 | let payload: unknown; |
| 21 | |
| 22 | try { |
| 23 | payload = (await request.json()) as unknown; |
| 24 | } catch (error) { |
| 25 | throw new HttpError(400, "invalid_json", "Request body must be valid JSON.", error); |
| 26 | } |
| 27 | |
| 28 | try { |
| 29 | return codec.assertDecode(payload); |
| 30 | } catch (error) { |
| 31 | throw new HttpError(400, "invalid_request", "Request body failed validation.", toCodecIssueDetails(error)); |
| 32 | } |
| 33 | }; |
| 34 | |
| 35 | export const toErrorResponse = (c: Context, error: unknown): Response => { |
| 36 | if (error instanceof HttpError) { |
| 37 | return c.json( |
| 38 | { |
| 39 | error: { |
| 40 | code: error.code, |
| 41 | message: error.message, |
| 42 | details: error.details ?? null, |
| 43 | }, |
| 44 | }, |
| 45 | error.status, |
| 46 | ); |
| 47 | } |
| 48 | |
| 49 | return c.json( |
| 50 | { |
| 51 | error: { |
| 52 | code: "internal_error", |
| 53 | message: "An unexpected error occurred.", |
| 54 | }, |
| 55 | }, |
| 56 | 500, |
| 57 | ); |
| 58 | }; |