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