condition_matcher/
condition.rs

1use std::any::Any;
2
3use crate::{
4    evaluators::{FieldEvaluator, LengthEvaluator, PathEvaluator, TypeEvaluator, ValueEvaluator},
5    matchable::Matchable,
6    result::ConditionResult,
7    traits::Predicate,
8};
9
10/// Mode for combining multiple conditions
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12#[cfg_attr(any(feature = "serde", feature = "json_condition"), derive(serde::Serialize, serde::Deserialize))]
13pub enum ConditionMode {
14    /// All conditions must match
15    #[default]
16    AND,
17    /// At least one condition must match
18    OR,
19    /// Exactly one condition must match
20    XOR,
21}
22
23/// Operators for comparing values in conditions
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[cfg_attr(any(feature = "serde", feature = "json_condition"), derive(serde::Serialize, serde::Deserialize))]
26#[cfg_attr(any(feature = "serde", feature = "json_condition"), serde(rename_all = "snake_case"))]
27pub enum ConditionOperator {
28    /// Exact equality check
29    Equals,
30    /// Inequality check
31    NotEquals,
32    /// Greater than comparison (numeric types)
33    GreaterThan,
34    /// Less than comparison (numeric types)
35    LessThan,
36    /// Greater than or equal comparison (numeric types)
37    GreaterThanOrEqual,
38    /// Less than or equal comparison (numeric types)
39    LessThanOrEqual,
40    /// String contains substring
41    Contains,
42    /// String does not contain substring
43    NotContains,
44    /// String starts with prefix
45    StartsWith,
46    /// String ends with suffix
47    EndsWith,
48    /// Value matches regex pattern
49    Regex,
50    /// Check if value is None/null
51    IsNone,
52    /// Check if value is Some/present
53    IsSome,
54    /// Check if collection is empty
55    IsEmpty,
56    /// Check if collection is not empty
57    IsNotEmpty,
58}
59
60// ============================================================================
61// Core condition types (always available, uses &dyn Any)
62// ============================================================================
63
64/// Selectors for targeting what to check in a condition
65#[derive(Debug)]
66pub enum ConditionSelector<'a, T> {
67    /// Check the length of a string or collection
68    Length(usize),
69    /// Check the type name
70    Type(String),
71    /// Compare against a specific value
72    Value(T),
73    /// Check a field value by name
74    FieldValue(&'a str, &'a dyn Any),
75    /// Check a nested field path (e.g., ["address", "city"])
76    FieldPath(&'a [&'a str], &'a dyn Any),
77    /// Negate a condition (inverts the result)
78    Not(Box<Condition<'a, T>>),
79    /// A nested group of conditions
80    Nested(Box<NestedCondition<'a, T>>),
81}
82
83/// A single condition to evaluate
84#[derive(Debug)]
85pub struct Condition<'a, T> {
86    pub operator: ConditionOperator,
87    pub selector: ConditionSelector<'a, T>,
88}
89
90/// A group of conditions combined with a logic mode
91#[derive(Debug)]
92pub struct NestedCondition<'a, T> {
93    /// How to combine conditions: AND, OR, XOR
94    pub mode: ConditionMode,
95    /// Simple conditions at this level
96    pub rules: Vec<Condition<'a, T>>,
97    /// Child groups (recursive)
98    pub nested: Vec<Box<NestedCondition<'a, T>>>,
99}
100
101// ============================================================================
102// JSON-serializable condition types (only with json_condition feature)
103// These are separate types that can be deserialized from JSON and converted
104// to the core types for evaluation.
105// ============================================================================
106
107/// A JSON-serializable condition for field comparisons.
108/// 
109/// Deserializes from JSON like:
110/// ```json
111/// { "field": "price", "operator": "greater_than_or_equal", "value": 100.0 }
112/// ```
113#[cfg(feature = "json_condition")]
114#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
115pub struct JsonCondition {
116    /// The field to check (supports dotted paths like "user.age")
117    pub field: String,
118    /// The comparison operator
119    pub operator: ConditionOperator,
120    /// The value to compare against
121    pub value: serde_json::Value,
122}
123
124/// A JSON-serializable group of conditions with nested support.
125/// 
126/// Deserializes from JSON like:
127/// ```json
128/// {
129///     "logic": "AND",
130///     "rules": [
131///         { "field": "price", "operator": "greater_than_or_equal", "value": 100.0 }
132///     ],
133///     "nested": [
134///         { "logic": "OR", "rules": [...] }
135///     ]
136/// }
137/// ```
138#[cfg(feature = "json_condition")]
139#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
140pub struct JsonNestedCondition {
141    /// How to combine conditions: AND, OR, XOR
142    #[serde(alias = "logic", alias = "comparator", default)]
143    pub mode: ConditionMode,
144    /// Simple conditions at this level
145    #[serde(default, alias = "conditions")]
146    pub rules: Vec<JsonCondition>,
147    /// Child groups (recursive)
148    #[serde(default, alias = "nested_rules", alias = "nested_conditions")]
149    pub nested: Vec<Box<JsonNestedCondition>>,
150}
151
152// ============================================================================
153// Predicate Implementation for Condition
154// ============================================================================
155
156impl<'a, T: Matchable + 'static> Predicate<T> for Condition<'a, T> {
157    fn test(&self, value: &T) -> bool {
158        self.test_detailed(value).passed
159    }
160
161    fn test_detailed(&self, value: &T) -> ConditionResult {
162        match &self.selector {
163            ConditionSelector::Length(expected) => {
164                LengthEvaluator::evaluate(value, *expected, &self.operator)
165            }
166            ConditionSelector::Type(type_name) => {
167                TypeEvaluator::evaluate(value, type_name, &self.operator)
168            }
169            ConditionSelector::Value(expected) => {
170                ValueEvaluator::evaluate(value, expected, &self.operator)
171            }
172            ConditionSelector::FieldValue(field, expected) => {
173                FieldEvaluator::evaluate(value, field, *expected, &self.operator)
174            }
175            ConditionSelector::FieldPath(path, expected) => {
176                PathEvaluator::evaluate(value, path, *expected, &self.operator)
177            }
178            ConditionSelector::Not(inner) => {
179                let mut result = inner.test_detailed(value);
180                result.passed = !result.passed;
181                result.description = format!("NOT({})", result.description);
182                result
183            }
184            ConditionSelector::Nested(group) => evaluate_nested(value, group),
185        }
186    }
187}
188
189/// Evaluate a nested condition group.
190fn evaluate_nested<'a, T: Matchable + 'static>(
191    value: &T,
192    group: &NestedCondition<'a, T>,
193) -> ConditionResult {
194    let mut results = Vec::new();
195
196    // Evaluate all rules at this level
197    for condition in &group.rules {
198        results.push(condition.test_detailed(value));
199    }
200
201    // Evaluate nested groups recursively
202    for nested_group in &group.nested {
203        results.push(evaluate_nested(value, nested_group));
204    }
205
206    let passed = match group.mode {
207        ConditionMode::AND => results.iter().all(|r| r.passed),
208        ConditionMode::OR => results.iter().any(|r| r.passed),
209        ConditionMode::XOR => results.iter().filter(|r| r.passed).count() == 1,
210    };
211
212    ConditionResult {
213        passed,
214        description: format!(
215            "{:?} group ({} rules, {} nested)",
216            group.mode,
217            group.rules.len(),
218            group.nested.len()
219        ),
220        actual_value: None,
221        expected_value: None,
222        error: None,
223    }
224}