use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum FactValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<FactValue>),
Null,
}
impl FactValue {
pub fn as_string(&self) -> String {
match self {
FactValue::String(s) => s.clone(),
FactValue::Integer(i) => i.to_string(),
FactValue::Float(f) => f.to_string(),
FactValue::Boolean(b) => b.to_string(),
FactValue::Array(arr) => format!("{:?}", arr),
FactValue::Null => "null".to_string(),
}
}
pub fn as_str(&self) -> std::borrow::Cow<'_, str> {
match self {
FactValue::String(s) => std::borrow::Cow::Borrowed(s),
FactValue::Integer(i) => std::borrow::Cow::Owned(i.to_string()),
FactValue::Float(f) => std::borrow::Cow::Owned(f.to_string()),
FactValue::Boolean(b) => std::borrow::Cow::Borrowed(if *b { "true" } else { "false" }),
FactValue::Array(arr) => std::borrow::Cow::Owned(format!("{:?}", arr)),
FactValue::Null => std::borrow::Cow::Borrowed("null"),
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
FactValue::Integer(i) => Some(*i),
FactValue::Float(f) => Some(*f as i64),
FactValue::String(s) => s.parse().ok(),
FactValue::Boolean(b) => Some(if *b { 1 } else { 0 }),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
FactValue::Float(f) => Some(*f),
FactValue::Integer(i) => Some(*i as f64),
FactValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
FactValue::Float(f) => Some(*f),
FactValue::Integer(i) => Some(*i as f64),
FactValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
FactValue::Boolean(b) => Some(*b),
FactValue::Integer(i) => Some(*i != 0),
FactValue::String(s) => match s.to_lowercase().as_str() {
"true" | "yes" | "1" => Some(true),
"false" | "no" | "0" => Some(false),
_ => None,
},
FactValue::Null => Some(false),
_ => None,
}
}
pub fn is_null(&self) -> bool {
matches!(self, FactValue::Null)
}
pub fn compare(&self, operator: &str, other: &FactValue) -> bool {
match operator {
"==" => self == other,
"!=" => self != other,
">" => self.compare_gt(other),
"<" => self.compare_lt(other),
">=" => self.compare_gte(other),
"<=" => self.compare_lte(other),
"contains" => self.contains(other),
"startsWith" => self.starts_with(other),
"endsWith" => self.ends_with(other),
"matches" => self.matches_pattern(other),
"in" => self.in_array(other),
_ => false,
}
}
fn compare_gt(&self, other: &FactValue) -> bool {
match (self.as_float(), other.as_float()) {
(Some(a), Some(b)) => a > b,
_ => false,
}
}
fn compare_lt(&self, other: &FactValue) -> bool {
match (self.as_float(), other.as_float()) {
(Some(a), Some(b)) => a < b,
_ => false,
}
}
fn compare_gte(&self, other: &FactValue) -> bool {
match (self.as_float(), other.as_float()) {
(Some(a), Some(b)) => a >= b,
_ => self == other,
}
}
fn compare_lte(&self, other: &FactValue) -> bool {
match (self.as_float(), other.as_float()) {
(Some(a), Some(b)) => a <= b,
_ => self == other,
}
}
fn contains(&self, other: &FactValue) -> bool {
match (self, other) {
(FactValue::String(s), FactValue::String(pattern)) => s.contains(pattern),
(FactValue::Array(arr), val) => arr.contains(val),
_ => false,
}
}
fn starts_with(&self, other: &FactValue) -> bool {
match (self, other) {
(FactValue::String(s), FactValue::String(prefix)) => s.starts_with(prefix),
_ => false,
}
}
fn ends_with(&self, other: &FactValue) -> bool {
match (self, other) {
(FactValue::String(s), FactValue::String(suffix)) => s.ends_with(suffix),
_ => false,
}
}
fn matches_pattern(&self, other: &FactValue) -> bool {
match (self, other) {
(FactValue::String(s), FactValue::String(pattern)) => {
wildcard_match(s, pattern)
}
_ => false,
}
}
fn in_array(&self, other: &FactValue) -> bool {
match other {
FactValue::Array(arr) => arr.contains(self),
_ => false,
}
}
}
impl fmt::Display for FactValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<String> for FactValue {
fn from(s: String) -> Self {
FactValue::String(s)
}
}
impl From<&str> for FactValue {
fn from(s: &str) -> Self {
FactValue::String(s.to_string())
}
}
impl From<i64> for FactValue {
fn from(i: i64) -> Self {
FactValue::Integer(i)
}
}
impl From<i32> for FactValue {
fn from(i: i32) -> Self {
FactValue::Integer(i as i64)
}
}
impl From<f64> for FactValue {
fn from(f: f64) -> Self {
FactValue::Float(f)
}
}
impl From<bool> for FactValue {
fn from(b: bool) -> Self {
FactValue::Boolean(b)
}
}
impl From<Vec<FactValue>> for FactValue {
fn from(arr: Vec<FactValue>) -> Self {
FactValue::Array(arr)
}
}
impl From<crate::types::Value> for FactValue {
fn from(value: crate::types::Value) -> Self {
match value {
crate::types::Value::String(s) => FactValue::String(s),
crate::types::Value::Number(n) => FactValue::Float(n),
crate::types::Value::Integer(i) => FactValue::Integer(i),
crate::types::Value::Boolean(b) => FactValue::Boolean(b),
crate::types::Value::Array(arr) => {
FactValue::Array(arr.into_iter().map(|v| v.into()).collect())
}
crate::types::Value::Object(obj) => {
FactValue::String(format!("{:?}", obj))
}
crate::types::Value::Null => FactValue::Null,
crate::types::Value::Expression(expr) => FactValue::String(expr),
}
}
}
#[derive(Debug, Clone)]
pub struct TypedFacts {
data: HashMap<String, FactValue>,
pub(crate) fact_handles: HashMap<String, super::FactHandle>,
}
impl TypedFacts {
pub fn new() -> Self {
Self {
data: HashMap::new(),
fact_handles: HashMap::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: HashMap::with_capacity(capacity),
fact_handles: HashMap::with_capacity(capacity),
}
}
pub fn set_fact_handle(&mut self, fact_type: String, handle: super::FactHandle) {
self.fact_handles.insert(fact_type, handle);
}
pub fn get_fact_handle(&self, fact_type: &str) -> Option<super::FactHandle> {
self.fact_handles.get(fact_type).copied()
}
pub fn set<K: Into<String>, V: Into<FactValue>>(&mut self, key: K, value: V) {
self.data.insert(key.into(), value.into());
}
pub fn get(&self, key: &str) -> Option<&FactValue> {
self.data.get(key)
}
pub fn remove(&mut self, key: &str) -> Option<FactValue> {
self.data.remove(key)
}
pub fn contains(&self, key: &str) -> bool {
self.data.contains_key(key)
}
pub fn get_all(&self) -> &HashMap<String, FactValue> {
&self.data
}
pub fn clear(&mut self) {
self.data.clear();
}
pub fn to_string_map(&self) -> HashMap<String, String> {
self.data
.iter()
.map(|(k, v)| (k.clone(), v.as_string()))
.collect()
}
pub fn from_string_map(map: &HashMap<String, String>) -> Self {
let mut facts = Self::new();
for (k, v) in map {
if let Ok(i) = v.parse::<i64>() {
facts.set(k.clone(), i);
} else if let Ok(f) = v.parse::<f64>() {
facts.set(k.clone(), f);
} else if let Ok(b) = v.parse::<bool>() {
facts.set(k.clone(), b);
} else {
facts.set(k.clone(), v.clone());
}
}
facts
}
pub fn evaluate_condition(&self, field: &str, operator: &str, value: &FactValue) -> bool {
if let Some(fact_value) = self.get(field) {
fact_value.compare(operator, value)
} else {
false
}
}
}
impl Default for TypedFacts {
fn default() -> Self {
Self::new()
}
}
fn wildcard_match(text: &str, pattern: &str) -> bool {
let text_chars: Vec<char> = text.chars().collect();
let pattern_chars: Vec<char> = pattern.chars().collect();
wildcard_match_impl(&text_chars, &pattern_chars, 0, 0)
}
fn wildcard_match_impl(text: &[char], pattern: &[char], ti: usize, pi: usize) -> bool {
if pi == pattern.len() {
return ti == text.len();
}
if pattern[pi] == '*' {
for i in ti..=text.len() {
if wildcard_match_impl(text, pattern, i, pi + 1) {
return true;
}
}
false
} else if ti < text.len() && (pattern[pi] == '?' || pattern[pi] == text[ti]) {
wildcard_match_impl(text, pattern, ti + 1, pi + 1)
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fact_value_types() {
let s = FactValue::String("hello".to_string());
let i = FactValue::Integer(42);
let f = FactValue::Float(std::f64::consts::PI);
let b = FactValue::Boolean(true);
assert_eq!(s.as_string(), "hello");
assert_eq!(i.as_integer(), Some(42));
assert_eq!(f.as_float(), Some(std::f64::consts::PI));
assert_eq!(b.as_boolean(), Some(true));
}
#[test]
fn test_comparisons() {
let a = FactValue::Integer(10);
let b = FactValue::Integer(20);
assert!(a.compare("<", &b));
assert!(b.compare(">", &a));
assert!(a.compare("<=", &a));
assert!(a.compare("!=", &b));
}
#[test]
fn test_string_operations() {
let text = FactValue::String("hello world".to_string());
let pattern = FactValue::String("world".to_string());
let prefix = FactValue::String("hello".to_string());
assert!(text.compare("contains", &pattern));
assert!(text.compare("startsWith", &prefix));
}
#[test]
fn test_wildcard_matching() {
let text = FactValue::String("hello world".to_string());
assert!(text.compare("matches", &FactValue::String("hello*".to_string())));
assert!(text.compare("matches", &FactValue::String("*world".to_string())));
assert!(text.compare("matches", &FactValue::String("hello?world".to_string())));
assert!(!text.compare("matches", &FactValue::String("hello?earth".to_string())));
}
#[test]
fn test_array_operations() {
let arr = FactValue::Array(vec![
FactValue::Integer(1),
FactValue::Integer(2),
FactValue::Integer(3),
]);
let val = FactValue::Integer(2);
assert!(val.compare("in", &arr));
let val2 = FactValue::Integer(5);
assert!(!val2.compare("in", &arr));
}
#[test]
fn test_typed_facts() {
let mut facts = TypedFacts::new();
facts.set("age", 25i64);
facts.set("name", "John");
facts.set("score", 95.5);
facts.set("active", true);
assert_eq!(facts.get("age").unwrap().as_integer(), Some(25));
assert_eq!(facts.get("name").unwrap().as_string(), "John");
assert_eq!(facts.get("score").unwrap().as_float(), Some(95.5));
assert_eq!(facts.get("active").unwrap().as_boolean(), Some(true));
}
#[test]
fn test_evaluate_condition() {
let mut facts = TypedFacts::new();
facts.set("age", 25i64);
facts.set("name", "John Smith");
assert!(facts.evaluate_condition("age", ">", &FactValue::Integer(18)));
assert!(facts.evaluate_condition("age", "<=", &FactValue::Integer(30)));
assert!(facts.evaluate_condition(
"name",
"contains",
&FactValue::String("Smith".to_string())
));
assert!(facts.evaluate_condition(
"name",
"startsWith",
&FactValue::String("John".to_string())
));
}
}