Skip to main content

sz_orm_core/
error.rs

1//! 错误类型与处理
2//!
3//! 全操作的集中错误类型定义
4
5use parking_lot::RwLock;
6use std::error::Error;
7use std::fmt;
8use std::io;
9use std::sync::{Arc, OnceLock};
10
11/// 错误上报 hook 类型
12type ErrorHook = Arc<dyn Fn(&DbError) + Send + Sync>;
13
14/// 全局错误上报 hook 存储(使用 OnceLock 实现 lazy 初始化,无需 once_cell 依赖)
15///
16/// P3-4 修复:使用 parking_lot::RwLock 替换 std::sync::RwLock,消除锁毒化风险
17/// (parking_lot 的 RwLock 不会返回 PoisonError,无需 unwrap())
18static GLOBAL_ERROR_HOOK: OnceLock<RwLock<Option<ErrorHook>>> = OnceLock::new();
19
20/// 获取全局错误 hook 存储的引用
21fn error_hook_storage() -> &'static RwLock<Option<ErrorHook>> {
22    GLOBAL_ERROR_HOOK.get_or_init(|| RwLock::new(None))
23}
24
25/// 设置全局错误上报 hook
26///
27/// 调用后,所有通过 `trigger_error_hook` 触发的错误都会被传入此 hook。
28pub fn set_error_hook(hook: ErrorHook) {
29    *error_hook_storage().write() = Some(hook);
30}
31
32/// 触发错误 hook(在 DbError 创建/返回时调用)
33///
34/// 如果未设置 hook,则静默跳过。
35pub fn trigger_error_hook(err: &DbError) {
36    let storage = error_hook_storage().read();
37    if let Some(ref hook) = *storage {
38        hook(err);
39    }
40}
41
42/// 数据库错误类型
43#[derive(Debug)]
44pub enum DbError {
45    /// 查询执行失败
46    QueryError(String),
47
48    /// 连接失败
49    ConnectionError(String),
50
51    /// 连接被拒绝
52    ConnectionRefused(String),
53
54    /// 连接超时
55    ConnectionTimeout(String),
56
57    /// 连接池错误
58    PoolError(PoolError),
59
60    /// 缓存错误
61    CacheError(CacheError),
62
63    /// 事务错误
64    TxError(TxError),
65
66    /// 迁移错误
67    MigrationError(String),
68
69    /// 方言不支持
70    Unsupported(String),
71
72    /// 配置错误
73    ConfigError(String),
74
75    /// 序列化/反序列化错误
76    SerdeError(String),
77
78    /// 未找到
79    NotFound(String),
80
81    /// 已存在
82    AlreadyExists(String),
83
84    /// 约束冲突(通用回退,无法确定具体类型时使用)
85    ConstraintViolation(String),
86
87    /// 唯一约束冲突(UNIQUE constraint)
88    UniqueViolation(String),
89
90    /// 外键约束冲突(FOREIGN KEY constraint)
91    ForeignKeyViolation(String),
92
93    /// 非空字段出现 null 值
94    NullValue(String),
95
96    /// 输入非法
97    InvalidInput(String),
98
99    /// 内部错误
100    Internal(String),
101
102    /// IO 错误
103    IoError(String),
104
105    /// 钩子执行失败
106    Hook(String),
107
108    /// 多租户错误(如租户 ID 缺失、跨租户访问)
109    TenantError(String),
110
111    /// 数据验证失败(业务规则校验未通过,由 before_validate 钩子触发)
112    Validation(String),
113
114    /// #6 修复:带上下文链的错误
115    ///
116    /// 包装原始错误 + 上下文链,用于在错误传播路径上附加调用方上下文。
117    /// 通过 `DbError::with_context("operation")` 创建。
118    Contextual {
119        /// 原始错误(Box 避免递归类型大小爆炸)
120        source: Box<DbError>,
121        /// 上下文链头节点
122        context: ErrorContext,
123    },
124}
125
126/// #6 修复:错误上下文链节点
127///
128/// 构成 `Context → Context → ...` 的单向链表,每一层记录
129/// `context`(操作描述)与 `span`(可选 tracing span 名称)。
130#[derive(Debug, Clone)]
131pub struct ErrorContext {
132    /// 当前层上下文描述(如 "fetching user by id")
133    pub context: String,
134    /// 可选 tracing span 名(如 "user_service")
135    pub span: Option<String>,
136    /// 上一层上下文(None 表示链尾)
137    pub previous: Option<Box<ErrorContext>>,
138}
139
140impl ErrorContext {
141    /// 创建新的上下文节点
142    pub fn new(context: impl Into<String>) -> Self {
143        Self {
144            context: context.into(),
145            span: None,
146            previous: None,
147        }
148    }
149
150    /// 附加上一层上下文(消费 self,返回新的链头)
151    pub fn with_previous(mut self, prev: ErrorContext) -> Self {
152        self.previous = Some(Box::new(prev));
153        self
154    }
155
156    /// 设置 tracing span 名
157    pub fn with_span(mut self, span: impl Into<String>) -> Self {
158        self.span = Some(span.into());
159        self
160    }
161
162    /// 遍历上下文链,从最外层到最内层
163    pub fn iter(&self) -> impl Iterator<Item = &ErrorContext> {
164        let mut current = Some(self);
165        std::iter::from_fn(move || {
166            let node = current?;
167            let result = node;
168            current = node.previous.as_deref();
169            Some(result)
170        })
171    }
172
173    /// 格式化为多行字符串(每行一个上下文层)
174    pub fn format_chain(&self) -> String {
175        self.iter()
176            .enumerate()
177            .map(|(i, ctx)| {
178                if let Some(ref span) = ctx.span {
179                    format!("  [{}] {} (span: {})", i, ctx.context, span)
180                } else {
181                    format!("  [{}] {}", i, ctx.context)
182                }
183            })
184            .collect::<Vec<_>>()
185            .join("\n")
186    }
187}
188
189impl DbError {
190    /// 新建查询错误
191    pub fn query(s: impl Into<String>) -> Self {
192        DbError::QueryError(s.into())
193    }
194
195    /// 新建连接错误
196    pub fn connection(s: impl Into<String>) -> Self {
197        DbError::ConnectionError(s.into())
198    }
199
200    /// 新建未找到错误
201    pub fn not_found(s: impl Into<String>) -> Self {
202        DbError::NotFound(s.into())
203    }
204
205    /// #6 修复:附加错误上下文(消费 self,返回带上下文的新错误)
206    ///
207    /// 用于在错误传播路径上附加调用方上下文,形成 `error.with_context("operation")`
208    /// 链式调用。多次调用会形成上下文链表。
209    ///
210    /// # 示例
211    ///
212    /// ```ignore
213    /// fn fetch_user(id: i64) -> Result<User, DbError> {
214    ///     db.query("SELECT * FROM users WHERE id = ?", &[id.into()])
215    ///         .await
216    ///         .map_err(|e| e.with_context(format!("fetching user id={}", id)))?;
217    ///     // ...
218    /// }
219    /// ```
220    pub fn with_context(self, context: impl Into<String>) -> Self {
221        let new_ctx = ErrorContext::new(context);
222        // 若已是 Contextual,将原 context 链作为 previous
223        match self {
224            DbError::Contextual {
225                source,
226                context: existing_ctx,
227            } => {
228                let new_ctx = new_ctx.with_previous(existing_ctx);
229                DbError::Contextual {
230                    source,
231                    context: new_ctx,
232                }
233            }
234            other => DbError::Contextual {
235                source: Box::new(other),
236                context: new_ctx,
237            },
238        }
239    }
240
241    /// #6 修复:附加错误上下文(含 tracing span 名)
242    pub fn with_context_in_span(self, context: impl Into<String>, span: impl Into<String>) -> Self {
243        let new_ctx = ErrorContext::new(context).with_span(span);
244        match self {
245            DbError::Contextual {
246                source,
247                context: existing_ctx,
248            } => {
249                let new_ctx = new_ctx.with_previous(existing_ctx);
250                DbError::Contextual {
251                    source,
252                    context: new_ctx,
253                }
254            }
255            other => DbError::Contextual {
256                source: Box::new(other),
257                context: new_ctx,
258            },
259        }
260    }
261
262    /// #6 修复:获取错误上下文链(None 表示无附加上下文)
263    pub fn context(&self) -> Option<&ErrorContext> {
264        match self {
265            DbError::Contextual { context, .. } => Some(context),
266            _ => None,
267        }
268    }
269
270    /// #6 修复:格式化错误上下文链为多行字符串
271    pub fn format_context_chain(&self) -> String {
272        match self {
273            DbError::Contextual { context, .. } => context.format_chain(),
274            _ => String::new(),
275        }
276    }
277
278    /// #6 修复:剥离上下文链,返回原始错误引用
279    pub fn root_cause(&self) -> &DbError {
280        match self {
281            DbError::Contextual { source, .. } => source.root_cause(),
282            other => other,
283        }
284    }
285
286    /// 该错误是否可重试
287    pub fn is_retryable(&self) -> bool {
288        self.root_cause_is_retryable()
289    }
290
291    /// 内部方法:检查根错误是否可重试
292    fn root_cause_is_retryable(&self) -> bool {
293        match self {
294            DbError::Contextual { source, .. } => source.root_cause_is_retryable(),
295            DbError::ConnectionError(_)
296            | DbError::ConnectionTimeout(_)
297            | DbError::PoolError(PoolError::Timeout) => true,
298            _ => false,
299        }
300    }
301
302    /// 获取错误码(用于日志/监控)
303    pub fn error_code(&self) -> &'static str {
304        match self {
305            DbError::Contextual { source, .. } => source.error_code(),
306            DbError::QueryError(_) => "DB001",
307            DbError::ConnectionError(_) => "DB002",
308            DbError::ConnectionRefused(_) => "DB003",
309            DbError::ConnectionTimeout(_) => "DB004",
310            DbError::PoolError(e) => e.error_code(),
311            DbError::CacheError(e) => e.error_code(),
312            DbError::TxError(_) => "DB007",
313            DbError::MigrationError(_) => "DB008",
314            DbError::Unsupported(_) => "DB009",
315            DbError::ConfigError(_) => "DB010",
316            DbError::SerdeError(_) => "DB011",
317            DbError::NotFound(_) => "DB012",
318            DbError::AlreadyExists(_) => "DB013",
319            DbError::ConstraintViolation(_) => "DB014",
320            DbError::UniqueViolation(_) => "DB022",
321            DbError::ForeignKeyViolation(_) => "DB023",
322            DbError::NullValue(_) => "DB015",
323            DbError::InvalidInput(_) => "DB016",
324            DbError::Internal(_) => "DB017",
325            DbError::IoError(_) => "DB018",
326            DbError::Hook(_) => "DB019",
327            DbError::TenantError(_) => "DB020",
328            DbError::Validation(_) => "DB021",
329        }
330    }
331
332    /// 映射到 HTTP 状态码(RFC 7231)
333    ///
334    /// 用于在 HTTP 服务(如 axum/actix)中根据数据库错误返回合适的 HTTP 状态码。
335    /// - 400 Bad Request:非法输入、参数校验失败、配置错误
336    /// - 404 Not Found:资源未找到
337    /// - 409 Conflict:资源已存在、约束冲突(唯一/外键/非空/通用)
338    /// - 422 Unprocessable Entity:序列化/反序列化错误
339    /// - 500 Internal Server Error:查询失败、内部错误、钩子失败、迁移失败、IO 错误、事务错误、租户错误
340    /// - 501 Not Implemented:方言/功能不支持
341    /// - 502 Bad Gateway:连接错误、连接被拒绝
342    /// - 503 Service Unavailable:连接池耗尽/关闭、缓存错误
343    /// - 504 Gateway Timeout:连接超时、连接池获取超时
344    pub fn http_status(&self) -> u16 {
345        match self {
346            DbError::Contextual { source, .. } => source.http_status(),
347            DbError::InvalidInput(_) | DbError::Validation(_) | DbError::ConfigError(_) => 400,
348            DbError::NotFound(_) => 404,
349            DbError::AlreadyExists(_)
350            | DbError::ConstraintViolation(_)
351            | DbError::UniqueViolation(_)
352            | DbError::ForeignKeyViolation(_)
353            | DbError::NullValue(_) => 409,
354            DbError::SerdeError(_) => 422,
355            DbError::Unsupported(_) => 501,
356            DbError::ConnectionError(_) | DbError::ConnectionRefused(_) => 502,
357            DbError::ConnectionTimeout(_) => 504,
358            DbError::PoolError(e) => match e {
359                PoolError::Timeout => 504,
360                PoolError::Exhausted | PoolError::Closed | PoolError::ConnectionFailed(_) => 503,
361                _ => 500,
362            },
363            DbError::CacheError(_) => 503,
364            // QueryError/Internal/Hook/MigrationError/IoError/TxError/TenantError 均为服务端内部错误
365            _ => 500,
366        }
367    }
368
369    /// 映射到 gRPC 状态码
370    ///
371    /// 用于在 gRPC 服务(tonic)中根据数据库错误返回合适的 gRPC 状态码。
372    /// 参考:<https://grpc.io/docs/guides/status-codes/>
373    /// - 2 UNKNOWN:查询失败、内部错误、钩子失败、迁移失败、IO 错误
374    /// - 3 INVALID_ARGUMENT:非法输入、参数校验失败、配置错误
375    /// - 4 DEADLINE_EXCEEDED:连接超时、连接池获取超时
376    /// - 5 NOT_FOUND:资源未找到
377    /// - 6 ALREADY_EXISTS:资源已存在、唯一约束冲突
378    /// - 7 PERMISSION_DENIED:租户错误(跨租户访问)
379    /// - 8 RESOURCE_EXHAUSTED:连接池耗尽/关闭、缓存错误
380    /// - 9 FAILED_PRECONDITION:约束冲突(通用/外键/非空)、事务错误
381    /// - 12 UNIMPLEMENTED:方言/功能不支持
382    /// - 13 INTERNAL:序列化/反序列化错误
383    /// - 14 UNAVAILABLE:连接错误、连接被拒绝、连接创建失败
384    pub fn grpc_status_code(&self) -> u32 {
385        match self {
386            DbError::Contextual { source, .. } => source.grpc_status_code(),
387            DbError::InvalidInput(_) | DbError::Validation(_) | DbError::ConfigError(_) => 3,
388            DbError::ConnectionTimeout(_) => 4,
389            DbError::PoolError(PoolError::Timeout) => 4,
390            DbError::NotFound(_) => 5,
391            DbError::AlreadyExists(_) | DbError::UniqueViolation(_) => 6,
392            DbError::TenantError(_) => 7,
393            DbError::PoolError(PoolError::Exhausted) | DbError::PoolError(PoolError::Closed) => 8,
394            DbError::CacheError(_) => 8,
395            DbError::ConstraintViolation(_)
396            | DbError::ForeignKeyViolation(_)
397            | DbError::NullValue(_)
398            | DbError::TxError(_) => 9,
399            DbError::Unsupported(_) => 12,
400            DbError::SerdeError(_) => 13,
401            DbError::ConnectionError(_)
402            | DbError::ConnectionRefused(_)
403            | DbError::PoolError(PoolError::ConnectionFailed(_)) => 14,
404            // QueryError/Internal/Hook/MigrationError/IoError/PoolError(其他) → UNKNOWN
405            _ => 2,
406        }
407    }
408}
409
410impl fmt::Display for DbError {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        match self {
413            DbError::QueryError(s) => write!(f, "Query error: {}", s),
414            DbError::ConnectionError(s) => write!(f, "Connection error: {}", s),
415            DbError::ConnectionRefused(s) => write!(f, "Connection refused: {}", s),
416            DbError::ConnectionTimeout(s) => write!(f, "Connection timeout: {}", s),
417            DbError::PoolError(e) => write!(f, "Pool error: {}", e),
418            DbError::CacheError(e) => write!(f, "Cache error: {}", e),
419            DbError::TxError(e) => write!(f, "Transaction error: {}", e),
420            DbError::MigrationError(s) => write!(f, "Migration error: {}", s),
421            DbError::Unsupported(s) => write!(f, "Unsupported: {}", s),
422            DbError::ConfigError(s) => write!(f, "Configuration error: {}", s),
423            DbError::SerdeError(s) => write!(f, "Serialization error: {}", s),
424            DbError::NotFound(s) => write!(f, "Not found: {}", s),
425            DbError::AlreadyExists(s) => write!(f, "Already exists: {}", s),
426            DbError::ConstraintViolation(s) => write!(f, "Constraint violation: {}", s),
427            DbError::UniqueViolation(s) => write!(f, "Unique constraint violation: {}", s),
428            DbError::ForeignKeyViolation(s) => write!(f, "Foreign key constraint violation: {}", s),
429            DbError::NullValue(s) => write!(f, "Null value: {}", s),
430            DbError::InvalidInput(s) => write!(f, "Invalid input: {}", s),
431            DbError::Internal(s) => write!(f, "Internal error: {}", s),
432            DbError::IoError(s) => write!(f, "IO error: {}", s),
433            DbError::Hook(s) => write!(f, "Hook error: {}", s),
434            DbError::TenantError(s) => write!(f, "Tenant error: {}", s),
435            DbError::Validation(s) => write!(f, "Validation error: {}", s),
436            DbError::Contextual {
437                context, source, ..
438            } => write!(f, "{}: {}", context.context, source),
439        }
440    }
441}
442
443impl Error for DbError {
444    fn source(&self) -> Option<&(dyn Error + 'static)> {
445        match self {
446            DbError::PoolError(e) => Some(e),
447            DbError::CacheError(e) => Some(e),
448            DbError::TxError(e) => Some(e),
449            // #6 修复:暴露 Contextual 包装的原始错误,使 std::error::Error::source()
450            // 链式遍历可透过 Contextual 层到达根因
451            DbError::Contextual { source, .. } => Some(source.as_ref()),
452            _ => None,
453        }
454    }
455}
456
457impl From<io::Error> for DbError {
458    fn from(err: io::Error) -> Self {
459        DbError::IoError(err.to_string())
460    }
461}
462
463impl From<serde_json::Error> for DbError {
464    fn from(err: serde_json::Error) -> Self {
465        DbError::SerdeError(err.to_string())
466    }
467}
468
469impl From<std::num::TryFromIntError> for DbError {
470    fn from(err: std::num::TryFromIntError) -> Self {
471        DbError::Internal(err.to_string())
472    }
473}
474
475impl From<std::string::FromUtf8Error> for DbError {
476    fn from(err: std::string::FromUtf8Error) -> Self {
477        DbError::Internal(err.to_string())
478    }
479}
480
481impl<T> From<std::sync::PoisonError<T>> for DbError {
482    fn from(err: std::sync::PoisonError<T>) -> Self {
483        DbError::Internal(format!("RwLock/Mutex poisoned: {}", err))
484    }
485}
486
487/// 连接池特有错误
488#[derive(Debug)]
489pub enum PoolError {
490    /// 连接池耗尽
491    Exhausted,
492
493    /// 获取连接超时
494    Timeout,
495
496    /// 连接已被获取
497    AlreadyAcquired,
498
499    /// 连接未被获取
500    NotAcquired,
501
502    /// 配置非法
503    InvalidConfig(String),
504
505    /// 内部错误
506    Internal(String),
507
508    /// 连接池已关闭(close_all 后拒绝新 acquire)
509    Closed,
510
511    /// 连接创建失败(保留原始错误信息)
512    ConnectionFailed(String),
513
514    /// #88 修复:断路器已跳闸,拒绝请求以防级联失败
515    ///
516    /// 当 `circuit-breaker` feature 启用且 `CircuitBreaker` 处于 `Open` 状态时,
517    /// `acquire`/`query_with_timeout` 等方法会返回此错误,避免对下游数据库
518    /// 造成更大压力。
519    CircuitOpen,
520
521    /// #93 修复:限流器拒绝请求
522    ///
523    /// 当 `rate-limit` feature 启用且 `RateLimiter` 拒绝当前 key 时返回。
524    /// `remaining` 为本次窗口剩余配额(已为 0),`reset_at` 为窗口重置时间戳(毫秒)。
525    RateLimited {
526        /// 本次窗口剩余配额(已为 0)
527        remaining: u64,
528        /// 窗口重置时间戳(毫秒)
529        reset_at: i64,
530    },
531}
532
533impl PoolError {
534    /// 返回错误对应的业务错误码
535    pub fn error_code(&self) -> &'static str {
536        match self {
537            PoolError::Exhausted => "PL001",
538            PoolError::Timeout => "PL002",
539            PoolError::AlreadyAcquired => "PL003",
540            PoolError::NotAcquired => "PL004",
541            PoolError::InvalidConfig(_) => "PL005",
542            PoolError::Internal(_) => "PL006",
543            PoolError::Closed => "PL007",
544            PoolError::ConnectionFailed(_) => "PL008",
545            PoolError::CircuitOpen => "PL009",
546            PoolError::RateLimited { .. } => "PL010",
547        }
548    }
549}
550
551impl fmt::Display for PoolError {
552    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553        match self {
554            PoolError::Exhausted => write!(f, "Connection pool exhausted"),
555            PoolError::Timeout => write!(f, "Connection acquire timeout"),
556            PoolError::AlreadyAcquired => write!(f, "Connection already acquired"),
557            PoolError::NotAcquired => write!(f, "Connection not acquired"),
558            PoolError::InvalidConfig(s) => write!(f, "Invalid pool config: {}", s),
559            PoolError::Internal(s) => write!(f, "Internal pool error: {}", s),
560            PoolError::Closed => write!(f, "Connection pool closed"),
561            PoolError::ConnectionFailed(s) => write!(f, "Connection failed: {}", s),
562            PoolError::CircuitOpen => write!(f, "Circuit breaker open"),
563            PoolError::RateLimited {
564                remaining,
565                reset_at,
566            } => write!(
567                f,
568                "Rate limited (remaining: {}, reset_at: {})",
569                remaining, reset_at
570            ),
571        }
572    }
573}
574
575impl Error for PoolError {}
576
577/// 缓存特有错误
578#[derive(Debug, Clone)]
579pub enum CacheError {
580    /// 键不存在
581    NotFound(String),
582
583    /// 序列化错误
584    SerializationError(String),
585
586    /// 反序列化错误
587    DeserializationError(String),
588
589    /// 连接错误
590    ConnectionError(String),
591
592    /// 超时
593    Timeout(String),
594
595    /// 内部错误
596    Internal(String),
597}
598
599impl CacheError {
600    /// 返回错误对应的业务错误码
601    pub fn error_code(&self) -> &'static str {
602        match self {
603            CacheError::NotFound(_) => "CH001",
604            CacheError::SerializationError(_) => "CH002",
605            CacheError::DeserializationError(_) => "CH003",
606            CacheError::ConnectionError(_) => "CH004",
607            CacheError::Timeout(_) => "CH005",
608            CacheError::Internal(_) => "CH006",
609        }
610    }
611}
612
613impl fmt::Display for CacheError {
614    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615        match self {
616            CacheError::NotFound(s) => write!(f, "Cache key not found: {}", s),
617            CacheError::SerializationError(s) => write!(f, "Cache serialization error: {}", s),
618            CacheError::DeserializationError(s) => write!(f, "Cache deserialization error: {}", s),
619            CacheError::ConnectionError(s) => write!(f, "Cache connection error: {}", s),
620            CacheError::Timeout(s) => write!(f, "Cache timeout: {}", s),
621            CacheError::Internal(s) => write!(f, "Cache internal error: {}", s),
622        }
623    }
624}
625
626impl Error for CacheError {}
627
628impl<T> From<std::sync::PoisonError<T>> for CacheError {
629    fn from(err: std::sync::PoisonError<T>) -> Self {
630        CacheError::Internal(format!("RwLock poisoned: {}", err))
631    }
632}
633
634/// 事务状态
635///
636/// 定义在 `error` 模块以避免 `transaction` ↔ `error` 循环依赖,
637/// `transaction` 模块通过 `pub use` 重导出本类型。
638#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
639pub enum TransactionState {
640    /// 事务活跃中(已开始但未提交或回滚)
641    #[default]
642    Active,
643    /// 事务已提交
644    Committed,
645    /// 事务已回滚
646    RolledBack,
647}
648
649impl fmt::Display for TransactionState {
650    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651        match self {
652            TransactionState::Active => write!(f, "Active"),
653            TransactionState::Committed => write!(f, "Committed"),
654            TransactionState::RolledBack => write!(f, "RolledBack"),
655        }
656    }
657}
658
659/// 事务特有错误
660#[derive(Debug)]
661pub enum TxError {
662    /// 事务未开始
663    NotStarted,
664
665    /// 事务已开始
666    AlreadyStarted,
667
668    /// 事务提交失败
669    CommitFailed(String),
670
671    /// 事务回滚失败
672    RollbackFailed(String),
673
674    /// 保存点错误
675    SavepointError(String),
676
677    /// 不支持嵌套事务
678    NestedNotSupported,
679
680    /// 事务不在 Active 状态(用于 execute/query 等操作前置校验)
681    NotActive(TransactionState),
682
683    /// 保存点名称非法(包含不支持的字符或以数字开头)
684    InvalidSavepointName(String),
685
686    /// 连接已被取走(take_connection 重复调用,或操作时连接已释放)
687    ConnectionTaken,
688
689    /// H-8 修复:嵌套事务深度超过限制
690    ///
691    /// `current_depth` 为当前已嵌套深度(含本次),`max_depth` 为配置的最大深度。
692    MaxNestingDepthExceeded {
693        /// 当前已嵌套深度(含本次)
694        current_depth: u32,
695        /// 配置的最大深度
696        max_depth: u32,
697    },
698
699    /// M-8 修复:死锁检测
700    ///
701    /// 当事务执行过程中检测到死锁(数据库返回死锁错误码)时返回。
702    /// 调用方可使用 `retry_on_deadlock` 包装器自动重试。
703    DeadlockDetected {
704        /// 当前重试次数
705        attempt: u32,
706        /// 最大重试次数
707        max_attempts: u32,
708    },
709}
710
711impl fmt::Display for TxError {
712    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
713        match self {
714            TxError::NotStarted => write!(f, "Transaction not started"),
715            TxError::AlreadyStarted => write!(f, "Transaction already started"),
716            TxError::CommitFailed(s) => write!(f, "Transaction commit failed: {}", s),
717            TxError::RollbackFailed(s) => write!(f, "Transaction rollback failed: {}", s),
718            TxError::SavepointError(s) => write!(f, "Savepoint error: {}", s),
719            TxError::NestedNotSupported => write!(f, "Nested transactions not supported"),
720            TxError::NotActive(state) => {
721                write!(f, "Transaction not active (current state: {})", state)
722            }
723            TxError::InvalidSavepointName(name) => {
724                write!(
725                    f,
726                    "Invalid savepoint name '{}': must be non-empty, start with a letter or underscore, and contain only ASCII alphanumeric or underscore",
727                    name
728                )
729            }
730            TxError::ConnectionTaken => write!(f, "Transaction connection already taken"),
731            TxError::MaxNestingDepthExceeded {
732                current_depth,
733                max_depth,
734            } => write!(
735                f,
736                "Transaction nesting depth {} exceeds maximum allowed {}",
737                current_depth, max_depth
738            ),
739            TxError::DeadlockDetected {
740                attempt,
741                max_attempts,
742            } => write!(
743                f,
744                "Deadlock detected on attempt {} of {}",
745                attempt, max_attempts
746            ),
747        }
748    }
749}
750
751impl Error for TxError {
752    fn source(&self) -> Option<&(dyn Error + 'static)> {
753        // TxError 各变体仅承载 String 描述或状态枚举(无嵌套 Error 对象),故无 source 可委托
754        None
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761
762    #[test]
763    fn test_db_error_display() {
764        let err = DbError::query("test");
765        assert_eq!(format!("{}", err), "Query error: test");
766
767        let err = DbError::not_found("user");
768        assert_eq!(format!("{}", err), "Not found: user");
769    }
770
771    #[test]
772    fn test_db_error_code() {
773        let err = DbError::query("test");
774        assert_eq!(err.error_code(), "DB001");
775
776        let err = DbError::PoolError(PoolError::Timeout);
777        assert_eq!(err.error_code(), "PL002");
778    }
779
780    #[test]
781    fn test_db_error_source() {
782        let err = DbError::PoolError(PoolError::Timeout);
783        assert!(err.source().is_some());
784    }
785
786    #[test]
787    fn test_db_error_contextual_source_chain() {
788        // #6 修复验证:Contextual 错误的 source() 应直接指向根错误,
789        // 上下文链通过 ErrorContext.previous 维护(不通过 source() 链)
790        let root = DbError::QueryError("table not found".to_string());
791        let wrapped = root.with_context("fetching user");
792        let outer = wrapped.with_context("user_service.fetch");
793
794        // 1. std::error::Error::source() 应直接返回根 QueryError(跳过 Contextual 层)
795        let source1 = outer.source().expect("outer should have source");
796        // source1 应为根 QueryError,不再有 source
797        assert!(source1.source().is_none());
798
799        // 2. 上下文链应有两层:外层 "user_service.fetch",内层 "fetching user"
800        let ctx_chain = outer.context().expect("outer should have context");
801        assert_eq!(ctx_chain.context, "user_service.fetch");
802        let inner_ctx = ctx_chain
803            .previous
804            .as_ref()
805            .expect("should have previous context");
806        assert_eq!(inner_ctx.context, "fetching user");
807        assert!(inner_ctx.previous.is_none());
808
809        // 3. root_cause() 应返回根 QueryError
810        let root_cause = outer.root_cause();
811        assert!(matches!(root_cause, DbError::QueryError(_)));
812    }
813
814    #[test]
815    fn test_pool_error() {
816        let err = PoolError::Timeout;
817        assert_eq!(format!("{}", err), "Connection acquire timeout");
818        assert_eq!(err.error_code(), "PL002");
819    }
820
821    #[test]
822    fn test_cache_error() {
823        let err = CacheError::NotFound("key".to_string());
824        assert_eq!(format!("{}", err), "Cache key not found: key");
825        assert_eq!(err.error_code(), "CH001");
826    }
827
828    #[test]
829    fn test_error_hook_set_and_trigger() {
830        use std::sync::atomic::{AtomicU32, Ordering};
831        let counter = Arc::new(AtomicU32::new(0));
832        let c = counter.clone();
833        set_error_hook(Arc::new(move |_err: &DbError| {
834            c.fetch_add(1, Ordering::SeqCst);
835        }));
836        let err = DbError::query("hook test");
837        trigger_error_hook(&err);
838        assert_eq!(counter.load(Ordering::SeqCst), 1);
839    }
840
841    #[test]
842    fn test_error_hook_no_hook_silent() {
843        // 不设置 hook 时 trigger 应静默跳过(不 panic)
844        let err = DbError::query("no hook");
845        trigger_error_hook(&err);
846    }
847
848    // ===== HTTP 状态码映射测试 =====
849
850    #[test]
851    fn test_http_status_bad_request() {
852        assert_eq!(DbError::InvalidInput("bad".into()).http_status(), 400);
853        assert_eq!(DbError::Validation("fail".into()).http_status(), 400);
854        assert_eq!(DbError::ConfigError("cfg".into()).http_status(), 400);
855    }
856
857    #[test]
858    fn test_http_status_not_found() {
859        assert_eq!(DbError::NotFound("user".into()).http_status(), 404);
860    }
861
862    #[test]
863    fn test_http_status_conflict() {
864        assert_eq!(DbError::AlreadyExists("x".into()).http_status(), 409);
865        assert_eq!(DbError::ConstraintViolation("c".into()).http_status(), 409);
866        assert_eq!(DbError::UniqueViolation("u".into()).http_status(), 409);
867        assert_eq!(DbError::ForeignKeyViolation("f".into()).http_status(), 409);
868        assert_eq!(DbError::NullValue("n".into()).http_status(), 409);
869    }
870
871    #[test]
872    fn test_http_status_unprocessable() {
873        assert_eq!(DbError::SerdeError("s".into()).http_status(), 422);
874    }
875
876    #[test]
877    fn test_http_status_internal_server_error() {
878        assert_eq!(DbError::QueryError("q".into()).http_status(), 500);
879        assert_eq!(DbError::Internal("i".into()).http_status(), 500);
880        assert_eq!(DbError::Hook("h".into()).http_status(), 500);
881        assert_eq!(DbError::MigrationError("m".into()).http_status(), 500);
882        assert_eq!(DbError::IoError("io".into()).http_status(), 500);
883        assert_eq!(DbError::TxError(TxError::NotStarted).http_status(), 500);
884        assert_eq!(DbError::TenantError("t".into()).http_status(), 500);
885    }
886
887    #[test]
888    fn test_http_status_not_implemented() {
889        assert_eq!(DbError::Unsupported("feat".into()).http_status(), 501);
890    }
891
892    #[test]
893    fn test_http_status_bad_gateway() {
894        assert_eq!(DbError::ConnectionError("c".into()).http_status(), 502);
895        assert_eq!(DbError::ConnectionRefused("r".into()).http_status(), 502);
896    }
897
898    #[test]
899    fn test_http_status_service_unavailable() {
900        assert_eq!(DbError::PoolError(PoolError::Exhausted).http_status(), 503);
901        assert_eq!(DbError::PoolError(PoolError::Closed).http_status(), 503);
902        assert_eq!(
903            DbError::PoolError(PoolError::ConnectionFailed("f".into())).http_status(),
904            503
905        );
906        assert_eq!(
907            DbError::CacheError(CacheError::Internal("e".into())).http_status(),
908            503
909        );
910    }
911
912    #[test]
913    fn test_http_status_gateway_timeout() {
914        assert_eq!(DbError::ConnectionTimeout("t".into()).http_status(), 504);
915        assert_eq!(DbError::PoolError(PoolError::Timeout).http_status(), 504);
916    }
917
918    // ===== gRPC 状态码映射测试 =====
919
920    #[test]
921    fn test_grpc_status_invalid_argument() {
922        assert_eq!(DbError::InvalidInput("bad".into()).grpc_status_code(), 3);
923        assert_eq!(DbError::Validation("fail".into()).grpc_status_code(), 3);
924        assert_eq!(DbError::ConfigError("cfg".into()).grpc_status_code(), 3);
925    }
926
927    #[test]
928    fn test_grpc_status_deadline_exceeded() {
929        assert_eq!(DbError::ConnectionTimeout("t".into()).grpc_status_code(), 4);
930        assert_eq!(DbError::PoolError(PoolError::Timeout).grpc_status_code(), 4);
931    }
932
933    #[test]
934    fn test_grpc_status_not_found() {
935        assert_eq!(DbError::NotFound("user".into()).grpc_status_code(), 5);
936    }
937
938    #[test]
939    fn test_grpc_status_already_exists() {
940        assert_eq!(DbError::AlreadyExists("x".into()).grpc_status_code(), 6);
941        assert_eq!(DbError::UniqueViolation("u".into()).grpc_status_code(), 6);
942    }
943
944    #[test]
945    fn test_grpc_status_permission_denied() {
946        assert_eq!(DbError::TenantError("t".into()).grpc_status_code(), 7);
947    }
948
949    #[test]
950    fn test_grpc_status_resource_exhausted() {
951        assert_eq!(
952            DbError::PoolError(PoolError::Exhausted).grpc_status_code(),
953            8
954        );
955        assert_eq!(DbError::PoolError(PoolError::Closed).grpc_status_code(), 8);
956        assert_eq!(
957            DbError::CacheError(CacheError::Internal("e".into())).grpc_status_code(),
958            8
959        );
960    }
961
962    #[test]
963    fn test_grpc_status_failed_precondition() {
964        assert_eq!(
965            DbError::ConstraintViolation("c".into()).grpc_status_code(),
966            9
967        );
968        assert_eq!(
969            DbError::ForeignKeyViolation("f".into()).grpc_status_code(),
970            9
971        );
972        assert_eq!(DbError::NullValue("n".into()).grpc_status_code(), 9);
973        assert_eq!(DbError::TxError(TxError::NotStarted).grpc_status_code(), 9);
974    }
975
976    #[test]
977    fn test_grpc_status_unimplemented() {
978        assert_eq!(DbError::Unsupported("feat".into()).grpc_status_code(), 12);
979    }
980
981    #[test]
982    fn test_grpc_status_internal() {
983        assert_eq!(DbError::SerdeError("s".into()).grpc_status_code(), 13);
984    }
985
986    #[test]
987    fn test_grpc_status_unavailable() {
988        assert_eq!(DbError::ConnectionError("c".into()).grpc_status_code(), 14);
989        assert_eq!(
990            DbError::ConnectionRefused("r".into()).grpc_status_code(),
991            14
992        );
993        assert_eq!(
994            DbError::PoolError(PoolError::ConnectionFailed("f".into())).grpc_status_code(),
995            14
996        );
997    }
998
999    #[test]
1000    fn test_grpc_status_unknown() {
1001        assert_eq!(DbError::QueryError("q".into()).grpc_status_code(), 2);
1002        assert_eq!(DbError::Internal("i".into()).grpc_status_code(), 2);
1003        assert_eq!(DbError::Hook("h".into()).grpc_status_code(), 2);
1004        assert_eq!(DbError::MigrationError("m".into()).grpc_status_code(), 2);
1005        assert_eq!(DbError::IoError("io".into()).grpc_status_code(), 2);
1006        // PoolError 的其他变体(AlreadyAcquired/NotAcquired/InvalidConfig/Internal)→ UNKNOWN
1007        assert_eq!(
1008            DbError::PoolError(PoolError::AlreadyAcquired).grpc_status_code(),
1009            2
1010        );
1011        assert_eq!(
1012            DbError::PoolError(PoolError::NotAcquired).grpc_status_code(),
1013            2
1014        );
1015        assert_eq!(
1016            DbError::PoolError(PoolError::InvalidConfig("x".into())).grpc_status_code(),
1017            2
1018        );
1019        assert_eq!(
1020            DbError::PoolError(PoolError::Internal("y".into())).grpc_status_code(),
1021            2
1022        );
1023    }
1024}