stacked_errors 0.8.0

high level error propagation with software controlled backtraces
Documentation
use stacked_errors::{ensure, ensure_eq, ensure_ne, Result, UnitError};

// there are also tests in the doc tests
#[test]
#[allow(clippy::partialeq_ne_impl)]
fn ensure_test() {
    // this is to test that movement within the macros do not result in errors, and
    // other properties
    #[derive(Debug)]
    struct X(i8);
    impl X {
        fn mov(self) -> X {
            self
        }
    }
    impl PartialEq for X {
        fn eq(&self, other: &Self) -> bool {
            self.0 == other.0
        }

        fn ne(&self, _: &Self) -> bool {
            panic!()
        }
    }
    #[derive(Debug)]
    struct Y(i8);
    impl Y {
        fn mov(self) -> Y {
            self
        }
    }
    impl PartialEq for Y {
        fn eq(&self, _: &Self) -> bool {
            panic!()
        }

        fn ne(&self, other: &Self) -> bool {
            self.0 != other.0
        }
    }

    fn successes() -> Result<u8> {
        let lhs = X(2);
        let rhs = X(2);
        ensure_eq!(lhs.mov(), rhs.mov());
        let lhs = X(2);
        let rhs = X(2);
        ensure_eq!(lhs.mov(), rhs.mov(), "hello");
        let lhs = Y(1);
        let rhs = Y(2);
        ensure_ne!(lhs.mov(), rhs.mov());
        let lhs = Y(1);
        let rhs = Y(2);
        ensure_ne!(lhs.mov(), rhs.mov(), "hello");
        ensure_eq!(String::new(), "");
        ensure_ne!("1".to_owned(), "2");
        let () = ensure_eq!(String::new(), "", "hello");
        ensure_ne!("1".to_owned(), "2", UnitError {});
        Ok(0)
    }
    assert_eq!(successes().unwrap(), 0);

    let fail = || -> Result<u8> {
        ensure_eq!(1, 2);
        Ok(0)
    };
    println!("{:?}", fail().unwrap_err());
    assert_eq!(
        format!("{}", fail().unwrap_err()),
        convert(
            r#"
    ensure_eq(
 lhs: 1
 rhs: 2
) -> equality assertion failed at tests/ensure.rs 64:9"#
        )
    );

    let fail = || -> Result<u8> {
        ensure_eq!(1, 2, "hello");
        Ok(0)
    };
    println!("{:?}", fail().unwrap_err());
    assert_eq!(
        format!("{}", fail().unwrap_err()),
        convert(
            r#"
    hello at tests/ensure.rs 80:9"#
        )
    );

    let fail = || -> Result<u8> {
        ensure_ne!(2, 2);
        Ok(0)
    };
    println!("{:?}", fail().unwrap_err());
    assert_eq!(
        format!("{}", fail().unwrap_err()),
        convert(
            r#"
    ensure_ne(
 lhs: 2
 rhs: 2
) -> inequality assertion failed
  at tests/ensure.rs 93:9"#
        )
    );

    let fail = || -> Result<u8> {
        ensure_ne!(2, 2, "hello");
        Ok(0)
    };
    println!("{:?}", fail().unwrap_err());
    assert_eq!(
        format!("{}", fail().unwrap_err()),
        convert(
            r#"
    hello at tests/ensure.rs 110:9"#
        )
    );
}

/// The message argument of the `ensure*` macros accepts everything that `bail`
/// does, and all of them accept a trailing comma
#[test]
fn ensure_message_forms() {
    fn err(res: Result<()>) -> String {
        format!("{}", res.unwrap_err())
    }

    let x = 5u64;

    // string literals interpolate like they do in `bail`
    assert!(err((|| {
        ensure!(false, "bad {x}");
        Ok(())
    })())
    .contains("bad 5"));

    // a format string with arguments
    assert!(err((|| {
        ensure!(false, "bad {}", x);
        Ok(())
    })())
    .contains("bad 5"));
    assert!(err((|| {
        ensure_eq!(1, 2, "bad {}", x);
        Ok(())
    })())
    .contains("bad 5"));
    assert!(err((|| {
        ensure_ne!(2, 2, "bad {}", x);
        Ok(())
    })())
    .contains("bad 5"));

    // a single expression
    let owned = String::from("owned");
    assert!(err((|| {
        ensure!(false, owned);
        Ok(())
    })())
    .contains("owned"));

    // trailing commas, with and without a message
    assert!(err((|| {
        ensure!(false,);
        Ok(())
    })())
    .contains("assertion failed"));
    assert!(err((|| {
        ensure_eq!(1, 2,);
        Ok(())
    })())
    .contains("equality assertion failed"));
    assert!(err((|| {
        ensure_ne!(2, 2,);
        Ok(())
    })())
    .contains("inequality assertion failed"));
    assert!(err((|| {
        ensure!(false, "lit",);
        Ok(())
    })())
    .contains("lit"));
    assert!(err((|| {
        ensure_eq!(1, 2, "lit",);
        Ok(())
    })())
    .contains("lit"));
    assert!(err((|| {
        ensure_ne!(2, 2, "lit",);
        Ok(())
    })())
    .contains("lit"));
}

pub fn convert(s: &str) -> String {
    let s = s.to_owned();
    if cfg!(windows) {
        s.replace("/", "\\")
    } else {
        s
    }
}