pub mod retry;
pub mod server;
pub mod sqlstate;
use std::fmt;
use crate::protocol::ProtocolError;
use crate::types::Error as TypeConversionError;
pub use crate::transport::TransportError;
pub use server::PgServerError;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PoolErrorVariant {
Exhausted,
Closed,
CreateFailed(String),
ResetFailed(String),
}
impl std::fmt::Display for PoolErrorVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PoolErrorVariant::Exhausted => write!(f, "connection pool exhausted"),
PoolErrorVariant::Closed => write!(f, "connection pool is closed"),
PoolErrorVariant::CreateFailed(msg) => {
write!(f, "failed to create pool connection: {msg}")
}
PoolErrorVariant::ResetFailed(msg) => write!(f, "connection reset failed: {msg}"),
}
}
}
impl std::error::Error for PoolErrorVariant {}
#[derive(Debug)]
#[non_exhaustive]
pub enum PgError {
Server(Box<PgServerError>),
Protocol(ProtocolError),
Transport(TransportError),
Auth(String),
TypeConversion(TypeConversionError),
Config(String),
ConnectionClosed,
UnexpectedNull { column: String },
ColumnNotFound { name: String },
ColumnIndexOutOfBounds { index: usize, count: usize },
Timeout,
Pool(PoolErrorVariant),
InvalidState(String),
Unsupported(String),
Cancelled,
Io(std::io::Error),
#[cfg(feature = "tls")]
Tls(rustls::Error),
Other(String),
}
impl std::fmt::Display for PgError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PgError::Server(e) => write!(f, "server error: {}", e),
PgError::Protocol(e) => write!(f, "protocol error: {}", e),
PgError::Transport(e) => write!(f, "transport error: {}", e),
PgError::Auth(msg) => write!(f, "authentication error: {}", msg),
PgError::TypeConversion(e) => write!(f, "type conversion error: {}", e),
PgError::Config(msg) => write!(f, "configuration error: {}", msg),
PgError::ConnectionClosed => write!(f, "connection closed"),
PgError::UnexpectedNull { column } => write!(f, "unexpected NULL in column {}", column),
PgError::ColumnNotFound { name } => write!(f, "column not found: {}", name),
PgError::ColumnIndexOutOfBounds { index, count } => {
write!(
f,
"column index {} out of bounds (have {} columns)",
index, count
)
}
PgError::Timeout => write!(f, "operation timed out"),
PgError::Pool(e) => write!(f, "pool error: {e}"),
PgError::InvalidState(msg) => write!(f, "invalid connection state: {}", msg),
PgError::Unsupported(msg) => write!(f, "unsupported: {}", msg),
PgError::Cancelled => write!(f, "cancelled"),
PgError::Io(e) => write!(f, "I/O error: {}", e),
#[cfg(feature = "tls")]
PgError::Tls(e) => write!(f, "TLS error: {}", e),
PgError::Other(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for PgError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PgError::Server(e) => Some(e.as_ref()),
PgError::Protocol(e) => Some(e),
PgError::Transport(e) => Some(e),
PgError::TypeConversion(e) => Some(e),
PgError::Io(e) => Some(e),
PgError::Pool(e) => Some(e),
#[cfg(feature = "tls")]
PgError::Tls(e) => Some(e),
_ => None,
}
}
}
impl From<PgServerError> for PgError {
fn from(e: PgServerError) -> Self {
PgError::Server(Box::new(e))
}
}
impl From<ProtocolError> for PgError {
fn from(e: ProtocolError) -> Self {
PgError::Protocol(e)
}
}
impl From<TransportError> for PgError {
fn from(e: TransportError) -> Self {
PgError::Transport(e)
}
}
impl From<TypeConversionError> for PgError {
fn from(e: TypeConversionError) -> Self {
PgError::TypeConversion(e)
}
}
impl From<std::io::Error> for PgError {
fn from(e: std::io::Error) -> Self {
PgError::Io(e)
}
}
#[cfg(feature = "tls")]
impl From<rustls::Error> for PgError {
fn from(e: rustls::Error) -> Self {
PgError::Tls(e)
}
}
pub type Error = PgError;
impl PgError {
pub fn is_connection_broken(&self) -> bool {
match self {
PgError::ConnectionClosed => true,
PgError::Transport(TransportError::ConnectionReset) => true,
PgError::Transport(TransportError::UnexpectedEof) => true,
PgError::Transport(TransportError::ConnectionRefused) => true,
PgError::Server(ref e) => {
e.is_connection_exception() || e.is_admin_shutdown() || e.is_crash_shutdown()
}
PgError::Io(ref e) => {
matches!(
e.kind(),
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::BrokenPipe
| std::io::ErrorKind::UnexpectedEof
)
}
_ => false,
}
}
pub fn is_retryable(&self) -> bool {
match self {
PgError::Server(e) => e.is_serialization_failure() || e.is_deadlock_detected(),
PgError::Transport(TransportError::Timeout) => true,
PgError::Timeout => true,
_ => false,
}
}
pub fn code(&self) -> Option<&str> {
match self {
PgError::Server(e) => Some(&e.code),
_ => None,
}
}
pub fn context(self, msg: impl Into<String>) -> Self {
PgError::Other(format!("{}: {}", msg.into(), self))
}
}
pub type Result<T> = std::result::Result<T, PgError>;
impl From<crate::auth::AuthError> for PgError {
fn from(e: crate::auth::AuthError) -> Self {
match e {
crate::auth::AuthError::PasswordRequired => PgError::Auth("password required".into()),
crate::auth::AuthError::UnsupportedSaslMechanisms(mechs) => {
PgError::Auth(format!("unsupported SASL mechanisms: {mechs:?}"))
}
crate::auth::AuthError::Scram(msg) => PgError::Auth(format!("SCRAM error: {msg}")),
crate::auth::AuthError::InsecureAuthentication { method } => PgError::Auth(format!(
"refusing {method} authentication over an unencrypted transport"
)),
crate::auth::AuthError::ServerError(msg) => PgError::Other(msg),
crate::auth::AuthError::UnexpectedMessage => {
PgError::Auth("unexpected message during authentication".into())
}
crate::auth::AuthError::Protocol(p) => PgError::Protocol(p),
crate::auth::AuthError::Transport(t) => PgError::Transport(t),
crate::auth::AuthError::Io(i) => PgError::Io(i),
crate::auth::AuthError::Utf8(u) => PgError::Other(u.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_pg_error_display() {
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "23505".to_string()),
(b'M', "duplicate key".to_string()),
])));
let display = err.to_string();
assert!(display.contains("server error"));
assert!(display.contains("duplicate key"));
assert!(display.contains("23505"));
}
#[test]
fn test_pg_error_is_connection_broken() {
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "FATAL".to_string()),
(b'C', "08006".to_string()),
(b'M', "connection failure".to_string()),
])));
assert!(err.is_connection_broken());
assert!(PgError::Transport(TransportError::ConnectionReset).is_connection_broken());
assert!(PgError::Transport(TransportError::UnexpectedEof).is_connection_broken());
assert!(PgError::Transport(TransportError::ConnectionRefused).is_connection_broken());
assert!(!PgError::Transport(TransportError::Timeout).is_connection_broken());
assert!(PgError::ConnectionClosed.is_connection_broken());
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "FATAL".to_string()),
(b'C', "57P01".to_string()),
(b'M', "admin shutdown".to_string()),
])));
assert!(err.is_connection_broken());
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "23505".to_string()),
(b'M', "duplicate key".to_string()),
])));
assert!(!err.is_connection_broken());
}
#[test]
fn test_pg_error_is_retryable() {
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "40001".to_string()),
(b'M', "serialization failure".to_string()),
])));
assert!(err.is_retryable());
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "40P01".to_string()),
(b'M', "deadlock detected".to_string()),
])));
assert!(err.is_retryable());
assert!(PgError::Transport(TransportError::Timeout).is_retryable());
assert!(PgError::Timeout.is_retryable());
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "23505".to_string()),
(b'M', "duplicate key".to_string()),
])));
assert!(!err.is_retryable());
assert!(!PgError::ConnectionClosed.is_retryable());
}
#[test]
fn test_pg_error_code() {
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "23505".to_string()),
(b'M', "duplicate key".to_string()),
])));
assert_eq!(err.code(), Some("23505"));
assert_eq!(PgError::ConnectionClosed.code(), None);
}
#[test]
fn test_pg_error_context() {
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "23505".to_string()),
(b'M', "duplicate key".to_string()),
])));
let with_ctx = err.context("inserting user");
match with_ctx {
PgError::Other(msg) => {
assert!(msg.contains("inserting user"));
assert!(msg.contains("duplicate key"));
}
_ => panic!("expected Other variant with context"),
}
}
#[test]
fn test_from_protocol_error() {
let err = PgError::from(ProtocolError::UnexpectedEof);
assert!(matches!(err, PgError::Protocol(_)));
}
#[test]
fn test_from_transport_error() {
let err = PgError::from(TransportError::ConnectionRefused);
assert!(matches!(err, PgError::Transport(_)));
}
#[test]
fn test_from_io_error() {
let err = PgError::from(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"broken",
));
assert!(matches!(err, PgError::Io(_)));
}
#[test]
fn test_io_error_is_connection_broken() {
let err = PgError::Io(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"broken",
));
assert!(err.is_connection_broken());
let err = PgError::Io(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"reset",
));
assert!(err.is_connection_broken());
let err = PgError::Io(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"));
assert!(!err.is_connection_broken());
}
#[test]
fn test_error_source_chain() {
let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
(b'S', "ERROR".to_string()),
(b'C', "23505".to_string()),
(b'M', "duplicate key".to_string()),
])));
assert!(err.source().is_some());
let err = PgError::Transport(TransportError::ConnectionReset);
assert!(err.source().is_some());
let err = PgError::ConnectionClosed;
assert!(err.source().is_none());
}
#[test]
fn test_unexpected_null_display() {
let err = PgError::UnexpectedNull {
column: "id".to_string(),
};
assert_eq!(err.to_string(), "unexpected NULL in column id");
}
#[test]
fn test_column_not_found_display() {
let err = PgError::ColumnNotFound {
name: "email".to_string(),
};
assert_eq!(err.to_string(), "column not found: email");
}
#[test]
fn test_column_index_out_of_bounds_display() {
let err = PgError::ColumnIndexOutOfBounds { index: 5, count: 3 };
assert!(err.to_string().contains("5"));
assert!(err.to_string().contains("3"));
}
}