Skip to main content

test_better_matchers/
option_result.rs

1//! Matchers for [`Option`] and [`Result`]: [`some`], [`none`], [`ok`], and
2//! [`err`].
3//!
4//! `some`, `ok`, and `err` each take an *inner* matcher and apply it to the
5//! wrapped value, so they nest: `some(ok(eq(42)))` matches `Some(Ok(42))`. When
6//! an inner matcher fails, its expectation is wrapped in a
7//! [`Description::labeled`] layer, so a nested failure renders as aligned,
8//! indented `some:` / `ok:` blocks.
9
10use std::fmt;
11
12use crate::description::Description;
13use crate::matcher::{MatchResult, Matcher, Mismatch};
14
15/// Wraps an inner matcher's failure in a `label:`-headed [`Description`] layer,
16/// keeping the inner actual and diff. This is what gives nested matchers their
17/// aligned, indented expected blocks.
18fn wrap_failure(label: &'static str, inner: Mismatch) -> MatchResult {
19    MatchResult::fail(Mismatch {
20        expected: Description::labeled(label, inner.expected),
21        actual: inner.actual,
22        diff: inner.diff,
23    })
24}
25
26/// The matcher behind [`some`].
27struct SomeMatcher<M> {
28    inner: M,
29}
30
31impl<T, M> Matcher<Option<T>> for SomeMatcher<M>
32where
33    M: Matcher<T>,
34{
35    fn check(&self, actual: &Option<T>) -> MatchResult {
36        match actual {
37            Some(value) => match self.inner.check(value).failure {
38                None => MatchResult::pass(),
39                Some(inner) => wrap_failure("some", inner),
40            },
41            // `Matcher::description` is spelled out: `SomeMatcher<M>`
42            // implements `Matcher<Option<T>>` for a family of `T`, so a bare
43            // `self.description()` is ambiguous from inside `check`.
44            None => MatchResult::fail(Mismatch::new(
45                Matcher::<Option<T>>::description(self),
46                "None",
47            )),
48        }
49    }
50
51    fn description(&self) -> Description {
52        Description::labeled("some", self.inner.description())
53    }
54}
55
56/// Matches a `Some` whose contained value satisfies `inner`.
57///
58/// ```
59/// use test_better_core::TestResult;
60/// use test_better_matchers::{eq, check, some};
61///
62/// fn main() -> TestResult {
63///     check!(Some(42)).satisfies(some(eq(42)))?;
64///     check!(None::<i32>).violates(some(eq(42)))?;
65///     Ok(())
66/// }
67/// ```
68#[must_use]
69pub fn some<T, M>(inner: M) -> impl Matcher<Option<T>>
70where
71    M: Matcher<T>,
72{
73    SomeMatcher { inner }
74}
75
76/// The matcher behind [`none`].
77struct NoneMatcher;
78
79impl<T> Matcher<Option<T>> for NoneMatcher
80where
81    T: fmt::Debug,
82{
83    fn check(&self, actual: &Option<T>) -> MatchResult {
84        match actual {
85            None => MatchResult::pass(),
86            Some(_) => MatchResult::fail(Mismatch::new(
87                Matcher::<Option<T>>::description(self),
88                format!("{actual:?}"),
89            )),
90        }
91    }
92
93    fn description(&self) -> Description {
94        Description::text("none")
95    }
96}
97
98/// Matches `None`.
99///
100/// ```
101/// use test_better_core::TestResult;
102/// use test_better_matchers::{check, none};
103///
104/// fn main() -> TestResult {
105///     check!(None::<i32>).satisfies(none())?;
106///     check!(Some(0)).violates(none())?;
107///     Ok(())
108/// }
109/// ```
110#[must_use]
111pub fn none<T>() -> impl Matcher<Option<T>>
112where
113    T: fmt::Debug,
114{
115    NoneMatcher
116}
117
118/// The matcher behind [`ok`].
119struct OkMatcher<M> {
120    inner: M,
121}
122
123impl<T, E, M> Matcher<Result<T, E>> for OkMatcher<M>
124where
125    M: Matcher<T>,
126    E: fmt::Debug,
127{
128    fn check(&self, actual: &Result<T, E>) -> MatchResult {
129        match actual {
130            Ok(value) => match self.inner.check(value).failure {
131                None => MatchResult::pass(),
132                Some(inner) => wrap_failure("ok", inner),
133            },
134            Err(error) => MatchResult::fail(Mismatch::new(
135                Matcher::<Result<T, E>>::description(self),
136                format!("Err({error:?})"),
137            )),
138        }
139    }
140
141    fn description(&self) -> Description {
142        Description::labeled("ok", self.inner.description())
143    }
144}
145
146/// Matches an `Ok` whose contained value satisfies `inner`.
147///
148/// ```
149/// use test_better_core::TestResult;
150/// use test_better_matchers::{eq, check, ok};
151///
152/// fn main() -> TestResult {
153///     check!(Ok::<i32, &str>(42)).satisfies(ok(eq(42)))?;
154///     check!(Err::<i32, &str>("boom")).violates(ok(eq(42)))?;
155///     Ok(())
156/// }
157/// ```
158#[must_use]
159pub fn ok<T, E, M>(inner: M) -> impl Matcher<Result<T, E>>
160where
161    M: Matcher<T>,
162    E: fmt::Debug,
163{
164    OkMatcher { inner }
165}
166
167/// The matcher behind [`err`].
168struct ErrMatcher<M> {
169    inner: M,
170}
171
172impl<T, E, M> Matcher<Result<T, E>> for ErrMatcher<M>
173where
174    M: Matcher<E>,
175    T: fmt::Debug,
176{
177    fn check(&self, actual: &Result<T, E>) -> MatchResult {
178        match actual {
179            Err(value) => match self.inner.check(value).failure {
180                None => MatchResult::pass(),
181                Some(inner) => wrap_failure("err", inner),
182            },
183            Ok(value) => MatchResult::fail(Mismatch::new(
184                Matcher::<Result<T, E>>::description(self),
185                format!("Ok({value:?})"),
186            )),
187        }
188    }
189
190    fn description(&self) -> Description {
191        Description::labeled("err", self.inner.description())
192    }
193}
194
195/// Matches an `Err` whose contained value satisfies `inner`.
196///
197/// ```
198/// use test_better_core::TestResult;
199/// use test_better_matchers::{eq, err, check};
200///
201/// fn main() -> TestResult {
202///     check!(Err::<i32, &str>("boom")).satisfies(err(eq("boom")))?;
203///     check!(Ok::<i32, &str>(0)).violates(err(eq("boom")))?;
204///     Ok(())
205/// }
206/// ```
207#[must_use]
208pub fn err<T, E, M>(inner: M) -> impl Matcher<Result<T, E>>
209where
210    M: Matcher<E>,
211    T: fmt::Debug,
212{
213    ErrMatcher { inner }
214}
215
216#[cfg(test)]
217mod tests {
218    use test_better_core::{OrFail, TestResult};
219
220    use super::*;
221    use crate::{check, eq, is_false, is_true};
222
223    #[test]
224    fn some_matches_a_some_whose_value_satisfies_the_inner_matcher() -> TestResult {
225        check!(some(eq(42)).check(&Some(42)).matched).satisfies(is_true())?;
226        check!(some(eq(42)).check(&Some(7)).matched).satisfies(is_false())?;
227        check!(some(eq(42)).check(&None).matched).satisfies(is_false())?;
228        Ok(())
229    }
230
231    #[test]
232    fn some_of_none_reports_none_as_the_actual() -> TestResult {
233        let failure = some(eq(42))
234            .check(&None)
235            .failure
236            .or_fail_with("None is not Some")?;
237        check!(failure.expected.to_string()).satisfies(eq("some:\n  equal to 42".to_string()))?;
238        check!(failure.actual).satisfies(eq("None".to_string()))?;
239        Ok(())
240    }
241
242    #[test]
243    fn none_matches_only_none() -> TestResult {
244        check!(none::<i32>().check(&None).matched).satisfies(is_true())?;
245        let failure = none()
246            .check(&Some(7))
247            .failure
248            .or_fail_with("Some(7) is not None")?;
249        check!(failure.expected.to_string()).satisfies(eq("none".to_string()))?;
250        check!(failure.actual).satisfies(eq("Some(7)".to_string()))?;
251        Ok(())
252    }
253
254    #[test]
255    fn ok_matches_an_ok_whose_value_satisfies_the_inner_matcher() -> TestResult {
256        check!(ok::<i32, &str, _>(eq(42)).check(&Ok(42)).matched).satisfies(is_true())?;
257        let failure = ok::<i32, &str, _>(eq(42))
258            .check(&Err("boom"))
259            .failure
260            .or_fail_with("Err is not Ok")?;
261        check!(failure.expected.to_string()).satisfies(eq("ok:\n  equal to 42".to_string()))?;
262        check!(failure.actual).satisfies(eq("Err(\"boom\")".to_string()))?;
263        Ok(())
264    }
265
266    #[test]
267    fn err_matches_an_err_whose_value_satisfies_the_inner_matcher() -> TestResult {
268        check!(err::<i32, &str, _>(eq("boom")).check(&Err("boom")).matched).satisfies(is_true())?;
269        let failure = err::<i32, &str, _>(eq("boom"))
270            .check(&Ok(0))
271            .failure
272            .or_fail_with("Ok is not Err")?;
273        check!(failure.expected.to_string())
274            .satisfies(eq("err:\n  equal to \"boom\"".to_string()))?;
275        check!(failure.actual).satisfies(eq("Ok(0)".to_string()))?;
276        Ok(())
277    }
278
279    #[test]
280    fn nested_matchers_render_aligned_indented_expected_blocks() -> TestResult {
281        let matcher = some(ok::<i32, &str, _>(eq(42)));
282        check!(matcher.check(&Some(Ok(42))).matched).satisfies(is_true())?;
283
284        let failure = matcher
285            .check(&Some(Ok(43)))
286            .failure
287            .or_fail_with("Some(Ok(43)) does not satisfy some(ok(eq(42)))")?;
288        check!(failure.expected.to_string())
289            .satisfies(eq("some:\n  ok:\n    equal to 42".to_string()))?;
290        check!(failure.actual).satisfies(eq("43".to_string()))?;
291        Ok(())
292    }
293}