#[cfg(feature = "backtrace")]
use std::backtrace::Backtrace;
use std::fmt::{self};
use crate::{str_error::StrError, ErrorUnion};
pub trait AnyError: std::any::Any + std::error::Error + Send + Sync + 'static {}
impl<T> AnyError for T where T: std::error::Error + Send + Sync + 'static {}
impl std::error::Error for Box<dyn AnyError> {}
pub struct TracedError<T = Box<dyn AnyError>>
where
T: AnyError,
{
inner: T,
#[cfg(feature = "backtrace")]
pub(crate) backtrace: Backtrace,
#[cfg(feature = "context")]
pub(crate) context: Vec<StrError>,
}
impl TracedError {
pub fn boxed<E: AnyError>(source: E) -> Self {
TracedError::new(Box::new(source))
}
}
impl<T: AnyError> TracedError<T> {
pub fn new(source: T) -> Self {
Self {
inner: source,
#[cfg(feature = "backtrace")]
backtrace: Backtrace::capture(),
#[cfg(feature = "context")]
context: Vec::new(),
}
}
pub fn traced_dyn(self) -> TracedError {
debug_assert!(
std::any::TypeId::of::<T>() != std::any::TypeId::of::<Box<dyn AnyError>>(),
"traced_dyn() called on already boxed TracedError"
);
TracedError {
inner: Box::new(self.inner),
#[cfg(feature = "backtrace")]
backtrace: self.backtrace,
#[cfg(feature = "context")]
context: self.context,
}
}
pub fn into_inner(self) -> T {
self.inner
}
pub fn inner(&self) -> &T {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
pub fn map<U, F>(self, f: F) -> TracedError<U>
where
U: AnyError,
F: FnOnce(T) -> U,
{
TracedError {
inner: f(self.inner),
#[cfg(feature = "backtrace")]
backtrace: self.backtrace,
#[cfg(feature = "context")]
context: self.context,
}
}
#[allow(unused_mut)]
#[allow(unused_variables)]
pub fn context<C: Into<StrError>>(mut self, context: C) -> Self {
#[cfg(feature = "context")]
self.context.push(context.into());
self
}
#[allow(unused_mut)]
#[allow(unused_variables)]
pub fn with_context<F, C: Into<StrError>>(mut self, f: F) -> TracedError<T>
where
F: FnOnce() -> C,
{
#[cfg(feature = "context")]
self.context.push(f().into());
self
}
pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
#[cfg(feature = "anyhow")]
impl TracedError {
pub fn anyhow(error: anyhow::Error) -> TracedError {
let mut chain = error.chain();
let root = chain.next().unwrap().to_string();
#[cfg(feature = "backtrace")]
let (root, backtrace) = {
let backtrace: &Backtrace = error.backtrace();
if matches!(
backtrace.status(),
std::backtrace::BacktraceStatus::Captured
) {
(
format!("{root}\n\nBacktrace:\n{}", backtrace.to_string()),
Backtrace::disabled(),
)
} else {
(root, Backtrace::capture())
}
};
let root = StrError::Owned(root);
#[cfg(feature = "context")]
let context = {
let mut context = Vec::new();
for link in chain {
context.push(StrError::Owned(link.to_string()));
}
context
};
TracedError {
inner: Box::new(root),
#[cfg(feature = "backtrace")]
backtrace,
#[cfg(feature = "context")]
context,
}
}
}
impl<T: AnyError> fmt::Display for TracedError<T> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{}", self.inner)?;
Ok(())
}
}
impl<T: AnyError> fmt::Debug for TracedError<T> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{}", self.inner)?;
#[cfg(feature = "context")]
{
if !self.context.is_empty() {
write!(formatter, "\n\nContext:")?;
for context_item in self.context.iter() {
write!(formatter, "\n\t- {}", context_item)?;
}
}
}
#[cfg(feature = "backtrace")]
{
use std::backtrace::BacktraceStatus;
if matches!(self.backtrace.status(), BacktraceStatus::Captured) {
write!(formatter, "\n\nBacktrace:\n")?;
fmt::Display::fmt(&self.backtrace, formatter)?;
}
}
Ok(())
}
}
fn _send_sync_error_assert() {
fn is_send<T: Send>(_: &T) {}
fn is_sync<T: Sync>(_: &T) {}
fn is_error<T: std::error::Error>(_: &T) {}
let traced_error: TracedError = crate::traced!("");
is_send(&traced_error);
is_sync(&traced_error);
is_error(&&traced_error);
}
impl<T: AnyError> std::error::Error for &TracedError<T> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
impl<T: AnyError> std::error::Error for &mut TracedError<T> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
impl<T: AnyError> From<T> for TracedError {
#[cfg(feature = "min_specialization")]
default fn from(e: T) -> Self {
TracedError::boxed(e)
}
#[cfg(not(feature = "min_specialization"))]
fn from(e: T) -> Self {
TracedError::boxed(e)
}
}
#[cfg(feature = "min_specialization")]
impl From<Box<dyn AnyError + '_>> for TracedError {
fn from(e: Box<dyn AnyError>) -> Self {
TracedError::new(e)
}
}
pub trait TracedDyn<O2> {
fn traced_dyn(self) -> O2;
}
impl<E> TracedDyn<TracedError> for E
where
E: AnyError,
{
#[cfg(feature = "min_specialization")]
default fn traced_dyn(self) -> TracedError {
TracedError::new(Box::new(self))
}
#[cfg(not(feature = "min_specialization"))]
fn traced_dyn(self) -> TracedError {
TracedError::new(Box::new(self))
}
}
#[cfg(feature = "min_specialization")]
impl TracedDyn<TracedError> for Box<dyn AnyError + '_> {
fn traced_dyn(self) -> TracedError {
TracedError::new(self)
}
}
impl<S, E> TracedDyn<Result<S, TracedError>> for Result<S, E>
where
E: AnyError,
{
fn traced_dyn(self) -> Result<S, TracedError> {
self.map_err(|e| e.traced_dyn())
}
}
impl<S, E: AnyError> TracedDyn<Result<S, TracedError>> for Result<S, TracedError<E>> {
#[cfg(feature = "min_specialization")]
default fn traced_dyn(self) -> Result<S, TracedError> {
self.map_err(|e| e.traced_dyn())
}
#[cfg(not(feature = "min_specialization"))]
fn traced_dyn(self) -> Result<S, TracedError> {
self.map_err(|e| e.traced_dyn())
}
}
#[cfg(feature = "min_specialization")]
impl<S> TracedDyn<Result<S, TracedError<Box<dyn AnyError + '_>>>>
for Result<S, TracedError<Box<dyn AnyError + '_>>>
{
fn traced_dyn(self) -> Result<S, TracedError> {
self
}
}
pub trait Traced<O1> {
fn traced(self) -> O1;
}
impl<E> Traced<TracedError<E>> for E
where
E: AnyError,
{
fn traced(self) -> TracedError<E> {
TracedError::new(self)
}
}
impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, E>
where
E: AnyError,
{
fn traced(self) -> Result<S, TracedError<E>> {
self.map_err(|e| e.traced())
}
}
impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, ErrorUnion<(TracedError<E>,)>>
where
E: AnyError,
{
fn traced(self) -> Result<S, TracedError<E>> {
self.map_err(|e| e.into_inner())
}
}
pub trait IntoTraced<O1> {
fn into_traced(self) -> O1;
}
impl<E1, E2> IntoTraced<TracedError<E2>> for E1
where
E1: AnyError,
E2: AnyError,
E1: Into<E2>,
{
fn into_traced(self) -> TracedError<E2> {
TracedError::new(self.into())
}
}
impl<S, E1, E2> IntoTraced<Result<S, TracedError<E2>>> for Result<S, E1>
where
E1: AnyError,
E2: AnyError,
E1: Into<E2>,
{
fn into_traced(self) -> Result<S, TracedError<E2>> {
self.map_err(|e| e.into_traced())
}
}
impl<S, E1, E2> IntoTraced<Result<S, TracedError<E2>>> for Result<S, TracedError<E1>>
where
E1: AnyError,
E2: AnyError,
E1: Into<E2>,
{
fn into_traced(self) -> Result<S, TracedError<E2>> {
self.map_err(|e| e.map(|e| e.into()))
}
}
impl<S, E> IntoTraced<Result<S, TracedError<E>>> for Result<S, ErrorUnion<(TracedError<E>,)>>
where
E: AnyError,
{
fn into_traced(self) -> Result<S, TracedError<E>> {
self.map_err(|e| e.into_inner())
}
}
#[cfg(feature = "min_specialization")]
#[cfg(all(feature = "context", feature = "backtrace"))]
#[cfg(test)]
mod test {
use crate::{Context, ErrorUnion, StrError, TracedError};
#[test]
fn adding_context_to_union() {
let concrete_traced_error: TracedError<std::io::Error> = TracedError::new(
std::io::Error::new(std::io::ErrorKind::AddrInUse, "Address in use"),
);
let concrete_union_error: ErrorUnion<(
TracedError<std::io::Error>,
i32,
TracedError<StrError>,
)> = ErrorUnion::new(concrete_traced_error);
let result: Result<
(),
ErrorUnion<(TracedError<std::io::Error>, i32, TracedError<StrError>)>,
> = Err(concrete_union_error).context("Context 1");
let concrete_union_error = result.unwrap_err();
let concrete_traced_error: TracedError<std::io::Error> =
match concrete_union_error.to_enum() {
crate::E3::A(traced_error) => traced_error,
_ => panic!("Wrong type"),
};
assert_eq!(
concrete_traced_error.context,
vec![StrError::from("Context 1")]
);
}
}