1use std::fmt;
10
11use crate::description::Description;
12use crate::matcher::{MatchResult, Matcher, Mismatch};
13
14struct NotMatcher<M> {
16 inner: M,
17}
18
19impl<T, M> Matcher<T> for NotMatcher<M>
20where
21 T: ?Sized + fmt::Debug,
22 M: Matcher<T>,
23{
24 fn check(&self, actual: &T) -> MatchResult {
25 if self.inner.check(actual).matched {
26 MatchResult::fail(Mismatch::new(self.description(), format!("{actual:?}")))
30 } else {
31 MatchResult::pass()
32 }
33 }
34
35 fn description(&self) -> Description {
36 !self.inner.description()
37 }
38}
39
40#[must_use]
57pub fn not<T, M>(matcher: M) -> impl Matcher<T>
58where
59 T: ?Sized + fmt::Debug,
60 M: Matcher<T>,
61{
62 NotMatcher { inner: matcher }
63}
64
65pub trait MatcherTuple<T: ?Sized> {
71 fn check_all(&self, actual: &T) -> MatchResult;
74
75 fn check_any(&self, actual: &T) -> MatchResult;
78
79 fn describe_all(&self) -> Description;
81
82 fn describe_any(&self) -> Description;
84}
85
86macro_rules! impl_matcher_tuple {
90 ($first:ident, $($rest:ident),+) => {
91 #[allow(non_snake_case)]
92 impl<T, $first, $($rest,)+> MatcherTuple<T> for ($first, $($rest,)+)
93 where
94 T: ?Sized,
95 $first: Matcher<T>,
96 $($rest: Matcher<T>,)+
97 {
98 fn check_all(&self, actual: &T) -> MatchResult {
99 let ($first, $($rest,)+) = self;
100 if let Some(mismatch) = $first.check(actual).failure {
101 return MatchResult::fail(mismatch);
102 }
103 $(
104 if let Some(mismatch) = $rest.check(actual).failure {
105 return MatchResult::fail(mismatch);
106 }
107 )+
108 MatchResult::pass()
109 }
110
111 fn check_any(&self, actual: &T) -> MatchResult {
112 let ($first, $($rest,)+) = self;
113 let first_actual = match $first.check(actual).failure {
114 None => return MatchResult::pass(),
115 Some(mismatch) => mismatch.actual,
116 };
117 $(
118 if $rest.check(actual).matched {
119 return MatchResult::pass();
120 }
121 )+
122 MatchResult::fail(Mismatch::new(self.describe_any(), first_actual))
123 }
124
125 fn describe_all(&self) -> Description {
126 let ($first, $($rest,)+) = self;
127 let desc = $first.description();
128 $( let desc = desc.and($rest.description()); )+
129 desc
130 }
131
132 fn describe_any(&self) -> Description {
133 let ($first, $($rest,)+) = self;
134 let desc = $first.description();
135 $( let desc = desc.or($rest.description()); )+
136 desc
137 }
138 }
139 };
140}
141
142impl_matcher_tuple!(M1, M2);
143impl_matcher_tuple!(M1, M2, M3);
144impl_matcher_tuple!(M1, M2, M3, M4);
145impl_matcher_tuple!(M1, M2, M3, M4, M5);
146impl_matcher_tuple!(M1, M2, M3, M4, M5, M6);
147impl_matcher_tuple!(M1, M2, M3, M4, M5, M6, M7);
148impl_matcher_tuple!(M1, M2, M3, M4, M5, M6, M7, M8);
149
150struct AllOfMatcher<Tup> {
152 matchers: Tup,
153}
154
155impl<T, Tup> Matcher<T> for AllOfMatcher<Tup>
156where
157 T: ?Sized,
158 Tup: MatcherTuple<T>,
159{
160 fn check(&self, actual: &T) -> MatchResult {
161 self.matchers.check_all(actual)
162 }
163
164 fn description(&self) -> Description {
165 self.matchers.describe_all()
166 }
167}
168
169#[must_use]
184pub fn all_of<T, Tup>(matchers: Tup) -> impl Matcher<T>
185where
186 T: ?Sized,
187 Tup: MatcherTuple<T>,
188{
189 AllOfMatcher { matchers }
190}
191
192struct AnyOfMatcher<Tup> {
194 matchers: Tup,
195}
196
197impl<T, Tup> Matcher<T> for AnyOfMatcher<Tup>
198where
199 T: ?Sized,
200 Tup: MatcherTuple<T>,
201{
202 fn check(&self, actual: &T) -> MatchResult {
203 self.matchers.check_any(actual)
204 }
205
206 fn description(&self) -> Description {
207 self.matchers.describe_any()
208 }
209}
210
211#[must_use]
226pub fn any_of<T, Tup>(matchers: Tup) -> impl Matcher<T>
227where
228 T: ?Sized,
229 Tup: MatcherTuple<T>,
230{
231 AnyOfMatcher { matchers }
232}
233
234#[cfg(test)]
235mod tests {
236 use test_better_core::{OrFail, TestResult};
237
238 use super::*;
239 use crate::{check, eq, gt, is_false, is_true, lt};
240
241 #[test]
242 fn not_inverts_the_inner_matcher() -> TestResult {
243 check!(not(eq(4)).check(&5).matched).satisfies(is_true())?;
244 check!(not(eq(4)).check(&4).matched).satisfies(is_false())?;
245 Ok(())
246 }
247
248 #[test]
249 fn not_failure_negates_the_description_and_renders_the_actual() -> TestResult {
250 let failure = not(eq(4))
251 .check(&4)
252 .failure
253 .or_fail_with("4 does match eq(4), so not(eq(4)) fails")?;
254 check!(failure.expected.to_string()).satisfies(eq("not equal to 4".to_string()))?;
255 check!(failure.actual).satisfies(eq("4".to_string()))?;
256 Ok(())
257 }
258
259 #[test]
260 fn all_of_passes_when_every_matcher_matches() -> TestResult {
261 check!(all_of((gt(0), lt(100))).check(&50).matched).satisfies(is_true())?;
262 Ok(())
263 }
264
265 #[test]
266 fn all_of_fails_with_the_first_failing_sub_matcher() -> TestResult {
267 let failure = all_of((gt(0), lt(100)))
268 .check(&150)
269 .failure
270 .or_fail_with("150 is not less than 100")?;
271 check!(failure.expected.to_string()).satisfies(eq("less than 100".to_string()))?;
272 check!(failure.actual).satisfies(eq("150".to_string()))?;
273 Ok(())
274 }
275
276 #[test]
277 fn all_of_describes_itself_as_a_conjunction() -> TestResult {
278 let description = all_of((gt(0), lt(100))).description();
279 check!(description.to_string())
280 .satisfies(eq("greater than 0 and less than 100".to_string()))?;
281 Ok(())
282 }
283
284 #[test]
285 fn any_of_passes_when_at_least_one_matcher_matches() -> TestResult {
286 check!(any_of((eq(7), eq(8), eq(9))).check(&8).matched).satisfies(is_true())?;
287 Ok(())
288 }
289
290 #[test]
291 fn any_of_fails_when_no_matcher_matches() -> TestResult {
292 let failure = any_of((eq(7), eq(8), eq(9)))
293 .check(&1)
294 .failure
295 .or_fail_with("1 is none of 7, 8, 9")?;
296 check!(failure.expected.to_string())
297 .satisfies(eq("equal to 7 or equal to 8 or equal to 9".to_string()))?;
298 check!(failure.actual).satisfies(eq("1".to_string()))?;
299 Ok(())
300 }
301
302 #[test]
303 fn combinators_nest() -> TestResult {
304 check!(all_of((not(eq(0)), lt(100))).check(&50).matched).satisfies(is_true())?;
306 check!(all_of((not(eq(0)), lt(100))).check(&0).matched).satisfies(is_false())?;
307 Ok(())
308 }
309
310 #[test]
311 fn all_of_supports_arity_eight() -> TestResult {
312 let matcher = all_of((gt(0), lt(100), gt(1), lt(99), gt(2), lt(98), gt(3), lt(97)));
313 check!(matcher.check(&50).matched).satisfies(is_true())?;
314 Ok(())
315 }
316}