use std::collections::HashMap;
use std::fmt::Debug;
use std::marker::PhantomData;
pub trait Specification<T>: Send + Sync {
type Error: Debug + Send;
fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error>;
fn and<S>(self, other: S) -> AndSpecification<Self, S, T>
where
Self: Sized,
S: Specification<T>,
{
AndSpecification::new(self, other)
}
fn or<S>(self, other: S) -> OrSpecification<Self, S, T>
where
Self: Sized,
S: Specification<T>,
{
OrSpecification::new(self, other)
}
fn not(self) -> NotSpecification<Self, T>
where
Self: Sized,
{
NotSpecification::new(self)
}
}
#[derive(Debug, Clone)]
pub struct AndSpecification<L, R, T> {
left: L,
right: R,
_marker: PhantomData<T>,
}
impl<L, R, T> AndSpecification<L, R, T> {
pub fn new(left: L, right: R) -> Self {
Self {
left,
right,
_marker: PhantomData,
}
}
}
impl<L, R, T> Specification<T> for AndSpecification<L, R, T>
where
L: Specification<T>,
R: Specification<T>,
T: Send + Sync,
{
type Error = String;
fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
let left_result = self
.left
.is_satisfied_by(candidate)
.map_err(|e| format!("Left specification failed: {:?}", e))?;
if !left_result {
return Ok(false);
}
self.right
.is_satisfied_by(candidate)
.map_err(|e| format!("Right specification failed: {:?}", e))
}
}
#[derive(Debug, Clone)]
pub struct OrSpecification<L, R, T> {
left: L,
right: R,
_marker: PhantomData<T>,
}
impl<L, R, T> OrSpecification<L, R, T> {
pub fn new(left: L, right: R) -> Self {
Self {
left,
right,
_marker: PhantomData,
}
}
}
impl<L, R, T> Specification<T> for OrSpecification<L, R, T>
where
L: Specification<T>,
R: Specification<T>,
T: Send + Sync,
{
type Error = String;
fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
let left_result = self
.left
.is_satisfied_by(candidate)
.map_err(|e| format!("Left specification failed: {:?}", e))?;
if left_result {
return Ok(true);
}
self.right
.is_satisfied_by(candidate)
.map_err(|e| format!("Right specification failed: {:?}", e))
}
}
#[derive(Debug, Clone)]
pub struct NotSpecification<S, T> {
spec: S,
_marker: PhantomData<T>,
}
impl<S, T> NotSpecification<S, T> {
pub fn new(spec: S) -> Self {
Self {
spec,
_marker: PhantomData,
}
}
}
impl<S, T> Specification<T> for NotSpecification<S, T>
where
S: Specification<T>,
T: Send + Sync,
{
type Error = String;
fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
let result = self
.spec
.is_satisfied_by(candidate)
.map_err(|e| format!("Inner specification failed: {:?}", e))?;
Ok(!result)
}
}
#[derive(Debug, Clone)]
pub struct SpecificationResult {
pub satisfied: bool,
pub specification_name: String,
pub message: String,
pub details: HashMap<String, String>,
}
impl SpecificationResult {
pub fn satisfied(name: impl Into<String>, message: impl Into<String>) -> Self {
Self {
satisfied: true,
specification_name: name.into(),
message: message.into(),
details: HashMap::new(),
}
}
pub fn unsatisfied(name: impl Into<String>, message: impl Into<String>) -> Self {
Self {
satisfied: false,
specification_name: name.into(),
message: message.into(),
details: HashMap::new(),
}
}
pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.details.insert(key.into(), value.into());
self
}
}
pub struct SpecificationEvaluator<T> {
_marker: PhantomData<T>,
}
impl<T: Send + Sync> SpecificationEvaluator<T> {
pub fn evaluate_all<S>(
specifications: &[&S],
candidate: &T,
) -> Vec<Result<bool, String>>
where
S: Specification<T, Error = String> + ?Sized,
{
specifications
.iter()
.map(|spec| spec.is_satisfied_by(candidate))
.collect()
}
pub fn all_satisfied<S>(specifications: &[&S], candidate: &T) -> Result<bool, String>
where
S: Specification<T, Error = String> + ?Sized,
{
for spec in specifications {
if !spec.is_satisfied_by(candidate)? {
return Ok(false);
}
}
Ok(true)
}
pub fn any_satisfied<S>(specifications: &[&S], candidate: &T) -> Result<bool, String>
where
S: Specification<T, Error = String> + ?Sized,
{
for spec in specifications {
if spec.is_satisfied_by(candidate)? {
return Ok(true);
}
}
Ok(false)
}
}
#[derive(Debug, Clone, Default)]
pub struct AlwaysTrue<T>(PhantomData<T>);
impl<T> AlwaysTrue<T> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<T: Send + Sync> Specification<T> for AlwaysTrue<T> {
type Error = std::convert::Infallible;
fn is_satisfied_by(&self, _candidate: &T) -> Result<bool, Self::Error> {
Ok(true)
}
}
#[derive(Debug, Clone, Default)]
pub struct AlwaysFalse<T>(PhantomData<T>);
impl<T> AlwaysFalse<T> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<T: Send + Sync> Specification<T> for AlwaysFalse<T> {
type Error = std::convert::Infallible;
fn is_satisfied_by(&self, _candidate: &T) -> Result<bool, Self::Error> {
Ok(false)
}
}
pub struct PredicateSpecification<T, F>
where
F: Fn(&T) -> bool + Send + Sync,
{
predicate: F,
_marker: PhantomData<T>,
}
impl<T, F> PredicateSpecification<T, F>
where
F: Fn(&T) -> bool + Send + Sync,
{
pub fn new(predicate: F) -> Self {
Self {
predicate,
_marker: PhantomData,
}
}
}
impl<T, F> Specification<T> for PredicateSpecification<T, F>
where
T: Send + Sync,
F: Fn(&T) -> bool + Send + Sync,
{
type Error = std::convert::Infallible;
fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
Ok((self.predicate)(candidate))
}
}
pub fn predicate<T, F>(f: F) -> PredicateSpecification<T, F>
where
F: Fn(&T) -> bool + Send + Sync,
{
PredicateSpecification::new(f)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct TestEntity {
value: i32,
active: bool,
}
struct PositiveValue;
impl Specification<TestEntity> for PositiveValue {
type Error = String;
fn is_satisfied_by(&self, e: &TestEntity) -> Result<bool, Self::Error> {
Ok(e.value > 0)
}
}
struct IsActive;
impl Specification<TestEntity> for IsActive {
type Error = String;
fn is_satisfied_by(&self, e: &TestEntity) -> Result<bool, Self::Error> {
Ok(e.active)
}
}
#[test]
fn test_and_specification() {
let spec = PositiveValue.and(IsActive);
let active_positive = TestEntity { value: 10, active: true };
assert!(spec.is_satisfied_by(&active_positive).unwrap());
let inactive_positive = TestEntity { value: 10, active: false };
assert!(!spec.is_satisfied_by(&inactive_positive).unwrap());
let active_negative = TestEntity { value: -5, active: true };
assert!(!spec.is_satisfied_by(&active_negative).unwrap());
}
#[test]
fn test_or_specification() {
let spec = PositiveValue.or(IsActive);
let inactive_positive = TestEntity { value: 10, active: false };
assert!(spec.is_satisfied_by(&inactive_positive).unwrap());
let active_negative = TestEntity { value: -5, active: true };
assert!(spec.is_satisfied_by(&active_negative).unwrap());
let inactive_negative = TestEntity { value: -5, active: false };
assert!(!spec.is_satisfied_by(&inactive_negative).unwrap());
}
#[test]
fn test_not_specification() {
let spec = PositiveValue.not();
let positive = TestEntity { value: 10, active: false };
assert!(!spec.is_satisfied_by(&positive).unwrap());
let negative = TestEntity { value: -5, active: false };
assert!(spec.is_satisfied_by(&negative).unwrap());
}
#[test]
fn test_predicate_specification() {
let spec = predicate(|e: &TestEntity| e.value > 5);
let high = TestEntity { value: 10, active: false };
assert!(spec.is_satisfied_by(&high).unwrap());
let low = TestEntity { value: 3, active: false };
assert!(!spec.is_satisfied_by(&low).unwrap());
}
#[test]
fn test_complex_composition() {
let spec = PositiveValue
.and(IsActive)
.or(predicate(|e: &TestEntity| e.value > 100));
let active_positive = TestEntity { value: 10, active: true };
assert!(spec.is_satisfied_by(&active_positive).unwrap());
let inactive_very_high = TestEntity { value: 200, active: false };
assert!(spec.is_satisfied_by(&inactive_very_high).unwrap());
let inactive_low = TestEntity { value: 5, active: false };
assert!(!spec.is_satisfied_by(&inactive_low).unwrap());
}
}