throw 0.1.7

Efficiently add statically-calculated stack traces to errors.
Documentation
extern crate regex;
extern crate serde_json;

#[macro_use]
extern crate throw;

use throw::Result;

macro_rules! assert_matches {
    ($expected:expr, $actual:expr) => {{
        let expected = ($expected).replace("    ", "\t");
        let re = regex::Regex::new(&expected).expect("expected hardcoded regex to compile");

        let actual = format!("{}", $actual);

        assert!(
            re.is_match(&actual),
            format!(
                "expected error to match regex `\n{}\n`, but found `\n{}\n`",
                expected, actual
            )
        );
    }};
}

fn throw_static_message() -> Result<(), &'static str> {
    throw_new!("hi");
}

fn throw1() -> Result<(), ()> {
    throw_new!(());
}

fn throw2() -> Result<(), ()> {
    up!(throw1());
    Ok(())
}

fn throw3() -> Result<(), ()> {
    up!(throw2());
    Ok(())
}

fn throw_with_context1() -> Result<(), &'static str> {
    throw_new!("Error with context", "code"=>78,"application"=>"rust_core")
}

fn throw_with_context2() -> Result<(), &'static str> {
    up!(throw_with_context1(), "project_secret"=>"omega");
    Ok(())
}

fn throw_with_context3() -> Result<(), &'static str> {
    up!(throw_with_context2(),  "score"=>0.75, "height"=>948);
    Ok(())
}

fn gives_ok() -> Result<&'static str, &'static str> {
    Ok("ok")
}

fn throws_ok() -> Result<&'static str, &'static str> {
    let ok_msg = up!(gives_ok());
    Ok(ok_msg)
}

mod mod_test {
    use throw::Result;

    pub fn throws() -> Result<(), &'static str> {
        throw_new!("ahhhh");
    }
}

fn throws_into() -> Result<(), String> {
    throw!(Err("some static string"));
    Ok(())
}

fn throws_into_key_value() -> Result<(), String> {
    throw!(Err("some static string"), "key" => "value");
    Ok(())
}

fn throws_into_multiple_key_value_pairs() -> Result<(), String> {
    throw!(
        Err("some static string"),
        "key" => "value",
        "key2" => "value2",
        "key3" => "value3",
        "key4" => "value4",
    );
    Ok(())
}

#[test]
fn test_static_message() {
    let error = throw_static_message().unwrap_err();
    assert_eq!(*error.error(), "hi");
    assert_matches!(
        r#"Error: hi
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)"#,
        error
    );
    assert_eq!("hi".to_owned(), error.into_origin());
}

#[test]
#[cfg(any(feature = "serde-1", feature = "serde-1-std"))]
fn serialize_json() {
    let error = throw_with_context3().unwrap_err();
    let json = serde_json::to_string(&error).unwrap();
    let whitespace_trim = regex::Regex::new("\\s+").unwrap();
    let expected = r#"\{
        "points":\[
            \{"line":[0-9]+,"column":[0-9]+,"module_path":"exceptions_work",
                "file":"tests/exceptions_work.rs"\},
            \{"line":[0-9]+,"column":[0-9]+,"module_path":"exceptions_work",
                "file":"tests/exceptions_work.rs"\},
            \{"line":[0-9]+,"column":[0-9]+,"module_path":"exceptions_work",
                "file":"tests/exceptions_work.rs"\}
        \],
        "context":\[
            \{"key":"code","value":78\},
            \{"key":"application","value":"rust_core"\},
            \{"key":"project_secret","value":"omega"\},
            \{"key":"score","value":0.75\},
            \{"key":"height","value":948\}
        \],
        "error":"Error with context"
    \}"#;
    assert_matches!(whitespace_trim.replace_all(expected, "\\s*"), json);
}

#[test]
fn test_throw_with_context() {
    let error = throw_with_context1().unwrap_err();
    assert_eq!(error.get_context().len(), 2);

    let error2 = throw_with_context3().unwrap_err();
    assert_eq!(error2.get_context().len(), 5);

    assert_matches!(
        r#"Error: "Error with context"
    height: 948
	score: 0.75
	project_secret: omega
	application: rust_core
	code: 78
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)"#,
        format!("{:?}", error2)
    );
}

#[test]
#[allow(deprecated)]
fn test_multiple_throws() {
    let error = throw3().unwrap_err();
    assert_eq!(error.error(), &());
    assert_eq!(error.error(), error.original_error());
    assert_matches!(
        r#"Error: \(\)
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)"#,
        format!("{:?}", error)
    );
}

#[test]
fn test_returns_ok() {
    let ok = throws_ok().unwrap();
    assert_eq!(ok, "ok");
}

#[test]
fn test_mod_throw() {
    let error = mod_test::throws().unwrap_err();
    assert_matches!(
        r#"Error: ahhhh
    at [0-9]+:[0-9] in exceptions_work::mod_test \([a-z/._-]+\)"#,
        error
    );
}

#[test]
fn test_throws_into() {
    let error = throws_into().unwrap_err();
    assert_matches!(
        r#"Error: some static string
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)"#,
        error
    )
}

#[test]
fn test_throws_into_key_value() {
    let error = throws_into_key_value().unwrap_err();
    assert_matches!(
        r#"Error: some static string
    key: value
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)"#,
        error
    )
}

#[test]
fn test_throws_into_multiple_key_value_pairs() {
    let error = throws_into_multiple_key_value_pairs().unwrap_err();
    assert_matches!(
        r#"Error: some static string
    key4: value4
    key3: value3
    key2: value2
    key: value
    at [0-9]+:[0-9] in exceptions_work \([a-z/._-]+\)"#,
        error
    )
}