172 lines
5.0 KiB
TypeScript
172 lines
5.0 KiB
TypeScript
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)
|
|
}
|
|
}
|