goof/
lib.rs

1//! The goof library is a collection of re-usable error handling
2//! structs and patterns that are meant to make error handling
3//! lightweight, portable and inter-convertible.
4
5use core::fmt::{Debug, Display};
6
7/// Assert that the object is exactly equal to the provided test value.
8///
9/// # Motivation
10///
11/// Oftentimes one really only needs an assertion to be propagated
12/// upwards.  Given that try blocks are not stable, this syntax has
13/// some merit.  This assert can be used inside function arguments, at
14/// the tops of functions to get rid of an ugly `if` and makes it
15/// explicit that what you want is to do what the standard library's
16/// `assert_eq!` does, but to create an error rather than panic.
17///
18/// # Examples
19/// ```rust
20/// use goof::{Mismatch, assert_eq};
21///
22/// fn fallible_func(thing: &[u8]) -> Result<(), Mismatch<usize>> {
23///     assert_eq(&32, &thing.len())?;
24///
25///     Ok(())
26/// }
27///
28/// assert_eq!(fallible_func(&[]).unwrap_err(), assert_eq(&32, &0).unwrap_err())
29/// ```
30pub fn assert_eq<T: Copy + Eq>(actual: &T, expected: &T) -> Result<T, Mismatch<T>> {
31    if expected.eq(&actual) {
32        Ok(*expected)
33    } else {
34        Err(Mismatch {
35            expected: *expected,
36            actual: *actual,
37        })
38    }
39}
40
41/// Assert that the object is exactly within the boundaries given by
42/// the `range` operand.
43///
44/// # Motivation
45///
46/// Oftentimes one really only needs an assertion to be propagated
47/// upwards.  Given that try blocks are not stable, this syntax has
48/// some merit.  This assert can be used inside function arguments, at
49/// the tops of functions to get rid of an ugly `if` and makes it
50/// explicit that what you want is to do what the standard library's
51/// `assert_eq!` does, but to create an error rather than panic.
52///
53/// # Examples
54/// ```rust
55/// use goof::{Outside, assert_in};
56///
57/// fn fallible_func(thing: &[u8]) -> Result<(), Outside<usize>> {
58///     assert_in(&thing.len(), &(32..64))?;
59///
60///     Ok(())
61/// }
62///
63/// assert_eq!(fallible_func(&vec![0; 32]).unwrap_err(), assert_in(&32, &0).unwrap_err())
64/// ```
65pub fn assert_in<T: Ord + Copy>(value: &T, range: &core::ops::Range<T>) -> Result<T, Outside<T>> {
66    if value > &range.start && value <= &range.end {
67        Ok(*value)
68    } else {
69        // TODO: isn't Range<T> supposed to be Copy?
70        Err(Outside {
71            range: range.clone(),
72            value: *value,
73        })
74    }
75}
76
77/// This structure should be used in cases where a value must be
78/// exactly equal to another value for the process to be valid.
79#[derive(PartialEq, Eq, Clone, Copy)]
80pub struct Mismatch<T: Copy + Eq> {
81    /// The expected return type
82    pub(crate) expected: T,
83    /// What was actually received
84    pub(crate) actual: T,
85}
86
87impl<T: Debug + Copy + Eq> Debug for Mismatch<T> {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("Mismatch")
90            .field("expected", &self.expected)
91            .field("actual", &self.actual)
92            .finish()
93    }
94}
95
96impl<T: Display + Copy + Eq> Display for Mismatch<T> {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        write!(f, "Expected {}, but got {}", self.expected, self.actual)
99    }
100}
101
102/// This structure should be used in cases where a value must lie
103/// within a specific range
104#[derive(Clone)]
105pub struct Outside<T: Ord + Copy> {
106    /// The inclusive range into which the value must enter.
107    pub(crate) range: core::ops::Range<T>,
108    /// The value that failed to be included into the range.
109    pub(crate) value: T,
110}
111
112impl<T: Ord + Copy + Debug> Debug for Outside<T> {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        f.debug_struct("Outside")
115            .field("range", &self.range)
116            .field("value", &self.value)
117            .finish()
118    }
119}
120
121impl<T: Ord + Copy + Display> Display for Outside<T> {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        if self.value >= self.range.end {
124            write!(f, "Value {} exceeds maximum {}", self.value, self.range.end)
125        } else if self.value < self.range.start {
126            write!(f, "Value {} below minimum {}", self.value, self.range.start)
127        } else {
128            panic!("An invalid instance of outside was created. Aborting")
129        }
130    }
131}
132
133impl<T: PartialEq + Ord + Copy> PartialEq for Outside<T> {
134    fn eq(&self, other: &Self) -> bool {
135        self.range == other.range && self.value == other.value
136    }
137}
138
139/// A thing is not a known value from a list
140#[derive(PartialEq, Eq, Clone)]
141pub struct Unknown<'a, T: Eq>{
142    /// The collection of things arranged in a linear sequence
143    pub(crate) knowns: Option<&'a [T]>,
144    /// The value that is not in the list
145    pub(crate) value: T,
146}
147
148impl<'a, T: Eq + Copy> Copy for Unknown<'a, T> {}
149
150impl<T: Eq + Debug> Debug for Unknown<'_, T> {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        f.debug_struct("Unknown")
153            .field("knowns", &self.knowns)
154            .field("value", &self.value)
155            .finish()
156    }
157}
158
159impl<T: Eq + Display> Display for Unknown<'_, T> {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "The value {} is not known", self.value)?;
162        if let Some(knowns) = self.knowns {
163            write!(f, "Because it's not one of [{}]", join(&knowns, ", ")?)
164        } else {
165            f.write_str(".")
166        }
167    }
168}
169
170pub fn join<T: Display>(items: &[T], separator: &'static str) -> Result<String, core::fmt::Error> {
171    use core::fmt::Write;
172
173    let first_element = items[0].to_string();
174    let mut buffer = String::with_capacity(
175        (items.len() - 1) * (separator.len() + first_element.len()) + first_element.len(),
176    );
177    for idx in 1..items.len() {
178        buffer.push_str(separator);
179        buffer.write_str(&items[idx].to_string())?;
180    }
181    Ok(buffer)
182}
183
184pub fn assert_known_enum<'a, T: Eq>(knowns: &'a [T], value: T) -> Result<T, Unknown<'a, T>> {
185    if knowns.contains(&value) {
186        Ok(value)
187    } else {
188        Err(Unknown {
189            knowns: Some(knowns),
190            value,
191        })
192    }
193}
194
195pub fn assert_known<'a, T: Eq>(knowns: &'a [T], value: T) -> Result<T, Unknown<'_, T>> {
196    if knowns.contains(&value) {
197        Ok(value)
198    } else {
199        Err(Unknown {
200            knowns: None,
201            value,
202        })
203    }
204}
205
206#[cfg(test)]
207pub mod tests {
208    use crate::{Mismatch, Outside, Unknown};
209
210    #[test]
211    fn usage_of_assert_eq() {
212        assert_eq!(crate::assert_eq(&32_u32, &32), Ok(32));
213        assert_eq!(
214            crate::assert_eq(&32_u32, &33),
215            Err(Mismatch {
216                expected: 32,
217                actual: 33
218            })
219        );
220    }
221
222    #[test]
223    fn usage_of_outside() {
224        assert_eq!(crate::assert_in(&2, &(1..5)), Ok(2));
225        assert_eq!(crate::assert_in(&5, &(1..5)), Ok(5));
226        assert_eq!(
227            crate::assert_in(&6, &(1..5)),
228            Err(Outside {
229                range: 1..5,
230                value: 6
231            })
232        );
233        assert_eq!(
234            crate::assert_in(&0, &(1..5)),
235            Err(Outside {
236                range: 1..5,
237                value: 0
238            })
239        );
240    }
241
242    #[test]
243    fn usage_of_unknown() {
244        let knowns = vec![1, 2, 4, 6, 7, 20_u32];
245        assert_eq!(crate::assert_known_enum(&knowns, 2), Ok(2));
246        assert_eq!(
247            crate::assert_known_enum(&knowns, 3),
248            Err(Unknown {
249                knowns: Some(&knowns),
250                value: 3
251            })
252        );
253        assert_eq!(crate::assert_known(&knowns, 2), Ok(2));
254        assert_eq!(
255            crate::assert_known(&knowns, 3),
256            Err(Unknown {
257                knowns: None,
258                value: 3
259            })
260        );
261    }
262}