merge_rs/
lib.rs

1/// Trait for types that support merging two values and producing a new value
2/// with the result of the merge. The input values are left unchanged.
3pub trait Merge {
4    fn merge(&self, other: &Self) -> Result<Self, Box<dyn std::error::Error>> where Self: Sized;
5}
6
7/// Trait for types that support merging two values and replace the target values
8/// with the result of the merge operation.
9pub trait MergeMut {
10    fn merge_mut(&mut self, other: &Self) -> Result<(), Box<dyn std::error::Error>>;
11}
12
13/// Implementation of merge for the Option type. The merge is defined with the following
14/// logic chart:
15///
16/// | Target  | Other   | Result           |
17/// |---------|---------|------------------|
18/// | None    | None    | None             |
19/// | Some(a) | None    | Some(a)          |
20/// | None    | Some(b) | Some(b)          |
21/// | Some(a) | Some(b) | Some(a.merge(b)) |
22///
23/// Two Option instances can only merge if their containing data elements also support
24/// merging.
25impl<T: Clone + Merge> Merge for Option<T> {
26    fn merge(&self, rhs: &Self) -> Result<Self, Box<dyn std::error::Error>> where Self: Sized {
27        Ok(match (self, rhs) {
28            (Some(left), Some(right)) => Some(left.merge(right)?),
29            (Some(left), None) => Some(left.clone()),
30            (None, Some(right)) => Some(right.clone()),
31            (None, None) => None,
32        })
33    }
34}
35
36/// Implementation of merge for the Result type. The merge strategy used is right-bias
37/// towards the error case. If the target or right hand side is an [Err] instance, the
38/// result will contain that [Err] value. When both values are [Ok], then the result will
39/// be an [Ok] containing the result of merging the inner values.
40impl<T: Clone + Merge, E: Clone + Merge> Merge for Result<T, E> {
41    fn merge(&self, rhs: &Self) -> Result<Self, Box<dyn std::error::Error>> where Self: Sized {
42        Ok(match (self, rhs) {
43            (Err(left), _) => Err(left.clone()),
44            (_, Err(right)) => Err(right.clone()),
45            (Ok(left), Ok(right)) => Ok(left.merge(right)?),
46        })
47    }
48}
49
50pub use merge_rs_derive::{Merge, MergeMut};
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[derive(Clone)]
57    struct MergeSpy(&'static str, bool);
58
59    impl MergeSpy {
60        fn new() -> Self {
61            Self::with_label("spy")
62        }
63
64        fn with_label(label: &'static str) -> Self {
65            MergeSpy(label, false)
66        }
67
68        fn is_merge_called(&self) -> bool {
69            self.1
70        }
71
72        fn label(&self) -> &str {
73            self.0
74        }
75    }
76
77    impl Merge for MergeSpy {
78        fn merge(&self, _: &Self) -> Result<Self, Box<dyn std::error::Error>> where Self: Sized {
79            Ok(MergeSpy("merged", true))
80        }
81    }
82
83    type TestResult = Result<(), Box<dyn std::error::Error>>;
84
85    #[test]
86    fn test_option_merge_both_none() -> TestResult {
87        let left: Option<MergeSpy> = None;
88        let right: Option<MergeSpy> = None;
89
90        let actual = left.merge(&right)?;
91        assert!(actual.is_none());
92        Ok(())
93    }
94
95    #[test]
96    fn test_option_merge_left_none() -> TestResult {
97        let left = None;
98        let right = Some(MergeSpy::new());
99
100        let actual = left.merge(&right)?;
101        assert!(matches!(actual, Some(res) if !res.is_merge_called()));
102        Ok(())
103    }
104
105    #[test]
106    fn test_option_merge_right_none() -> TestResult {
107        let left = Some(MergeSpy::new());
108        let right = None;
109
110        let actual = left.merge(&right)?;
111        assert!(matches!(actual, Some(res) if !res.is_merge_called()));
112        Ok(())
113    }
114
115    #[test]
116    fn test_option_merge_both_some() -> TestResult {
117        let left = Some(MergeSpy::new());
118        let right = Some(MergeSpy::new());
119
120        let actual = left.merge(&right)?;
121        assert!(matches!(actual, Some(res) if res.is_merge_called()));
122        Ok(())
123    }
124
125    #[test]
126    fn test_result_merge_left_err() -> TestResult {
127        let left = Err(MergeSpy::with_label("left"));
128        let right = Ok(MergeSpy::with_label("right"));
129
130        let actual = left.merge(&right)?;
131        assert!(matches!(actual, Err(res) if res.label() == "left" && !res.is_merge_called()));
132        Ok(())
133    }
134
135    #[test]
136    fn test_result_merge_left_both_err() -> TestResult {
137        let left: Result<MergeSpy, MergeSpy> = Err(MergeSpy::with_label("left"));
138        let right: Result<MergeSpy, MergeSpy> = Err(MergeSpy::with_label("right"));
139
140        let actual = left.merge(&right)?;
141        assert!(matches!(actual, Err(res) if res.label() == "left" && !res.is_merge_called()));
142        Ok(())
143    }
144
145    #[test]
146    fn test_result_merge_right_err() -> TestResult {
147        let left = Ok(MergeSpy::with_label("left"));
148        let right = Err(MergeSpy::with_label("right"));
149
150        let actual = left.merge(&right)?;
151        assert!(matches!(actual, Err(res) if res.label() == "right" && !res.is_merge_called()));
152        Ok(())
153    }
154
155    #[test]
156    fn test_result_merge_both_ok() -> TestResult {
157        let left: Result<MergeSpy, MergeSpy> = Ok(MergeSpy::with_label("left"));
158        let right: Result<MergeSpy, MergeSpy> = Ok(MergeSpy::with_label("right"));
159
160        let actual = left.merge(&right)?;
161        assert!(matches!(actual, Ok(res) if res.is_merge_called()));
162        Ok(())
163    }
164}