Skip to main content

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