Add unit tests for `MaybeMath` (#162)
1//! Contains numerical helper traits and functions
2
3/// A trait to conveniently calculate minimums and maximums when some data may not be defined
4///
5/// If the left-hand value is [`None`], these operations return [`None`].
6/// If the right-hand value is [`None`], it is treated as zero.
7pub(crate) trait MaybeMath<In, Out> {
8 /// Returns the minimum of `self` and `rhs`
9 fn maybe_min(self, rhs: In) -> Out;
10
11 /// Returns the maximum of `self` and `rhs`
12 fn maybe_max(self, rhs: In) -> Out;
13
14 /// Adds `self` and `rhs`.
15 fn maybe_add(self, rhs: In) -> Out;
16
17 /// Subtracts rhs from `self`, treating [`None`] values as default
18 fn maybe_sub(self, rhs: In) -> Out;
19}
20
21impl MaybeMath<Option<f32>, Option<f32>> for Option<f32> {
22 fn maybe_min(self, rhs: Option<f32>) -> Option<f32> {
23 match (self, rhs) {
24 (Some(l), Some(r)) => Some(l.min(r)),
25 (Some(_l), None) => self,
26 (None, Some(_r)) => None,
27 (None, None) => None,
28 }
29 }
30
31 fn maybe_max(self, rhs: Option<f32>) -> Option<f32> {
32 match (self, rhs) {
33 (Some(l), Some(r)) => Some(l.max(r)),
34 (Some(_l), None) => self,
35 (None, Some(_r)) => None,
36 (None, None) => None,
37 }
38 }
39
40 fn maybe_add(self, rhs: Option<f32>) -> Option<f32> {
41 match (self, rhs) {
42 (Some(l), Some(r)) => Some(l + r),
43 (Some(_l), None) => self,
44 (None, Some(_r)) => None,
45 (None, None) => None,
46 }
47 }
48
49 fn maybe_sub(self, rhs: Option<f32>) -> Option<f32> {
50 match (self, rhs) {
51 (Some(l), Some(r)) => Some(l - r),
52 (Some(_l), None) => self,
53 (None, Some(_r)) => None,
54 (None, None) => None,
55 }
56 }
57}
58
59impl MaybeMath<f32, Option<f32>> for Option<f32> {
60 // The logic here is subtle and critical;
61 // explicit match statements make it more clear what's going on
62 #[allow(clippy::manual_map)]
63 fn maybe_min(self, rhs: f32) -> Option<f32> {
64 match self {
65 Some(val) => Some(val.min(rhs)),
66 None => None,
67 }
68 }
69
70 #[allow(clippy::manual_map)]
71 fn maybe_max(self, rhs: f32) -> Option<f32> {
72 match self {
73 Some(val) => Some(val.max(rhs)),
74 None => None,
75 }
76 }
77
78 #[allow(clippy::manual_map)]
79 fn maybe_add(self, rhs: f32) -> Option<f32> {
80 match self {
81 Some(val) => Some(val + rhs),
82 None => None,
83 }
84 }
85
86 #[allow(clippy::manual_map)]
87 fn maybe_sub(self, rhs: f32) -> Option<f32> {
88 match self {
89 Some(val) => Some(val - rhs),
90 None => None,
91 }
92 }
93}
94
95impl MaybeMath<Option<f32>, f32> for f32 {
96 fn maybe_min(self, rhs: Option<f32>) -> f32 {
97 match rhs {
98 Some(val) => self.min(val),
99 None => self,
100 }
101 }
102
103 fn maybe_max(self, rhs: Option<f32>) -> f32 {
104 match rhs {
105 Some(val) => self.max(val),
106 None => self,
107 }
108 }
109
110 fn maybe_add(self, rhs: Option<f32>) -> f32 {
111 match rhs {
112 Some(val) => self + val,
113 None => self,
114 }
115 }
116
117 fn maybe_sub(self, rhs: Option<f32>) -> f32 {
118 match rhs {
119 Some(val) => self - val,
120 None => self,
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 mod lhs_option_f32_rhs_option_f32 {
128
129 use crate::math::MaybeMath;
130 use rstest::rstest;
131
132 #[rstest]
133 #[case(Some(3.0), Some(5.0), Some(3.0))]
134 #[case(Some(5.0), Some(3.0), Some(3.0))]
135 #[case(Some(3.0), None, Some(3.0))]
136 #[case(None, Some(3.0), None)]
137 #[case(None, None, None)]
138 fn test_maybe_min(#[case] lhs: Option<f32>, #[case] rhs: Option<f32>, #[case] expected: Option<f32>) {
139 assert_eq!(lhs.maybe_min(rhs), expected);
140 }
141
142 #[rstest]
143 #[case(Some(3.0), Some(5.0), Some(5.0))]
144 #[case(Some(5.0), Some(3.0), Some(5.0))]
145 #[case(Some(3.0), None, Some(3.0))]
146 #[case(None, Some(3.0), None)]
147 #[case(None, None, None)]
148 fn test_maybe_max(#[case] lhs: Option<f32>, #[case] rhs: Option<f32>, #[case] expected: Option<f32>) {
149 assert_eq!(lhs.maybe_max(rhs), expected);
150 }
151
152 #[rstest]
153 #[case(Some(3.0), Some(5.0), Some(8.0))]
154 #[case(Some(5.0), Some(3.0), Some(8.0))]
155 #[case(Some(3.0), None, Some(3.0))]
156 #[case(None, Some(3.0), None)]
157 #[case(None, None, None)]
158 fn test_maybe_add(#[case] lhs: Option<f32>, #[case] rhs: Option<f32>, #[case] expected: Option<f32>) {
159 assert_eq!(lhs.maybe_add(rhs), expected);
160 }
161
162 #[rstest]
163 #[case(Some(3.0), Some(5.0), Some(-2.0))]
164 #[case(Some(5.0), Some(3.0), Some(2.0))]
165 #[case(Some(3.0), None, Some(3.0))]
166 #[case(None, Some(3.0), None)]
167 #[case(None, None, None)]
168 fn test_maybe_sub(#[case] lhs: Option<f32>, #[case] rhs: Option<f32>, #[case] expected: Option<f32>) {
169 assert_eq!(lhs.maybe_sub(rhs), expected);
170 }
171 }
172
173 mod lhs_option_f32_rhs_f32 {
174
175 use crate::math::MaybeMath;
176 use rstest::rstest;
177
178 #[rstest]
179 #[case(Some(3.0), 5.0, Some(3.0))]
180 #[case(Some(5.0), 3.0, Some(3.0))]
181 #[case(None, 3.0, None)]
182 fn test_maybe_min(#[case] lhs: Option<f32>, #[case] rhs: f32, #[case] expected: Option<f32>) {
183 assert_eq!(lhs.maybe_min(rhs), expected);
184 }
185
186 #[rstest]
187 #[case(Some(3.0), 5.0, Some(5.0))]
188 #[case(Some(5.0), 3.0, Some(5.0))]
189 #[case(None, 3.0, None)]
190 fn test_maybe_max(#[case] lhs: Option<f32>, #[case] rhs: f32, #[case] expected: Option<f32>) {
191 assert_eq!(lhs.maybe_max(rhs), expected);
192 }
193
194 #[rstest]
195 #[case(Some(3.0), 5.0, Some(8.0))]
196 #[case(Some(5.0), 3.0, Some(8.0))]
197 #[case(None, 3.0, None)]
198 fn test_maybe_add(#[case] lhs: Option<f32>, #[case] rhs: f32, #[case] expected: Option<f32>) {
199 assert_eq!(lhs.maybe_add(rhs), expected);
200 }
201
202 #[rstest]
203 #[case(Some(3.0), 5.0, Some(-2.0))]
204 #[case(Some(5.0), 3.0, Some(2.0))]
205 #[case(None, 3.0, None)]
206 fn test_maybe_sub(#[case] lhs: Option<f32>, #[case] rhs: f32, #[case] expected: Option<f32>) {
207 assert_eq!(lhs.maybe_sub(rhs), expected);
208 }
209 }
210
211 mod lhs_f32_rhs_option_f32 {
212
213 use crate::math::MaybeMath;
214 use rstest::rstest;
215
216 #[rstest]
217 #[case(3.0, Some(5.0), 3.0)]
218 #[case(5.0, Some(3.0), 3.0)]
219 #[case(3.0, None, 3.0)]
220 fn test_maybe_min(#[case] lhs: f32, #[case] rhs: Option<f32>, #[case] expected: f32) {
221 assert_eq!(lhs.maybe_min(rhs), expected);
222 }
223
224 #[rstest]
225 #[case(3.0, Some(5.0), 5.0)]
226 #[case(5.0, Some(3.0), 5.0)]
227 #[case(3.0, None, 3.0)]
228 fn test_maybe_max(#[case] lhs: f32, #[case] rhs: Option<f32>, #[case] expected: f32) {
229 assert_eq!(lhs.maybe_max(rhs), expected);
230 }
231
232 #[rstest]
233 #[case(3.0, Some(5.0), 8.0)]
234 #[case(5.0, Some(3.0), 8.0)]
235 #[case(3.0, None, 3.0)]
236 fn test_maybe_add(#[case] lhs: f32, #[case] rhs: Option<f32>, #[case] expected: f32) {
237 assert_eq!(lhs.maybe_add(rhs), expected);
238 }
239
240 #[rstest]
241 #[case(3.0, Some(5.0), -2.0)]
242 #[case(5.0, Some(3.0), 2.0)]
243 #[case(3.0, None, 3.0)]
244 fn test_maybe_sub(#[case] lhs: f32, #[case] rhs: Option<f32>, #[case] expected: f32) {
245 assert_eq!(lhs.maybe_sub(rhs), expected);
246 }
247 }
248}