condition_matcher/
matchable.rs

1use std::any::Any;
2use std::collections::HashMap;
3
4/// Trait for types that can be matched against conditions.
5/// 
6/// This trait allows different types to opt-in to specific matching capabilities.
7/// For structs, you can use `#[derive(Matchable)]` to automatically implement field access.
8/// 
9/// ## Example
10/// 
11/// ```rust
12/// use condition_matcher::{Matchable, MatchableDerive};
13/// use std::any::Any;
14/// 
15/// #[derive(MatchableDerive, PartialEq)]
16/// struct MyStruct {
17///     value: i32,
18///     name: String,
19/// }
20/// 
21/// // The derive macro automatically implements get_field for all fields
22/// ```
23pub trait Matchable: PartialEq + Sized {
24    /// Get the length of the value if supported (for strings, collections, etc.)
25    fn get_length(&self) -> Option<usize> {
26        None
27    }
28    
29    /// Get a field value by name as a type-erased reference.
30    /// Returns None if field access is not supported or field doesn't exist.
31    fn get_field(&self, _field: &str) -> Option<&dyn Any> {
32        None
33    }
34    
35    /// Get a nested field value by path.
36    /// Default implementation walks through get_field calls.
37    fn get_field_path(&self, _path: &[&str]) -> Option<&dyn Any> {
38        None
39    }
40    
41    /// Get the type name as a string
42    fn type_name(&self) -> &str {
43        std::any::type_name::<Self>()
44    }
45    
46    /// Check if the value is considered "empty" (for collections, strings, options)
47    fn is_empty(&self) -> Option<bool> {
48        self.get_length().map(|len| len == 0)
49    }
50    
51    /// Check if this is a None/null value (for Option types)
52    fn is_none(&self) -> bool {
53        false
54    }
55}
56
57// ============================================================================
58// Matchable Implementations for Common Types
59// ============================================================================
60
61impl Matchable for &str {
62    fn get_length(&self) -> Option<usize> {
63        Some(self.len())
64    }
65
66    fn is_empty(&self) -> Option<bool> {
67        Some((*self).is_empty())
68    }
69}
70
71impl Matchable for String {
72    fn get_length(&self) -> Option<usize> {
73        Some(self.len())
74    }
75
76    fn is_empty(&self) -> Option<bool> {
77        Some(self.is_empty())
78    }
79}
80
81impl<T: Matchable> Matchable for Vec<T> {
82    fn get_length(&self) -> Option<usize> {
83        Some(self.len())
84    }
85
86    fn is_empty(&self) -> Option<bool> {
87        Some(self.is_empty())
88    }
89}
90
91impl<K, V> Matchable for HashMap<K, V>
92where
93    K: std::borrow::Borrow<str> + std::hash::Hash + Eq,
94    V: PartialEq + 'static,
95{
96    fn get_length(&self) -> Option<usize> {
97        Some(self.len())
98    }
99
100    fn get_field(&self, field: &str) -> Option<&dyn Any> {
101        self.get(field).map(|v| v as &dyn Any)
102    }
103
104    fn is_empty(&self) -> Option<bool> {
105        Some(self.is_empty())
106    }
107}
108
109impl<T: Matchable + 'static> Matchable for Option<T> {
110    fn get_length(&self) -> Option<usize> {
111        self.as_ref().and_then(|v| v.get_length())
112    }
113
114    fn get_field(&self, field: &str) -> Option<&dyn Any> {
115        self.as_ref().and_then(|v| v.get_field(field))
116    }
117
118    fn is_none(&self) -> bool {
119        self.is_none()
120    }
121
122    fn is_empty(&self) -> Option<bool> {
123        Some(self.is_none())
124    }
125}
126
127// Implement for primitive types
128macro_rules! impl_matchable_primitive {
129    ($($t:ty),*) => {
130        $(
131            impl Matchable for $t {}
132        )*
133    };
134}
135
136impl_matchable_primitive!(
137    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, char
138);