Skip to main content

hitbox_core/predicate/
combinators.rs

1//! Logical combinators for composing predicates.
2//!
3//! This module provides generic combinators that work with any [`Predicate`]
4//! implementation, regardless of the protocol.
5//!
6//! ## Extension Trait
7//!
8//! The [`PredicateExt`] trait provides fluent methods for predicate composition:
9//!
10//! ```ignore
11//! use hitbox_core::predicate::{Neutral, PredicateExt};
12//!
13//! let predicate = Neutral::new()
14//!     .and(predicate1)
15//!     .or(predicate2)
16//!     .not();
17//! ```
18
19use async_trait::async_trait;
20
21use super::{Predicate, PredicateResult};
22
23/// Inverts a predicate result.
24///
25/// - `Cacheable` becomes `NonCacheable`
26/// - `NonCacheable` becomes `Cacheable`
27#[derive(Debug)]
28pub struct Not<P> {
29    predicate: P,
30}
31
32impl<P> Not<P> {
33    /// Creates a new `Not` combinator wrapping the given predicate.
34    pub fn new(predicate: P) -> Self {
35        Self { predicate }
36    }
37}
38
39#[async_trait]
40impl<P> Predicate for Not<P>
41where
42    P: Predicate + Send + Sync,
43    P::Subject: Send,
44{
45    type Subject = P::Subject;
46
47    async fn check(&self, subject: Self::Subject) -> PredicateResult<Self::Subject> {
48        match self.predicate.check(subject).await {
49            PredicateResult::Cacheable(s) => PredicateResult::NonCacheable(s),
50            PredicateResult::NonCacheable(s) => PredicateResult::Cacheable(s),
51        }
52    }
53}
54
55/// Requires both predicates to return `Cacheable`.
56///
57/// Short-circuits: if the left predicate returns `NonCacheable`,
58/// the right predicate is not evaluated.
59#[derive(Debug)]
60pub struct And<L, R> {
61    left: L,
62    right: R,
63}
64
65impl<L, R> And<L, R> {
66    /// Creates a new `And` combinator from two predicates.
67    pub fn new(left: L, right: R) -> Self {
68        Self { left, right }
69    }
70}
71
72#[async_trait]
73impl<L, R> Predicate for And<L, R>
74where
75    L: Predicate + Send + Sync,
76    R: Predicate<Subject = L::Subject> + Send + Sync,
77    L::Subject: Send,
78{
79    type Subject = L::Subject;
80
81    async fn check(&self, subject: Self::Subject) -> PredicateResult<Self::Subject> {
82        match self.left.check(subject).await {
83            PredicateResult::Cacheable(s) => self.right.check(s).await,
84            non_cacheable => non_cacheable,
85        }
86    }
87}
88
89/// Requires either predicate to return `Cacheable`.
90///
91/// Short-circuits: if the left predicate returns `Cacheable`,
92/// the right predicate is not evaluated.
93#[derive(Debug)]
94pub struct Or<L, R> {
95    left: L,
96    right: R,
97}
98
99impl<L, R> Or<L, R> {
100    /// Creates a new `Or` combinator from two predicates.
101    pub fn new(left: L, right: R) -> Self {
102        Self { left, right }
103    }
104}
105
106#[async_trait]
107impl<L, R> Predicate for Or<L, R>
108where
109    L: Predicate + Send + Sync,
110    R: Predicate<Subject = L::Subject> + Send + Sync,
111    L::Subject: Send,
112{
113    type Subject = L::Subject;
114
115    async fn check(&self, subject: Self::Subject) -> PredicateResult<Self::Subject> {
116        match self.left.check(subject).await {
117            PredicateResult::NonCacheable(s) => self.right.check(s).await,
118            cacheable => cacheable,
119        }
120    }
121}
122
123/// Extension trait for fluent predicate composition.
124pub trait PredicateExt: Predicate + Sized {
125    /// Combines this predicate with another using AND logic.
126    ///
127    /// Returns `Cacheable` only if both predicates return `Cacheable`.
128    fn and<R>(self, right: R) -> And<Self, R>
129    where
130        R: Predicate<Subject = Self::Subject>,
131    {
132        And::new(self, right)
133    }
134
135    /// Combines this predicate with another using OR logic.
136    ///
137    /// Returns `Cacheable` if either predicate returns `Cacheable`.
138    fn or<R>(self, right: R) -> Or<Self, R>
139    where
140        R: Predicate<Subject = Self::Subject>,
141    {
142        Or::new(self, right)
143    }
144
145    /// Inverts this predicate's result.
146    ///
147    /// `Cacheable` becomes `NonCacheable` and vice versa.
148    fn not(self) -> Not<Self> {
149        Not::new(self)
150    }
151
152    /// Boxes this predicate into a trait object.
153    ///
154    /// Useful for type erasure when storing predicates in collections
155    /// or returning them from functions.
156    fn boxed(self) -> Box<dyn Predicate<Subject = Self::Subject> + Send + Sync>
157    where
158        Self: Send + Sync + 'static,
159    {
160        Box::new(self)
161    }
162}
163
164impl<T: Predicate + Sized> PredicateExt for T {}