hitbox_core/predicate/
mod.rs1pub mod combinators;
22pub mod neutral;
23
24use std::sync::Arc;
25
26use async_trait::async_trait;
27
28pub use combinators::{And, Not, Or, PredicateExt};
29pub use neutral::Neutral;
30
31#[derive(Debug)]
36pub enum PredicateResult<S> {
37 Cacheable(S),
39 NonCacheable(S),
41}
42
43impl<S> PredicateResult<S> {
44 pub async fn and_then<F, Fut>(self, f: F) -> PredicateResult<S>
58 where
59 F: FnOnce(S) -> Fut,
60 Fut: std::future::Future<Output = PredicateResult<S>>,
61 {
62 match self {
63 PredicateResult::Cacheable(value) => f(value).await,
64 PredicateResult::NonCacheable(value) => PredicateResult::NonCacheable(value),
65 }
66 }
67}
68
69#[async_trait]
88pub trait Predicate {
89 type Subject;
91
92 async fn check(&self, subject: Self::Subject) -> PredicateResult<Self::Subject>;
97}
98
99#[async_trait]
100impl<T> Predicate for Box<T>
101where
102 T: Predicate + ?Sized + Sync,
103 T::Subject: Send,
104{
105 type Subject = T::Subject;
106
107 async fn check(&self, subject: T::Subject) -> PredicateResult<T::Subject> {
108 self.as_ref().check(subject).await
109 }
110}
111
112#[async_trait]
113impl<T> Predicate for &T
114where
115 T: Predicate + ?Sized + Sync,
116 T::Subject: Send,
117{
118 type Subject = T::Subject;
119
120 async fn check(&self, subject: T::Subject) -> PredicateResult<T::Subject> {
121 (*self).check(subject).await
122 }
123}
124
125#[async_trait]
126impl<T> Predicate for Arc<T>
127where
128 T: Predicate + Send + Sync + ?Sized,
129 T::Subject: Send,
130{
131 type Subject = T::Subject;
132
133 async fn check(&self, subject: T::Subject) -> PredicateResult<T::Subject> {
134 self.as_ref().check(subject).await
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[tokio::test]
143 async fn test_predicate_ext_with_box_dyn() {
144 let p1: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
145 let p2: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
146
147 let combined = p1.or(p2);
149
150 let result = combined.check(42).await;
151 assert!(matches!(result, PredicateResult::Cacheable(42)));
152 }
153
154 #[tokio::test]
155 async fn test_predicate_ext_chaining_with_box_dyn() {
156 let p1: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
157 let p2: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
158 let p3: Box<dyn Predicate<Subject = i32> + Send + Sync> = Box::new(Neutral::<i32>::new());
159
160 let combined = p1.and(p2).or(p3).not();
162
163 let result = combined.check(42).await;
164 assert!(matches!(result, PredicateResult::NonCacheable(42)));
166 }
167
168 #[tokio::test]
169 async fn test_predicate_ext_boxed() {
170 let p1 = Neutral::<i32>::new().boxed();
172 let p2 = Neutral::<i32>::new().boxed();
173
174 let combined = p1.or(p2);
176
177 let result = combined.check(42).await;
178 assert!(matches!(result, PredicateResult::Cacheable(42)));
179 }
180
181 #[tokio::test]
182 async fn test_predicate_ext_boxed_in_vec() {
183 let predicates: Vec<Box<dyn Predicate<Subject = i32> + Send + Sync>> = vec![
185 Neutral::<i32>::new().boxed(),
186 Neutral::<i32>::new().not().boxed(),
187 ];
188
189 let result1 = predicates[0].check(1).await;
190 let result2 = predicates[1].check(2).await;
191
192 assert!(matches!(result1, PredicateResult::Cacheable(1)));
193 assert!(matches!(result2, PredicateResult::NonCacheable(2)));
194 }
195}