1use std::collections::HashMap;
10use std::fmt;
11
12#[derive(Debug, Clone, PartialEq)]
14pub enum FactValue {
15 String(String),
17 Integer(i64),
19 Float(f64),
21 Boolean(bool),
23 Array(Vec<FactValue>),
25 Null,
27}
28
29impl FactValue {
30 pub fn as_string(&self) -> String {
32 match self {
33 FactValue::String(s) => s.clone(),
34 FactValue::Integer(i) => i.to_string(),
35 FactValue::Float(f) => f.to_string(),
36 FactValue::Boolean(b) => b.to_string(),
37 FactValue::Array(arr) => format!("{:?}", arr),
38 FactValue::Null => "null".to_string(),
39 }
40 }
41
42 pub fn as_integer(&self) -> Option<i64> {
44 match self {
45 FactValue::Integer(i) => Some(*i),
46 FactValue::Float(f) => Some(*f as i64),
47 FactValue::String(s) => s.parse().ok(),
48 FactValue::Boolean(b) => Some(if *b { 1 } else { 0 }),
49 _ => None,
50 }
51 }
52
53 pub fn as_float(&self) -> Option<f64> {
55 match self {
56 FactValue::Float(f) => Some(*f),
57 FactValue::Integer(i) => Some(*i as f64),
58 FactValue::String(s) => s.parse().ok(),
59 _ => None,
60 }
61 }
62
63 pub fn as_boolean(&self) -> Option<bool> {
65 match self {
66 FactValue::Boolean(b) => Some(*b),
67 FactValue::Integer(i) => Some(*i != 0),
68 FactValue::String(s) => match s.to_lowercase().as_str() {
69 "true" | "yes" | "1" => Some(true),
70 "false" | "no" | "0" => Some(false),
71 _ => None,
72 },
73 FactValue::Null => Some(false),
74 _ => None,
75 }
76 }
77
78 pub fn is_null(&self) -> bool {
80 matches!(self, FactValue::Null)
81 }
82
83 pub fn compare(&self, operator: &str, other: &FactValue) -> bool {
85 match operator {
86 "==" => self == other,
87 "!=" => self != other,
88 ">" => self.compare_gt(other),
89 "<" => self.compare_lt(other),
90 ">=" => self.compare_gte(other),
91 "<=" => self.compare_lte(other),
92 "contains" => self.contains(other),
93 "startsWith" => self.starts_with(other),
94 "endsWith" => self.ends_with(other),
95 "matches" => self.matches_pattern(other),
96 "in" => self.in_array(other),
97 _ => false,
98 }
99 }
100
101 fn compare_gt(&self, other: &FactValue) -> bool {
102 match (self.as_float(), other.as_float()) {
103 (Some(a), Some(b)) => a > b,
104 _ => false,
105 }
106 }
107
108 fn compare_lt(&self, other: &FactValue) -> bool {
109 match (self.as_float(), other.as_float()) {
110 (Some(a), Some(b)) => a < b,
111 _ => false,
112 }
113 }
114
115 fn compare_gte(&self, other: &FactValue) -> bool {
116 match (self.as_float(), other.as_float()) {
117 (Some(a), Some(b)) => a >= b,
118 _ => self == other,
119 }
120 }
121
122 fn compare_lte(&self, other: &FactValue) -> bool {
123 match (self.as_float(), other.as_float()) {
124 (Some(a), Some(b)) => a <= b,
125 _ => self == other,
126 }
127 }
128
129 fn contains(&self, other: &FactValue) -> bool {
130 match (self, other) {
131 (FactValue::String(s), FactValue::String(pattern)) => s.contains(pattern),
132 (FactValue::Array(arr), val) => arr.contains(val),
133 _ => false,
134 }
135 }
136
137 fn starts_with(&self, other: &FactValue) -> bool {
138 match (self, other) {
139 (FactValue::String(s), FactValue::String(prefix)) => s.starts_with(prefix),
140 _ => false,
141 }
142 }
143
144 fn ends_with(&self, other: &FactValue) -> bool {
145 match (self, other) {
146 (FactValue::String(s), FactValue::String(suffix)) => s.ends_with(suffix),
147 _ => false,
148 }
149 }
150
151 fn matches_pattern(&self, other: &FactValue) -> bool {
152 match (self, other) {
153 (FactValue::String(s), FactValue::String(pattern)) => {
154 wildcard_match(s, pattern)
156 }
157 _ => false,
158 }
159 }
160
161 fn in_array(&self, other: &FactValue) -> bool {
162 match other {
163 FactValue::Array(arr) => arr.contains(self),
164 _ => false,
165 }
166 }
167}
168
169impl fmt::Display for FactValue {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 write!(f, "{}", self.as_string())
172 }
173}
174
175impl From<String> for FactValue {
176 fn from(s: String) -> Self {
177 FactValue::String(s)
178 }
179}
180
181impl From<&str> for FactValue {
182 fn from(s: &str) -> Self {
183 FactValue::String(s.to_string())
184 }
185}
186
187impl From<i64> for FactValue {
188 fn from(i: i64) -> Self {
189 FactValue::Integer(i)
190 }
191}
192
193impl From<i32> for FactValue {
194 fn from(i: i32) -> Self {
195 FactValue::Integer(i as i64)
196 }
197}
198
199impl From<f64> for FactValue {
200 fn from(f: f64) -> Self {
201 FactValue::Float(f)
202 }
203}
204
205impl From<bool> for FactValue {
206 fn from(b: bool) -> Self {
207 FactValue::Boolean(b)
208 }
209}
210
211impl From<Vec<FactValue>> for FactValue {
212 fn from(arr: Vec<FactValue>) -> Self {
213 FactValue::Array(arr)
214 }
215}
216
217#[derive(Debug, Clone)]
219pub struct TypedFacts {
220 data: HashMap<String, FactValue>,
221}
222
223impl TypedFacts {
224 pub fn new() -> Self {
226 Self {
227 data: HashMap::new(),
228 }
229 }
230
231 pub fn set<K: Into<String>, V: Into<FactValue>>(&mut self, key: K, value: V) {
233 self.data.insert(key.into(), value.into());
234 }
235
236 pub fn get(&self, key: &str) -> Option<&FactValue> {
238 self.data.get(key)
239 }
240
241 pub fn remove(&mut self, key: &str) -> Option<FactValue> {
243 self.data.remove(key)
244 }
245
246 pub fn contains(&self, key: &str) -> bool {
248 self.data.contains_key(key)
249 }
250
251 pub fn get_all(&self) -> &HashMap<String, FactValue> {
253 &self.data
254 }
255
256 pub fn clear(&mut self) {
258 self.data.clear();
259 }
260
261 pub fn to_string_map(&self) -> HashMap<String, String> {
263 self.data
264 .iter()
265 .map(|(k, v)| (k.clone(), v.as_string()))
266 .collect()
267 }
268
269 pub fn from_string_map(map: &HashMap<String, String>) -> Self {
271 let mut facts = Self::new();
272 for (k, v) in map {
273 if let Ok(i) = v.parse::<i64>() {
275 facts.set(k.clone(), i);
276 } else if let Ok(f) = v.parse::<f64>() {
277 facts.set(k.clone(), f);
278 } else if let Ok(b) = v.parse::<bool>() {
279 facts.set(k.clone(), b);
280 } else {
281 facts.set(k.clone(), v.clone());
282 }
283 }
284 facts
285 }
286
287 pub fn evaluate_condition(&self, field: &str, operator: &str, value: &FactValue) -> bool {
289 if let Some(fact_value) = self.get(field) {
290 fact_value.compare(operator, value)
291 } else {
292 false
293 }
294 }
295}
296
297impl Default for TypedFacts {
298 fn default() -> Self {
299 Self::new()
300 }
301}
302
303fn wildcard_match(text: &str, pattern: &str) -> bool {
306 let text_chars: Vec<char> = text.chars().collect();
307 let pattern_chars: Vec<char> = pattern.chars().collect();
308
309 wildcard_match_impl(&text_chars, &pattern_chars, 0, 0)
310}
311
312fn wildcard_match_impl(text: &[char], pattern: &[char], ti: usize, pi: usize) -> bool {
313 if pi == pattern.len() {
314 return ti == text.len();
315 }
316
317 if pattern[pi] == '*' {
318 for i in ti..=text.len() {
320 if wildcard_match_impl(text, pattern, i, pi + 1) {
321 return true;
322 }
323 }
324 false
325 } else if ti < text.len() && (pattern[pi] == '?' || pattern[pi] == text[ti]) {
326 wildcard_match_impl(text, pattern, ti + 1, pi + 1)
328 } else {
329 false
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn test_fact_value_types() {
339 let s = FactValue::String("hello".to_string());
340 let i = FactValue::Integer(42);
341 let f = FactValue::Float(3.14);
342 let b = FactValue::Boolean(true);
343
344 assert_eq!(s.as_string(), "hello");
345 assert_eq!(i.as_integer(), Some(42));
346 assert_eq!(f.as_float(), Some(3.14));
347 assert_eq!(b.as_boolean(), Some(true));
348 }
349
350 #[test]
351 fn test_comparisons() {
352 let a = FactValue::Integer(10);
353 let b = FactValue::Integer(20);
354
355 assert!(a.compare("<", &b));
356 assert!(b.compare(">", &a));
357 assert!(a.compare("<=", &a));
358 assert!(a.compare("!=", &b));
359 }
360
361 #[test]
362 fn test_string_operations() {
363 let text = FactValue::String("hello world".to_string());
364 let pattern = FactValue::String("world".to_string());
365 let prefix = FactValue::String("hello".to_string());
366
367 assert!(text.compare("contains", &pattern));
368 assert!(text.compare("startsWith", &prefix));
369 }
370
371 #[test]
372 fn test_wildcard_matching() {
373 let text = FactValue::String("hello world".to_string());
374
375 assert!(text.compare("matches", &FactValue::String("hello*".to_string())));
376 assert!(text.compare("matches", &FactValue::String("*world".to_string())));
377 assert!(text.compare("matches", &FactValue::String("hello?world".to_string())));
378 assert!(!text.compare("matches", &FactValue::String("hello?earth".to_string())));
379 }
380
381 #[test]
382 fn test_array_operations() {
383 let arr = FactValue::Array(vec![
384 FactValue::Integer(1),
385 FactValue::Integer(2),
386 FactValue::Integer(3),
387 ]);
388
389 let val = FactValue::Integer(2);
390 assert!(val.compare("in", &arr));
391
392 let val2 = FactValue::Integer(5);
393 assert!(!val2.compare("in", &arr));
394 }
395
396 #[test]
397 fn test_typed_facts() {
398 let mut facts = TypedFacts::new();
399 facts.set("age", 25i64);
400 facts.set("name", "John");
401 facts.set("score", 95.5);
402 facts.set("active", true);
403
404 assert_eq!(facts.get("age").unwrap().as_integer(), Some(25));
405 assert_eq!(facts.get("name").unwrap().as_string(), "John");
406 assert_eq!(facts.get("score").unwrap().as_float(), Some(95.5));
407 assert_eq!(facts.get("active").unwrap().as_boolean(), Some(true));
408 }
409
410 #[test]
411 fn test_evaluate_condition() {
412 let mut facts = TypedFacts::new();
413 facts.set("age", 25i64);
414 facts.set("name", "John Smith");
415
416 assert!(facts.evaluate_condition("age", ">", &FactValue::Integer(18)));
417 assert!(facts.evaluate_condition("age", "<=", &FactValue::Integer(30)));
418 assert!(facts.evaluate_condition("name", "contains", &FactValue::String("Smith".to_string())));
419 assert!(facts.evaluate_condition("name", "startsWith", &FactValue::String("John".to_string())));
420 }
421}