Skip to main content

atomr_patterns/specification/
mod.rs

1//! Specification pattern — composable predicates over a domain type.
2//!
3//! Useful for invariant checks, query filters, and command routing.
4//! Specifications combine with `and`, `or`, and `not` so complex
5//! predicates stay declarative.
6//!
7//! ```ignore
8//! struct OverThreshold(i64);
9//! impl Specification<Order> for OverThreshold {
10//!     fn is_satisfied_by(&self, o: &Order) -> bool { o.amount > self.0 }
11//! }
12//! struct InRegion(String);
13//! impl Specification<Order> for InRegion {
14//!     fn is_satisfied_by(&self, o: &Order) -> bool { o.region == self.0 }
15//! }
16//! let spec = OverThreshold(100).and(InRegion("EU".into()));
17//! orders.iter().filter(|o| spec.is_satisfied_by(o));
18//! ```
19
20/// Composable predicate over `T`.
21pub trait Specification<T>: Send + Sync {
22    fn is_satisfied_by(&self, t: &T) -> bool;
23
24    fn and<S>(self, other: S) -> AndSpec<Self, S>
25    where
26        Self: Sized,
27        S: Specification<T>,
28    {
29        AndSpec { a: self, b: other }
30    }
31
32    fn or<S>(self, other: S) -> OrSpec<Self, S>
33    where
34        Self: Sized,
35        S: Specification<T>,
36    {
37        OrSpec { a: self, b: other }
38    }
39
40    fn not(self) -> NotSpec<Self>
41    where
42        Self: Sized,
43    {
44        NotSpec { inner: self }
45    }
46}
47
48pub struct AndSpec<A, B> {
49    a: A,
50    b: B,
51}
52impl<T, A: Specification<T>, B: Specification<T>> Specification<T> for AndSpec<A, B> {
53    fn is_satisfied_by(&self, t: &T) -> bool {
54        self.a.is_satisfied_by(t) && self.b.is_satisfied_by(t)
55    }
56}
57
58pub struct OrSpec<A, B> {
59    a: A,
60    b: B,
61}
62impl<T, A: Specification<T>, B: Specification<T>> Specification<T> for OrSpec<A, B> {
63    fn is_satisfied_by(&self, t: &T) -> bool {
64        self.a.is_satisfied_by(t) || self.b.is_satisfied_by(t)
65    }
66}
67
68pub struct NotSpec<S> {
69    inner: S,
70}
71impl<T, S: Specification<T>> Specification<T> for NotSpec<S> {
72    fn is_satisfied_by(&self, t: &T) -> bool {
73        !self.inner.is_satisfied_by(t)
74    }
75}
76
77/// Convenience: lift any `Fn(&T) -> bool` into a [`Specification`].
78pub struct FnSpec<F>(pub F);
79impl<T, F: Fn(&T) -> bool + Send + Sync> Specification<T> for FnSpec<F> {
80    fn is_satisfied_by(&self, t: &T) -> bool {
81        (self.0)(t)
82    }
83}