pub trait Specification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool;
fn and<S>(self, other: S) -> AndSpecification<T>
where
Self: Sized + 'static,
S: Specification<T> + 'static,
{
AndSpecification::new(Box::new(self), Box::new(other))
}
fn or<S>(self, other: S) -> OrSpecification<T>
where
Self: Sized + 'static,
S: Specification<T> + 'static,
{
OrSpecification::new(Box::new(self), Box::new(other))
}
fn not(self) -> NotSpecification<T>
where
Self: Sized + 'static,
{
NotSpecification::new(Box::new(self))
}
}
impl<T> Specification<T> for Box<dyn Specification<T>> {
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.as_ref().is_satisfied_by(candidate)
}
}
pub struct AndSpecification<T> {
left: Box<dyn Specification<T>>,
right: Box<dyn Specification<T>>,
}
impl<T> AndSpecification<T> {
pub fn new(left: Box<dyn Specification<T>>, right: Box<dyn Specification<T>>) -> Self {
Self { left, right }
}
}
impl<T> Specification<T> for AndSpecification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.left.is_satisfied_by(candidate) && self.right.is_satisfied_by(candidate)
}
}
pub struct OrSpecification<T> {
left: Box<dyn Specification<T>>,
right: Box<dyn Specification<T>>,
}
impl<T> OrSpecification<T> {
pub fn new(left: Box<dyn Specification<T>>, right: Box<dyn Specification<T>>) -> Self {
Self { left, right }
}
}
impl<T> Specification<T> for OrSpecification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.left.is_satisfied_by(candidate) || self.right.is_satisfied_by(candidate)
}
}
pub struct NotSpecification<T> {
inner: Box<dyn Specification<T>>,
}
impl<T> NotSpecification<T> {
pub fn new(inner: Box<dyn Specification<T>>) -> Self {
Self { inner }
}
}
impl<T> Specification<T> for NotSpecification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool {
!self.inner.is_satisfied_by(candidate)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct AlwaysTrueSpec;
impl Specification<i32> for AlwaysTrueSpec {
fn is_satisfied_by(&self, _: &i32) -> bool {
true
}
}
struct AlwaysFalseSpec;
impl Specification<i32> for AlwaysFalseSpec {
fn is_satisfied_by(&self, _: &i32) -> bool {
false
}
}
#[test]
fn test_and_specification() {
let spec = AlwaysTrueSpec.and(AlwaysTrueSpec);
assert!(spec.is_satisfied_by(&42));
let spec = AlwaysTrueSpec.and(AlwaysFalseSpec);
assert!(!spec.is_satisfied_by(&42));
let spec = AlwaysFalseSpec.and(AlwaysFalseSpec);
assert!(!spec.is_satisfied_by(&42));
}
#[test]
fn test_or_specification() {
let spec = AlwaysTrueSpec.or(AlwaysTrueSpec);
assert!(spec.is_satisfied_by(&42));
let spec = AlwaysTrueSpec.or(AlwaysFalseSpec);
assert!(spec.is_satisfied_by(&42));
let spec = AlwaysFalseSpec.or(AlwaysFalseSpec);
assert!(!spec.is_satisfied_by(&42));
}
#[test]
fn test_not_specification() {
let spec = AlwaysTrueSpec.not();
assert!(!spec.is_satisfied_by(&42));
let spec = AlwaysFalseSpec.not();
assert!(spec.is_satisfied_by(&42));
}
#[test]
fn test_complex_combination() {
let spec = AlwaysTrueSpec
.and(AlwaysFalseSpec)
.or(AlwaysFalseSpec.not());
assert!(spec.is_satisfied_by(&42));
}
}