#![forbid(unsafe_code)]
#![deny(clippy::all)]
pub mod arena;
pub mod codec;
pub mod oid_map;
pub mod pool;
pub(crate) mod types;
mod auth;
mod conn;
mod proto;
mod stmt_cache;
mod sync_io;
#[cfg(feature = "tls")]
mod tls_sync;
#[cfg(feature = "async")]
pub mod async_conn;
#[cfg(feature = "async")]
mod async_io;
pub use arena::Arena;
#[cfg(feature = "async")]
pub use async_conn::AsyncConnection;
pub use codec::Encode;
pub use conn::release_col_offsets;
pub use conn::release_resp_buf;
pub use conn::Connection;
pub use pool::{Pool, PoolBuilder, PoolGuard, PoolStatus, Transaction};
pub use types::{
hash_sql, ColumnDesc, Config, Notification, PgDataRow, PrepareResult, QueryResult, Row,
SimpleRow, SslMode, StatementCacheMode,
};
#[derive(Debug)]
pub enum DriverError {
Io(std::io::Error),
Auth(String),
Protocol(String),
Server {
code: [u8; 5],
message: Box<str>,
detail: Option<Box<str>>,
hint: Option<Box<str>>,
position: Option<u32>,
},
Pool(String),
}
impl std::fmt::Display for DriverError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Auth(msg) => write!(f, "auth error: {msg}"),
Self::Protocol(msg) => write!(f, "protocol error: {msg}"),
Self::Server {
code,
message,
detail,
hint,
position,
} => {
write!(
f,
"server error [{}]: {message}",
std::str::from_utf8(code).unwrap_or("?????")
)?;
if let Some(pos) = position {
write!(f, " (at position {pos})")?;
}
if let Some(d) = detail {
write!(f, " DETAIL: {d}")?;
}
if let Some(h) = hint {
write!(f, " HINT: {h}")?;
}
Ok(())
}
Self::Pool(msg) => write!(f, "pool error: {msg}"),
}
}
}
impl std::error::Error for DriverError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for DriverError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn driver_error_display_io() {
let e = DriverError::Io(std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"refused",
));
assert!(e.to_string().contains("I/O error"));
assert!(e.to_string().contains("refused"));
}
#[test]
fn driver_error_display_auth() {
let e = DriverError::Auth("wrong password".into());
assert_eq!(e.to_string(), "auth error: wrong password");
}
#[test]
fn driver_error_display_protocol() {
let e = DriverError::Protocol("unexpected message".into());
assert_eq!(e.to_string(), "protocol error: unexpected message");
}
#[test]
fn driver_error_display_server() {
let e = DriverError::Server {
code: *b"42P01",
message: "relation does not exist".into(),
detail: Some("table was dropped".into()),
hint: None,
position: None,
};
let s = e.to_string();
assert!(s.contains("42P01"));
assert!(s.contains("relation does not exist"));
assert!(s.contains("table was dropped"));
}
#[test]
fn driver_error_display_server_no_detail() {
let e = DriverError::Server {
code: *b"23505",
message: Box::from("duplicate key"),
detail: None,
hint: None,
position: None,
};
assert_eq!(e.to_string(), "server error [23505]: duplicate key");
}
#[test]
fn driver_error_display_server_with_position() {
let e = DriverError::Server {
code: *b"42601",
message: Box::from("syntax error"),
detail: None,
hint: None,
position: Some(8),
};
let s = e.to_string();
assert!(s.contains("(at position 8)"));
}
#[test]
fn driver_error_display_pool() {
let e = DriverError::Pool("exhausted".into());
assert_eq!(e.to_string(), "pool error: exhausted");
}
#[test]
fn driver_error_source_io() {
let inner = std::io::Error::other("test");
let e = DriverError::Io(inner);
assert!(std::error::Error::source(&e).is_some());
}
#[test]
fn driver_error_source_non_io() {
let e = DriverError::Auth("test".into());
assert!(std::error::Error::source(&e).is_none());
}
#[test]
fn driver_error_from_io() {
let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused");
let e: DriverError = io_err.into();
assert!(matches!(e, DriverError::Io(_)));
}
#[test]
fn forbid_unsafe_code() {
}
#[test]
fn driver_error_display_server_all_none() {
let e = DriverError::Server {
code: *b"00000",
message: "successful completion".into(),
detail: None,
hint: None,
position: None,
};
let s = e.to_string();
assert_eq!(s, "server error [00000]: successful completion");
assert!(!s.contains("DETAIL"));
assert!(!s.contains("HINT"));
assert!(!s.contains("position"));
}
#[test]
fn driver_error_display_server_detail_only() {
let e = DriverError::Server {
code: *b"23505",
message: "duplicate key".into(),
detail: Some("Key (id)=(1) exists.".into()),
hint: None,
position: None,
};
let s = e.to_string();
assert!(s.contains("DETAIL: Key (id)=(1) exists."));
assert!(!s.contains("HINT"));
}
#[test]
fn driver_error_display_server_hint_only() {
let e = DriverError::Server {
code: *b"42601",
message: "syntax error".into(),
detail: None,
hint: Some("check SQL".into()),
position: None,
};
let s = e.to_string();
assert!(s.contains("HINT: check SQL"));
assert!(!s.contains("DETAIL"));
}
#[test]
fn driver_error_display_server_position_only() {
let e = DriverError::Server {
code: *b"42601",
message: "syntax error".into(),
detail: None,
hint: None,
position: Some(15),
};
let s = e.to_string();
assert!(s.contains("(at position 15)"));
}
#[test]
fn driver_error_display_server_all_fields() {
let e = DriverError::Server {
code: *b"42P01",
message: "relation does not exist".into(),
detail: Some("table was dropped".into()),
hint: Some("recreate the table".into()),
position: Some(42),
};
let s = e.to_string();
assert!(s.contains("[42P01]"));
assert!(s.contains("relation does not exist"));
assert!(s.contains("(at position 42)"));
assert!(s.contains("DETAIL: table was dropped"));
assert!(s.contains("HINT: recreate the table"));
}
#[test]
fn driver_error_io_preserves_kind() {
let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused");
let e = DriverError::Io(io_err);
match &e {
DriverError::Io(inner) => {
assert_eq!(inner.kind(), std::io::ErrorKind::ConnectionRefused);
}
_ => panic!("expected Io variant"),
}
}
#[test]
fn driver_error_io_timeout() {
let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "connection timed out");
let e = DriverError::Io(io_err);
let s = e.to_string();
assert!(s.contains("timed out"));
}
#[test]
fn driver_error_io_unexpected_eof() {
let io_err = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "connection closed");
let e: DriverError = io_err.into();
let s = e.to_string();
assert!(s.contains("connection closed"));
}
#[test]
fn driver_error_auth_empty() {
let e = DriverError::Auth(String::new());
assert_eq!(e.to_string(), "auth error: ");
}
#[test]
fn driver_error_protocol_empty() {
let e = DriverError::Protocol(String::new());
assert_eq!(e.to_string(), "protocol error: ");
}
#[test]
fn driver_error_pool_empty() {
let e = DriverError::Pool(String::new());
assert_eq!(e.to_string(), "pool error: ");
}
#[test]
fn driver_error_source_protocol_is_none() {
let e = DriverError::Protocol("test".into());
assert!(std::error::Error::source(&e).is_none());
}
#[test]
fn driver_error_source_server_is_none() {
let e = DriverError::Server {
code: *b"42601",
message: "err".into(),
detail: None,
hint: None,
position: None,
};
assert!(std::error::Error::source(&e).is_none());
}
#[test]
fn driver_error_source_pool_is_none() {
let e = DriverError::Pool("test".into());
assert!(std::error::Error::source(&e).is_none());
}
#[test]
fn driver_error_debug_all_variants() {
let variants: Vec<DriverError> = vec![
DriverError::Io(std::io::Error::other("io")),
DriverError::Auth("auth".into()),
DriverError::Protocol("proto".into()),
DriverError::Server {
code: *b"00000",
message: "ok".into(),
detail: None,
hint: None,
position: None,
},
DriverError::Pool("pool".into()),
];
for v in &variants {
let dbg = format!("{v:?}");
assert!(!dbg.is_empty());
}
}
}