ForcePilot/backend/package/yuxi/scheduler/exceptions.py
Kris 67d42fc246 refactor(scheduler/exceptions): 完善异常基类与子类的错误码和序列化能力
1. 为SchedulerError基类新增error_code类变量、trace_id参数和to_dict序列化方法
2. 为所有异常子类补充对应的业务错误码
3. 优化异常的字符串展示格式,增加error_code前缀
2026-06-24 21:52:02 +08:00

188 lines
6.8 KiB
Python
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.

"""定时任务调度限界上下文的异常层次。
设计原则(见设计方案 §8.1
- **根异常 + 分层异常,一个文件**:保留 ``SchedulerError`` 根异常,按层内聚子异常。
- **统一兜底**:所有异常继承 ``SchedulerError``最外层Router / 全局异常处理器)统一捕获。
- **``status_code`` 作为 HTTP 映射依据**:子类按需覆盖类级属性,无需自定义 ``__init__``。
本文件位于包根目录(非 ``core/`` 下因为异常被所有层共享core / use_cases / framework /
adapters放在根目录便于统一导入路径。
异常层次:
::
SchedulerError (500) # 根异常
├── SchedulerValidationError (400, ValueError) # 业务规则校验失败
│ ├── InvalidCronExpressionError # cron 表达式非法
│ ├── SchedulerPayloadTooLargeError (413) # payload 超过大小限制
│ └── HandlerNotFoundError # 找不到 handler
├── SchedulerEntityNotFoundError (404) # 领域实体不存在
├── SchedulerPermissionDeniedError (403) # 权限不足
└── SchedulerConflictError (409) # 资源冲突
├── DuplicateHandlerError # handler 重复注册
└── TaskStatusTransitionError # 任务状态机非法转换
"""
from __future__ import annotations
from typing import Any
class SchedulerError(Exception):
"""定时任务调度限界上下文根异常。
所有本上下文抛出的异常都应继承此类,最外层统一捕获后映射为 HTTP 响应。
``status_code`` 作为异常到 HTTP 状态码的映射依据,子类按需覆盖。
``error_code`` 作为稳定错误码,子类以类变量覆盖,用于驱动协议响应与日志关联。
``trace_id`` 用于跨链路追踪关联。
"""
status_code: int = 500
error_code: str = "SCHEDULER_ERROR"
def __init__(
self,
message: str,
*,
details: dict[str, Any] | None = None,
trace_id: str | None = None,
) -> None:
"""初始化异常实例。
参数:
message: 人类可读错误信息。
details: 业务字段字典,默认空字典。
trace_id: 调用链路追踪 ID用于跨链路关联。
"""
super().__init__(message)
self.message = message
self.details = details or {}
self.trace_id = trace_id
def to_dict(self) -> dict[str, Any]:
"""序列化为字典,便于跨层传递、日志记录与 API 响应。
返回 ``error_code`` / ``message`` / ``trace_id`` 三个基础字段,
并展开 ``details`` 中的业务字段。
"""
return {
"error_code": self.error_code,
"message": self.message,
"trace_id": self.trace_id,
**self.details,
}
def __str__(self) -> str:
"""返回 ``[error_code] message`` 格式的字符串表示。"""
return f"[{self.error_code}] {self.message}"
# ─── 业务规则异常 ──────────────────────────────────────────────────────────
class SchedulerValidationError(SchedulerError, ValueError):
"""调度业务规则校验失败。
同时继承 ``ValueError`` 以兼容 Pydantic 的 ``field_validator`` 与 ``raise ValueError(...)``
习惯写法(在 dataclass 薄校验中可直接 ``raise SchedulerValidationError(...)``)。
"""
status_code = 400
error_code = "VALIDATION_ERROR"
class InvalidCronExpressionError(SchedulerValidationError):
"""cron 表达式非法。
由 cron 解析器在表达式语法错误或字段越界时抛出。``details`` 可携带
``expression`` / ``field`` / ``reason`` 等定位信息。
"""
error_code = "INVALID_CRON"
class SchedulerPayloadTooLargeError(SchedulerValidationError):
"""任务 payload 超过大小限制。
由 payload 校验器在序列化后字节数超阈值时抛出。``details`` 可携带
``size`` / ``limit`` / ``unit`` 等量化信息。
"""
status_code = 413
error_code = "PAYLOAD_TOO_LARGE"
class HandlerNotFoundError(SchedulerValidationError):
"""找不到对应的任务 handler。
由 handler 注册表在按 key 查找 handler 未命中时抛出。虽然名字像 404
但语义上属于"调度配置校验失败"(任务引用了未注册的 handler按 spec
归入 ``SchedulerValidationError``。``details`` 可携带 ``handler_key``。
"""
error_code = "HANDLER_NOT_FOUND"
# ─── 实体不存在 ──────────────────────────────────────────────────────────
class SchedulerEntityNotFoundError(SchedulerError):
"""调度领域实体不存在。
统一覆盖任务 / 任务执行记录 / handler 注册项等实体不存在场景。
具体实体类型由 ``message`` 描述,``details`` 可携带 ``entity_type`` / ``identifier``。
"""
status_code = 404
error_code = "NOT_FOUND"
# ─── 权限 ────────────────────────────────────────────────────────────────
class SchedulerPermissionDeniedError(SchedulerError):
"""调度操作权限不足。
由权限校验器在当前用户/角色无权执行调度操作时抛出。
``details`` 可携带 ``action`` / ``resource`` / ``principal``。
"""
status_code = 403
error_code = "PERMISSION_DENIED"
# ─── 冲突 ────────────────────────────────────────────────────────────────
class SchedulerConflictError(SchedulerError):
"""调度资源冲突。
表示写操作因唯一约束冲突或状态竞争无法完成(如 handler 重复注册、
任务状态机非法转换等)。
"""
status_code = 409
error_code = "CONFLICT"
class DuplicateHandlerError(SchedulerConflictError):
"""handler 重复注册。
由 handler 注册表在以已存在的 key 再次注册时抛出。
``details`` 可携带 ``handler_key`` / ``existing_class``。
"""
error_code = "DUPLICATE_HANDLER"
class TaskStatusTransitionError(SchedulerConflictError):
"""任务状态机非法转换。
由任务状态机在不允许的转换路径上抛出(如从 ``succeeded`` 转回 ``running``)。
``details`` 可携带 ``from_state`` / ``to_state`` / ``allowed_transitions``。
"""
error_code = "STATUS_TRANSITION_INVALID"