1use std::fmt;
11
12use crate::description::Description;
13use crate::matcher::{MatchResult, Matcher, Mismatch};
14
15fn 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
26struct 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 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#[must_use]
69pub fn some<T, M>(inner: M) -> impl Matcher<Option<T>>
70where
71 M: Matcher<T>,
72{
73 SomeMatcher { inner }
74}
75
76struct 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#[must_use]
111pub fn none<T>() -> impl Matcher<Option<T>>
112where
113 T: fmt::Debug,
114{
115 NoneMatcher
116}
117
118struct 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#[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
167struct 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#[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}