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