Files
Edu/services/teacher-bff/src/shared/errors/application-error.ts
SpecialX 99155a5ea1 feat(teacher-bff): 完整实现 teacher-bff GraphQL 聚合层
包含 clients/graphql/middleware、health probes、shared-ts contracts 等
2026-07-10 19:10:07 +08:00

143 lines
4.3 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.
// BFF_TEACHER_ 错误码B5 + G14 裁决,统一 BFF_ 前缀)
// 越权防御 3 类错误码president §2.7 裁决):
// - BFF_TEACHER_UNAUTHORIZED (401): x-user-id 缺失或无效
// - BFF_TEACHER_FORBIDDEN_RESOURCE (403): teacherId 与资源无归属关系(场景 A
// - BFF_TEACHER_IDENTITY_MISMATCH (403): JWT teacherId 与 body teacherId 不一致(场景 B
// 其他聚合层错误码contract §1.5
export type ErrorType =
| "validation"
| "not_found"
| "unauthorized"
| "forbidden_resource"
| "identity_mismatch"
| "conflict"
| "business"
| "bad_gateway"
| "aggregation_failed"
| "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;
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,
},
};
}
}
/** 输入参数校验失败400 */
export class ValidationError extends ApplicationError {
readonly type = "validation" as const;
readonly statusCode = 400;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_VALIDATION_FAILED", details);
}
}
/** 资源不存在404 */
export class NotFoundError extends ApplicationError {
readonly type = "not_found" as const;
readonly statusCode = 404;
constructor(resource: string, id: string) {
super(`${resource} not found: ${id}`, "BFF_TEACHER_NOT_FOUND", {
resource,
id,
});
}
}
/** x-user-id 缺失或无效401president §2.7 */
export class UnauthorizedError extends ApplicationError {
readonly type = "unauthorized" as const;
readonly statusCode = 401;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_UNAUTHORIZED", details);
}
}
/** teacherId 与资源无归属关系403 场景 Apresident §2.7 */
export class ForbiddenResourceError extends ApplicationError {
readonly type = "forbidden_resource" as const;
readonly statusCode = 403;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_FORBIDDEN_RESOURCE", details);
}
}
/** JWT teacherId 与请求 body teacherId 不一致403 场景 Bpresident §2.7 */
export class IdentityMismatchError extends ApplicationError {
readonly type = "identity_mismatch" as const;
readonly statusCode = 403;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_IDENTITY_MISMATCH", details);
}
}
/** 并发冲突409 */
export class ConflictError extends ApplicationError {
readonly type = "conflict" as const;
readonly statusCode = 409;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_CONFLICT", details);
}
}
/** 业务规则违反422 */
export class BusinessError extends ApplicationError {
readonly type = "business" as const;
readonly statusCode = 422;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_BUSINESS_ERROR", details);
}
}
/** 下游 gRPC 不可达502 */
export class BadGatewayError extends ApplicationError {
readonly type = "bad_gateway" as const;
readonly statusCode = 502;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_UPSTREAM_UNAVAILABLE", details);
}
}
/** 聚合多下游时部分失败且无降级数据500 */
export class AggregationFailedError extends ApplicationError {
readonly type = "aggregation_failed" as const;
readonly statusCode = 500;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_AGGREGATION_FAILED", details);
}
}
/** 兜底内部错误500 */
export class InternalError extends ApplicationError {
readonly type = "internal" as const;
readonly statusCode = 500;
constructor(message: string, details?: ErrorDetails) {
super(message, "BFF_TEACHER_INTERNAL_ERROR", details);
}
}