import type { ErrorCode } from '../constants/errors.js' export type { ErrorCode } from '../constants/errors.js' export interface ErrorDetails { [key: string]: unknown } export class AppError extends Error { public readonly statusCode: number public readonly details?: ErrorDetails constructor( public readonly code: ErrorCode, message: string, statusCode: number = 500, details?: ErrorDetails ) { super(message) this.name = 'AppError' this.statusCode = statusCode this.details = details } toJSON() { return { name: this.name, code: this.code, message: this.message, statusCode: this.statusCode, details: this.details } } } export class ValidationError extends AppError { constructor(message: string, details?: ErrorDetails) { super('VALIDATION_ERROR', message, 400, details) this.name = 'ValidationError' } } export class NotFoundError extends AppError { constructor(message: string = 'Resource not found', details?: ErrorDetails) { super('NOT_FOUND', message, 404, details) this.name = 'NotFoundError' } } export class AccessDeniedError extends AppError { constructor(message: string = 'Access denied', details?: ErrorDetails) { super('ACCESS_DENIED', message, 403, details) this.name = 'AccessDeniedError' } } export class BadRequestError extends AppError { constructor(message: string, details?: ErrorDetails) { super('BAD_REQUEST', message, 400, details) this.name = 'BadRequestError' } } export class PathNotFoundError extends AppError { constructor(message: string = 'Path not found', details?: ErrorDetails) { super('PATH_NOT_FOUND', message, 404, details) this.name = 'PathNotFoundError' } } export class NotADirectoryError extends AppError { constructor(message: string = '不是目录', details?: ErrorDetails) { super('NOT_A_DIRECTORY', message, 400, details) this.name = 'NotADirectoryError' } } export class FileExistsError extends AppError { constructor(message: string = 'File already exists', details?: ErrorDetails) { super('FILE_EXISTS', message, 409, details) this.name = 'FileExistsError' } } export class InvalidPathError extends AppError { constructor(message: string = '无效路径', details?: ErrorDetails) { super('INVALID_PATH', message, 400, details) this.name = 'InvalidPathError' } } export class AlreadyExistsError extends AppError { constructor(message: string = 'Resource already exists', details?: ErrorDetails) { super('ALREADY_EXISTS', message, 409, details) this.name = 'AlreadyExistsError' } } export class ForbiddenError extends AppError { constructor(message: string = '禁止访问', details?: ErrorDetails) { super('FORBIDDEN', message, 403, details) this.name = 'ForbiddenError' } } export class UnsupportedMediaTypeError extends AppError { constructor(message: string = '不支持的媒体类型', details?: ErrorDetails) { super('UNSUPPORTED_MEDIA_TYPE', message, 415, details) this.name = 'UnsupportedMediaTypeError' } } export class PayloadTooLargeError extends AppError { constructor(message: string = 'Payload too large', details?: ErrorDetails) { super('PAYLOAD_TOO_LARGE', message, 413, details) this.name = 'PayloadTooLargeError' } } export class ResourceLockedError extends AppError { constructor(message: string = '资源已锁定', details?: ErrorDetails) { super('RESOURCE_LOCKED', message, 423, details) this.name = 'ResourceLockedError' } } export class InternalError extends AppError { constructor(message: string = '服务器内部错误', details?: ErrorDetails) { super('INTERNAL_ERROR', message, 500, details) this.name = 'InternalError' } } export function isAppError(error: unknown): error is AppError { return error instanceof AppError } export function isNodeError(error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && 'code' in error } export function createErrorFromCode(code: ErrorCode, message: string, details?: ErrorDetails): AppError { switch (code) { case 'VALIDATION_ERROR': return new ValidationError(message, details) case 'NOT_FOUND': case 'PATH_NOT_FOUND': return new NotFoundError(message, details) case 'ACCESS_DENIED': case 'FORBIDDEN': return new AccessDeniedError(message, details) case 'BAD_REQUEST': case 'NOT_A_DIRECTORY': case 'INVALID_PATH': case 'INVALID_NAME': return new BadRequestError(message, details) case 'FILE_EXISTS': case 'ALREADY_EXISTS': return new FileExistsError(message, details) case 'UNSUPPORTED_MEDIA_TYPE': return new UnsupportedMediaTypeError(message, details) case 'PAYLOAD_TOO_LARGE': return new PayloadTooLargeError(message, details) case 'RESOURCE_LOCKED': return new ResourceLockedError(message, details) case 'INTERNAL_ERROR': return new InternalError(message, details) default: return new AppError(code, message, 500, details) } }