export type ErrorType = | "validation" | "not_found" | "permission_denied" | "conflict" | "business" | "database" | "internal" | "neo4j_unavailable"; export interface ErrorDetails { [key: string]: unknown; } export abstract class ApplicationError extends Error { abstract readonly type: ErrorType; abstract readonly statusCode: number; readonly code: string; readonly details?: ErrorDetails; // FIX #1: traceId 改为可写,以便 GlobalErrorFilter 注入请求级 traceId traceId?: string; constructor(message: string, code: string, details?: ErrorDetails) { super(message); this.name = this.constructor.name; this.code = code; this.details = details; } toJSON(): Record { return { success: false, error: { code: this.code, message: this.message, details: this.details, traceId: this.traceId, }, }; } } export class ValidationError extends ApplicationError { readonly type = "validation" as const; readonly statusCode = 400; constructor(message: string, details?: ErrorDetails) { super(message, "CONTENT_VALIDATION_ERROR", details); } } export class NotFoundError extends ApplicationError { readonly type = "not_found" as const; readonly statusCode = 404; constructor(resource: string, id: string) { super(`${resource} not found: ${id}`, "CONTENT_NOT_FOUND", { resource, id, }); } } export class PermissionDeniedError extends ApplicationError { readonly type = "permission_denied" as const; readonly statusCode = 403; constructor(permission: string) { super(`Permission denied: ${permission}`, "CONTENT_PERMISSION_DENIED", { permission, }); } } export class ConflictError extends ApplicationError { readonly type = "conflict" as const; readonly statusCode = 409; constructor(message: string, details?: ErrorDetails) { super(message, "CONTENT_CONFLICT", details); } } export class BusinessError extends ApplicationError { readonly type = "business" as const; readonly statusCode = 422; constructor(message: string, details?: ErrorDetails) { super(message, "CONTENT_BUSINESS_ERROR", details); } } export class DatabaseError extends ApplicationError { readonly type = "database" as const; readonly statusCode = 500; constructor(message: string, details?: ErrorDetails) { super(message, "CONTENT_DATABASE_ERROR", details); } } export class InternalError extends ApplicationError { readonly type = "internal" as const; readonly statusCode = 500; constructor(message: string, details?: ErrorDetails) { super(message, "CONTENT_INTERNAL_ERROR", details); } } export class Neo4jUnavailableError extends ApplicationError { readonly type = "neo4j_unavailable" as const; readonly statusCode = 503; constructor(message: string, details?: ErrorDetails) { super(message, "CONTENT_NEO4J_UNAVAILABLE", details); } }