#![doc(hidden)]
#[macro_export]
#[doc(hidden)]
macro_rules! __any {
($($matcher:expr),* $(,)?) => {{
use $crate::matchers::__internal::AnyMatcher;
AnyMatcher::new([$(Box::new($matcher)),*])
}}
}
#[doc(hidden)]
pub mod __internal {
use crate::description::Description;
use crate::matcher::{Describable, Matcher, MatcherResult};
use crate::matchers::anything;
use std::fmt::Debug;
#[doc(hidden)]
pub struct AnyMatcher<'a, T: Debug + ?Sized, const N: usize> {
components: [Box<dyn Matcher<T> + 'a>; N],
}
impl<'a, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, T, N> {
pub fn new(components: [Box<dyn Matcher<T> + 'a>; N]) -> Self {
Self { components }
}
}
impl<'a, T: Debug + ?Sized, const N: usize> Matcher<T> for AnyMatcher<'a, T, N> {
fn matches(&self, actual: &T) -> MatcherResult {
MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match()))
}
fn explain_match(&self, actual: &T) -> Description {
match N {
0 => format!("which {}", anything().describe(MatcherResult::NoMatch)).into(),
1 => self.components[0].explain_match(actual),
_ => {
let failures = self
.components
.iter()
.filter(|component| component.matches(actual).is_no_match())
.collect::<Vec<_>>();
if failures.len() == 1 {
failures[0].explain_match(actual)
} else {
Description::new()
.collect(
failures
.into_iter()
.map(|component| component.explain_match(actual)),
)
.bullet_list()
}
}
}
}
}
impl<'a, T: Debug + ?Sized, const N: usize> Describable for AnyMatcher<'a, T, N> {
fn describe(&self, matcher_result: MatcherResult) -> Description {
match N {
0 => anything().describe(matcher_result),
1 => self.components[0].describe(matcher_result),
_ => {
let properties = self
.components
.iter()
.map(|m| m.describe(matcher_result))
.collect::<Description>()
.bullet_list()
.indent();
format!(
"{}:\n{properties}",
if matcher_result.into() {
"has at least one of the following properties"
} else {
"has none of the following properties"
}
)
.into()
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::__internal;
use crate::matcher::{Describable as _, Matcher, MatcherResult};
use crate::prelude::*;
use indoc::indoc;
#[test]
fn description_shows_more_than_one_matcher() -> TestResult<()> {
let first_matcher = starts_with("A");
let second_matcher = ends_with("string");
let matcher: __internal::AnyMatcher<String, 2> = any!(first_matcher, second_matcher);
verify_that!(
matcher.describe(MatcherResult::Match),
displays_as(eq(indoc!(
"
has at least one of the following properties:
* starts with prefix \"A\"
* ends with suffix \"string\""
)))
)
}
#[test]
fn description_shows_one_matcher_directly() -> TestResult<()> {
let first_matcher = starts_with("A");
let matcher: __internal::AnyMatcher<String, 1> = any!(first_matcher);
verify_that!(
matcher.describe(MatcherResult::Match),
displays_as(eq("starts with prefix \"A\""))
)
}
#[test]
fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent()
-> TestResult<()> {
let first_matcher = starts_with("Another");
let second_matcher = ends_with("string");
let matcher: __internal::AnyMatcher<str, 2> = any!(first_matcher, second_matcher);
verify_that!(
matcher.explain_match("A string"),
displays_as(eq("which does not start with \"Another\""))
)
}
#[test]
fn mismatch_description_is_simple_when_only_one_constituent() -> TestResult<()> {
let first_matcher = starts_with("Another");
let matcher: __internal::AnyMatcher<str, 1> = any!(first_matcher);
verify_that!(
matcher.explain_match("A string"),
displays_as(eq("which does not start with \"Another\""))
)
}
}