111 lines
2.9 KiB
TypeScript
111 lines
2.9 KiB
TypeScript
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<string, unknown> {
|
|
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);
|
|
}
|
|
}
|