import { randomUUID } from "node:crypto"; import type { Context } from "hono"; import type { ContentfulStatusCode } from "hono/utils/http-status"; export type ErrorCode = | "UNAUTHENTICATED" | "FORBIDDEN" | "NOT_FOUND" | "VALIDATION_ERROR" | "CONFLICT" | "INTERNAL_ERROR" | "DATABASE_UNAVAILABLE"; export class ApiError extends Error { constructor( readonly status: ContentfulStatusCode, readonly code: ErrorCode, message: string, ) { super(message); this.name = "ApiError"; } } export const CORRELATION_HEADER = "x-correlation-id"; export type ErrorEnvelope = { error: { code: ErrorCode; message: string; correlationId: string; }; }; export function correlationIdFrom(c: Context): string { const raw = c.req.header(CORRELATION_HEADER)?.trim(); if (raw && raw.length <= 128) { return raw; } return randomUUID(); } export function errorBody(code: ErrorCode, message: string, correlationId: string): ErrorEnvelope { return { error: { code, message, correlationId } }; } export function errorJson( c: Context, status: ContentfulStatusCode, code: ErrorCode, message: string, correlationId?: string, ) { const id = correlationId ?? correlationIdFrom(c); c.header(CORRELATION_HEADER, id); return c.json(errorBody(code, message, id), status); }