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