condition_matcher/
matcher.rs

1//! # Matcher Module (Legacy)
2//!
3//! This module provides backwards compatibility with the old Matcher struct.
4//! For new code, use [`RuleMatcher`](crate::matchers::RuleMatcher) instead.
5//!
6//! ## Example
7//!
8//! ```rust
9//! use condition_matcher::{RuleMatcher, MatcherMode, Condition, ConditionSelector, ConditionOperator, Matchable, MatchableDerive, Matcher};
10//!
11//! #[derive(MatchableDerive, PartialEq, Debug)]
12//! struct User {
13//!     name: String,
14//!     age: u32,
15//! }
16//!
17//! let user = User { name: "Alice".to_string(), age: 30 };
18//!
19//! let mut matcher = RuleMatcher::new(MatcherMode::AND);
20//! matcher.add_condition(Condition {
21//!     selector: ConditionSelector::FieldValue("age", &30u32),
22//!     operator: ConditionOperator::Equals,
23//! });
24//!
25//! assert!(matcher.matches(&user));
26//! ```
27
28use std::{any::Any, fmt};
29
30use crate::{
31    MatchError, Matchable,
32    condition::{Condition, ConditionMode, ConditionOperator, ConditionSelector, NestedCondition},
33    result::{ConditionResult, MatchResult},
34};
35
36/// The legacy matcher struct (kept for backwards compatibility).
37///
38/// For new code, use [`RuleMatcher`](crate::matchers::RuleMatcher) instead.
39///
40/// ## Example
41///
42/// ```rust,ignore
43/// use condition_matcher::{Matcher, MatcherMode, Condition, ConditionSelector, ConditionOperator};
44///
45/// let mut matcher: Matcher<&str> = Matcher::new(MatcherMode::AND);
46/// matcher
47///     .add_condition(Condition {
48///         selector: ConditionSelector::Length(5),
49///         operator: ConditionOperator::GreaterThanOrEqual,
50///     })
51///     .add_condition(Condition {
52///         selector: ConditionSelector::Value("test"),
53///         operator: ConditionOperator::NotEquals,
54///     });
55///
56/// assert!(matcher.run(&"hello").unwrap());
57/// ```
58#[derive(Debug)]
59pub struct Matcher<'a, T: Matchable> {
60    pub mode: ConditionMode,
61    pub conditions: Vec<Condition<'a, T>>,
62}
63
64impl<'a, T: Matchable + 'static> Matcher<'a, T> {
65    /// Create a new matcher with the specified mode
66    pub fn new(mode: ConditionMode) -> Self {
67        Self {
68            mode,
69            conditions: Vec::new(),
70        }
71    }
72
73    /// Create a new matcher with AND mode
74    pub fn and() -> Self {
75        Self::new(ConditionMode::AND)
76    }
77
78    /// Create a new matcher with OR mode
79    pub fn or() -> Self {
80        Self::new(ConditionMode::OR)
81    }
82
83    /// Create a new matcher with XOR mode
84    pub fn xor() -> Self {
85        Self::new(ConditionMode::XOR)
86    }
87
88    /// Add a condition to this matcher
89    pub fn add_condition(&mut self, condition: Condition<'a, T>) -> &mut Self {
90        self.conditions.push(condition);
91        self
92    }
93
94    /// Add multiple conditions at once
95    pub fn add_conditions(
96        &mut self,
97        conditions: impl IntoIterator<Item = Condition<'a, T>>,
98    ) -> &mut Self {
99        self.conditions.extend(conditions);
100        self
101    }
102
103    /// Run the matcher and return a simple boolean result.
104    /// Returns Err if any condition evaluation fails critically.
105    pub fn run(&self, value: &T) -> Result<bool, MatchError> {
106        let result = self.run_detailed(value)?;
107        Ok(result.matched)
108    }
109
110    /// Run the matcher and return a simple boolean result.
111    /// Returns Err if any condition evaluation fails critically.
112    pub fn run_batch(&self, values: impl Iterator<Item = &'a T>) -> Result<Vec<bool>, MatchError> {
113        values.into_iter().map(|value| self.run(value)).collect()
114    }
115
116    /// Run the matcher and return detailed results for each condition
117    pub fn run_detailed(&self, value: &T) -> Result<MatchResult, MatchError> {
118        let mut condition_results = Vec::new();
119
120        for condition in self.conditions.iter() {
121            let result = self.evaluate_condition(condition, value);
122            condition_results.push(result);
123        }
124
125        let matched = match self.mode {
126            ConditionMode::AND => condition_results.iter().all(|r| r.passed),
127            ConditionMode::OR => condition_results.iter().any(|r| r.passed),
128            ConditionMode::XOR => condition_results.iter().filter(|r| r.passed).count() == 1,
129        };
130
131        Ok(MatchResult {
132            matched,
133            condition_results,
134            mode: self.mode,
135        })
136    }
137
138    /// Run the matcher and return detailed results for each condition
139    pub fn run_detailed_batch(
140        &self,
141        values: impl Iterator<Item = &'a T>,
142    ) -> Result<Vec<MatchResult>, MatchError> {
143        values
144            .into_iter()
145            .map(|value| self.run_detailed(value))
146            .collect()
147    }
148
149    fn evaluate_condition(&self, condition: &Condition<'a, T>, value: &T) -> ConditionResult {
150        match &condition.selector {
151            ConditionSelector::Length(expected_length) => {
152                self.eval_length(value, *expected_length, &condition.operator)
153            }
154            ConditionSelector::Type(type_name) => {
155                self.eval_type(value, type_name, &condition.operator)
156            }
157            ConditionSelector::Value(value_to_check) => {
158                self.eval_value(value, value_to_check, &condition.operator)
159            }
160            ConditionSelector::FieldValue(field_name, expected_value) => {
161                self.eval_field_value(value, field_name, *expected_value, &condition.operator)
162            }
163            ConditionSelector::FieldPath(path, expected_value) => {
164                self.eval_field_path(value, path, *expected_value, &condition.operator)
165            }
166            ConditionSelector::Not(inner_condition) => {
167                let mut result = self.evaluate_condition(inner_condition, value);
168                result.passed = !result.passed;
169                result.description = format!("NOT({})", result.description);
170                result
171            }
172            ConditionSelector::Nested(nested_group) => self.eval_nested(value, nested_group),
173        }
174    }
175
176    fn eval_nested(&self, value: &T, group: &NestedCondition<'a, T>) -> ConditionResult {
177        let mut results = Vec::new();
178
179        // Evaluate all rules at this level
180        for condition in &group.rules {
181            results.push(self.evaluate_condition(condition, value));
182        }
183
184        // Evaluate nested groups recursively
185        for nested_group in &group.nested {
186            results.push(self.eval_nested(value, nested_group));
187        }
188
189        let passed = match group.mode {
190            ConditionMode::AND => results.iter().all(|r| r.passed),
191            ConditionMode::OR => results.iter().any(|r| r.passed),
192            ConditionMode::XOR => results.iter().filter(|r| r.passed).count() == 1,
193        };
194
195        ConditionResult {
196            passed,
197            description: format!(
198                "{:?} group ({} rules, {} nested)",
199                group.mode,
200                group.rules.len(),
201                group.nested.len()
202            ),
203            actual_value: None,
204            expected_value: None,
205            error: None,
206        }
207    }
208
209    /// Evaluate a NestedCondition group against a value
210    pub fn evaluate_nested(&self, value: &T, group: &NestedCondition<'a, T>) -> ConditionResult {
211        self.eval_nested(value, group)
212    }
213
214    fn eval_length(
215        &self,
216        value: &T,
217        expected: usize,
218        operator: &ConditionOperator,
219    ) -> ConditionResult {
220        match value.get_length() {
221            Some(actual) => {
222                let passed = compare_numeric(actual, expected, operator);
223                ConditionResult {
224                    passed,
225                    description: format!("length {:?} {}", operator, expected),
226                    actual_value: Some(actual.to_string()),
227                    expected_value: Some(expected.to_string()),
228                    error: None,
229                }
230            }
231            None => ConditionResult {
232                passed: false,
233                description: format!("length {:?} {}", operator, expected),
234                actual_value: None,
235                expected_value: Some(expected.to_string()),
236                error: Some(MatchError::LengthNotSupported {
237                    type_name: value.type_name().to_string(),
238                }),
239            },
240        }
241    }
242
243    fn eval_type(
244        &self,
245        value: &T,
246        expected_type: &str,
247        operator: &ConditionOperator,
248    ) -> ConditionResult {
249        let actual_type = value.type_name();
250        let passed = match operator {
251            ConditionOperator::Equals => actual_type == expected_type,
252            ConditionOperator::NotEquals => actual_type != expected_type,
253            ConditionOperator::Contains => actual_type.contains(expected_type),
254            _ => false,
255        };
256
257        ConditionResult {
258            passed,
259            description: format!("type {:?} {}", operator, expected_type),
260            actual_value: Some(actual_type.to_string()),
261            expected_value: Some(expected_type.to_string()),
262            error: None,
263        }
264    }
265
266    fn eval_value(&self, value: &T, expected: &T, operator: &ConditionOperator) -> ConditionResult {
267        let passed = match operator {
268            ConditionOperator::Equals => value == expected,
269            ConditionOperator::NotEquals => value != expected,
270            _ => false,
271        };
272
273        ConditionResult {
274            passed,
275            description: format!("value {:?}", operator),
276            actual_value: None,
277            expected_value: None,
278            error: None,
279        }
280    }
281
282    fn eval_field_value(
283        &self,
284        value: &T,
285        field: &str,
286        expected: &dyn Any,
287        operator: &ConditionOperator,
288    ) -> ConditionResult {
289        match value.get_field(field) {
290            Some(actual) => {
291                let (passed, actual_str, expected_str) =
292                    compare_any_values(actual, expected, operator);
293                ConditionResult {
294                    passed,
295                    description: format!("field '{}' {:?}", field, operator),
296                    actual_value: actual_str,
297                    expected_value: expected_str,
298                    error: None,
299                }
300            }
301            None => ConditionResult {
302                passed: false,
303                description: format!("field '{}' {:?}", field, operator),
304                actual_value: None,
305                expected_value: None,
306                error: Some(MatchError::FieldNotFound {
307                    field: field.to_string(),
308                    type_name: value.type_name().to_string(),
309                }),
310            },
311        }
312    }
313
314    fn eval_field_path(
315        &self,
316        value: &T,
317        path: &[&str],
318        expected: &dyn Any,
319        operator: &ConditionOperator,
320    ) -> ConditionResult {
321        if path.is_empty() {
322            return ConditionResult {
323                passed: false,
324                description: "field path".to_string(),
325                actual_value: None,
326                expected_value: None,
327                error: Some(MatchError::EmptyFieldPath),
328            };
329        }
330
331        // Try to use get_field_path first
332        if let Some(actual) = value.get_field_path(path) {
333            let (passed, actual_str, expected_str) = compare_any_values(actual, expected, operator);
334            return ConditionResult {
335                passed,
336                description: format!("field path '{:?}' {:?}", path, operator),
337                actual_value: actual_str,
338                expected_value: expected_str,
339                error: None,
340            };
341        }
342
343        // Fallback: try first field only (basic implementation)
344        match value.get_field(path[0]) {
345            Some(actual) if path.len() == 1 => {
346                let (passed, actual_str, expected_str) =
347                    compare_any_values(actual, expected, operator);
348                ConditionResult {
349                    passed,
350                    description: format!("field path '{:?}' {:?}", path, operator),
351                    actual_value: actual_str,
352                    expected_value: expected_str,
353                    error: None,
354                }
355            }
356            _ => ConditionResult {
357                passed: false,
358                description: format!("field path '{:?}' {:?}", path, operator),
359                actual_value: None,
360                expected_value: None,
361                error: Some(MatchError::NestedFieldNotFound {
362                    path: path.iter().map(|s| s.to_string()).collect(),
363                    failed_at: path[0].to_string(),
364                }),
365            },
366        }
367    }
368}
369
370// ============================================================================
371// Comparison Functions
372// ============================================================================
373
374fn compare_numeric<N: PartialOrd>(actual: N, expected: N, operator: &ConditionOperator) -> bool {
375    match operator {
376        ConditionOperator::Equals => actual == expected,
377        ConditionOperator::NotEquals => actual != expected,
378        ConditionOperator::GreaterThan => actual > expected,
379        ConditionOperator::LessThan => actual < expected,
380        ConditionOperator::GreaterThanOrEqual => actual >= expected,
381        ConditionOperator::LessThanOrEqual => actual <= expected,
382        _ => false,
383    }
384}
385
386fn compare_any_values(
387    actual: &dyn Any,
388    expected: &dyn Any,
389    operator: &ConditionOperator,
390) -> (bool, Option<String>, Option<String>) {
391    // Integer types
392    if let Some(result) = try_compare::<i8>(actual, expected, operator) {
393        return result;
394    }
395    if let Some(result) = try_compare::<i16>(actual, expected, operator) {
396        return result;
397    }
398    if let Some(result) = try_compare::<i32>(actual, expected, operator) {
399        return result;
400    }
401    if let Some(result) = try_compare::<i64>(actual, expected, operator) {
402        return result;
403    }
404    if let Some(result) = try_compare::<i128>(actual, expected, operator) {
405        return result;
406    }
407    if let Some(result) = try_compare::<isize>(actual, expected, operator) {
408        return result;
409    }
410
411    // Unsigned integer types
412    if let Some(result) = try_compare::<u8>(actual, expected, operator) {
413        return result;
414    }
415    if let Some(result) = try_compare::<u16>(actual, expected, operator) {
416        return result;
417    }
418    if let Some(result) = try_compare::<u32>(actual, expected, operator) {
419        return result;
420    }
421    if let Some(result) = try_compare::<u64>(actual, expected, operator) {
422        return result;
423    }
424    if let Some(result) = try_compare::<u128>(actual, expected, operator) {
425        return result;
426    }
427    if let Some(result) = try_compare::<usize>(actual, expected, operator) {
428        return result;
429    }
430
431    // Float types
432    if let Some(result) = try_compare::<f32>(actual, expected, operator) {
433        return result;
434    }
435    if let Some(result) = try_compare::<f64>(actual, expected, operator) {
436        return result;
437    }
438
439    // Boolean
440    if let Some(result) = try_compare::<bool>(actual, expected, operator) {
441        return result;
442    }
443
444    // String types with string operations
445    if let Some(result) = try_compare_strings(actual, expected, operator) {
446        return result;
447    }
448
449    // Char
450    if let Some(result) = try_compare::<char>(actual, expected, operator) {
451        return result;
452    }
453
454    // No match found
455    (false, None, None)
456}
457
458fn try_compare<T: PartialOrd + PartialEq + fmt::Display + 'static>(
459    actual: &dyn Any,
460    expected: &dyn Any,
461    operator: &ConditionOperator,
462) -> Option<(bool, Option<String>, Option<String>)> {
463    if let (Some(a), Some(e)) = (actual.downcast_ref::<T>(), expected.downcast_ref::<T>()) {
464        let passed = match operator {
465            ConditionOperator::Equals => a == e,
466            ConditionOperator::NotEquals => a != e,
467            ConditionOperator::GreaterThan => a > e,
468            ConditionOperator::LessThan => a < e,
469            ConditionOperator::GreaterThanOrEqual => a >= e,
470            ConditionOperator::LessThanOrEqual => a <= e,
471            _ => return None,
472        };
473        Some((passed, Some(a.to_string()), Some(e.to_string())))
474    } else {
475        None
476    }
477}
478
479fn try_compare_strings(
480    actual: &dyn Any,
481    expected: &dyn Any,
482    operator: &ConditionOperator,
483) -> Option<(bool, Option<String>, Option<String>)> {
484    // Get the actual string
485    let actual_str: Option<&str> = actual
486        .downcast_ref::<String>()
487        .map(|s| s.as_str())
488        .or_else(|| actual.downcast_ref::<&str>().copied());
489
490    // Get the expected string
491    let expected_str: Option<&str> = expected
492        .downcast_ref::<String>()
493        .map(|s| s.as_str())
494        .or_else(|| expected.downcast_ref::<&str>().copied());
495
496    match (actual_str, expected_str) {
497        (Some(a), Some(e)) => {
498            let passed = match operator {
499                ConditionOperator::Equals => a == e,
500                ConditionOperator::NotEquals => a != e,
501                ConditionOperator::Contains => a.contains(e),
502                ConditionOperator::NotContains => !a.contains(e),
503                ConditionOperator::StartsWith => a.starts_with(e),
504                ConditionOperator::EndsWith => a.ends_with(e),
505                ConditionOperator::GreaterThan => a > e,
506                ConditionOperator::LessThan => a < e,
507                ConditionOperator::GreaterThanOrEqual => a >= e,
508                ConditionOperator::LessThanOrEqual => a <= e,
509                ConditionOperator::IsEmpty => a.is_empty(),
510                ConditionOperator::IsNotEmpty => !a.is_empty(),
511                #[cfg(feature = "regex")]
512                ConditionOperator::Regex => regex::Regex::new(e)
513                    .map(|re| re.is_match(a))
514                    .unwrap_or(false),
515                #[cfg(not(feature = "regex"))]
516                ConditionOperator::Regex => false,
517                _ => return None,
518            };
519            Some((passed, Some(a.to_string()), Some(e.to_string())))
520        }
521        _ => None,
522    }
523}
524
525// ============================================================================
526// JSON Value Comparison (when json_condition feature is enabled)
527// ============================================================================
528
529#[cfg(feature = "json_condition")]
530fn extract_as_f64(actual: &dyn Any) -> Option<f64> {
531    // Try various numeric types
532    if let Some(v) = actual.downcast_ref::<f64>() {
533        return Some(*v);
534    }
535    if let Some(v) = actual.downcast_ref::<f32>() {
536        return Some(*v as f64);
537    }
538    if let Some(v) = actual.downcast_ref::<i64>() {
539        return Some(*v as f64);
540    }
541    if let Some(v) = actual.downcast_ref::<i32>() {
542        return Some(*v as f64);
543    }
544    if let Some(v) = actual.downcast_ref::<i16>() {
545        return Some(*v as f64);
546    }
547    if let Some(v) = actual.downcast_ref::<i8>() {
548        return Some(*v as f64);
549    }
550    if let Some(v) = actual.downcast_ref::<u64>() {
551        return Some(*v as f64);
552    }
553    if let Some(v) = actual.downcast_ref::<u32>() {
554        return Some(*v as f64);
555    }
556    if let Some(v) = actual.downcast_ref::<u16>() {
557        return Some(*v as f64);
558    }
559    if let Some(v) = actual.downcast_ref::<u8>() {
560        return Some(*v as f64);
561    }
562    if let Some(v) = actual.downcast_ref::<isize>() {
563        return Some(*v as f64);
564    }
565    if let Some(v) = actual.downcast_ref::<usize>() {
566        return Some(*v as f64);
567    }
568    None
569}
570
571#[cfg(feature = "json_condition")]
572fn compare_json_to_any(
573    actual: &dyn Any,
574    expected: &serde_json::Value,
575    operator: &ConditionOperator,
576) -> (bool, Option<String>, Option<String>) {
577    // Numeric comparison
578    if let Some(exp_f64) = expected.as_f64() {
579        if let Some(act_f64) = extract_as_f64(actual) {
580            let passed = match operator {
581                ConditionOperator::Equals => (act_f64 - exp_f64).abs() < f64::EPSILON,
582                ConditionOperator::NotEquals => (act_f64 - exp_f64).abs() >= f64::EPSILON,
583                ConditionOperator::GreaterThan => act_f64 > exp_f64,
584                ConditionOperator::LessThan => act_f64 < exp_f64,
585                ConditionOperator::GreaterThanOrEqual => act_f64 >= exp_f64,
586                ConditionOperator::LessThanOrEqual => act_f64 <= exp_f64,
587                _ => false,
588            };
589            return (passed, Some(act_f64.to_string()), Some(exp_f64.to_string()));
590        }
591    }
592
593    // String comparison
594    if let Some(exp_str) = expected.as_str() {
595        let act_str: Option<&str> = actual
596            .downcast_ref::<String>()
597            .map(|s| s.as_str())
598            .or_else(|| actual.downcast_ref::<&str>().copied());
599
600        if let Some(a) = act_str {
601            let passed = match operator {
602                ConditionOperator::Equals => a == exp_str,
603                ConditionOperator::NotEquals => a != exp_str,
604                ConditionOperator::Contains => a.contains(exp_str),
605                ConditionOperator::NotContains => !a.contains(exp_str),
606                ConditionOperator::StartsWith => a.starts_with(exp_str),
607                ConditionOperator::EndsWith => a.ends_with(exp_str),
608                ConditionOperator::GreaterThan => a > exp_str,
609                ConditionOperator::LessThan => a < exp_str,
610                ConditionOperator::GreaterThanOrEqual => a >= exp_str,
611                ConditionOperator::LessThanOrEqual => a <= exp_str,
612                ConditionOperator::IsEmpty => a.is_empty(),
613                ConditionOperator::IsNotEmpty => !a.is_empty(),
614                #[cfg(feature = "regex")]
615                ConditionOperator::Regex => regex::Regex::new(exp_str)
616                    .map(|re| re.is_match(a))
617                    .unwrap_or(false),
618                #[cfg(not(feature = "regex"))]
619                ConditionOperator::Regex => false,
620                _ => false,
621            };
622            return (passed, Some(a.to_string()), Some(exp_str.to_string()));
623        }
624    }
625
626    // Boolean comparison
627    if let Some(exp_bool) = expected.as_bool() {
628        if let Some(act_bool) = actual.downcast_ref::<bool>() {
629            let passed = match operator {
630                ConditionOperator::Equals => *act_bool == exp_bool,
631                ConditionOperator::NotEquals => *act_bool != exp_bool,
632                _ => false,
633            };
634            return (
635                passed,
636                Some(act_bool.to_string()),
637                Some(exp_bool.to_string()),
638            );
639        }
640    }
641
642    (false, None, None)
643}
644
645// ============================================================================
646// JSON Condition Evaluation (when json_condition feature is enabled)
647// ============================================================================
648
649#[cfg(feature = "json_condition")]
650use crate::condition::{JsonCondition, JsonNestedCondition};
651#[cfg(feature = "json_condition")]
652use crate::result::{JsonConditionResult, JsonEvalResult};
653
654/// Evaluate a JsonNestedCondition against a Matchable context.
655///
656/// This function allows you to deserialize conditions from JSON and evaluate
657/// them against any type that implements Matchable.
658///
659/// # Example
660///
661/// ```ignore
662/// use condition_matcher::{evaluate_json_condition, JsonNestedCondition, Matchable};
663///
664/// let json = r#"{"logic": "AND", "rules": [{"field": "age", "operator": "gte", "value": 18}]}"#;
665/// let group: JsonNestedCondition = serde_json::from_str(json)?;
666/// let result = evaluate_json_condition(&my_struct, &group);
667/// ```
668#[cfg(feature = "json_condition")]
669pub fn evaluate_json_condition<M: Matchable>(
670    context: &M,
671    group: &JsonNestedCondition,
672) -> JsonEvalResult {
673    let mut details = Vec::new();
674    eval_json_nested_recursive(context, group, &mut details)
675}
676
677#[cfg(feature = "json_condition")]
678fn eval_json_nested_recursive<M: Matchable>(
679    context: &M,
680    group: &JsonNestedCondition,
681    details: &mut Vec<JsonConditionResult>,
682) -> JsonEvalResult {
683    let mut flags = Vec::new();
684
685    // Evaluate all rules at this level
686    for rule in &group.rules {
687        let result = eval_json_rule(context, rule);
688        flags.push(result.passed);
689        details.push(result);
690    }
691
692    // Evaluate nested groups recursively
693    for nested_group in &group.nested {
694        let nested_result = eval_json_nested_recursive(context, nested_group, details);
695        flags.push(nested_result.matched);
696    }
697
698    let matched = match group.mode {
699        ConditionMode::AND => flags.iter().all(|&f| f),
700        ConditionMode::OR => flags.iter().any(|&f| f),
701        ConditionMode::XOR => flags.iter().filter(|&&f| f).count() == 1,
702    };
703
704    JsonEvalResult {
705        matched,
706        details: details.clone(),
707    }
708}
709
710#[cfg(feature = "json_condition")]
711fn eval_json_rule<M: Matchable>(context: &M, rule: &JsonCondition) -> JsonConditionResult {
712    let field = &rule.field;
713
714    // Support dotted paths like "user.age" by splitting on '.'
715    let path_segments: Vec<&str> = field.split('.').collect();
716
717    // Try to resolve the field value
718    let actual_value = if path_segments.len() == 1 {
719        context.get_field(field)
720    } else {
721        context.get_field_path(&path_segments)
722    };
723
724    match actual_value {
725        Some(actual) => {
726            let (passed, actual_str, _expected_str) =
727                compare_json_to_any(actual, &rule.value, &rule.operator);
728            JsonConditionResult {
729                passed,
730                field: field.clone(),
731                operator: rule.operator,
732                expected: rule.value.clone(),
733                actual: actual_str
734                    .clone()
735                    .and_then(|s| serde_json::from_str(&format!("\"{}\"", s)).ok())
736                    .or_else(|| {
737                        actual_str.and_then(|s| s.parse::<f64>().ok().map(serde_json::Value::from))
738                    }),
739                error: None,
740            }
741        }
742        None => JsonConditionResult {
743            passed: false,
744            field: field.clone(),
745            operator: rule.operator,
746            expected: rule.value.clone(),
747            actual: None,
748            error: Some(format!("Field '{}' not found", field)),
749        },
750    }
751}