pub trait Specification<T: ?Sized> {
fn holds(&self, candidate: &T) -> bool;
fn and<S: Specification<T>>(self, other: S) -> And<Self, S>
where
Self: Sized, {
And(self, other)
}
fn or<S: Specification<T>>(self, other: S) -> Or<Self, S>
where
Self: Sized, {
Or(self, other)
}
fn not(self) -> Not<Self>
where
Self: Sized, {
Not(self)
}
}
pub struct And<A, B>(A, B);
pub struct Or<A, B>(A, B);
pub struct Not<A>(A);
impl<T: ?Sized, A: Specification<T>, B: Specification<T>> Specification<T> for And<A, B> {
fn holds(&self, candidate: &T) -> bool {
self.0.holds(candidate) && self.1.holds(candidate)
}
}
impl<T: ?Sized, A: Specification<T>, B: Specification<T>> Specification<T> for Or<A, B> {
fn holds(&self, candidate: &T) -> bool {
self.0.holds(candidate) || self.1.holds(candidate)
}
}
impl<T: ?Sized, A: Specification<T>> Specification<T> for Not<A> {
fn holds(&self, candidate: &T) -> bool {
!self.0.holds(candidate)
}
}
impl<T: ?Sized, F: Fn(&T) -> bool> Specification<T> for F {
fn holds(&self, candidate: &T) -> bool {
self(candidate)
}
}
#[cfg(test)]
mod tests {
use super::*;
const POSITIVE: fn(&i32) -> bool = |n| *n > 0;
const EVEN: fn(&i32) -> bool = |n| n % 2 == 0;
#[test]
fn and_requires_both() {
let spec = POSITIVE.and(EVEN);
assert!(spec.holds(&4));
assert!(!spec.holds(&3)); assert!(!spec.holds(&-2)); }
#[test]
fn or_requires_either() {
let spec = POSITIVE.or(EVEN);
assert!(spec.holds(&3)); assert!(spec.holds(&-2)); assert!(!spec.holds(&-3)); }
#[test]
fn not_inverts() {
assert!(EVEN.not().holds(&3));
assert!(!EVEN.not().holds(&4));
}
#[test]
fn nests_arbitrarily() {
let spec = POSITIVE.and(EVEN.or((|n: &i32| *n > 100).not()));
assert!(spec.holds(&4)); assert!(spec.holds(&7)); assert!(!spec.holds(&101)); }
#[test]
fn dyn_dispatch_via_holds() {
let spec: Box<dyn Specification<i32>> = Box::new(EVEN);
assert!(spec.holds(&2));
}
}