clearcheck 0.0.2

Elegant and extensible assertions in rust.
Documentation
use std::fmt::Debug;
use std::ops::{Range, RangeInclusive};

use crate::matchers::{Matcher, MatcherResult};

/// MembershipMatcher offers a flexible way for verifying the minimum and maximum values within a collection.
///
/// clearcheck implements MembershipMatcher for collection types including vector, arrays and reference to slices.
///
/// # Example
///```
/// use clearcheck::matchers::collection::min_max::have_min_in_inclusive_range;
/// use clearcheck::matchers::Matcher;
///
/// let collection = vec!["assert", "clearcheck", "junit"];
/// let matcher = have_min_in_inclusive_range("assert"..="junit");
///
/// assert!(matcher.test(&collection).passed());
/// ```
pub enum MinMaxMatcher<T: Ord> {
    Min(T),
    Max(T),
    MinInInclusiveRange(RangeInclusive<T>),
    MinInExclusiveRange(Range<T>),
    MaxInInclusiveRange(RangeInclusive<T>),
    MaxInExclusiveRange(Range<T>),
}

impl<T: Ord + Debug> MinMaxMatcher<T> {
    fn test(&self, collection: &[T]) -> MatcherResult {
        match self {
            MinMaxMatcher::Min(min) => MatcherResult::formatted(
                collection.iter().min() == Some(min),
                format!("{:?} should have {:?} as the minimum element", collection, min),
                format!("{:?} should not have {:?} as the minimum element", collection, min),
            ),
            MinMaxMatcher::Max(max) => MatcherResult::formatted(
                collection.iter().max() == Some(max),
                format!("{:?} should have {:?} as the maximum element", collection, max),
                format!("{:?} should not have {:?} as the maximum element", collection, max),
            ),
            MinMaxMatcher::MinInInclusiveRange(range) => MatcherResult::formatted(
                collection.iter().min().is_some_and(|min| range.contains(min)),
                format!("{:?} should have minimum in the range {:?}", collection, range),
                format!("{:?} should not have minimum in the range {:?}", collection, range),
            ),
            MinMaxMatcher::MinInExclusiveRange(range) => MatcherResult::formatted(
                collection.iter().min().is_some_and(|min| range.contains(min)),
                format!("{:?} should have minimum in the range {:?}", collection, range),
                format!("{:?} should not have minimum in the range {:?}", collection, range),
            ),
            MinMaxMatcher::MaxInInclusiveRange(range) => MatcherResult::formatted(
                collection.iter().max().is_some_and(|max| range.contains(max)),
                format!("{:?} should have maximum in the range {:?}", collection, range),
                format!("{:?} should not have maximum in the range {:?}", collection, range),
            ),
            MinMaxMatcher::MaxInExclusiveRange(range) => MatcherResult::formatted(
                collection.iter().max().is_some_and(|max| range.contains(max)),
                format!("{:?} should have maximum in the range {:?}", collection, range),
                format!("{:?} should not have maximum in the range {:?}", collection, range),
            ),
        }
    }
}

impl<T: Ord + Debug> Matcher<Vec<T>> for MinMaxMatcher<T> {
    fn test(&self, collection: &Vec<T>) -> MatcherResult {
        self.test(collection)
    }
}

impl<T: Ord + Debug, const N: usize> Matcher<[T; N]> for MinMaxMatcher<T> {
    fn test(&self, collection: &[T; N]) -> MatcherResult {
        self.test(collection as &[T])
    }
}

impl<T: Ord + Debug> Matcher<&[T]> for MinMaxMatcher<T> {
    fn test(&self, collection: &&[T]) -> MatcherResult {
        self.test(collection)
    }
}

/// Creates a MinMaxMatcher that asserts whether the minimum value in the underlying collection equals the given minimum value.
pub fn have_min<T: Ord>(min: T) -> MinMaxMatcher<T> {
    MinMaxMatcher::Min(min)
}

/// Creates a MinMaxMatcher that asserts whether the maximum value in the underlying collection equals the given maximum value.
pub fn have_max<T: Ord>(max: T) -> MinMaxMatcher<T> {
    MinMaxMatcher::Max(max)
}

/// Creates a MinMaxMatcher that asserts whether the minimum value in the underlying collection falls within the given inclusive range.
pub fn have_min_in_inclusive_range<T: Ord>(range: RangeInclusive<T>) -> MinMaxMatcher<T> {
    MinMaxMatcher::MinInInclusiveRange(range)
}

/// Creates a MinMaxMatcher that asserts whether the minimum value in the underlying collection falls within the given exclusive range.
pub fn have_min_in_exclusive_range<T: Ord>(range: Range<T>) -> MinMaxMatcher<T> {
    MinMaxMatcher::MinInExclusiveRange(range)
}

/// Creates a MinMaxMatcher that asserts whether the maximum value in the underlying collection falls within the given inclusive range.
pub fn have_max_in_inclusive_range<T: Ord>(range: RangeInclusive<T>) -> MinMaxMatcher<T> {
    MinMaxMatcher::MaxInInclusiveRange(range)
}

/// Creates a MinMaxMatcher that asserts whether the maximum value in the underlying collection falls within the given exclusive range.
pub fn have_max_in_exclusive_range<T: Ord>(range: Range<T>) -> MinMaxMatcher<T> {
    MinMaxMatcher::MaxInExclusiveRange(range)
}

#[cfg(test)]
mod tests {
    use crate::assertions::bool::TrueFalseAssertion;
    use crate::matchers::collection::min_max::{have_max, have_min};

    #[test]
    fn should_a_min_element() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_min("assert");

        matcher.test(&collection).passed.should_be_true();
    }

    #[test]
    #[should_panic]
    fn should_have_the_given_min_element_but_was_not() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_min("junit");

        matcher.test(&collection).passed.should_be_true();
    }

    #[test]
    fn should_a_max_element() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_max("junit");

        matcher.test(&collection).passed.should_be_true();
    }

    #[test]
    #[should_panic]
    fn should_have_the_given_max_element_but_was_not() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_max("clearcheck");

        matcher.test(&collection).passed.should_be_true();
    }
}

#[cfg(test)]
mod range_tests {
    use crate::assertions::bool::TrueFalseAssertion;
    use crate::matchers::collection::min_max::{have_max_in_exclusive_range, have_max_in_inclusive_range, have_min_in_exclusive_range, have_min_in_inclusive_range};

    #[test]
    fn should_a_min_in_inclusive_range() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_min_in_inclusive_range("assert"..="junit");

        matcher.test(&collection).passed.should_be_true();
    }

    #[test]
    fn should_a_min_in_exclusive_range() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_min_in_exclusive_range("assert".."junit");

        matcher.test(&collection).passed.should_be_true();
    }

    #[test]
    fn should_a_max_in_inclusive_range() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_max_in_inclusive_range("assert"..="junit");

        matcher.test(&collection).passed.should_be_true();
    }

    #[test]
    fn should_a_max_in_exclusive_range() {
        let collection = vec!["assert", "clearcheck", "junit"];
        let matcher = have_max_in_exclusive_range("assert".."testify");

        matcher.test(&collection).passed.should_be_true();
    }
}