1use std::error::Error;
6use std::fmt;
7use std::io;
8
9#[derive(Debug)]
11pub enum DbError {
12 QueryError(String),
14
15 ConnectionError(String),
17
18 ConnectionRefused(String),
20
21 ConnectionTimeout(String),
23
24 PoolError(PoolError),
26
27 CacheError(CacheError),
29
30 TxError(TxError),
32
33 MigrationError(String),
35
36 Unsupported(String),
38
39 ConfigError(String),
41
42 SerdeError(String),
44
45 NotFound(String),
47
48 AlreadyExists(String),
50
51 ConstraintViolation(String),
53
54 NullValue(String),
56
57 InvalidInput(String),
59
60 Internal(String),
62
63 IoError(String),
65
66 Hook(String),
68
69 TenantError(String),
71
72 Validation(String),
74}
75
76impl DbError {
77 pub fn query(s: impl Into<String>) -> Self {
79 DbError::QueryError(s.into())
80 }
81
82 pub fn connection(s: impl Into<String>) -> Self {
84 DbError::ConnectionError(s.into())
85 }
86
87 pub fn not_found(s: impl Into<String>) -> Self {
89 DbError::NotFound(s.into())
90 }
91
92 pub fn is_retryable(&self) -> bool {
94 matches!(
95 self,
96 DbError::ConnectionError(_)
97 | DbError::ConnectionTimeout(_)
98 | DbError::PoolError(PoolError::Timeout)
99 )
100 }
101
102 pub fn error_code(&self) -> &'static str {
104 match self {
105 DbError::QueryError(_) => "DB001",
106 DbError::ConnectionError(_) => "DB002",
107 DbError::ConnectionRefused(_) => "DB003",
108 DbError::ConnectionTimeout(_) => "DB004",
109 DbError::PoolError(e) => e.error_code(),
110 DbError::CacheError(e) => e.error_code(),
111 DbError::TxError(_) => "DB007",
112 DbError::MigrationError(_) => "DB008",
113 DbError::Unsupported(_) => "DB009",
114 DbError::ConfigError(_) => "DB010",
115 DbError::SerdeError(_) => "DB011",
116 DbError::NotFound(_) => "DB012",
117 DbError::AlreadyExists(_) => "DB013",
118 DbError::ConstraintViolation(_) => "DB014",
119 DbError::NullValue(_) => "DB015",
120 DbError::InvalidInput(_) => "DB016",
121 DbError::Internal(_) => "DB017",
122 DbError::IoError(_) => "DB018",
123 DbError::Hook(_) => "DB019",
124 DbError::TenantError(_) => "DB020",
125 DbError::Validation(_) => "DB021",
126 }
127 }
128}
129
130impl fmt::Display for DbError {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 DbError::QueryError(s) => write!(f, "Query error: {}", s),
134 DbError::ConnectionError(s) => write!(f, "Connection error: {}", s),
135 DbError::ConnectionRefused(s) => write!(f, "Connection refused: {}", s),
136 DbError::ConnectionTimeout(s) => write!(f, "Connection timeout: {}", s),
137 DbError::PoolError(e) => write!(f, "Pool error: {}", e),
138 DbError::CacheError(e) => write!(f, "Cache error: {}", e),
139 DbError::TxError(e) => write!(f, "Transaction error: {}", e),
140 DbError::MigrationError(s) => write!(f, "Migration error: {}", s),
141 DbError::Unsupported(s) => write!(f, "Unsupported: {}", s),
142 DbError::ConfigError(s) => write!(f, "Configuration error: {}", s),
143 DbError::SerdeError(s) => write!(f, "Serialization error: {}", s),
144 DbError::NotFound(s) => write!(f, "Not found: {}", s),
145 DbError::AlreadyExists(s) => write!(f, "Already exists: {}", s),
146 DbError::ConstraintViolation(s) => write!(f, "Constraint violation: {}", s),
147 DbError::NullValue(s) => write!(f, "Null value: {}", s),
148 DbError::InvalidInput(s) => write!(f, "Invalid input: {}", s),
149 DbError::Internal(s) => write!(f, "Internal error: {}", s),
150 DbError::IoError(s) => write!(f, "IO error: {}", s),
151 DbError::Hook(s) => write!(f, "Hook error: {}", s),
152 DbError::TenantError(s) => write!(f, "Tenant error: {}", s),
153 DbError::Validation(s) => write!(f, "Validation error: {}", s),
154 }
155 }
156}
157
158impl Error for DbError {
159 fn source(&self) -> Option<&(dyn Error + 'static)> {
160 match self {
161 DbError::PoolError(e) => Some(e),
162 DbError::CacheError(e) => Some(e),
163 _ => None,
164 }
165 }
166}
167
168impl From<io::Error> for DbError {
169 fn from(err: io::Error) -> Self {
170 DbError::IoError(err.to_string())
171 }
172}
173
174impl From<serde_json::Error> for DbError {
175 fn from(err: serde_json::Error) -> Self {
176 DbError::SerdeError(err.to_string())
177 }
178}
179
180impl From<std::num::TryFromIntError> for DbError {
181 fn from(err: std::num::TryFromIntError) -> Self {
182 DbError::Internal(err.to_string())
183 }
184}
185
186impl From<std::string::FromUtf8Error> for DbError {
187 fn from(err: std::string::FromUtf8Error) -> Self {
188 DbError::Internal(err.to_string())
189 }
190}
191
192impl<T> From<std::sync::PoisonError<T>> for DbError {
193 fn from(err: std::sync::PoisonError<T>) -> Self {
194 DbError::Internal(format!("RwLock/Mutex poisoned: {}", err))
195 }
196}
197
198#[derive(Debug)]
200pub enum PoolError {
201 Exhausted,
203
204 Timeout,
206
207 AlreadyAcquired,
209
210 NotAcquired,
212
213 InvalidConfig(String),
215
216 Internal(String),
218
219 Closed,
221
222 ConnectionFailed(String),
224}
225
226impl PoolError {
227 pub fn error_code(&self) -> &'static str {
228 match self {
229 PoolError::Exhausted => "PL001",
230 PoolError::Timeout => "PL002",
231 PoolError::AlreadyAcquired => "PL003",
232 PoolError::NotAcquired => "PL004",
233 PoolError::InvalidConfig(_) => "PL005",
234 PoolError::Internal(_) => "PL006",
235 PoolError::Closed => "PL007",
236 PoolError::ConnectionFailed(_) => "PL008",
237 }
238 }
239}
240
241impl fmt::Display for PoolError {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 match self {
244 PoolError::Exhausted => write!(f, "Connection pool exhausted"),
245 PoolError::Timeout => write!(f, "Connection acquire timeout"),
246 PoolError::AlreadyAcquired => write!(f, "Connection already acquired"),
247 PoolError::NotAcquired => write!(f, "Connection not acquired"),
248 PoolError::InvalidConfig(s) => write!(f, "Invalid pool config: {}", s),
249 PoolError::Internal(s) => write!(f, "Internal pool error: {}", s),
250 PoolError::Closed => write!(f, "Connection pool closed"),
251 PoolError::ConnectionFailed(s) => write!(f, "Connection failed: {}", s),
252 }
253 }
254}
255
256impl Error for PoolError {}
257
258#[derive(Debug)]
260pub enum CacheError {
261 NotFound(String),
263
264 SerializationError(String),
266
267 DeserializationError(String),
269
270 ConnectionError(String),
272
273 Timeout(String),
275
276 Internal(String),
278}
279
280impl CacheError {
281 pub fn error_code(&self) -> &'static str {
282 match self {
283 CacheError::NotFound(_) => "CH001",
284 CacheError::SerializationError(_) => "CH002",
285 CacheError::DeserializationError(_) => "CH003",
286 CacheError::ConnectionError(_) => "CH004",
287 CacheError::Timeout(_) => "CH005",
288 CacheError::Internal(_) => "CH006",
289 }
290 }
291}
292
293impl fmt::Display for CacheError {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 match self {
296 CacheError::NotFound(s) => write!(f, "Cache key not found: {}", s),
297 CacheError::SerializationError(s) => write!(f, "Cache serialization error: {}", s),
298 CacheError::DeserializationError(s) => write!(f, "Cache deserialization error: {}", s),
299 CacheError::ConnectionError(s) => write!(f, "Cache connection error: {}", s),
300 CacheError::Timeout(s) => write!(f, "Cache timeout: {}", s),
301 CacheError::Internal(s) => write!(f, "Cache internal error: {}", s),
302 }
303 }
304}
305
306impl Error for CacheError {}
307
308impl<T> From<std::sync::PoisonError<T>> for CacheError {
309 fn from(err: std::sync::PoisonError<T>) -> Self {
310 CacheError::Internal(format!("RwLock poisoned: {}", err))
311 }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
319pub enum TransactionState {
320 #[default]
321 Active,
322 Committed,
323 RolledBack,
324}
325
326impl fmt::Display for TransactionState {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 match self {
329 TransactionState::Active => write!(f, "Active"),
330 TransactionState::Committed => write!(f, "Committed"),
331 TransactionState::RolledBack => write!(f, "RolledBack"),
332 }
333 }
334}
335
336#[derive(Debug)]
338pub enum TxError {
339 NotStarted,
341
342 AlreadyStarted,
344
345 CommitFailed(String),
347
348 RollbackFailed(String),
350
351 SavepointError(String),
353
354 NestedNotSupported,
356
357 NotActive(TransactionState),
359
360 InvalidSavepointName(String),
362
363 ConnectionTaken,
365
366 MaxNestingDepthExceeded { current_depth: u32, max_depth: u32 },
370
371 DeadlockDetected { attempt: u32, max_attempts: u32 },
376}
377
378impl fmt::Display for TxError {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 match self {
381 TxError::NotStarted => write!(f, "Transaction not started"),
382 TxError::AlreadyStarted => write!(f, "Transaction already started"),
383 TxError::CommitFailed(s) => write!(f, "Transaction commit failed: {}", s),
384 TxError::RollbackFailed(s) => write!(f, "Transaction rollback failed: {}", s),
385 TxError::SavepointError(s) => write!(f, "Savepoint error: {}", s),
386 TxError::NestedNotSupported => write!(f, "Nested transactions not supported"),
387 TxError::NotActive(state) => {
388 write!(f, "Transaction not active (current state: {})", state)
389 }
390 TxError::InvalidSavepointName(name) => {
391 write!(
392 f,
393 "Invalid savepoint name '{}': must be non-empty, start with a letter or underscore, and contain only ASCII alphanumeric or underscore",
394 name
395 )
396 }
397 TxError::ConnectionTaken => write!(f, "Transaction connection already taken"),
398 TxError::MaxNestingDepthExceeded {
399 current_depth,
400 max_depth,
401 } => write!(
402 f,
403 "Transaction nesting depth {} exceeds maximum allowed {}",
404 current_depth, max_depth
405 ),
406 TxError::DeadlockDetected {
407 attempt,
408 max_attempts,
409 } => write!(
410 f,
411 "Deadlock detected on attempt {} of {}",
412 attempt, max_attempts
413 ),
414 }
415 }
416}
417
418impl Error for TxError {
419 fn source(&self) -> Option<&(dyn Error + 'static)> {
420 None
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 #[test]
430 fn test_db_error_display() {
431 let err = DbError::query("test");
432 assert_eq!(format!("{}", err), "Query error: test");
433
434 let err = DbError::not_found("user");
435 assert_eq!(format!("{}", err), "Not found: user");
436 }
437
438 #[test]
439 fn test_db_error_code() {
440 let err = DbError::query("test");
441 assert_eq!(err.error_code(), "DB001");
442
443 let err = DbError::PoolError(PoolError::Timeout);
444 assert_eq!(err.error_code(), "PL002");
445 }
446
447 #[test]
448 fn test_db_error_source() {
449 let err = DbError::PoolError(PoolError::Timeout);
450 assert!(err.source().is_some());
451 }
452
453 #[test]
454 fn test_pool_error() {
455 let err = PoolError::Timeout;
456 assert_eq!(format!("{}", err), "Connection acquire timeout");
457 assert_eq!(err.error_code(), "PL002");
458 }
459
460 #[test]
461 fn test_cache_error() {
462 let err = CacheError::NotFound("key".to_string());
463 assert_eq!(format!("{}", err), "Cache key not found: key");
464 assert_eq!(err.error_code(), "CH001");
465 }
466}