Skip to main content

yang_db/
error.rs

1/// 数据库错误类型
2#[derive(Debug, thiserror::Error)]
3pub enum DbError {
4    #[error("连接错误: {0}")]
5    ConnectionError(String),
6
7    #[error("查询错误: {0}")]
8    QueryError(String),
9
10    #[error("SQL 语法错误: {0}")]
11    SqlSyntaxError(String),
12
13    #[error("约束错误: {0}")]
14    ConstraintError(String),
15
16    #[error("类型转换错误: {0}")]
17    TypeConversionError(String),
18
19    #[error("序列化错误: {0}")]
20    SerializationError(String),
21
22    #[error("反序列化错误: {0}")]
23    DeserializationError(String),
24
25    #[error("事务错误: {0}")]
26    TransactionError(String),
27
28    #[error("表不存在: {0}")]
29    TableNotFound(String),
30
31    #[error("缺少 WHERE 条件,禁止全表操作")]
32    MissingWhereClause,
33
34    // Redis 相关错误
35    #[error("Redis 连接错误: {0}")]
36    RedisConnectionError(String),
37
38    #[error("Redis 命令错误: {0}")]
39    RedisCommandError(String),
40
41    #[error("Redis 连接池错误: {0}")]
42    RedisPoolError(String),
43
44    #[error("Redis 类型转换错误: {0}")]
45    RedisTypeConversionError(String),
46
47    #[error("Redis 超时错误: {0}")]
48    RedisTimeoutError(String),
49
50    #[error("未知错误: {0}")]
51    Unknown(String),
52}
53
54/// 从 sqlx::Error 转换为 DbError
55impl From<sqlx::Error> for DbError {
56    fn from(err: sqlx::Error) -> Self {
57        match err {
58            sqlx::Error::Configuration(_) => DbError::ConnectionError(format!("配置错误: {}", err)),
59            sqlx::Error::Database(db_err) => {
60                let code = db_err.code().unwrap_or_default();
61                let message = db_err.message();
62
63                // MySQL 错误码映射
64                match code.as_ref() {
65                    "23000" => DbError::ConstraintError(message.to_string()),
66                    "42S02" => DbError::TableNotFound(message.to_string()),
67                    "42000" => DbError::SqlSyntaxError(message.to_string()),
68                    _ => DbError::QueryError(format!("数据库错误 [{}]: {}", code, message)),
69                }
70            }
71            sqlx::Error::Io(_) => DbError::ConnectionError(format!("IO 错误: {}", err)),
72            sqlx::Error::Tls(_) => DbError::ConnectionError(format!("TLS 错误: {}", err)),
73            sqlx::Error::Protocol(_) => DbError::QueryError(format!("协议错误: {}", err)),
74            sqlx::Error::RowNotFound => DbError::QueryError("未找到记录".to_string()),
75            sqlx::Error::TypeNotFound { type_name } => {
76                DbError::TypeConversionError(format!("类型未找到: {}", type_name))
77            }
78            sqlx::Error::ColumnIndexOutOfBounds { index, len } => {
79                DbError::QueryError(format!("列索引越界: {} (总列数: {})", index, len))
80            }
81            sqlx::Error::ColumnNotFound(col) => DbError::QueryError(format!("列不存在: {}", col)),
82            sqlx::Error::ColumnDecode { index, source } => {
83                DbError::TypeConversionError(format!("列 {} 解码失败: {}", index, source))
84            }
85            sqlx::Error::Decode(source) => {
86                DbError::DeserializationError(format!("解码失败: {}", source))
87            }
88            sqlx::Error::PoolTimedOut => DbError::ConnectionError("连接池超时".to_string()),
89            sqlx::Error::PoolClosed => DbError::ConnectionError("连接池已关闭".to_string()),
90            sqlx::Error::WorkerCrashed => DbError::ConnectionError("工作线程崩溃".to_string()),
91            _ => DbError::Unknown(format!("未知错误: {}", err)),
92        }
93    }
94}
95
96/// 从 redis::RedisError 转换为 DbError
97impl From<redis::RedisError> for DbError {
98    fn from(err: redis::RedisError) -> Self {
99        // redis 1.1.0 中的 ErrorKind 是不同的,我们直接根据错误信息判断
100        let err_str = format!("{}", err);
101
102        if err_str.contains("IO error") || err_str.contains("Connection") {
103            DbError::RedisConnectionError(format!("IO 错误: {}", err))
104        } else if err_str.contains("type") || err_str.contains("Type") {
105            DbError::RedisTypeConversionError(format!("类型错误: {}", err))
106        } else if err_str.contains("timeout") || err_str.contains("Timeout") {
107            DbError::RedisTimeoutError(format!("超时错误: {}", err))
108        } else {
109            DbError::RedisCommandError(format!("Redis 错误: {}", err))
110        }
111    }
112}
113
114/// 从 deadpool_redis::PoolError 转换为 DbError
115impl From<deadpool_redis::PoolError> for DbError {
116    fn from(err: deadpool_redis::PoolError) -> Self {
117        match err {
118            deadpool_redis::PoolError::Timeout(_) => {
119                DbError::RedisTimeoutError("连接池获取连接超时".to_string())
120            }
121            deadpool_redis::PoolError::Closed => {
122                DbError::RedisPoolError("连接池已关闭".to_string())
123            }
124            deadpool_redis::PoolError::NoRuntimeSpecified => {
125                DbError::RedisPoolError("未指定运行时".to_string())
126            }
127            deadpool_redis::PoolError::PostCreateHook(source) => {
128                DbError::RedisPoolError(format!("连接创建后钩子失败: {:?}", source))
129            }
130            deadpool_redis::PoolError::Backend(err) => DbError::from(err),
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_error_display_chinese() {
141        // 测试所有错误类型的中文消息
142        let errors = vec![
143            DbError::ConnectionError("测试".to_string()),
144            DbError::QueryError("测试".to_string()),
145            DbError::SqlSyntaxError("测试".to_string()),
146            DbError::ConstraintError("测试".to_string()),
147            DbError::TypeConversionError("测试".to_string()),
148            DbError::SerializationError("测试".to_string()),
149            DbError::DeserializationError("测试".to_string()),
150            DbError::TransactionError("测试".to_string()),
151            DbError::TableNotFound("测试".to_string()),
152            DbError::MissingWhereClause,
153            DbError::Unknown("测试".to_string()),
154        ];
155
156        for error in errors {
157            let msg = format!("{}", error);
158            // 验证错误消息包含中文字符
159            let has_chinese = msg.chars().any(|c| matches!(c, '\u{4e00}'..='\u{9fff}'));
160            assert!(has_chinese, "错误消息应该包含中文: {}", msg);
161        }
162    }
163
164    #[test]
165    fn test_connection_error() {
166        let err = DbError::ConnectionError("无法连接到数据库".to_string());
167        assert_eq!(format!("{}", err), "连接错误: 无法连接到数据库");
168    }
169
170    #[test]
171    fn test_query_error() {
172        let err = DbError::QueryError("查询超时".to_string());
173        assert_eq!(format!("{}", err), "查询错误: 查询超时");
174    }
175
176    #[test]
177    fn test_sql_syntax_error() {
178        let err = DbError::SqlSyntaxError("语法错误".to_string());
179        assert_eq!(format!("{}", err), "SQL 语法错误: 语法错误");
180    }
181
182    #[test]
183    fn test_constraint_error() {
184        let err = DbError::ConstraintError("主键冲突".to_string());
185        assert_eq!(format!("{}", err), "约束错误: 主键冲突");
186    }
187
188    #[test]
189    fn test_type_conversion_error() {
190        let err = DbError::TypeConversionError("无法转换类型".to_string());
191        assert_eq!(format!("{}", err), "类型转换错误: 无法转换类型");
192    }
193
194    #[test]
195    fn test_serialization_error() {
196        let err = DbError::SerializationError("序列化失败".to_string());
197        assert_eq!(format!("{}", err), "序列化错误: 序列化失败");
198    }
199
200    #[test]
201    fn test_deserialization_error() {
202        let err = DbError::DeserializationError("反序列化失败".to_string());
203        assert_eq!(format!("{}", err), "反序列化错误: 反序列化失败");
204    }
205
206    #[test]
207    fn test_transaction_error() {
208        let err = DbError::TransactionError("事务已提交".to_string());
209        assert_eq!(format!("{}", err), "事务错误: 事务已提交");
210    }
211
212    #[test]
213    fn test_table_not_found() {
214        let err = DbError::TableNotFound("users".to_string());
215        assert_eq!(format!("{}", err), "表不存在: users");
216    }
217
218    #[test]
219    fn test_missing_where_clause() {
220        let err = DbError::MissingWhereClause;
221        assert_eq!(format!("{}", err), "缺少 WHERE 条件,禁止全表操作");
222    }
223
224    #[test]
225    fn test_unknown_error() {
226        let err = DbError::Unknown("未知问题".to_string());
227        assert_eq!(format!("{}", err), "未知错误: 未知问题");
228    }
229
230    #[test]
231    fn test_error_implements_std_error() {
232        // 验证 DbError 实现了 std::error::Error trait
233        let err = DbError::QueryError("测试".to_string());
234        let _: &dyn std::error::Error = &err;
235    }
236
237    // Redis 错误测试
238    #[test]
239    fn test_redis_connection_error() {
240        let err = DbError::RedisConnectionError("连接失败".to_string());
241        assert_eq!(format!("{}", err), "Redis 连接错误: 连接失败");
242    }
243
244    #[test]
245    fn test_redis_command_error() {
246        let err = DbError::RedisCommandError("命令执行失败".to_string());
247        assert_eq!(format!("{}", err), "Redis 命令错误: 命令执行失败");
248    }
249
250    #[test]
251    fn test_redis_pool_error() {
252        let err = DbError::RedisPoolError("连接池错误".to_string());
253        assert_eq!(format!("{}", err), "Redis 连接池错误: 连接池错误");
254    }
255
256    #[test]
257    fn test_redis_type_conversion_error() {
258        let err = DbError::RedisTypeConversionError("类型转换失败".to_string());
259        assert_eq!(format!("{}", err), "Redis 类型转换错误: 类型转换失败");
260    }
261
262    #[test]
263    fn test_redis_timeout_error() {
264        let err = DbError::RedisTimeoutError("操作超时".to_string());
265        assert_eq!(format!("{}", err), "Redis 超时错误: 操作超时");
266    }
267
268    #[test]
269    fn test_pool_error_conversion() {
270        // 测试 deadpool_redis::PoolError 转换
271        let pool_err = deadpool_redis::PoolError::Closed;
272        let db_err: DbError = pool_err.into();
273        assert!(matches!(db_err, DbError::RedisPoolError(_)));
274    }
275}