logerr 0.2.0

Seamless error type logging
Documentation
//! # Generic error logging for implementors of [`Error`].
//!
//! > Requires the `generic` feature!
//!
//! Prints errors using their [`Display`](std::fmt::Display) impl, printing an error chain if
//! `source()` is not `None`.
use crate::LoggableError;
use std::error::Error;

impl<T, E: Error> LoggableError<T> for std::result::Result<T, E> {
    fn print_error<F: Fn(&str)>(self, fun: F) -> Self {
        if let Err(ref err) = self {
            let mut err = err as &dyn std::error::Error;
            fun(&format!("Error: {err}"));

            while let Some(source_err) = err.source() {
                fun(&format!("  because: {source_err}"));
                err = source_err;
            }
        }
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;
    use std::error::Error;
    use std::fmt;

    #[derive(Debug)]
    pub struct AError(BError);

    impl Error for AError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            Some(&self.0)
        }
    }

    impl fmt::Display for AError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "failed because A")
        }
    }

    #[derive(Debug)]
    pub struct BError(std::io::Error);

    impl Error for BError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            Some(&self.0)
        }
    }

    impl fmt::Display for BError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "failed because B")
        }
    }

    #[test]
    fn print_with_source() {
        let dump = RefCell::new(String::new());
        let err: std::result::Result<(), AError> =
            Err(AError(BError(std::io::ErrorKind::NotFound.into())));

        let _ = err.print_error(|msg| dump.borrow_mut().push_str(&msg));

        for (index, line) in dump.borrow_mut().lines().enumerate() {
            match index {
                0 => assert!(line.contains("failed because A")),
                1 => assert!(line.contains("failed because B")),
                2 => assert!(line.contains("entity not found")),
                _ => panic!("extra lines found in output: {}", line),
            }
        }
    }
}