Skip to main content

sz_orm_core/
error.rs

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