use std::{
any::Any,
fmt,
panic::{AssertUnwindSafe, catch_unwind},
sync::Mutex,
};
#[derive(Debug)]
pub enum PoolError<E> {
Factory(E),
Panic(PanicError),
}
impl<E: fmt::Display> fmt::Display for PoolError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Factory(error) => write!(f, "pool factory failed: {error}"),
Self::Panic(error) => write!(f, "pool operation panicked: {error}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for PoolError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Factory(error) => Some(error),
Self::Panic(error) => Some(error),
}
}
}
impl<E> PoolError<E> {
pub fn catch<T>(operation: impl FnOnce() -> Result<T, E>) -> Result<T, Self> {
catch_operation(operation)
.map_err(Self::Panic)?
.map_err(Self::Factory)
}
}
pub struct PanicError {
payload: Mutex<Box<dyn Any + Send>>,
}
impl PanicError {
#[must_use]
pub fn into_payload(self) -> Box<dyn Any + Send> {
self.payload
.into_inner()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
impl fmt::Display for PanicError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let payload = self
.payload
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(message) = payload.downcast_ref::<String>() {
f.write_str(message)
} else if let Some(message) = payload.downcast_ref::<&str>() {
f.write_str(message)
} else {
f.write_str("non-string panic payload")
}
}
}
impl fmt::Debug for PanicError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PanicError")
.field("message", &format_args!("{self}"))
.finish_non_exhaustive()
}
}
impl std::error::Error for PanicError {}
#[allow(clippy::redundant_pub_crate)]
pub(super) fn catch_operation<T>(operation: impl FnOnce() -> T) -> Result<T, PanicError> {
catch_unwind(AssertUnwindSafe(operation)).map_err(|payload| PanicError {
payload: Mutex::new(payload),
})
}
#[cfg(test)]
mod tests;