1#[macro_export]
3macro_rules! chain {
4 ($f:expr) => {
6 $f
7 };
8
9 ($f:expr, $g:expr $(, $rest:expr)*) => {
11 |a| $f(a).and_then(|b| $g(b))$(.and_then(|x| $rest(x)))*
12 };
13}
14
15pub fn chain_opt<A, B, C>(
17 f: impl Fn(A) -> Option<B>,
18 g: impl Fn(B) -> Option<C>,
19) -> impl Fn(A) -> Option<C> {
20 move |a| f(a).and_then(|b| g(b))
21}
22
23pub fn chain_result<A, B, C, E>(
25 f: impl Fn(A) -> Result<B, E>,
26 g: impl Fn(B) -> Result<C, E>,
27) -> impl Fn(A) -> Result<C, E> {
28 move |a| f(a).and_then(|b| g(b))
29}
30
31pub fn chain_vec<A, B, C>(
33 f: impl Fn(A) -> Vec<B>,
34 g: impl Fn(B) -> Vec<C>,
35) -> impl Fn(A) -> Vec<C> {
36 move |a| f(a).into_iter().flat_map(|b| g(b)).collect()
37}
38
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 fn str_to_int(s: &str) -> Option<i32> {
45 s.parse().ok()
46 }
47
48 fn double(n: i32) -> Option<i32> {
49 Some(n * 2)
50 }
51
52 fn to_string(n: i32) -> Option<String> {
53 Some(format!("Number: {}", n))
54 }
55
56 #[test]
57 fn test_chain_opt_success() {
58 let f = chain_opt(str_to_int, double);
59 assert_eq!(f("21"), Some(42));
60 }
61
62 #[test]
63 fn test_chain_opt_failure() {
64 let f = chain_opt(str_to_int, double);
65 assert_eq!(f("not-a-number"), None);
66 }
67
68 #[test]
69 fn test_chain_vec_basic() {
70 let f = chain_vec(|n| vec![n, n + 1], |x| vec![x * 2]);
71 assert_eq!(f(3), vec![6, 8]);
72 }
73
74 #[test]
75 fn test_chain_vec_empty() {
76 let f = chain_vec(|_: i32| Vec::<i32>::new(), |x| vec![x * 2]);
77 assert_eq!(f(3), vec![]);
78 }
79
80 #[test]
81 fn test_chain_result_success() {
82 let f = chain_result(|s: &str| s.parse::<i32>(), |n| Ok(n * 10));
83 assert_eq!(f("5").unwrap(), 50);
84 }
85
86 #[test]
87 fn test_chain_result_failure_first() {
88 let f = chain_result(|s: &str| s.parse::<i32>(), |n| Ok(n * 10));
89 assert!(f("foo").is_err());
90 }
91
92 #[test]
93 fn test_chain_macro_option_success() {
94 let f = chain!(str_to_int, double, to_string);
95 assert_eq!(f("7"), Some("Number: 14".to_string()));
96 }
97
98 #[test]
99 fn test_chain_macro_option_failure() {
100 let f = chain!(str_to_int, double, to_string);
101 assert_eq!(f("oops"), None);
102 }
103}