1use std::{any::Any, collections::HashMap, fmt};
29
30use crate::condition::{Condition, ConditionOperator, ConditionSelector};
31
32pub use condition_matcher_derive::Matchable as MatchableDerive;
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum MatchError {
38 FieldNotFound {
40 field: String,
41 type_name: String,
42 },
43 TypeMismatch {
45 field: String,
46 expected: String,
47 actual: String,
48 },
49 UnsupportedOperator {
51 operator: String,
52 context: String,
53 },
54 LengthNotSupported {
56 type_name: String,
57 },
58 #[cfg(feature = "regex")]
60 RegexError {
61 pattern: String,
62 message: String,
63 },
64 EmptyFieldPath,
66 NestedFieldNotFound {
68 path: Vec<String>,
69 failed_at: String,
70 },
71}
72
73impl fmt::Display for MatchError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 MatchError::FieldNotFound { field, type_name } => {
77 write!(f, "Field '{}' not found on type '{}'", field, type_name)
78 }
79 MatchError::TypeMismatch { field, expected, actual } => {
80 write!(f, "Type mismatch for field '{}': expected '{}', got '{}'", field, expected, actual)
81 }
82 MatchError::UnsupportedOperator { operator, context } => {
83 write!(f, "Operator '{}' not supported for {}", operator, context)
84 }
85 MatchError::LengthNotSupported { type_name } => {
86 write!(f, "Length check not supported for type '{}'", type_name)
87 }
88 #[cfg(feature = "regex")]
89 MatchError::RegexError { pattern, message } => {
90 write!(f, "Invalid regex pattern '{}': {}", pattern, message)
91 }
92 MatchError::EmptyFieldPath => {
93 write!(f, "Field path cannot be empty")
94 }
95 MatchError::NestedFieldNotFound { path, failed_at } => {
96 write!(f, "Nested field not found at '{}' in path {:?}", failed_at, path)
97 }
98 }
99 }
100}
101
102impl std::error::Error for MatchError {}
103
104#[derive(Debug, Clone)]
106pub struct MatchResult {
107 pub matched: bool,
109 pub condition_results: Vec<ConditionResult>,
111 pub mode: MatcherMode,
113}
114
115impl MatchResult {
116 pub fn is_match(&self) -> bool {
118 self.matched
119 }
120
121 pub fn passed_conditions(&self) -> Vec<&ConditionResult> {
123 self.condition_results.iter().filter(|r| r.passed).collect()
124 }
125
126 pub fn failed_conditions(&self) -> Vec<&ConditionResult> {
128 self.condition_results.iter().filter(|r| !r.passed).collect()
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct ConditionResult {
135 pub passed: bool,
137 pub description: String,
139 pub actual_value: Option<String>,
141 pub expected_value: Option<String>,
143 pub error: Option<MatchError>,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150pub enum MatcherMode {
151 #[default]
153 AND,
154 OR,
156 XOR,
158}
159
160pub trait Matchable: PartialEq + Sized {
180 fn get_length(&self) -> Option<usize> {
182 None
183 }
184
185 fn get_field(&self, _field: &str) -> Option<&dyn Any> {
188 None
189 }
190
191 fn get_field_path(&self, _path: &[&str]) -> Option<&dyn Any> {
194 None
195 }
196
197 fn type_name(&self) -> &str {
199 std::any::type_name::<Self>()
200 }
201
202 fn is_empty(&self) -> Option<bool> {
204 self.get_length().map(|len| len == 0)
205 }
206
207 fn is_none(&self) -> bool {
209 false
210 }
211}
212
213#[derive(Debug)]
234pub struct Matcher<'a, T: Matchable> {
235 pub mode: MatcherMode,
236 pub conditions: Vec<Condition<'a, T>>,
237}
238
239impl<'a, T: Matchable + 'static> Matcher<'a, T> {
240 pub fn new(mode: MatcherMode) -> Self {
242 Self {
243 mode,
244 conditions: Vec::new(),
245 }
246 }
247
248 pub fn and() -> Self {
250 Self::new(MatcherMode::AND)
251 }
252
253 pub fn or() -> Self {
255 Self::new(MatcherMode::OR)
256 }
257
258 pub fn xor() -> Self {
260 Self::new(MatcherMode::XOR)
261 }
262
263 pub fn add_condition(&mut self, condition: Condition<'a, T>) -> &mut Self {
265 self.conditions.push(condition);
266 self
267 }
268
269 pub fn add_conditions(&mut self, conditions: impl IntoIterator<Item = Condition<'a, T>>) -> &mut Self {
271 self.conditions.extend(conditions);
272 self
273 }
274
275 pub fn run(&self, value: &T) -> Result<bool, MatchError> {
278 let result = self.run_detailed(value)?;
279 Ok(result.matched)
280 }
281
282 pub fn run_detailed(&self, value: &T) -> Result<MatchResult, MatchError> {
284 let mut condition_results = Vec::new();
285
286 for condition in self.conditions.iter() {
287 let result = self.evaluate_condition(condition, value);
288 condition_results.push(result);
289 }
290
291 let matched = match self.mode {
292 MatcherMode::AND => condition_results.iter().all(|r| r.passed),
293 MatcherMode::OR => condition_results.iter().any(|r| r.passed),
294 MatcherMode::XOR => condition_results.iter().filter(|r| r.passed).count() == 1,
295 };
296
297 Ok(MatchResult {
298 matched,
299 condition_results,
300 mode: self.mode,
301 })
302 }
303
304 fn evaluate_condition(&self, condition: &Condition<'a, T>, value: &T) -> ConditionResult {
305 match &condition.selector {
306 ConditionSelector::Length(expected_length) => {
307 self.eval_length(value, *expected_length, &condition.operator)
308 }
309 ConditionSelector::Type(type_name) => {
310 self.eval_type(value, type_name, &condition.operator)
311 }
312 ConditionSelector::Value(value_to_check) => {
313 self.eval_value(value, value_to_check, &condition.operator)
314 }
315 ConditionSelector::FieldValue(field_name, expected_value) => {
316 self.eval_field_value(value, field_name, *expected_value, &condition.operator)
317 }
318 ConditionSelector::FieldPath(path, expected_value) => {
319 self.eval_field_path(value, path, *expected_value, &condition.operator)
320 }
321 ConditionSelector::Not(inner_condition) => {
322 let mut result = self.evaluate_condition(inner_condition, value);
323 result.passed = !result.passed;
324 result.description = format!("NOT({})", result.description);
325 result
326 }
327 }
328 }
329
330 fn eval_length(&self, value: &T, expected: usize, operator: &ConditionOperator) -> ConditionResult {
331 match value.get_length() {
332 Some(actual) => {
333 let passed = compare_numeric(actual, expected, operator);
334 ConditionResult {
335 passed,
336 description: format!("length {:?} {}", operator, expected),
337 actual_value: Some(actual.to_string()),
338 expected_value: Some(expected.to_string()),
339 error: None,
340 }
341 }
342 None => ConditionResult {
343 passed: false,
344 description: format!("length {:?} {}", operator, expected),
345 actual_value: None,
346 expected_value: Some(expected.to_string()),
347 error: Some(MatchError::LengthNotSupported {
348 type_name: value.type_name().to_string(),
349 }),
350 },
351 }
352 }
353
354 fn eval_type(&self, value: &T, expected_type: &str, operator: &ConditionOperator) -> ConditionResult {
355 let actual_type = value.type_name();
356 let passed = match operator {
357 ConditionOperator::Equals => actual_type == expected_type,
358 ConditionOperator::NotEquals => actual_type != expected_type,
359 ConditionOperator::Contains => actual_type.contains(expected_type),
360 _ => false,
361 };
362
363 ConditionResult {
364 passed,
365 description: format!("type {:?} {}", operator, expected_type),
366 actual_value: Some(actual_type.to_string()),
367 expected_value: Some(expected_type.to_string()),
368 error: None,
369 }
370 }
371
372 fn eval_value(&self, value: &T, expected: &T, operator: &ConditionOperator) -> ConditionResult {
373 let passed = match operator {
374 ConditionOperator::Equals => value == expected,
375 ConditionOperator::NotEquals => value != expected,
376 _ => false,
377 };
378
379 ConditionResult {
380 passed,
381 description: format!("value {:?}", operator),
382 actual_value: None,
383 expected_value: None,
384 error: None,
385 }
386 }
387
388 fn eval_field_value(
389 &self,
390 value: &T,
391 field: &str,
392 expected: &dyn Any,
393 operator: &ConditionOperator,
394 ) -> ConditionResult {
395 match value.get_field(field) {
396 Some(actual) => {
397 let (passed, actual_str, expected_str) = compare_any_values(actual, expected, operator);
398 ConditionResult {
399 passed,
400 description: format!("field '{}' {:?}", field, operator),
401 actual_value: actual_str,
402 expected_value: expected_str,
403 error: None,
404 }
405 }
406 None => ConditionResult {
407 passed: false,
408 description: format!("field '{}' {:?}", field, operator),
409 actual_value: None,
410 expected_value: None,
411 error: Some(MatchError::FieldNotFound {
412 field: field.to_string(),
413 type_name: value.type_name().to_string(),
414 }),
415 },
416 }
417 }
418
419 fn eval_field_path(
420 &self,
421 value: &T,
422 path: &[&str],
423 expected: &dyn Any,
424 operator: &ConditionOperator,
425 ) -> ConditionResult {
426 if path.is_empty() {
427 return ConditionResult {
428 passed: false,
429 description: "field path".to_string(),
430 actual_value: None,
431 expected_value: None,
432 error: Some(MatchError::EmptyFieldPath),
433 };
434 }
435
436 if let Some(actual) = value.get_field_path(path) {
438 let (passed, actual_str, expected_str) = compare_any_values(actual, expected, operator);
439 return ConditionResult {
440 passed,
441 description: format!("field path '{:?}' {:?}", path, operator),
442 actual_value: actual_str,
443 expected_value: expected_str,
444 error: None,
445 };
446 }
447
448 match value.get_field(path[0]) {
450 Some(actual) if path.len() == 1 => {
451 let (passed, actual_str, expected_str) = compare_any_values(actual, expected, operator);
452 ConditionResult {
453 passed,
454 description: format!("field path '{:?}' {:?}", path, operator),
455 actual_value: actual_str,
456 expected_value: expected_str,
457 error: None,
458 }
459 }
460 _ => ConditionResult {
461 passed: false,
462 description: format!("field path '{:?}' {:?}", path, operator),
463 actual_value: None,
464 expected_value: None,
465 error: Some(MatchError::NestedFieldNotFound {
466 path: path.iter().map(|s| s.to_string()).collect(),
467 failed_at: path[0].to_string(),
468 }),
469 },
470 }
471 }
472}
473
474fn compare_numeric<N: PartialOrd>(actual: N, expected: N, operator: &ConditionOperator) -> bool {
479 match operator {
480 ConditionOperator::Equals => actual == expected,
481 ConditionOperator::NotEquals => actual != expected,
482 ConditionOperator::GreaterThan => actual > expected,
483 ConditionOperator::LessThan => actual < expected,
484 ConditionOperator::GreaterThanOrEqual => actual >= expected,
485 ConditionOperator::LessThanOrEqual => actual <= expected,
486 _ => false,
487 }
488}
489
490fn compare_any_values(
491 actual: &dyn Any,
492 expected: &dyn Any,
493 operator: &ConditionOperator,
494) -> (bool, Option<String>, Option<String>) {
495 if let Some(result) = try_compare::<i8>(actual, expected, operator) { return result; }
497 if let Some(result) = try_compare::<i16>(actual, expected, operator) { return result; }
498 if let Some(result) = try_compare::<i32>(actual, expected, operator) { return result; }
499 if let Some(result) = try_compare::<i64>(actual, expected, operator) { return result; }
500 if let Some(result) = try_compare::<i128>(actual, expected, operator) { return result; }
501 if let Some(result) = try_compare::<isize>(actual, expected, operator) { return result; }
502
503 if let Some(result) = try_compare::<u8>(actual, expected, operator) { return result; }
505 if let Some(result) = try_compare::<u16>(actual, expected, operator) { return result; }
506 if let Some(result) = try_compare::<u32>(actual, expected, operator) { return result; }
507 if let Some(result) = try_compare::<u64>(actual, expected, operator) { return result; }
508 if let Some(result) = try_compare::<u128>(actual, expected, operator) { return result; }
509 if let Some(result) = try_compare::<usize>(actual, expected, operator) { return result; }
510
511 if let Some(result) = try_compare::<f32>(actual, expected, operator) { return result; }
513 if let Some(result) = try_compare::<f64>(actual, expected, operator) { return result; }
514
515 if let Some(result) = try_compare::<bool>(actual, expected, operator) { return result; }
517
518 if let Some(result) = try_compare_strings(actual, expected, operator) { return result; }
520
521 if let Some(result) = try_compare::<char>(actual, expected, operator) { return result; }
523
524 (false, None, None)
526}
527
528fn try_compare<T: PartialOrd + PartialEq + fmt::Display + 'static>(
529 actual: &dyn Any,
530 expected: &dyn Any,
531 operator: &ConditionOperator,
532) -> Option<(bool, Option<String>, Option<String>)> {
533 if let (Some(a), Some(e)) = (actual.downcast_ref::<T>(), expected.downcast_ref::<T>()) {
534 let passed = match operator {
535 ConditionOperator::Equals => a == e,
536 ConditionOperator::NotEquals => a != e,
537 ConditionOperator::GreaterThan => a > e,
538 ConditionOperator::LessThan => a < e,
539 ConditionOperator::GreaterThanOrEqual => a >= e,
540 ConditionOperator::LessThanOrEqual => a <= e,
541 _ => return None,
542 };
543 Some((passed, Some(a.to_string()), Some(e.to_string())))
544 } else {
545 None
546 }
547}
548
549fn try_compare_strings(
550 actual: &dyn Any,
551 expected: &dyn Any,
552 operator: &ConditionOperator,
553) -> Option<(bool, Option<String>, Option<String>)> {
554 let actual_str: Option<&str> = actual.downcast_ref::<String>().map(|s| s.as_str())
556 .or_else(|| actual.downcast_ref::<&str>().copied());
557
558 let expected_str: Option<&str> = expected.downcast_ref::<String>().map(|s| s.as_str())
560 .or_else(|| expected.downcast_ref::<&str>().copied());
561
562 match (actual_str, expected_str) {
563 (Some(a), Some(e)) => {
564 let passed = match operator {
565 ConditionOperator::Equals => a == e,
566 ConditionOperator::NotEquals => a != e,
567 ConditionOperator::Contains => a.contains(e),
568 ConditionOperator::NotContains => !a.contains(e),
569 ConditionOperator::StartsWith => a.starts_with(e),
570 ConditionOperator::EndsWith => a.ends_with(e),
571 ConditionOperator::GreaterThan => a > e,
572 ConditionOperator::LessThan => a < e,
573 ConditionOperator::GreaterThanOrEqual => a >= e,
574 ConditionOperator::LessThanOrEqual => a <= e,
575 ConditionOperator::IsEmpty => a.is_empty(),
576 ConditionOperator::IsNotEmpty => !a.is_empty(),
577 #[cfg(feature = "regex")]
578 ConditionOperator::Regex => {
579 regex::Regex::new(e).map(|re| re.is_match(a)).unwrap_or(false)
580 }
581 #[cfg(not(feature = "regex"))]
582 ConditionOperator::Regex => false,
583 _ => return None,
584 };
585 Some((passed, Some(a.to_string()), Some(e.to_string())))
586 }
587 _ => None,
588 }
589}
590
591impl Matchable for &str {
596 fn get_length(&self) -> Option<usize> {
597 Some(self.len())
598 }
599
600 fn is_empty(&self) -> Option<bool> {
601 Some((*self).is_empty())
602 }
603}
604
605impl Matchable for String {
606 fn get_length(&self) -> Option<usize> {
607 Some(self.len())
608 }
609
610 fn is_empty(&self) -> Option<bool> {
611 Some(self.is_empty())
612 }
613}
614
615impl<T: Matchable> Matchable for Vec<T> {
616 fn get_length(&self) -> Option<usize> {
617 Some(self.len())
618 }
619
620 fn is_empty(&self) -> Option<bool> {
621 Some(self.is_empty())
622 }
623}
624
625impl<K, V> Matchable for HashMap<K, V>
626where
627 K: std::borrow::Borrow<str> + std::hash::Hash + Eq,
628 V: PartialEq + 'static,
629{
630 fn get_length(&self) -> Option<usize> {
631 Some(self.len())
632 }
633
634 fn get_field(&self, field: &str) -> Option<&dyn Any> {
635 self.get(field).map(|v| v as &dyn Any)
636 }
637
638 fn is_empty(&self) -> Option<bool> {
639 Some(self.is_empty())
640 }
641}
642
643impl<T: Matchable + 'static> Matchable for Option<T> {
644 fn get_length(&self) -> Option<usize> {
645 self.as_ref().and_then(|v| v.get_length())
646 }
647
648 fn get_field(&self, field: &str) -> Option<&dyn Any> {
649 self.as_ref().and_then(|v| v.get_field(field))
650 }
651
652 fn is_none(&self) -> bool {
653 self.is_none()
654 }
655
656 fn is_empty(&self) -> Option<bool> {
657 Some(self.is_none())
658 }
659}
660
661macro_rules! impl_matchable_primitive {
663 ($($t:ty),*) => {
664 $(
665 impl Matchable for $t {}
666 )*
667 };
668}
669
670impl_matchable_primitive!(
671 i8, i16, i32, i64, i128, isize,
672 u8, u16, u32, u64, u128, usize,
673 f32, f64,
674 bool, char
675);
676
677pub struct MatcherBuilder<'a, T: Matchable> {
696 mode: MatcherMode,
697 conditions: Vec<Condition<'a, T>>,
698}
699
700impl<'a, T: Matchable + 'static> MatcherBuilder<'a, T> {
701 pub fn new() -> Self {
703 Self {
704 mode: MatcherMode::AND,
705 conditions: Vec::new(),
706 }
707 }
708
709 pub fn mode(mut self, mode: MatcherMode) -> Self {
711 self.mode = mode;
712 self
713 }
714
715 pub fn value_equals(mut self, expected: T) -> Self {
717 self.conditions.push(Condition {
718 selector: ConditionSelector::Value(expected),
719 operator: ConditionOperator::Equals,
720 });
721 self
722 }
723
724 pub fn value_not_equals(mut self, expected: T) -> Self {
726 self.conditions.push(Condition {
727 selector: ConditionSelector::Value(expected),
728 operator: ConditionOperator::NotEquals,
729 });
730 self
731 }
732
733 pub fn length(mut self, len: usize, operator: ConditionOperator) -> Self {
735 self.conditions.push(Condition {
736 selector: ConditionSelector::Length(len),
737 operator,
738 });
739 self
740 }
741
742 pub fn length_equals(self, len: usize) -> Self {
744 self.length(len, ConditionOperator::Equals)
745 }
746
747 pub fn length_gte(self, len: usize) -> Self {
749 self.length(len, ConditionOperator::GreaterThanOrEqual)
750 }
751
752 pub fn length_lte(self, len: usize) -> Self {
754 self.length(len, ConditionOperator::LessThanOrEqual)
755 }
756
757 pub fn condition(mut self, condition: Condition<'a, T>) -> Self {
759 self.conditions.push(condition);
760 self
761 }
762
763 pub fn build(self) -> Matcher<'a, T> {
765 Matcher {
766 mode: self.mode,
767 conditions: self.conditions,
768 }
769 }
770}
771
772impl<'a, T: Matchable + 'static> Default for MatcherBuilder<'a, T> {
773 fn default() -> Self {
774 Self::new()
775 }
776}
777
778pub struct FieldConditionBuilder<'a, T> {
799 field: &'a str,
800 _phantom: std::marker::PhantomData<T>,
801}
802
803impl<'a, T: Matchable> FieldConditionBuilder<'a, T> {
804 pub fn new(field: &'a str) -> Self {
806 Self {
807 field,
808 _phantom: std::marker::PhantomData,
809 }
810 }
811
812 pub fn equals(self, value: &'a dyn Any) -> Condition<'a, T> {
814 Condition {
815 selector: ConditionSelector::FieldValue(self.field, value),
816 operator: ConditionOperator::Equals,
817 }
818 }
819
820 pub fn not_equals(self, value: &'a dyn Any) -> Condition<'a, T> {
822 Condition {
823 selector: ConditionSelector::FieldValue(self.field, value),
824 operator: ConditionOperator::NotEquals,
825 }
826 }
827
828 pub fn gt(self, value: &'a dyn Any) -> Condition<'a, T> {
830 Condition {
831 selector: ConditionSelector::FieldValue(self.field, value),
832 operator: ConditionOperator::GreaterThan,
833 }
834 }
835
836 pub fn gte(self, value: &'a dyn Any) -> Condition<'a, T> {
838 Condition {
839 selector: ConditionSelector::FieldValue(self.field, value),
840 operator: ConditionOperator::GreaterThanOrEqual,
841 }
842 }
843
844 pub fn lt(self, value: &'a dyn Any) -> Condition<'a, T> {
846 Condition {
847 selector: ConditionSelector::FieldValue(self.field, value),
848 operator: ConditionOperator::LessThan,
849 }
850 }
851
852 pub fn lte(self, value: &'a dyn Any) -> Condition<'a, T> {
854 Condition {
855 selector: ConditionSelector::FieldValue(self.field, value),
856 operator: ConditionOperator::LessThanOrEqual,
857 }
858 }
859
860 pub fn contains(self, value: &'a dyn Any) -> Condition<'a, T> {
862 Condition {
863 selector: ConditionSelector::FieldValue(self.field, value),
864 operator: ConditionOperator::Contains,
865 }
866 }
867
868 pub fn starts_with(self, value: &'a dyn Any) -> Condition<'a, T> {
870 Condition {
871 selector: ConditionSelector::FieldValue(self.field, value),
872 operator: ConditionOperator::StartsWith,
873 }
874 }
875
876 pub fn ends_with(self, value: &'a dyn Any) -> Condition<'a, T> {
878 Condition {
879 selector: ConditionSelector::FieldValue(self.field, value),
880 operator: ConditionOperator::EndsWith,
881 }
882 }
883}
884
885pub fn field<'a, T: Matchable>(name: &'a str) -> FieldConditionBuilder<'a, T> {
887 FieldConditionBuilder::new(name)
888}
889
890#[cfg(test)]
895mod tests {
896 use super::*;
897 use crate::condition::{ConditionOperator, ConditionSelector};
898
899 #[test]
900 fn test_matcher_and_mode() {
901 let mut matcher: Matcher<&str> = Matcher::new(MatcherMode::AND);
902 matcher
903 .add_condition(Condition {
904 selector: ConditionSelector::Length(5),
905 operator: ConditionOperator::GreaterThanOrEqual,
906 })
907 .add_condition(Condition {
908 selector: ConditionSelector::Value("something"),
909 operator: ConditionOperator::NotEquals,
910 });
911
912 assert_eq!(matcher.run(&"test").unwrap(), false);
913 assert_eq!(matcher.run(&"test12345").unwrap(), true);
914 assert_eq!(matcher.run(&"something").unwrap(), false);
915 assert_eq!(matcher.run(&"somethingelse").unwrap(), true);
916 }
917
918 #[test]
919 fn test_matcher_or_mode() {
920 let mut matcher: Matcher<&str> = Matcher::new(MatcherMode::OR);
921 matcher
922 .add_condition(Condition {
923 selector: ConditionSelector::Length(4),
924 operator: ConditionOperator::Equals,
925 })
926 .add_condition(Condition {
927 selector: ConditionSelector::Value("hello"),
928 operator: ConditionOperator::Equals,
929 });
930
931 assert_eq!(matcher.run(&"test").unwrap(), true);
932 assert_eq!(matcher.run(&"hello").unwrap(), true);
933 assert_eq!(matcher.run(&"world").unwrap(), false);
934 }
935
936 #[test]
937 fn test_matcher_xor_mode() {
938 let mut matcher: Matcher<&str> = Matcher::new(MatcherMode::XOR);
939 matcher
940 .add_condition(Condition {
941 selector: ConditionSelector::Length(4),
942 operator: ConditionOperator::Equals,
943 })
944 .add_condition(Condition {
945 selector: ConditionSelector::Value("test"),
946 operator: ConditionOperator::Equals,
947 });
948
949 assert_eq!(matcher.run(&"test").unwrap(), false);
950 assert_eq!(matcher.run(&"hello").unwrap(), false);
951 assert_eq!(matcher.run(&"abcd").unwrap(), true);
952 }
953
954 #[test]
955 fn test_type_checking() {
956 let mut matcher: Matcher<&str> = Matcher::new(MatcherMode::AND);
957 matcher.add_condition(Condition {
958 selector: ConditionSelector::Type("&str".to_string()),
959 operator: ConditionOperator::Equals,
960 });
961
962 assert_eq!(matcher.run(&"test").unwrap(), true);
963 }
964
965 #[test]
966 fn test_field_checking() {
967 use crate::matcher::MatchableDerive;
968
969 #[derive(MatchableDerive, PartialEq, Debug)]
970 struct TestStruct {
971 a: i32,
972 b: String,
973 }
974
975 let test_value = TestStruct {
976 a: 1,
977 b: "test".to_string(),
978 };
979
980 let mut matcher: Matcher<TestStruct> = Matcher::new(MatcherMode::AND);
982 matcher.add_condition(Condition {
983 selector: ConditionSelector::FieldValue("a", &1i32),
984 operator: ConditionOperator::Equals,
985 });
986 assert_eq!(matcher.run(&test_value).unwrap(), true);
987
988 let mut matcher2: Matcher<TestStruct> = Matcher::new(MatcherMode::AND);
990 matcher2.add_condition(Condition {
991 selector: ConditionSelector::FieldValue("a", &2i32),
992 operator: ConditionOperator::Equals,
993 });
994 assert_eq!(matcher2.run(&test_value).unwrap(), false);
995
996 let mut matcher3: Matcher<TestStruct> = Matcher::new(MatcherMode::AND);
998 matcher3.add_condition(Condition {
999 selector: ConditionSelector::FieldValue("b", &"test"),
1000 operator: ConditionOperator::Equals,
1001 });
1002 assert_eq!(matcher3.run(&test_value).unwrap(), true);
1003 }
1004
1005 #[test]
1006 fn test_numeric_comparisons_on_fields() {
1007 use crate::matcher::MatchableDerive;
1008
1009 #[derive(MatchableDerive, PartialEq, Debug)]
1010 struct Person {
1011 age: u32,
1012 score: f64,
1013 }
1014
1015 let person = Person { age: 25, score: 85.5 };
1016
1017 let mut matcher: Matcher<Person> = Matcher::new(MatcherMode::AND);
1019 matcher.add_condition(Condition {
1020 selector: ConditionSelector::FieldValue("age", &18u32),
1021 operator: ConditionOperator::GreaterThan,
1022 });
1023 assert!(matcher.run(&person).unwrap());
1024
1025 let mut matcher2: Matcher<Person> = Matcher::new(MatcherMode::AND);
1027 matcher2.add_condition(Condition {
1028 selector: ConditionSelector::FieldValue("age", &25u32),
1029 operator: ConditionOperator::LessThanOrEqual,
1030 });
1031 assert!(matcher2.run(&person).unwrap());
1032
1033 let mut matcher3: Matcher<Person> = Matcher::new(MatcherMode::AND);
1035 matcher3.add_condition(Condition {
1036 selector: ConditionSelector::FieldValue("score", &80.0f64),
1037 operator: ConditionOperator::GreaterThan,
1038 });
1039 assert!(matcher3.run(&person).unwrap());
1040 }
1041
1042 #[test]
1043 fn test_string_operations() {
1044 use crate::matcher::MatchableDerive;
1045
1046 #[derive(MatchableDerive, PartialEq, Debug)]
1047 struct Email {
1048 address: String,
1049 }
1050
1051 let email = Email {
1052 address: "user@example.com".to_string(),
1053 };
1054
1055 let mut matcher: Matcher<Email> = Matcher::new(MatcherMode::AND);
1057 matcher.add_condition(Condition {
1058 selector: ConditionSelector::FieldValue("address", &"@example"),
1059 operator: ConditionOperator::Contains,
1060 });
1061 assert!(matcher.run(&email).unwrap());
1062
1063 let mut matcher2: Matcher<Email> = Matcher::new(MatcherMode::AND);
1065 matcher2.add_condition(Condition {
1066 selector: ConditionSelector::FieldValue("address", &"user@"),
1067 operator: ConditionOperator::StartsWith,
1068 });
1069 assert!(matcher2.run(&email).unwrap());
1070
1071 let mut matcher3: Matcher<Email> = Matcher::new(MatcherMode::AND);
1073 matcher3.add_condition(Condition {
1074 selector: ConditionSelector::FieldValue("address", &".com"),
1075 operator: ConditionOperator::EndsWith,
1076 });
1077 assert!(matcher3.run(&email).unwrap());
1078
1079 let mut matcher4: Matcher<Email> = Matcher::new(MatcherMode::AND);
1081 matcher4.add_condition(Condition {
1082 selector: ConditionSelector::FieldValue("address", &"@gmail"),
1083 operator: ConditionOperator::NotContains,
1084 });
1085 assert!(matcher4.run(&email).unwrap());
1086 }
1087
1088 #[test]
1089 fn test_detailed_results() {
1090 let mut matcher: Matcher<&str> = Matcher::new(MatcherMode::AND);
1091 matcher
1092 .add_condition(Condition {
1093 selector: ConditionSelector::Length(4),
1094 operator: ConditionOperator::Equals,
1095 })
1096 .add_condition(Condition {
1097 selector: ConditionSelector::Value("test"),
1098 operator: ConditionOperator::Equals,
1099 });
1100
1101 let result = matcher.run_detailed(&"test").unwrap();
1102 assert!(result.is_match());
1103 assert_eq!(result.passed_conditions().len(), 2);
1104 assert_eq!(result.failed_conditions().len(), 0);
1105
1106 let result2 = matcher.run_detailed(&"hello").unwrap();
1107 assert!(!result2.is_match());
1108 assert_eq!(result2.passed_conditions().len(), 0);
1109 assert_eq!(result2.failed_conditions().len(), 2);
1110 }
1111
1112 #[test]
1113 fn test_builder_api() {
1114 let matcher = MatcherBuilder::<&str>::new()
1115 .mode(MatcherMode::AND)
1116 .length_gte(4)
1117 .value_not_equals("bad")
1118 .build();
1119
1120 assert!(matcher.run(&"good").unwrap());
1121 assert!(!matcher.run(&"bad").unwrap());
1122 assert!(!matcher.run(&"hi").unwrap());
1123 }
1124
1125 #[test]
1126 fn test_field_builder() {
1127 use crate::matcher::MatchableDerive;
1128
1129 #[derive(MatchableDerive, PartialEq, Debug)]
1130 struct User {
1131 age: u32,
1132 }
1133
1134 let user = User { age: 25 };
1135
1136 let condition = field::<User>("age").gte(&18u32);
1137 let mut matcher = Matcher::new(MatcherMode::AND);
1138 matcher.add_condition(condition);
1139
1140 assert!(matcher.run(&user).unwrap());
1141 }
1142
1143 #[test]
1144 fn test_convenience_constructors() {
1145 let and_matcher: Matcher<&str> = Matcher::and();
1146 assert_eq!(and_matcher.mode, MatcherMode::AND);
1147
1148 let or_matcher: Matcher<&str> = Matcher::or();
1149 assert_eq!(or_matcher.mode, MatcherMode::OR);
1150
1151 let xor_matcher: Matcher<&str> = Matcher::xor();
1152 assert_eq!(xor_matcher.mode, MatcherMode::XOR);
1153 }
1154
1155 #[test]
1156 fn test_error_on_missing_field() {
1157 use crate::matcher::MatchableDerive;
1158
1159 #[derive(MatchableDerive, PartialEq, Debug)]
1160 struct User {
1161 name: String,
1162 }
1163
1164 let user = User { name: "Alice".to_string() };
1165
1166 let mut matcher: Matcher<User> = Matcher::new(MatcherMode::AND);
1167 matcher.add_condition(Condition {
1168 selector: ConditionSelector::FieldValue("nonexistent", &"value"),
1169 operator: ConditionOperator::Equals,
1170 });
1171
1172 let result = matcher.run_detailed(&user).unwrap();
1173 assert!(!result.is_match());
1174
1175 let failed = result.failed_conditions();
1176 assert_eq!(failed.len(), 1);
1177 assert!(failed[0].error.is_some());
1178 }
1179
1180 #[test]
1181 fn test_not_operator() {
1182 use crate::matcher::MatchableDerive;
1183
1184 #[derive(MatchableDerive, PartialEq, Debug)]
1185 struct Item {
1186 active: bool,
1187 }
1188
1189 let item = Item { active: false };
1190
1191 let inner_condition = Condition {
1193 selector: ConditionSelector::FieldValue("active", &true),
1194 operator: ConditionOperator::Equals,
1195 };
1196
1197 let mut matcher: Matcher<Item> = Matcher::new(MatcherMode::AND);
1198 matcher.add_condition(Condition {
1199 selector: ConditionSelector::Not(Box::new(inner_condition)),
1200 operator: ConditionOperator::Equals, });
1202
1203 assert!(matcher.run(&item).unwrap());
1204 }
1205
1206 #[test]
1207 fn test_optional_fields() {
1208 use crate::matcher::MatchableDerive;
1209
1210 #[derive(MatchableDerive, PartialEq, Debug)]
1211 struct Profile {
1212 name: String,
1213 nickname: Option<String>,
1214 }
1215
1216 let profile_with_nick = Profile {
1217 name: "Alice".to_string(),
1218 nickname: Some("Ali".to_string()),
1219 };
1220
1221 let profile_without_nick = Profile {
1222 name: "Bob".to_string(),
1223 nickname: None,
1224 };
1225
1226 let mut matcher: Matcher<Profile> = Matcher::new(MatcherMode::AND);
1228 matcher.add_condition(Condition {
1229 selector: ConditionSelector::FieldValue("nickname", &"Ali"),
1230 operator: ConditionOperator::Equals,
1231 });
1232
1233 assert!(matcher.run(&profile_with_nick).unwrap());
1234 assert!(!matcher.run(&profile_without_nick).unwrap());
1236 }
1237
1238 #[cfg(feature = "regex")]
1239 #[test]
1240 fn test_regex_matching() {
1241 use crate::matcher::MatchableDerive;
1242
1243 #[derive(MatchableDerive, PartialEq, Debug)]
1244 struct Email {
1245 address: String,
1246 }
1247
1248 let email = Email {
1249 address: "user@example.com".to_string(),
1250 };
1251
1252 let mut matcher: Matcher<Email> = Matcher::new(MatcherMode::AND);
1253 matcher.add_condition(Condition {
1254 selector: ConditionSelector::FieldValue("address", &r"^[a-z]+@[a-z]+\.[a-z]+$"),
1255 operator: ConditionOperator::Regex,
1256 });
1257
1258 assert!(matcher.run(&email).unwrap());
1259
1260 let bad_email = Email {
1262 address: "not-an-email".to_string(),
1263 };
1264 assert!(!matcher.run(&bad_email).unwrap());
1265 }
1266}