fallback_if/
lib.rs

1/// Fallback to an alternative, if the previous result outcome was a fail, and a predicate is true.
2pub trait FallbackIf<R> {
3    /// Fallback to an alternative when an outcome is considered a **fail** and **the predicate evaluates
4    /// to true**, otherwise keep the current result.
5    fn fallback_if<P, F>(self, predicate: P, alternative: F) -> R
6    where
7        P: Into<bool>,
8        F: FnOnce() -> R;
9}
10
11impl<T, E> FallbackIf<Result<T, E>> for Result<T, E> {
12    /// Fallback to an alternative when a result **produces an error** and **the predicate evaluates
13    /// to true**, otherwise keep the current result.
14    fn fallback_if<P, F>(self, predicate: P, alternative: F) -> Result<T, E>
15    where
16        P: Into<bool>,
17        F: FnOnce() -> Result<T, E>,
18    {
19        if self.is_err() && predicate.into() {
20            alternative()
21        } else {
22            self
23        }
24    }
25}
26impl<T> FallbackIf<Option<T>> for Option<T> {
27    fn fallback_if<P, F>(self, predicate: P, alternative: F) -> Option<T>
28    where
29        P: Into<bool>,
30        F: FnOnce() -> Option<T>,
31    {
32        if self.is_none() && predicate.into() {
33            alternative()
34        } else {
35            self
36        }
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[yare::parameterized(
45        ok =  { Ok(1), Ok(9), Ok(1) },                  // Does not enter the alternative, because input = Ok
46        err_to_ok = { Err(()), Ok(9), Ok(9)  },         // Does enter the alternative, because input = Err and fallback pred. = true
47        err_to_err = { Err(()), Err(()), Err(())  },    // Does enter the alternative, because input = Err and fallback pred. = true
48    )]
49    fn result_do_fallback(
50        result: Result<u32, ()>,
51        fallback_result: Result<u32, ()>,
52        expected: Result<u32, ()>,
53    ) {
54        let outcome = result.fallback_if(true, || fallback_result);
55
56        assert_eq!(outcome, expected)
57    }
58
59    #[yare::parameterized(
60        ok =  { Ok(1), Ok(9), Ok(1) },                  // Does not enter the alternative, because input = Ok
61        err_to_ok = { Err(()), Ok(9), Err(())  },       // Does not enter the alternative, because while input = Err, fallback pred. = false
62        err_to_err = { Err(()), Err(()), Err(())  },    // Does not enter the alternative, because while input = Err, fallback pred. = false
63    )]
64    fn result_do_not_fallback(
65        result: Result<u32, ()>,
66        fallback_result: Result<u32, ()>,
67        expected: Result<u32, ()>,
68    ) {
69        let outcome = result.fallback_if(false, || fallback_result);
70
71        assert_eq!(outcome, expected)
72    }
73
74    #[yare::parameterized(
75        ok =  { Some(1), Some(9), Some(1) },        // Does not enter the alternative, because input = Some
76        none_to_some = { None, Some(9), Some(9)  }, // Does enter the alternative, because input = None and fallback pred. = true
77        none_to_none = { None, None, None  },       // Does enter the alternative, because input = None and fallback pred. = true
78    )]
79    fn option_do_fallback(result: Option<u32>, fallback_value: Option<u32>, expected: Option<u32>) {
80        let outcome = result.fallback_if(true, || fallback_value);
81
82        assert_eq!(outcome, expected)
83    }
84
85    #[yare::parameterized(
86        ok =  { Some(1), Some(9), Some(1) },        // Does not enter the alternative, because input = Some
87        none_to_some = { None, Some(9), None  },    // Does not enter the alternative, because while input = None, fallback pred. = false
88        none_to_none = { None, None, None  },       // Does not enter the alternative, because while input = None, fallback pred. = false
89    )]
90    fn option_do_not_fallback(
91        result: Option<u32>,
92        fallback_value: Option<u32>,
93        expected: Option<u32>,
94    ) {
95        let outcome = result.fallback_if(false, || fallback_value);
96
97        assert_eq!(outcome, expected)
98    }
99
100    #[test]
101    fn readme() {
102        struct Config {
103            fallback_to_local: bool,
104        }
105
106        #[derive(Debug, PartialEq)]
107        struct Manifest;
108
109        impl Manifest {
110            pub fn try_fetch_remote() -> Result<Self, ()> {
111                // Oh noes! failed to fetch manifest remotely
112                Err(())
113            }
114
115            pub fn try_fetch_local() -> Result<Self, ()> {
116                // Yesss! Fetched locally!
117                Ok(Manifest)
118            }
119        }
120
121        let config = Config {
122            fallback_to_local: true,
123        };
124        let result = Manifest::try_fetch_remote();
125
126        let outcome = result.fallback_if(config.fallback_to_local, || Manifest::try_fetch_local());
127
128        assert_eq!(outcome, Ok(Manifest))
129    }
130}