ForcePilot/backend/package/yuxi/scheduler/exceptions.py

188 lines
6.8 KiB
Python
Raw Normal View History

"""定时任务调度限界上下文的异常层次。
设计原则见设计方案 §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"