diff --git a/services/cards-server/src/lib/errors.ts b/services/cards-server/src/lib/errors.ts index cb4a26aee..f013b358e 100644 --- a/services/cards-server/src/lib/errors.ts +++ b/services/cards-server/src/lib/errors.ts @@ -1,51 +1,63 @@ /** - * Domain errors — caught by serviceErrorHandler from @mana/shared-hono - * and translated to JSON responses with the right status code. + * Domain errors — caught by `serviceErrorHandler` from @mana/shared-hono. + * + * The shared handler only translates Hono `HTTPException`s; anything + * else degrades to 500. So our errors extend HTTPException directly + * rather than maintaining a parallel hierarchy. + * + * `details` (e.g. zod issue tree) is passed via `cause` because the + * shared handler picks that up and surfaces it in the JSON body. */ -export class HttpError extends Error { - constructor( - public status: number, - message: string, - public code?: string, - public details?: unknown - ) { - super(message); - this.name = 'HttpError'; - } +import { HTTPException } from 'hono/http-exception'; +import type { ContentfulStatusCode } from 'hono/utils/http-status'; + +function makeException( + status: ContentfulStatusCode, + message: string, + code?: string, + details?: unknown +) { + return new HTTPException(status, { + message, + cause: details ? { code, details } : code ? { code } : undefined, + }); } -export class UnauthorizedError extends HttpError { +export class HttpError extends HTTPException {} + +export class UnauthorizedError extends HTTPException { constructor(message = 'Unauthorized') { - super(401, message, 'unauthorized'); - this.name = 'UnauthorizedError'; + super(401, { message, cause: { code: 'unauthorized' } }); } } -export class ForbiddenError extends HttpError { +export class ForbiddenError extends HTTPException { constructor(message = 'Forbidden') { - super(403, message, 'forbidden'); - this.name = 'ForbiddenError'; + super(403, { message, cause: { code: 'forbidden' } }); } } -export class NotFoundError extends HttpError { +export class NotFoundError extends HTTPException { constructor(message = 'Not found') { - super(404, message, 'not_found'); - this.name = 'NotFoundError'; + super(404, { message, cause: { code: 'not_found' } }); } } -export class ConflictError extends HttpError { +export class ConflictError extends HTTPException { constructor(message = 'Conflict') { - super(409, message, 'conflict'); - this.name = 'ConflictError'; + super(409, { message, cause: { code: 'conflict' } }); } } -export class BadRequestError extends HttpError { +export class BadRequestError extends HTTPException { constructor(message = 'Bad request', details?: unknown) { - super(400, message, 'bad_request', details); - this.name = 'BadRequestError'; + super(400, { + message, + cause: details ? { code: 'bad_request', details } : { code: 'bad_request' }, + }); } } + +// Keep makeException exported in case future code wants the raw factory. +export { makeException };