use tokio_postgres::error::SqlState;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{}", db_error_message(.0))]
Postgres(#[from] tokio_postgres::Error),
#[error("{}", pool_error_message(.0))]
Pool(#[from] deadpool_postgres::PoolError),
#[error(transparent)]
Build(#[from] deadpool_postgres::BuildError),
#[error(transparent)]
WireLength(#[from] std::array::TryFromSliceError),
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(
"no decoder for PostgreSQL type OID {oid} — if this type was created after the \
connection opened, the type registry needs refreshing"
)]
UnknownTypeOid { oid: u32 },
#[error("{0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl From<Box<dyn std::error::Error + Send + Sync>> for Error {
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
Error::Other(e)
}
}
fn db_error_message(err: &tokio_postgres::Error) -> String {
if let Some(db) = err.as_db_error() {
return db.message().to_string();
}
let mut message = err.to_string();
let mut cause = std::error::Error::source(err);
while let Some(next) = cause {
let reason = next.to_string();
if !message.contains(&reason) {
message.push_str(": ");
message.push_str(&reason);
}
cause = next.source();
}
message
}
fn pool_error_message(err: &deadpool_postgres::PoolError) -> String {
match err {
deadpool_postgres::PoolError::Backend(e) => db_error_message(e),
other => other.to_string(),
}
}
impl Error {
pub fn sqlstate(&self) -> Option<&SqlState> {
match self {
Error::Postgres(e) => e.code(),
Error::Pool(deadpool_postgres::PoolError::Backend(e)) => e.code(),
_ => None,
}
}
pub(crate) fn message(msg: impl Into<String>) -> Self {
Error::Other(msg.into().into())
}
fn as_db_error(&self) -> Option<&tokio_postgres::error::DbError> {
match self {
Error::Postgres(e) => e.as_db_error(),
Error::Pool(deadpool_postgres::PoolError::Backend(e)) => e.as_db_error(),
_ => None,
}
}
pub fn pg_message(&self) -> String {
match self.as_db_error() {
Some(db) => db.message().to_string(),
None => self.to_string(),
}
}
pub fn violated_scalar(&self) -> Option<(&str, &str)> {
let db = self.as_db_error()?;
Some((db.schema()?, db.datatype()?))
}
pub fn violated_table(&self) -> Option<(&str, &str)> {
let db = self.as_db_error()?;
Some((db.schema()?, db.table()?))
}
}