Initial commit
This commit is contained in:
171
shared/errors/index.ts
Normal file
171
shared/errors/index.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user