use crate::{
description::Description,
matcher::{Describable, Matcher, MatcherResult},
};
use alloc::string::{String, ToString};
use core::{fmt::Debug, marker::PhantomData};
pub fn predicate<T: Debug + ?Sized, P>(
predicate: P,
) -> PredicateMatcher<T, P, __internal::NoDescription, __internal::NoDescription>
where
for<'a> P: Fn(&'a T) -> bool,
{
PredicateMatcher {
predicate,
positive_description: __internal::NoDescription,
negative_description: __internal::NoDescription,
phantom: Default::default(),
}
}
impl<T, P> PredicateMatcher<T, P, __internal::NoDescription, __internal::NoDescription> {
pub fn with_description<D1: PredicateDescription, D2: PredicateDescription>(
self,
positive_description: D1,
negative_description: D2,
) -> PredicateMatcher<T, P, D1, D2> {
PredicateMatcher {
predicate: self.predicate,
positive_description,
negative_description,
phantom: Default::default(),
}
}
}
pub struct PredicateMatcher<T: ?Sized, P, D1, D2> {
predicate: P,
positive_description: D1,
negative_description: D2,
phantom: PhantomData<T>,
}
pub trait PredicateDescription {
fn to_description(&self) -> Description;
}
impl PredicateDescription for &str {
fn to_description(&self) -> Description {
self.to_string().into()
}
}
impl PredicateDescription for String {
fn to_description(&self) -> Description {
self.to_string().into()
}
}
impl<T, S> PredicateDescription for T
where
T: Fn() -> S,
S: Into<String>,
{
fn to_description(&self) -> Description {
self().into().into()
}
}
pub mod __internal {
#[doc(hidden)]
pub struct NoDescription;
}
impl<T: Debug, P, D1: PredicateDescription, D2: PredicateDescription> Matcher<T>
for PredicateMatcher<T, P, D1, D2>
where
for<'a> P: Fn(&'a T) -> bool,
{
fn matches(&self, actual: &T) -> MatcherResult {
(self.predicate)(actual).into()
}
}
impl<T: Debug, P> Matcher<T>
for PredicateMatcher<T, P, __internal::NoDescription, __internal::NoDescription>
where
for<'a> P: Fn(&'a T) -> bool,
{
fn matches(&self, actual: &T) -> MatcherResult {
(self.predicate)(actual).into()
}
}
impl<T: Debug, P> Describable
for PredicateMatcher<T, P, __internal::NoDescription, __internal::NoDescription>
{
fn describe(&self, result: MatcherResult) -> Description {
match result {
MatcherResult::Match => "matches".into(),
MatcherResult::NoMatch => "does not match".into(),
}
}
}
impl<T: Debug, P, D1: PredicateDescription, D2: PredicateDescription> Describable
for PredicateMatcher<T, P, D1, D2>
{
fn describe(&self, result: MatcherResult) -> Description {
match result {
MatcherResult::Match => self.positive_description.to_description(),
MatcherResult::NoMatch => self.negative_description.to_description(),
}
}
}
#[cfg(test)]
mod tests {
use super::predicate;
use crate::matcher::Matcher;
use crate::prelude::*;
fn is_odd() -> impl Matcher<i32> {
predicate(|x| x % 2 == 1).with_description("is odd", "is even")
}
#[test]
fn predicate_matcher_odd() -> TestResult<()> {
verify_that!(1, is_odd())
}
#[test]
fn predicate_matcher_odd_explain_match_matches() -> TestResult<()> {
verify_that!(is_odd().explain_match(&1), displays_as(eq("which is odd")))
}
#[test]
fn predicate_matcher_odd_explain_match_does_not_match() -> TestResult<()> {
verify_that!(is_odd().explain_match(&2), displays_as(eq("which is even")))
}
fn is_even() -> impl Matcher<i32> {
predicate(|x| x % 2 == 0)
}
#[test]
fn predicate_matcher_even() -> TestResult<()> {
verify_that!(2, is_even())
}
#[test]
fn predicate_matcher_even_explain_match_matches() -> TestResult<()> {
verify_that!(is_even().explain_match(&2), displays_as(eq("which matches")))
}
#[test]
fn predicate_matcher_even_explain_match_does_not_match() -> TestResult<()> {
verify_that!(is_even().explain_match(&1), displays_as(eq("which does not match")))
}
#[test]
fn predicate_matcher_generator_lambda() -> TestResult<()> {
let is_divisible_by = |quotient| {
predicate(move |x: &i32| x % quotient == 0).with_description(
move || format!("is divisible by {quotient}"),
move || format!("is not divisible by {quotient}"),
)
};
verify_that!(49, is_divisible_by(7))
}
#[test]
fn predicate_matcher_inline() -> TestResult<()> {
verify_that!(2048, predicate(|x: &i32| x.count_ones() == 1))
}
#[test]
fn predicate_matcher_function_pointer() -> TestResult<()> {
use core::time::Duration;
verify_that!(Duration::new(0, 0), predicate(Duration::is_zero))
}
}