Files
Edu/services/msg/src/shared/errors/application-error.ts
SpecialX 7b7abbb309 feat(msg): 完整实现 msg 消息服务
包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
2026-07-10 19:09:52 +08:00

175 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export type ErrorType =
| "validation"
| "not_found"
| "permission_denied"
| "conflict"
| "business"
| "database"
| "internal";
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, "MSG_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}`, "MSG_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}`, "MSG_PERMISSION_DENIED", {
permission,
});
}
}
export class ConflictError extends ApplicationError {
readonly type = "conflict" as const;
readonly statusCode = 409;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_CONFLICT", details);
}
}
export class BusinessError extends ApplicationError {
readonly type = "business" as const;
readonly statusCode = 422;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_BUSINESS_ERROR", details);
}
}
export class DatabaseError extends ApplicationError {
readonly type = "database" as const;
readonly statusCode = 500;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_DATABASE_ERROR", details);
}
}
export class InternalError extends ApplicationError {
readonly type = "internal" as const;
readonly statusCode = 500;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_INTERNAL_ERROR", details);
}
}
/**
* ES 不可用且无降级路径503
* 仲裁依据02-architecture-design.md §6.2
*/
export class EsUnavailableError extends ApplicationError {
readonly type = "internal" as const;
readonly statusCode = 503;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_ES_UNAVAILABLE", details);
}
}
/**
* Redis 不可用且无降级路径503
* 仲裁依据02-architecture-design.md §6.2
*/
export class RedisUnavailableError extends ApplicationError {
readonly type = "internal" as const;
readonly statusCode = 503;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_REDIS_UNAVAILABLE", details);
}
}
/**
* Push Gateway 不可用503
* 仲裁依据02-architecture-design.md §6.2 + coord M4软失败仅必要时抛出
*/
export class PushGatewayUnavailableError extends ApplicationError {
readonly type = "internal" as const;
readonly statusCode = 503;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_PUSH_GATEWAY_UNAVAILABLE", details);
}
}
/**
* 通知模板不存在404
* 仲裁依据02-architecture-design.md §6.2
*/
export class TemplateNotFoundError extends ApplicationError {
readonly type = "not_found" as const;
readonly statusCode = 404;
constructor(templateCode: string) {
super(
`Notification template not found: ${templateCode}`,
"MSG_TEMPLATE_NOT_FOUND",
{ templateCode },
);
}
}
/**
* 模板渲染失败变量缺失等422
* 仲裁依据02-architecture-design.md §6.2
*/
export class TemplateRenderError extends ApplicationError {
readonly type = "business" as const;
readonly statusCode = 422;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_TEMPLATE_RENDER_ERROR", details);
}
}
/**
* 通知频率超限429
* 仲裁依据02-architecture-design.md §6.2NotificationPreference.frequency_limit 预留)
*/
export class RateLimitExceededError extends ApplicationError {
readonly type = "business" as const;
readonly statusCode = 429;
constructor(message: string, details?: ErrorDetails) {
super(message, "MSG_RATE_LIMIT_EXCEEDED", details);
}
}