1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
pub trait Oops<T> {
    fn oops(self, msg: &str) -> std::io::Result<T>;

    fn lazy_oops<F: FnOnce() -> String>(self, lazy_msg: F) -> std::io::Result<T>;
}

impl<T> Oops<T> for Option<T> {
    fn oops(self, msg: &str) -> std::io::Result<T> {
        self.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, msg))
    }

    fn lazy_oops<F: FnOnce() -> String>(self, lazy_msg: F) -> std::io::Result<T> {
        match self {
            Some(x) => Ok(x),
            _ => self.oops(&lazy_msg()),
        }
    }
}

impl<T, E> Oops<T> for Result<T, E> {
    fn oops(self, msg: &str) -> std::io::Result<T> {
        self.ok().oops(msg)
    }

    fn lazy_oops<F: FnOnce() -> String>(self, lazy_msg: F) -> std::io::Result<T> {
        match self {
            Ok(x) => Ok(x),
            _ => self.oops(&lazy_msg()),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::*;

    fn third_element(slice: &[usize]) -> std::io::Result<&usize> {
        slice.iter().nth(3).oops("No third element")
    }

    #[test]
    fn test_oops() {
        let v: std::io::Result<()> = None.oops("_");
        assert!(v.is_err());

        let slice = &[1, 2];
        assert!(slice.iter().nth(3).oops("No 3rd element").is_err());

        assert!(third_element(&[1, 2, 3]).is_err());
    }

    #[test]
    fn test_oops_result() {
        let v: std::result::Result<(), String> = Err("hello world".into());
        assert!(v.is_err());

        let oops: std::io::Result<()> = v.oops("oh no");
        assert_eq!(oops.err().unwrap().kind(), std::io::ErrorKind::Other)
    }

    #[test]
    fn test_oops_lazy() {
        // Closure is not evaluated for Ok
        let v: std::result::Result<(), ()> = Ok(());
        v.lazy_oops(|| panic!("Impossibru")).unwrap();

        // Closure is convenient for format!
        (0..10).for_each(|v| {
            Ok::<(), ()>(())
                .lazy_oops(|| format!("Something happened when {}", v))
                .unwrap();
        });

        // Closure is evaluated with Err
        let q: std::result::Result<(), ()> = Err(());
        assert_eq!(
            q.lazy_oops(|| "oh no".into()).err().unwrap().kind(),
            std::io::ErrorKind::Other
        );
    }

    #[test]
    fn test_doc() {
        use std::io::Result;

        fn third_element(slice: &[usize]) -> Result<&usize> {
            // Using oops to add context to a None
            slice.iter().nth(3).oops("No third element")
        }

        fn parse_batch(slice: &[&str]) -> Result<Vec<usize>> {
            slice
                .iter()
                .map(|v| {
                    v.parse::<usize>()
                        // Using lazy_oops to add debug messages
                        .lazy_oops(|| format!("Failed to parse {} from {:?}", v, slice))
                })
                .collect()
        }

        assert_eq!(
            // No third element
            third_element(&[1, 2, 3]).err().unwrap().kind(),
            std::io::ErrorKind::Other
        );

        assert_eq!(
            // Failed to parse lo from ["2", "3", "7", "lo", "11"]
            parse_batch(&["2", "3", "7", "lo", "11"])
                .err()
                .unwrap()
                .kind(),
            std::io::ErrorKind::Other
        );
    }
}