1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8pub const STATUS_AUTH: &str = "28000";
10pub const STATUS_PASSWORD_RESET: &str = "08P01";
12pub const STATUS_SYNTAX: &str = "42000";
14
15#[derive(Error, Debug)]
17pub enum Error {
18 #[error("Connection error: {0}")]
20 Connection(String),
21
22 #[error("Query error: {code} - {message}")]
24 Query { code: String, message: String },
25
26 #[error("Authentication error: {0}")]
28 Auth(String),
29
30 #[error("I/O error: {0}")]
32 Io(#[from] std::io::Error),
33
34 #[error("JSON error: {0}")]
36 Json(#[from] serde_json::Error),
37
38 #[error("QUIC error: {0}")]
40 Quic(String),
41
42 #[error("TLS error: {0}")]
44 Tls(String),
45
46 #[error("Invalid DSN: {0}")]
48 InvalidDsn(String),
49
50 #[error("Type error: {0}")]
52 Type(String),
53
54 #[error("Operation timed out")]
56 Timeout,
57
58 #[error("Pool error: {0}")]
60 Pool(String),
61
62 #[error("Validation error: {0}")]
64 Validation(String),
65
66 #[error("Limit exceeded: {0}")]
68 Limit(String),
69
70 #[error("{0}")]
72 Other(String),
73}
74
75impl Error {
76 pub fn connection<S: Into<String>>(msg: S) -> Self {
78 Error::Connection(msg.into())
79 }
80
81 pub fn query<S: Into<String>>(msg: S) -> Self {
83 Error::Query {
84 code: "QUERY_ERROR".to_string(),
85 message: msg.into(),
86 }
87 }
88
89 pub fn protocol<S: Into<String>>(msg: S) -> Self {
91 Error::Connection(format!("Protocol error: {}", msg.into()))
92 }
93
94 pub fn transaction<S: Into<String>>(msg: S) -> Self {
96 Error::Connection(format!("Transaction error: {}", msg.into()))
97 }
98
99 pub fn timeout() -> Self {
101 Error::Timeout
102 }
103
104 pub fn auth<S: Into<String>>(msg: S) -> Self {
106 Error::Auth(msg.into())
107 }
108
109 pub fn quic<S: Into<String>>(msg: S) -> Self {
111 Error::Quic(msg.into())
112 }
113
114 pub fn tls<S: Into<String>>(msg: S) -> Self {
116 Error::Tls(msg.into())
117 }
118
119 pub fn type_error<S: Into<String>>(msg: S) -> Self {
121 Error::Type(msg.into())
122 }
123
124 pub fn pool<S: Into<String>>(msg: S) -> Self {
126 Error::Pool(msg.into())
127 }
128
129 pub fn validation<S: Into<String>>(msg: S) -> Self {
131 Error::Validation(msg.into())
132 }
133
134 pub fn limit<S: Into<String>>(msg: S) -> Self {
136 Error::Limit(msg.into())
137 }
138
139 pub fn invalid_dsn<S: Into<String>>(msg: S) -> Self {
141 Error::InvalidDsn(msg.into())
142 }
143
144 #[inline]
152 pub fn is_retryable(&self) -> bool {
153 match self {
154 Error::Connection(_) => true,
155 Error::Timeout => true,
156 Error::Quic(_) => true,
157 Error::Query { code, .. } => {
158 code == "40001" || code == "40P01" || code == "40502"
160 }
161 Error::Pool(_) => true,
162 _ => false,
163 }
164 }
165
166 pub fn code(&self) -> Option<&str> {
168 match self {
169 Error::Query { code, .. } => Some(code),
170 _ => None,
171 }
172 }
173
174 #[inline]
182 pub fn is_auth_error(&self) -> bool {
183 matches!(
184 self,
185 Error::Query { code, .. } if code == STATUS_AUTH || code == STATUS_PASSWORD_RESET
186 )
187 }
188
189 #[inline]
197 pub fn is_syntax_error(&self) -> bool {
198 matches!(self, Error::Query { code, .. } if code == STATUS_SYNTAX)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use std::io;
206
207 #[test]
208 fn test_error_display_connection() {
209 let err = Error::Connection("connection refused".to_string());
210 assert_eq!(err.to_string(), "Connection error: connection refused");
211 }
212
213 #[test]
214 fn test_error_display_query() {
215 let err = Error::Query {
216 code: "42000".to_string(),
217 message: "syntax error".to_string(),
218 };
219 assert_eq!(err.to_string(), "Query error: 42000 - syntax error");
220 }
221
222 #[test]
223 fn test_error_display_auth() {
224 let err = Error::Auth("invalid credentials".to_string());
225 assert_eq!(err.to_string(), "Authentication error: invalid credentials");
226 }
227
228 #[test]
229 fn test_error_display_io() {
230 let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
231 let err = Error::Io(io_err);
232 assert!(err.to_string().starts_with("I/O error:"));
233 }
234
235 #[test]
236 fn test_error_display_json() {
237 let json_err: serde_json::Error = serde_json::from_str::<i32>("invalid").unwrap_err();
238 let err = Error::Json(json_err);
239 assert!(err.to_string().starts_with("JSON error:"));
240 }
241
242 #[test]
243 fn test_error_display_quic() {
244 let err = Error::Quic("connection reset".to_string());
245 assert_eq!(err.to_string(), "QUIC error: connection reset");
246 }
247
248 #[test]
249 fn test_error_display_tls() {
250 let err = Error::Tls("certificate expired".to_string());
251 assert_eq!(err.to_string(), "TLS error: certificate expired");
252 }
253
254 #[test]
255 fn test_error_display_invalid_dsn() {
256 let err = Error::InvalidDsn("missing host".to_string());
257 assert_eq!(err.to_string(), "Invalid DSN: missing host");
258 }
259
260 #[test]
261 fn test_error_display_type() {
262 let err = Error::Type("cannot convert int to string".to_string());
263 assert_eq!(err.to_string(), "Type error: cannot convert int to string");
264 }
265
266 #[test]
267 fn test_error_display_timeout() {
268 let err = Error::Timeout;
269 assert_eq!(err.to_string(), "Operation timed out");
270 }
271
272 #[test]
273 fn test_error_display_pool() {
274 let err = Error::Pool("pool exhausted".to_string());
275 assert_eq!(err.to_string(), "Pool error: pool exhausted");
276 }
277
278 #[test]
279 fn test_error_display_limit() {
280 let err = Error::Limit("frame too large".to_string());
281 assert_eq!(err.to_string(), "Limit exceeded: frame too large");
282 }
283
284 #[test]
285 fn test_error_display_other() {
286 let err = Error::Other("unknown error".to_string());
287 assert_eq!(err.to_string(), "unknown error");
288 }
289
290 #[test]
291 fn test_error_from_io() {
292 let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "refused");
293 let err: Error = io_err.into();
294 assert!(matches!(err, Error::Io(_)));
295 }
296
297 #[test]
298 fn test_error_from_json() {
299 let json_err: serde_json::Error = serde_json::from_str::<i32>("not_a_number").unwrap_err();
300 let err: Error = json_err.into();
301 assert!(matches!(err, Error::Json(_)));
302 }
303
304 #[test]
305 fn test_error_helper_connection() {
306 let err = Error::connection("test connection error");
307 assert!(matches!(err, Error::Connection(msg) if msg == "test connection error"));
308 }
309
310 #[test]
311 fn test_error_helper_query() {
312 let err = Error::query("test query error");
313 assert!(matches!(err, Error::Query { code, message }
314 if code == "QUERY_ERROR" && message == "test query error"));
315 }
316
317 #[test]
318 fn test_error_helper_protocol() {
319 let err = Error::protocol("invalid frame");
320 assert!(matches!(err, Error::Connection(msg) if msg.contains("Protocol error")));
321 }
322
323 #[test]
324 fn test_error_helper_transaction() {
325 let err = Error::transaction("rollback failed");
326 assert!(matches!(err, Error::Connection(msg) if msg.contains("Transaction error")));
327 }
328
329 #[test]
330 fn test_error_helper_timeout() {
331 let err = Error::timeout();
332 assert!(matches!(err, Error::Timeout));
333 }
334
335 #[test]
336 fn test_error_helper_auth() {
337 let err = Error::auth("bad token");
338 assert!(matches!(err, Error::Auth(msg) if msg == "bad token"));
339 }
340
341 #[test]
342 fn test_error_helper_quic() {
343 let err = Error::quic("stream closed");
344 assert!(matches!(err, Error::Quic(msg) if msg == "stream closed"));
345 }
346
347 #[test]
348 fn test_error_helper_tls() {
349 let err = Error::tls("handshake failed");
350 assert!(matches!(err, Error::Tls(msg) if msg == "handshake failed"));
351 }
352
353 #[test]
354 fn test_error_helper_type_error() {
355 let err = Error::type_error("invalid cast");
356 assert!(matches!(err, Error::Type(msg) if msg == "invalid cast"));
357 }
358
359 #[test]
360 fn test_error_helper_pool() {
361 let err = Error::pool("no connections available");
362 assert!(matches!(err, Error::Pool(msg) if msg == "no connections available"));
363 }
364
365 #[test]
366 fn test_error_helper_limit() {
367 let err = Error::limit("max rows exceeded");
368 assert!(matches!(err, Error::Limit(msg) if msg == "max rows exceeded"));
369 }
370
371 #[test]
372 fn test_error_is_retryable_connection() {
373 let err = Error::Connection("network error".to_string());
374 assert!(err.is_retryable());
375 }
376
377 #[test]
378 fn test_error_is_retryable_timeout() {
379 let err = Error::Timeout;
380 assert!(err.is_retryable());
381 }
382
383 #[test]
384 fn test_error_is_retryable_quic() {
385 let err = Error::Quic("reset".to_string());
386 assert!(err.is_retryable());
387 }
388
389 #[test]
390 fn test_error_is_retryable_pool() {
391 let err = Error::Pool("exhausted".to_string());
392 assert!(err.is_retryable());
393 }
394
395 #[test]
396 fn test_error_is_retryable_serialization_failure() {
397 let err = Error::Query {
398 code: "40001".to_string(),
399 message: "serialization failure".to_string(),
400 };
401 assert!(err.is_retryable());
402 }
403
404 #[test]
405 fn test_error_is_retryable_deadlock() {
406 let err = Error::Query {
407 code: "40P01".to_string(),
408 message: "deadlock detected".to_string(),
409 };
410 assert!(err.is_retryable());
411 }
412
413 #[test]
414 fn test_error_is_retryable_transaction_deadlock() {
415 let err = Error::Query {
416 code: "40502".to_string(),
417 message: "transaction deadlock".to_string(),
418 };
419 assert!(err.is_retryable());
420 }
421
422 #[test]
423 fn test_error_not_retryable_syntax() {
424 let err = Error::Query {
425 code: "42000".to_string(),
426 message: "syntax error".to_string(),
427 };
428 assert!(!err.is_retryable());
429 }
430
431 #[test]
432 fn test_error_not_retryable_auth() {
433 let err = Error::Auth("invalid".to_string());
434 assert!(!err.is_retryable());
435 }
436
437 #[test]
438 fn test_error_not_retryable_tls() {
439 let err = Error::Tls("cert error".to_string());
440 assert!(!err.is_retryable());
441 }
442
443 #[test]
444 fn test_error_not_retryable_dsn() {
445 let err = Error::InvalidDsn("bad format".to_string());
446 assert!(!err.is_retryable());
447 }
448
449 #[test]
450 fn test_error_not_retryable_type() {
451 let err = Error::Type("cast failed".to_string());
452 assert!(!err.is_retryable());
453 }
454
455 #[test]
456 fn test_is_auth_error_class_28000() {
457 let err = Error::Query {
458 code: "28000".to_string(),
459 message: "invalid authorization specification".to_string(),
460 };
461 assert!(err.is_auth_error());
462 }
463
464 #[test]
465 fn test_is_auth_error_geode_08p01() {
466 let err = Error::Query {
467 code: "08P01".to_string(),
468 message: "password reset required".to_string(),
469 };
470 assert!(err.is_auth_error());
471 }
472
473 #[test]
474 fn test_is_auth_error_false_for_other_codes() {
475 let err = Error::Query {
476 code: "42000".to_string(),
477 message: "syntax error".to_string(),
478 };
479 assert!(!err.is_auth_error());
480
481 let err = Error::Connection("network".to_string());
482 assert!(!err.is_auth_error());
483 }
484
485 #[test]
486 fn test_is_syntax_error_class_42000() {
487 let err = Error::Query {
488 code: "42000".to_string(),
489 message: "syntax error".to_string(),
490 };
491 assert!(err.is_syntax_error());
492 }
493
494 #[test]
495 fn test_is_syntax_error_false_for_other_codes() {
496 let err = Error::Query {
497 code: "28000".to_string(),
498 message: "auth".to_string(),
499 };
500 assert!(!err.is_syntax_error());
501
502 let err = Error::Validation("bad".to_string());
503 assert!(!err.is_syntax_error());
504 }
505
506 #[test]
507 fn test_error_code_query() {
508 let err = Error::Query {
509 code: "42000".to_string(),
510 message: "syntax error".to_string(),
511 };
512 assert_eq!(err.code(), Some("42000"));
513 }
514
515 #[test]
516 fn test_error_code_non_query() {
517 let err = Error::Connection("test".to_string());
518 assert_eq!(err.code(), None);
519 }
520
521 #[test]
522 fn test_result_type_alias() {
523 fn returns_result() -> Result<i32> {
524 Ok(42)
525 }
526 assert_eq!(returns_result().unwrap(), 42);
527 }
528
529 #[test]
530 fn test_result_type_alias_error() {
531 fn returns_error() -> Result<i32> {
532 Err(Error::Other("test".to_string()))
533 }
534 assert!(returns_error().is_err());
535 }
536
537 #[test]
538 fn test_error_string_conversion() {
539 let err = Error::connection("test");
541 assert!(matches!(err, Error::Connection(_)));
542
543 let err = Error::connection(String::from("test"));
545 assert!(matches!(err, Error::Connection(_)));
546 }
547
548 #[test]
549 fn test_error_debug() {
550 let err = Error::Connection("test".to_string());
551 let debug_str = format!("{:?}", err);
552 assert!(debug_str.contains("Connection"));
553 assert!(debug_str.contains("test"));
554 }
555}