import { ZodError } from 'zod'; type HttpErrorOptions = { code?: string; details?: unknown; expose?: boolean; }; type JsonBodyParseError = SyntaxError & { status?: number; type?: string; }; export function resolveHttpErrorCode(statusCode: number) { switch (statusCode) { case 400: return 'BAD_REQUEST'; case 401: return 'UNAUTHORIZED'; case 429: return 'TOO_MANY_REQUESTS'; case 403: return 'FORBIDDEN'; case 404: return 'NOT_FOUND'; case 409: return 'CONFLICT'; case 502: return 'UPSTREAM_ERROR'; default: return 'INTERNAL_SERVER_ERROR'; } } export function resolveHttpErrorMessage(statusCode: number) { switch (statusCode) { case 400: return '请求参数不合法'; case 401: return '未授权访问'; case 429: return '请求过于频繁'; case 403: return '禁止访问'; case 404: return '资源不存在'; case 409: return '请求冲突'; case 502: return '上游服务请求失败'; default: return '服务器内部错误'; } } function isJsonBodyParseError(error: unknown): error is JsonBodyParseError { return ( error instanceof SyntaxError && typeof error === 'object' && 'status' in error && 'type' in error && (error.status === 400 || error.type === 'entity.parse.failed') ); } function serializeZodIssues(error: ZodError) { return error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message, code: issue.code, })); } export class HttpError extends Error { statusCode: number; expose: boolean; code: string; details?: unknown; constructor( statusCode: number, message: string, options: HttpErrorOptions = {}, ) { super(message); this.name = 'HttpError'; this.statusCode = statusCode; this.expose = options.expose ?? statusCode < 500; this.code = options.code ?? resolveHttpErrorCode(statusCode); this.details = options.details; } } export function badRequest(message: string, details?: unknown) { return new HttpError(400, message, { code: 'BAD_REQUEST', details, }); } export function invalidRequest(message = '请求参数不合法', details?: unknown) { return new HttpError(400, message, { code: 'INVALID_REQUEST', details, }); } export function unauthorized(message = '未授权访问') { return new HttpError(401, message, { code: 'UNAUTHORIZED', }); } export function forbidden(message = '禁止访问') { return new HttpError(403, message, { code: 'FORBIDDEN', }); } export function tooManyRequests(message = '请求过于频繁', details?: unknown) { return new HttpError(429, message, { code: 'TOO_MANY_REQUESTS', details, }); } export function captchaRequired(message = '需要完成人机校验', details?: unknown) { return new HttpError(403, message, { code: 'CAPTCHA_REQUIRED', details, }); } export function notFound(message = '资源不存在') { return new HttpError(404, message, { code: 'NOT_FOUND', }); } export function conflict(message: string, details?: unknown) { return new HttpError(409, message, { code: 'CONFLICT', details, }); } export function upstreamError(message: string, details?: unknown) { return new HttpError(502, message, { code: 'UPSTREAM_ERROR', details, }); } export function toHttpError(error: unknown) { if (error instanceof HttpError) { return error; } if (error instanceof ZodError) { return invalidRequest('请求参数不合法', { issues: serializeZodIssues(error), }); } if (isJsonBodyParseError(error)) { return badRequest('JSON 请求体格式错误'); } return new HttpError(500, '服务器内部错误', { code: 'INTERNAL_SERVER_ERROR', expose: false, }); }