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