export class ApiError extends Error { statusCode: number; code: string; details?: unknown; constructor(statusCode: number, code: string, message: string, details?: unknown) { super(message); this.name = "ApiError"; this.statusCode = statusCode; this.code = code; this.details = details; } } export function badRequest(code: string, message: string, details?: unknown) { return new ApiError(400, code, message, details); } export function unauthorized(code: string, message: string, details?: unknown) { return new ApiError(401, code, message, details); } export function forbidden(code: string, message: string, details?: unknown) { return new ApiError(403, code, message, details); } export function notFound(code: string, message: string, details?: unknown) { return new ApiError(404, code, message, details); } export function conflict(code: string, message: string, details?: unknown) { return new ApiError(409, code, message, details); } export function toErrorBody(err: unknown, requestId: string) { if (err instanceof ApiError) { return { statusCode: err.statusCode, body: { ok: false, code: err.code, message: err.message, requestId, ...(err.details !== undefined ? { details: err.details } : {}), }, }; } const fallback = err as { statusCode?: number; code?: string; message?: string }; const statusCode = typeof fallback?.statusCode === "number" ? fallback.statusCode : 500; return { statusCode, body: { ok: false, code: fallback?.code ?? "INTERNAL", message: statusCode >= 500 ? "Something went wrong" : fallback?.message ?? "Bad request", requestId, }, }; }