import { z } from 'zod'; import { UnauthorizedError } from './unauthorized-error.ts'; import { ApplicationError } from './application-error.ts'; /** Stable error payload shared by HTTP, SSE, and WebSocket. */ export type PublicError = { /** Protocol-independent status carried by every transport. */ statusCode: number; /** Client-safe summary which never exposes an unexpected exception. */ error: string; /** Structured field failures supplied only for validation errors. */ details?: Array<{ path: string; message: string }>; }; /** * Convert application failures into the common public transport contract. * * @param error - Any thrown value from a route or transport boundary. * @returns A sanitized error payload safe to encode on the wire. */ export const normalizePublicError = (error: unknown): PublicError => { if (error instanceof z.ZodError) { return { statusCode: 400, error: 'Validation Error', details: error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message, })), }; } if (error instanceof UnauthorizedError) { return { statusCode: 401, error: error.message }; } if (error instanceof ApplicationError) { return { statusCode: error.statusCode, error: error.message }; } // Unknown exceptions are logged by adapters, but their messages stay private. return { statusCode: 500, error: 'Internal Server Error' }; };