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_number(&self) -> Option<f64> {
65 match self {
66 FactValue::Float(f) => Some(*f),
67 FactValue::Integer(i) => Some(*i as f64),
68 FactValue::String(s) => s.parse().ok(),
69 _ => None,
70 }
71 }
72
73 pub fn as_boolean(&self) -> Option<bool> {
75 match self {
76 FactValue::Boolean(b) => Some(*b),
77 FactValue::Integer(i) => Some(*i != 0),
78 FactValue::String(s) => match s.to_lowercase().as_str() {
79 "true" | "yes" | "1" => Some(true),
80 "false" | "no" | "0" => Some(false),
81 _ => None,
82 },
83 FactValue::Null => Some(false),
84 _ => None,
85 }
86 }
87
88 pub fn is_null(&self) -> bool {
90 matches!(self, FactValue::Null)
91 }
92
93 pub fn compare(&self, operator: &str, other: &FactValue) -> bool {
95 match operator {
96 "==" => self == other,
97 "!=" => self != other,
98 ">" => self.compare_gt(other),
99 "<" => self.compare_lt(other),
100 ">=" => self.compare_gte(other),
101 "<=" => self.compare_lte(other),
102 "contains" => self.contains(other),
103 "startsWith" => self.starts_with(other),
104 "endsWith" => self.ends_with(other),
105 "matches" => self.matches_pattern(other),
106 "in" => self.in_array(other),
107 _ => false,
108 }
109 }
110
111 fn compare_gt(&self, other: &FactValue) -> bool {
112 match (self.as_float(), other.as_float()) {
113 (Some(a), Some(b)) => a > b,
114 _ => false,
115 }
116 }
117
118 fn compare_lt(&self, other: &FactValue) -> bool {
119 match (self.as_float(), other.as_float()) {
120 (Some(a), Some(b)) => a < b,
121 _ => false,
122 }
123 }
124
125 fn compare_gte(&self, other: &FactValue) -> bool {
126 match (self.as_float(), other.as_float()) {
127 (Some(a), Some(b)) => a >= b,
128 _ => self == other,
129 }
130 }
131
132 fn compare_lte(&self, other: &FactValue) -> bool {
133 match (self.as_float(), other.as_float()) {
134 (Some(a), Some(b)) => a <= b,
135 _ => self == other,
136 }
137 }
138
139 fn contains(&self, other: &FactValue) -> bool {
140 match (self, other) {
141 (FactValue::String(s), FactValue::String(pattern)) => s.contains(pattern),
142 (FactValue::Array(arr), val) => arr.contains(val),
143 _ => false,
144 }
145 }
146
147 fn starts_with(&self, other: &FactValue) -> bool {
148 match (self, other) {
149 (FactValue::String(s), FactValue::String(prefix)) => s.starts_with(prefix),
150 _ => false,
151 }
152 }
153
154 fn ends_with(&self, other: &FactValue) -> bool {
155 match (self, other) {
156 (FactValue::String(s), FactValue::String(suffix)) => s.ends_with(suffix),
157 _ => false,
158 }
159 }
160
161 fn matches_pattern(&self, other: &FactValue) -> bool {
162 match (self, other) {
163 (FactValue::String(s), FactValue::String(pattern)) => {
164 wildcard_match(s, pattern)
166 }
167 _ => false,
168 }
169 }
170
171 fn in_array(&self, other: &FactValue) -> bool {
172 match other {
173 FactValue::Array(arr) => arr.contains(self),
174 _ => false,
175 }
176 }
177}
178
179impl fmt::Display for FactValue {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(f, "{}", self.as_string())
182 }
183}
184
185impl From<String> for FactValue {
186 fn from(s: String) -> Self {
187 FactValue::String(s)
188 }
189}
190
191impl From<&str> for FactValue {
192 fn from(s: &str) -> Self {
193 FactValue::String(s.to_string())
194 }
195}
196
197impl From<i64> for FactValue {
198 fn from(i: i64) -> Self {
199 FactValue::Integer(i)
200 }
201}
202
203impl From<i32> for FactValue {
204 fn from(i: i32) -> Self {
205 FactValue::Integer(i as i64)
206 }
207}
208
209impl From<f64> for FactValue {
210 fn from(f: f64) -> Self {
211 FactValue::Float(f)
212 }
213}
214
215impl From<bool> for FactValue {
216 fn from(b: bool) -> Self {
217 FactValue::Boolean(b)
218 }
219}
220
221impl From<Vec<FactValue>> for FactValue {
222 fn from(arr: Vec<FactValue>) -> Self {
223 FactValue::Array(arr)
224 }
225}
226
227impl From<crate::types::Value> for FactValue {
229 fn from(value: crate::types::Value) -> Self {
230 match value {
231 crate::types::Value::String(s) => FactValue::String(s),
232 crate::types::Value::Number(n) => FactValue::Float(n),
233 crate::types::Value::Integer(i) => FactValue::Integer(i),
234 crate::types::Value::Boolean(b) => FactValue::Boolean(b),
235 crate::types::Value::Array(arr) => {
236 FactValue::Array(arr.into_iter().map(|v| v.into()).collect())
237 }
238 crate::types::Value::Object(obj) => {
239 FactValue::String(format!("{:?}", obj))
241 }
242 crate::types::Value::Null => FactValue::Null,
243 crate::types::Value::Expression(expr) => FactValue::String(expr),
244 }
245 }
246}
247
248#[derive(Debug, Clone)]
250pub struct TypedFacts {
251 data: HashMap<String, FactValue>,
252 pub(crate) fact_handles: HashMap<String, super::FactHandle>,
255}
256
257impl TypedFacts {
258 pub fn new() -> Self {
260 Self {
261 data: HashMap::new(),
262 fact_handles: HashMap::new(),
263 }
264 }
265
266 pub fn with_capacity(capacity: usize) -> Self {
268 Self {
269 data: HashMap::with_capacity(capacity),
270 fact_handles: HashMap::with_capacity(capacity),
271 }
272 }
273
274 pub fn set_fact_handle(&mut self, fact_type: String, handle: super::FactHandle) {
276 self.fact_handles.insert(fact_type, handle);
277 }
278
279 pub fn get_fact_handle(&self, fact_type: &str) -> Option<super::FactHandle> {
281 self.fact_handles.get(fact_type).copied()
282 }
283
284 pub fn set<K: Into<String>, V: Into<FactValue>>(&mut self, key: K, value: V) {
286 self.data.insert(key.into(), value.into());
287 }
288
289 pub fn get(&self, key: &str) -> Option<&FactValue> {
291 self.data.get(key)
292 }
293
294 pub fn remove(&mut self, key: &str) -> Option<FactValue> {
296 self.data.remove(key)
297 }
298
299 pub fn contains(&self, key: &str) -> bool {
301 self.data.contains_key(key)
302 }
303
304 pub fn get_all(&self) -> &HashMap<String, FactValue> {
306 &self.data
307 }
308
309 pub fn clear(&mut self) {
311 self.data.clear();
312 }
313
314 pub fn to_string_map(&self) -> HashMap<String, String> {
316 self.data
317 .iter()
318 .map(|(k, v)| (k.clone(), v.as_string()))
319 .collect()
320 }
321
322 pub fn from_string_map(map: &HashMap<String, String>) -> Self {
324 let mut facts = Self::new();
325 for (k, v) in map {
326 if let Ok(i) = v.parse::<i64>() {
328 facts.set(k.clone(), i);
329 } else if let Ok(f) = v.parse::<f64>() {
330 facts.set(k.clone(), f);
331 } else if let Ok(b) = v.parse::<bool>() {
332 facts.set(k.clone(), b);
333 } else {
334 facts.set(k.clone(), v.clone());
335 }
336 }
337 facts
338 }
339
340 pub fn evaluate_condition(&self, field: &str, operator: &str, value: &FactValue) -> bool {
342 if let Some(fact_value) = self.get(field) {
343 fact_value.compare(operator, value)
344 } else {
345 false
346 }
347 }
348}
349
350impl Default for TypedFacts {
351 fn default() -> Self {
352 Self::new()
353 }
354}
355
356fn wildcard_match(text: &str, pattern: &str) -> bool {
359 let text_chars: Vec<char> = text.chars().collect();
360 let pattern_chars: Vec<char> = pattern.chars().collect();
361
362 wildcard_match_impl(&text_chars, &pattern_chars, 0, 0)
363}
364
365fn wildcard_match_impl(text: &[char], pattern: &[char], ti: usize, pi: usize) -> bool {
366 if pi == pattern.len() {
367 return ti == text.len();
368 }
369
370 if pattern[pi] == '*' {
371 for i in ti..=text.len() {
373 if wildcard_match_impl(text, pattern, i, pi + 1) {
374 return true;
375 }
376 }
377 false
378 } else if ti < text.len() && (pattern[pi] == '?' || pattern[pi] == text[ti]) {
379 wildcard_match_impl(text, pattern, ti + 1, pi + 1)
381 } else {
382 false
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_fact_value_types() {
392 let s = FactValue::String("hello".to_string());
393 let i = FactValue::Integer(42);
394 let f = FactValue::Float(std::f64::consts::PI);
395 let b = FactValue::Boolean(true);
396
397 assert_eq!(s.as_string(), "hello");
398 assert_eq!(i.as_integer(), Some(42));
399 assert_eq!(f.as_float(), Some(std::f64::consts::PI));
400 assert_eq!(b.as_boolean(), Some(true));
401 }
402
403 #[test]
404 fn test_comparisons() {
405 let a = FactValue::Integer(10);
406 let b = FactValue::Integer(20);
407
408 assert!(a.compare("<", &b));
409 assert!(b.compare(">", &a));
410 assert!(a.compare("<=", &a));
411 assert!(a.compare("!=", &b));
412 }
413
414 #[test]
415 fn test_string_operations() {
416 let text = FactValue::String("hello world".to_string());
417 let pattern = FactValue::String("world".to_string());
418 let prefix = FactValue::String("hello".to_string());
419
420 assert!(text.compare("contains", &pattern));
421 assert!(text.compare("startsWith", &prefix));
422 }
423
424 #[test]
425 fn test_wildcard_matching() {
426 let text = FactValue::String("hello world".to_string());
427
428 assert!(text.compare("matches", &FactValue::String("hello*".to_string())));
429 assert!(text.compare("matches", &FactValue::String("*world".to_string())));
430 assert!(text.compare("matches", &FactValue::String("hello?world".to_string())));
431 assert!(!text.compare("matches", &FactValue::String("hello?earth".to_string())));
432 }
433
434 #[test]
435 fn test_array_operations() {
436 let arr = FactValue::Array(vec![
437 FactValue::Integer(1),
438 FactValue::Integer(2),
439 FactValue::Integer(3),
440 ]);
441
442 let val = FactValue::Integer(2);
443 assert!(val.compare("in", &arr));
444
445 let val2 = FactValue::Integer(5);
446 assert!(!val2.compare("in", &arr));
447 }
448
449 #[test]
450 fn test_typed_facts() {
451 let mut facts = TypedFacts::new();
452 facts.set("age", 25i64);
453 facts.set("name", "John");
454 facts.set("score", 95.5);
455 facts.set("active", true);
456
457 assert_eq!(facts.get("age").unwrap().as_integer(), Some(25));
458 assert_eq!(facts.get("name").unwrap().as_string(), "John");
459 assert_eq!(facts.get("score").unwrap().as_float(), Some(95.5));
460 assert_eq!(facts.get("active").unwrap().as_boolean(), Some(true));
461 }
462
463 #[test]
464 fn test_evaluate_condition() {
465 let mut facts = TypedFacts::new();
466 facts.set("age", 25i64);
467 facts.set("name", "John Smith");
468
469 assert!(facts.evaluate_condition("age", ">", &FactValue::Integer(18)));
470 assert!(facts.evaluate_condition("age", "<=", &FactValue::Integer(30)));
471 assert!(facts.evaluate_condition(
472 "name",
473 "contains",
474 &FactValue::String("Smith".to_string())
475 ));
476 assert!(facts.evaluate_condition(
477 "name",
478 "startsWith",
479 &FactValue::String("John".to_string())
480 ));
481 }
482}