use crate::error::PgError;
use crate::transport::TransportError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorClass {
Broken,
Transient,
Permanent,
}
pub fn classify_error(err: &PgError) -> ErrorClass {
match err {
PgError::ConnectionClosed => ErrorClass::Broken,
PgError::Transport(TransportError::ConnectionReset) => ErrorClass::Broken,
PgError::Transport(TransportError::UnexpectedEof) => ErrorClass::Broken,
PgError::Transport(TransportError::ConnectionRefused) => ErrorClass::Broken,
PgError::Server(ref e) if e.is_serialization_failure() => ErrorClass::Transient,
PgError::Server(ref e) if e.is_deadlock_detected() => ErrorClass::Transient,
PgError::Server(ref e) if e.is_connection_exception() => ErrorClass::Broken,
PgError::Server(ref e) if e.is_admin_shutdown() => ErrorClass::Broken,
PgError::Server(ref e) if e.is_crash_shutdown() => ErrorClass::Broken,
PgError::Transport(TransportError::Timeout) => ErrorClass::Transient,
PgError::Timeout => ErrorClass::Transient,
PgError::Io(ref e) => match e.kind() {
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::BrokenPipe
| std::io::ErrorKind::UnexpectedEof => ErrorClass::Broken,
std::io::ErrorKind::TimedOut => ErrorClass::Transient,
_ => ErrorClass::Permanent,
},
PgError::Server(_) => ErrorClass::Permanent,
PgError::TypeConversion(_) => ErrorClass::Permanent,
PgError::Config(_) => ErrorClass::Permanent,
PgError::Auth(_) => ErrorClass::Permanent,
_ => ErrorClass::Permanent,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::PgServerError;
use crate::error::PoolErrorVariant;
fn make_server_error(code: &str, message: &str) -> PgError {
PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', code.to_string()),
(b'M', message.to_string()),
])))
}
#[test]
fn test_classify_broken() {
assert_eq!(
classify_error(&PgError::ConnectionClosed),
ErrorClass::Broken
);
assert_eq!(
classify_error(&PgError::Transport(TransportError::ConnectionReset)),
ErrorClass::Broken
);
assert_eq!(
classify_error(&PgError::Transport(TransportError::UnexpectedEof)),
ErrorClass::Broken
);
assert_eq!(
classify_error(&PgError::Transport(TransportError::ConnectionRefused)),
ErrorClass::Broken
);
let err = make_server_error("08006", "connection failure");
assert_eq!(classify_error(&err), ErrorClass::Broken);
let err = make_server_error("57P01", "admin shutdown");
assert_eq!(classify_error(&err), ErrorClass::Broken);
}
#[test]
fn test_classify_transient() {
let err = make_server_error("40001", "could not serialize access");
assert_eq!(classify_error(&err), ErrorClass::Transient);
let err = make_server_error("40P01", "deadlock detected");
assert_eq!(classify_error(&err), ErrorClass::Transient);
assert_eq!(
classify_error(&PgError::Transport(TransportError::Timeout)),
ErrorClass::Transient
);
assert_eq!(classify_error(&PgError::Timeout), ErrorClass::Transient);
}
#[test]
fn test_classify_permanent() {
let err = make_server_error("23505", "duplicate key");
assert_eq!(classify_error(&err), ErrorClass::Permanent);
let err = make_server_error("42601", "syntax error");
assert_eq!(classify_error(&err), ErrorClass::Permanent);
assert_eq!(
classify_error(&PgError::Config("bad config".into())),
ErrorClass::Permanent
);
assert_eq!(
classify_error(&PgError::Auth("bad password".into())),
ErrorClass::Permanent
);
assert_eq!(
classify_error(&PgError::TypeConversion(crate::types::Error::Conversion(
"conversion failed".into(),
))),
ErrorClass::Permanent
);
}
#[test]
fn test_classify_io_errors() {
let broken = PgError::Io(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"reset",
));
assert_eq!(classify_error(&broken), ErrorClass::Broken);
let broken = PgError::Io(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "pipe"));
assert_eq!(classify_error(&broken), ErrorClass::Broken);
let transient = PgError::Io(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"));
assert_eq!(classify_error(&transient), ErrorClass::Transient);
let permanent = PgError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"bad input",
));
assert_eq!(classify_error(&permanent), ErrorClass::Permanent);
}
#[test]
fn test_classify_unknown_errors() {
assert_eq!(
classify_error(&PgError::Pool(PoolErrorVariant::Exhausted)),
ErrorClass::Permanent
);
assert_eq!(
classify_error(&PgError::InvalidState("wrong state".into())),
ErrorClass::Permanent
);
}
}