#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
use std::{any::Any, ops::Deref};
use crate::traits::loggable::Warnable;
pub mod adapters;
mod display;
pub mod traits;
pub trait BhError: std::fmt::Display + Send + Sync + 'static {}
pub trait BhErrorAny: BhError + Any {
fn as_any(&self) -> &dyn Any;
}
impl<E: BhError> BhErrorAny for E {
fn as_any(&self) -> &dyn Any {
self
}
}
impl<E: BhError + ?Sized> BhError for Box<E> {}
pub type ErrorDyn = Error<Box<dyn BhErrorAny>>;
trait KnownError: std::error::Error + Send + Sync {
fn as_err(&self) -> &(dyn std::error::Error + 'static);
}
impl<T> KnownError for Error<T>
where
T: BhError,
{
fn as_err(&self) -> &(dyn std::error::Error + 'static) {
self
}
}
enum ErrorSource {
KnownError(Box<dyn KnownError>),
ForeignError(Box<dyn std::error::Error + Send + Sync>),
}
pub struct Error<E>
where
E: BhError,
{
pub error: E,
context: Vec<Box<dyn std::fmt::Display + Send + Sync>>,
source: Option<ErrorSource>,
}
pub type Result<T, E> = std::result::Result<T, Error<E>>;
impl<E> Error<E>
where
E: BhError,
{
#[track_caller]
pub fn root(error: E) -> Self {
Self {
error,
context: Vec::new(),
source: None,
}
.log_warn(*std::panic::Location::caller())
}
fn from_foreign_source<S>(error: E, source: S) -> Self
where
S: std::error::Error + Send + Sync + 'static,
{
Self {
error,
context: Vec::new(),
source: Some(ErrorSource::ForeignError(Box::new(source))),
}
}
fn from_known_source<S>(error: E, source: S) -> Self
where
S: KnownError + 'static,
{
Self {
error,
context: Vec::new(),
source: Some(ErrorSource::KnownError(Box::new(source))),
}
}
fn from_foreign_boxed_source(
error: E,
source: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
Self {
error,
context: Vec::new(),
source: Some(ErrorSource::ForeignError(source)),
}
}
pub fn ctx<C>(mut self, context: C) -> Self
where
C: std::fmt::Display + Send + Sync + 'static,
{
self.context.push(Box::new(context));
self
}
pub fn erased(self) -> ErrorDyn {
Error {
error: Box::new(self.error),
context: self.context,
source: self.source,
}
}
}
impl ErrorDyn {
pub fn downcast_ref_inner<E: BhError>(&self) -> Option<&E> {
self.error.deref().as_any().downcast_ref()
}
}
impl<E> std::error::Error for Error<E>
where
E: BhError,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|source| match source {
ErrorSource::KnownError(source) => source.as_ref().as_err(),
ErrorSource::ForeignError(source) => source.as_ref() as _,
})
}
}
#[cfg(test)]
mod tests {
use std::error::Error as _;
use super::*;
#[derive(Debug, PartialEq)]
enum DummyError {
SystemError,
UsageError,
}
impl std::fmt::Display for DummyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SystemError => write!(f, "SystemError"),
Self::UsageError => write!(f, "UsageError"),
}
}
}
impl BhError for DummyError {}
#[test]
fn test_root() {
let error = Error::root(DummyError::SystemError);
assert_eq!(error.error, DummyError::SystemError);
assert!(error.source.is_none());
}
#[test]
fn test_from_foreign_source() {
let error_sys = Error::root(DummyError::SystemError);
let error_us = Error::from_foreign_source(DummyError::UsageError, error_sys);
assert_eq!(error_us.error, DummyError::UsageError);
assert!(matches!(
error_us.source,
Some(ErrorSource::ForeignError(_))
));
}
#[test]
fn test_from_known_source() {
let error_sys = Error::root(DummyError::SystemError);
let error_us = Error::from_known_source(DummyError::UsageError, error_sys);
assert_eq!(error_us.error, DummyError::UsageError);
assert!(matches!(error_us.source, Some(ErrorSource::KnownError(_))));
}
#[test]
fn test_ctx() {
let error = Error::root(DummyError::UsageError).ctx("Dummy first context");
assert_eq!(error.error, DummyError::UsageError);
assert!(error.source.is_none());
assert!(error
.context
.iter()
.map(ToString::to_string)
.any(|ctx| &ctx == "Dummy first context"));
let error = error.ctx("Dummy second context");
assert_eq!(error.error, DummyError::UsageError);
assert!(error.source.is_none());
let ctx_vec: Vec<String> = error.context.iter().map(ToString::to_string).collect();
assert!(ctx_vec.contains(&String::from("Dummy first context")));
assert!(ctx_vec.contains(&String::from("Dummy second context")));
}
#[test]
fn test_source() {
let error = Error {
error: DummyError::SystemError,
context: Vec::new(),
source: None,
};
assert!(error.source().is_none());
let error = Error {
error: DummyError::UsageError,
context: Vec::new(),
source: Some(ErrorSource::ForeignError(Box::new(error))),
};
assert!(error.source().is_some());
let error = Error {
error: DummyError::SystemError,
context: Vec::new(),
source: Some(ErrorSource::KnownError(Box::new(error))),
};
assert!(error.source().is_some());
}
#[test]
fn test_downcast_erased() {
let error = Error {
error: DummyError::SystemError,
context: Vec::new(),
source: None,
};
let erased_error = error.erased();
let downcast_error = erased_error.downcast_ref_inner::<DummyError>();
assert_eq!(downcast_error, Some(&DummyError::SystemError));
}
}