use std::fmt;
use datumesh_core::ErrorCode;
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
Invalid(datumesh_core::Error),
NotLater {
what: &'static str,
key: String,
given: String,
latest: String,
},
NotFound {
what: &'static str,
key: String,
},
Mismatch {
what: &'static str,
expected: String,
found: String,
},
Conflict {
hash: String,
},
Database(DatabaseError),
PoolTimeout,
Migration(String),
Config(String),
}
#[derive(Debug)]
pub struct DatabaseError {
message: String,
code: Option<String>,
source: postgres::Error,
}
impl DatabaseError {
pub(crate) fn new(e: postgres::Error) -> DatabaseError {
let (message, code) = match e.as_db_error() {
Some(d) => (d.message().to_string(), Some(d.code().code().to_string())),
None => (driver_text(&e), None),
};
DatabaseError { message, code, source: e }
}
pub fn message(&self) -> &str {
&self.message
}
pub fn code(&self) -> Option<&str> {
self.code.as_deref()
}
}
fn driver_text(e: &postgres::Error) -> String {
let mut text = e.to_string();
let mut source = std::error::Error::source(e);
while let Some(s) = source {
let s_text = s.to_string();
if !text.contains(&s_text) {
text.push_str(": ");
text.push_str(&s_text);
}
source = s.source();
}
text
}
impl fmt::Display for DatabaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.code {
Some(c) => write!(f, "{} ({c})", self.message),
None => f.write_str(&self.message),
}
}
}
impl std::error::Error for DatabaseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl Error {
pub fn code(&self) -> Option<ErrorCode> {
match self {
Error::Invalid(e) => Some(e.code()),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Invalid(e) => write!(f, "invalid input: {e}"),
Error::NotLater { what, key, given, latest } => {
write!(f, "{what} {key}: {given} is not later than the latest, {latest}")
}
Error::NotFound { what, key } => write!(f, "no {what} {key} in the store"),
Error::Mismatch { what, expected, found } => {
write!(f, "the receipt's {what} is {found}, not {expected}")
}
Error::Conflict { hash } => {
write!(f, "receipt {hash} is stored with another valid signature; kept the stored one")
}
Error::Database(e) => write!(f, "database: {e}"),
Error::PoolTimeout => f.write_str("no database connection became free in time"),
Error::Migration(m) => write!(f, "migration: {m}"),
Error::Config(m) => write!(f, "configuration: {m}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Invalid(e) => Some(e),
Error::Database(e) => std::error::Error::source(e),
_ => None,
}
}
}
pub(crate) trait Db<T> {
fn db(self) -> Result<T>;
}
impl<T> Db<T> for core::result::Result<T, postgres::Error> {
fn db(self) -> Result<T> {
self.map_err(|e| Error::Database(DatabaseError::new(e)))
}
}
impl From<datumesh_core::Error> for Error {
fn from(e: datumesh_core::Error) -> Error {
Error::Invalid(e)
}
}
pub type Result<T> = core::result::Result<T, Error>;