pub mod combinators;
pub mod neutral;
use std::sync::Arc;
use async_trait::async_trait;
pub use combinators::{And, Not, Or, PredicateExt};
pub use neutral::Neutral;
#[derive(Debug)]
pub enum PredicateResult<S> {
Cacheable(S),
NonCacheable(S),
}
impl<S> PredicateResult<S> {
pub async fn and_then<F, Fut>(self, f: F) -> PredicateResult<S>
where
F: FnOnce(S) -> Fut,
Fut: std::future::Future<Output = PredicateResult<S>>,
{
match self {
PredicateResult::Cacheable(value) => f(value).await,
PredicateResult::NonCacheable(value) => PredicateResult::NonCacheable(value),
}
}
}
#[async_trait]
pub trait Predicate {
type Subject;
async fn check(&self, subject: Self::Subject) -> PredicateResult<Self::Subject>;
}
#[async_trait]
impl<T> Predicate for Box<T>
where
T: Predicate + ?Sized + Sync,
T::Subject: Send,
{
type Subject = T::Subject;
async fn check(&self, subject: T::Subject) -> PredicateResult<T::Subject> {
self.as_ref().check(subject).await
}
}
#[async_trait]
impl<T> Predicate for &T
where
T: Predicate + ?Sized + Sync,
T::Subject: Send,
{
type Subject = T::Subject;
async fn check(&self, subject: T::Subject) -> PredicateResult<T::Subject> {
(*self).check(subject).await
}
}
#[async_trait]
impl<T> Predicate for Arc<T>
where
T: Predicate + Send + Sync + ?Sized,
T::Subject: Send,
{
type Subject = T::Subject;
async fn check(&self, subject: T::Subject) -> PredicateResult<T::Subject> {
self.as_ref().check(subject).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_predicate_ext_with_box_dyn() {
let p1: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
let p2: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
let combined = p1.or(p2);
let result = combined.check(42).await;
assert!(matches!(result, PredicateResult::Cacheable(42)));
}
#[tokio::test]
async fn test_predicate_ext_chaining_with_box_dyn() {
let p1: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
let p2: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
let p3: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
let combined = p1.and(p2).or(p3).not();
let result = combined.check(42).await;
assert!(matches!(result, PredicateResult::NonCacheable(42)));
}
#[tokio::test]
async fn test_predicate_ext_boxed() {
let p1 = Neutral::<i32>::new().boxed();
let p2 = Neutral::<i32>::new().boxed();
let combined = p1.or(p2);
let result = combined.check(42).await;
assert!(matches!(result, PredicateResult::Cacheable(42)));
}
#[tokio::test]
async fn test_predicate_ext_boxed_in_vec() {
let predicates: Vec<Box<dyn Predicate<Subject = i32> + Send + Sync>> = vec![
Neutral::<i32>::new().boxed(),
Neutral::<i32>::new().not().boxed(),
];
let result1 = predicates[0].check(1).await;
let result2 = predicates[1].check(2).await;
assert!(matches!(result1, PredicateResult::Cacheable(1)));
assert!(matches!(result2, PredicateResult::NonCacheable(2)));
}
}