use std::{
error::Error,
fmt::Debug,
ops::{Deref, DerefMut},
};
#[cfg(feature = "clean-backtrace")]
use backtrace::Backtrace;
#[cfg(not(feature = "clean-backtrace"))]
use std::backtrace::Backtrace;
pub type Result<T, E> = std::result::Result<T, Backtraced<E>>;
pub trait IntoTraced {
type Value;
type Error;
fn with_trace(self) -> Result<Self::Value, Self::Error>;
}
impl<T, E> IntoTraced for std::result::Result<T, E> {
type Value = T;
type Error = E;
fn with_trace(self) -> Result<Self::Value, Self::Error> {
self.map_err(|e| e.into())
}
}
pub struct Backtraced<E> {
pub inner: E,
backtrace: Backtrace,
}
impl<E> Debug for Backtraced<E>
where
E: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !f.alternate() {
writeln!(
f,
"{:?}\n\nBacktrace:\n{}",
&self.inner,
format_backtrace(&self.backtrace)
)
} else {
writeln!(f, "{:?}", &self.inner)
}
}
}
impl<E> From<E> for Backtraced<E> {
fn from(value: E) -> Self {
Backtraced {
inner: value,
#[cfg(feature = "clean-backtrace")]
backtrace: Backtrace::new(),
#[cfg(not(feature = "clean-backtrace"))]
backtrace: Backtrace::capture(),
}
}
}
impl<T> Backtraced<T> {
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Backtraced<U> {
Backtraced {
inner: f(self.inner),
backtrace: self.backtrace,
}
}
pub fn map_into<U: From<T>>(self) -> Backtraced<U> {
Backtraced {
inner: self.inner.into(),
backtrace: self.backtrace,
}
}
}
pub trait ResultBacktrace {
type Value;
type Error;
fn map_trace<E>(
self,
f: impl FnOnce(Self::Error) -> E,
) -> std::result::Result<Self::Value, Backtraced<E>>;
fn map_trace_into<E: From<Self::Error>>(self)
-> std::result::Result<Self::Value, Backtraced<E>>;
fn backtrace(self) -> Self;
}
impl<T, E> ResultBacktrace for std::result::Result<T, Backtraced<E>> {
type Value = T;
type Error = E;
fn map_trace<F>(
self,
f: impl FnOnce(Self::Error) -> F,
) -> std::result::Result<Self::Value, Backtraced<F>> {
self.map_err(|e| Backtraced::<E>::map(e, f))
}
fn map_trace_into<F: From<Self::Error>>(self)
-> std::result::Result<Self::Value, Backtraced<F>>
{
self.map_trace(F::from)
}
fn backtrace(self) -> Self {
#[cfg(all(feature = "debug-only", not(debug_assertions)))]
return self;
let Err(ref error) = self else {
return self;
};
println!("Error backtrace:\n{}", format_backtrace(&error.backtrace));
self
}
}
#[cfg(not(feature = "clean-backtrace"))]
fn format_backtrace(backtrace: &Backtrace) -> String {
format!("{backtrace:?}")
}
#[cfg(feature = "clean-backtrace")]
fn clean_backtrace(backtrace: &Backtrace) -> Backtrace {
let frames = &backtrace.frames()[1..];
let bt = Backtrace::new();
let current_frames: Vec<_> = bt.frames().iter().map(|f| f.ip()).collect();
let frames: Vec<_> = frames
.iter()
.filter(|f| {
f.symbols()
.get(0)
.map(|symbol| {
symbol
.name()
.map(|name| {
let formated_name = format!("{:#?}", name);
&formated_name != "<T as core::convert::Into<U>>::into"
})
.unwrap_or(true)
})
.unwrap_or(true)
})
.filter(|x| !current_frames.contains(&x.ip()))
.cloned()
.collect();
frames.to_vec().into()
}
#[cfg(feature = "clean-backtrace")]
fn format_backtrace(backtrace: &Backtrace) -> String {
let backtrace: Backtrace = clean_backtrace(backtrace);
format!("{backtrace:?}")
}
impl<E> Deref for Backtraced<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<E> DerefMut for Backtraced<E> {
fn deref_mut(&mut self) -> &mut E {
&mut self.inner
}
}
impl<E: PartialEq> PartialEq for Backtraced<E> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
#[derive(Debug)]
pub struct GenericError(pub Box<dyn Error>);
impl GenericError {
pub fn from_static<T: Error + 'static>(e: T) -> Self {
Self(Box::new(e))
}
}
impl<E: Error + 'static> From<Backtraced<E>> for Backtraced<GenericError> {
fn from(value: Backtraced<E>) -> Self {
Backtraced {
inner: GenericError(value.inner.into()),
backtrace: value.backtrace,
}
}
}
impl<E: Error + 'static> From<E> for Backtraced<GenericError> {
fn from(value: E) -> Self {
Backtraced::<E>::from(value).into()
}
}
impl From<GenericError> for Box<dyn Error> {
fn from(value: GenericError) -> Self {
value.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
fn fail_io() -> io::Result<()> {
Err(io::Error::other("simple"))
}
#[test]
#[ignore = "compilation test"]
fn fail_backtraced() -> Result<(), io::Error> {
Err(io::Error::other("backtraced"))?
}
#[test]
#[ignore = "compilation test"]
fn fail_try_backtraced() -> Result<(), io::Error> {
fail_io()?;
Ok(())
}
#[test]
#[ignore = "compilation test"]
fn fail_into_backtraced() -> Result<(), io::Error> {
use super::IntoTraced;
fail_io().with_trace()
}
#[allow(dead_code)]
#[derive(Debug)]
struct MyError(io::Error);
impl From<io::Error> for MyError {
fn from(value: io::Error) -> Self {
MyError(value)
}
}
#[test]
#[ignore = "compilation test"]
fn map_err_into() -> Result<(), MyError> {
let e = fail_into_backtraced();
e.map_err(Backtraced::map_into)
}
#[test]
#[ignore = "compilation test"]
fn map_trace() -> Result<(), MyError> {
let e = fail_into_backtraced();
e.map_trace(MyError::from)
}
#[test]
#[ignore = "compilation test"]
fn map_trace_into() -> Result<(), MyError> {
let e = fail_into_backtraced();
e.map_trace_into()
}
#[test]
#[ignore = "compilation test"]
fn test_error_generic_question() -> Result<(), GenericError> {
let out = fail_io()?;
Ok(out)
}
#[test]
#[ignore = "compilation test"]
fn test_backtraced_generic_question() -> Result<(), GenericError> {
let out = fail_backtraced()?;
Ok(out)
}
fn fail_boxed() -> std::result::Result<(), Box<dyn Error>> {
Ok(fail_io()?)
}
#[test]
#[ignore = "compilation test"]
fn concrete_to_generic() -> std::result::Result<(), GenericError> {
Ok(fail_io().map_err(GenericError::from_static)?)
}
#[test]
#[ignore = "compilation test"]
fn boxed_to_generic() -> std::result::Result<(), GenericError> {
Ok(fail_boxed().map_err(GenericError)?)
}
#[test]
#[ignore = "compilation test"]
fn literal_to_generic() -> std::result::Result<(), GenericError> {
let e = io::Error::other("simple");
Err(GenericError::from_static(e))
}
#[test]
#[ignore = "compilation test"]
fn literal_to_generic_traced() -> Result<(), GenericError> {
let e = io::Error::other("simple");
Ok(Err(GenericError::from_static(e))?)
}
#[test]
#[ignore = "compilation test"]
fn boxed_to_generic_traced() -> Result<(), GenericError> {
Ok(fail_boxed().map_err(GenericError)?)
}
}