rust_rule_engine/engine/
knowledge_base.rs

1use crate::engine::rule::Rule;
2use crate::errors::{Result, RuleEngineError};
3use crate::parser::grl::GRLParser;
4use crate::types::Value;
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8/// Knowledge Base - manages collections of rules and facts
9/// Similar to Grule's KnowledgeBase concept
10#[derive(Debug)]
11pub struct KnowledgeBase {
12    name: String,
13    rules: Arc<RwLock<Vec<Rule>>>,
14    rule_index: Arc<RwLock<HashMap<String, usize>>>,
15    version: Arc<RwLock<u64>>,
16}
17
18impl KnowledgeBase {
19    /// Create a new knowledge base
20    pub fn new(name: &str) -> Self {
21        Self {
22            name: name.to_string(),
23            rules: Arc::new(RwLock::new(Vec::new())),
24            rule_index: Arc::new(RwLock::new(HashMap::new())),
25            version: Arc::new(RwLock::new(0)),
26        }
27    }
28
29    /// Get the knowledge base name
30    pub fn name(&self) -> &str {
31        &self.name
32    }
33
34    /// Get the current version of the knowledge base
35    pub fn version(&self) -> u64 {
36        *self.version.read().unwrap()
37    }
38
39    /// Add a rule to the knowledge base
40    pub fn add_rule(&self, rule: Rule) -> Result<()> {
41        let mut rules = self.rules.write().unwrap();
42        let mut index = self.rule_index.write().unwrap();
43        let mut version = self.version.write().unwrap();
44
45        // Check for duplicate rule names
46        if index.contains_key(&rule.name) {
47            return Err(RuleEngineError::ParseError {
48                message: format!("Rule '{}' already exists", rule.name),
49            });
50        }
51
52        let rule_position = rules.len();
53        index.insert(rule.name.clone(), rule_position);
54        rules.push(rule);
55
56        // Sort rules by priority (salience)
57        rules.sort_by(|a, b| b.salience.cmp(&a.salience));
58
59        // Rebuild index after sorting
60        index.clear();
61        for (pos, rule) in rules.iter().enumerate() {
62            index.insert(rule.name.clone(), pos);
63        }
64
65        *version += 1;
66        Ok(())
67    }
68
69    /// Add multiple rules from GRL text
70    pub fn add_rules_from_grl(&self, grl_text: &str) -> Result<usize> {
71        let rules = GRLParser::parse_rules(grl_text)?;
72        let count = rules.len();
73
74        for rule in rules {
75            self.add_rule(rule)?;
76        }
77
78        Ok(count)
79    }
80
81    /// Remove a rule by name
82    pub fn remove_rule(&self, rule_name: &str) -> Result<bool> {
83        let mut rules = self.rules.write().unwrap();
84        let mut index = self.rule_index.write().unwrap();
85        let mut version = self.version.write().unwrap();
86
87        if let Some(&position) = index.get(rule_name) {
88            rules.remove(position);
89
90            // Rebuild index
91            index.clear();
92            for (pos, rule) in rules.iter().enumerate() {
93                index.insert(rule.name.clone(), pos);
94            }
95
96            *version += 1;
97            Ok(true)
98        } else {
99            Ok(false)
100        }
101    }
102
103    /// Get a rule by name
104    pub fn get_rule(&self, rule_name: &str) -> Option<Rule> {
105        let rules = self.rules.read().unwrap();
106        let index = self.rule_index.read().unwrap();
107
108        if let Some(&position) = index.get(rule_name) {
109            rules.get(position).cloned()
110        } else {
111            None
112        }
113    }
114
115    /// Get all rules
116    pub fn get_rules(&self) -> Vec<Rule> {
117        let rules = self.rules.read().unwrap();
118        rules.clone()
119    }
120
121    /// Get all rule names
122    pub fn get_rule_names(&self) -> Vec<String> {
123        let index = self.rule_index.read().unwrap();
124        index.keys().cloned().collect()
125    }
126
127    /// Get rule count
128    pub fn rule_count(&self) -> usize {
129        let rules = self.rules.read().unwrap();
130        rules.len()
131    }
132
133    /// Enable or disable a rule
134    pub fn set_rule_enabled(&self, rule_name: &str, enabled: bool) -> Result<bool> {
135        let mut rules = self.rules.write().unwrap();
136        let index = self.rule_index.read().unwrap();
137        let mut version = self.version.write().unwrap();
138
139        if let Some(&position) = index.get(rule_name) {
140            if let Some(rule) = rules.get_mut(position) {
141                rule.enabled = enabled;
142                *version += 1;
143                Ok(true)
144            } else {
145                Ok(false)
146            }
147        } else {
148            Ok(false)
149        }
150    }
151
152    /// Clear all rules
153    pub fn clear(&self) {
154        let mut rules = self.rules.write().unwrap();
155        let mut index = self.rule_index.write().unwrap();
156        let mut version = self.version.write().unwrap();
157
158        rules.clear();
159        index.clear();
160        *version += 1;
161    }
162
163    /// Get a snapshot of all rules (for execution)
164    pub fn get_rules_snapshot(&self) -> Vec<Rule> {
165        let rules = self.rules.read().unwrap();
166        rules.clone()
167    }
168
169    /// Get knowledge base statistics
170    pub fn get_statistics(&self) -> KnowledgeBaseStats {
171        let rules = self.rules.read().unwrap();
172
173        let enabled_count = rules.iter().filter(|r| r.enabled).count();
174        let disabled_count = rules.len() - enabled_count;
175
176        let mut priority_distribution = HashMap::new();
177        for rule in rules.iter() {
178            *priority_distribution.entry(rule.salience).or_insert(0) += 1;
179        }
180
181        KnowledgeBaseStats {
182            name: self.name.clone(),
183            version: self.version(),
184            total_rules: rules.len(),
185            enabled_rules: enabled_count,
186            disabled_rules: disabled_count,
187            priority_distribution,
188        }
189    }
190
191    /// Export rules to GRL format
192    pub fn export_to_grl(&self) -> String {
193        let rules = self.rules.read().unwrap();
194        let mut grl_output = String::new();
195
196        grl_output.push_str(&format!("// Knowledge Base: {}\n", self.name));
197        grl_output.push_str(&format!("// Version: {}\n", self.version()));
198        grl_output.push_str(&format!("// Rules: {}\n\n", rules.len()));
199
200        for rule in rules.iter() {
201            grl_output.push_str(&rule.to_grl());
202            grl_output.push_str("\n\n");
203        }
204
205        grl_output
206    }
207}
208
209impl Clone for KnowledgeBase {
210    fn clone(&self) -> Self {
211        let rules = self.rules.read().unwrap();
212        let new_kb = KnowledgeBase::new(&self.name);
213
214        for rule in rules.iter() {
215            let _ = new_kb.add_rule(rule.clone());
216        }
217
218        new_kb
219    }
220}
221
222/// Statistics about a Knowledge Base
223#[derive(Debug, Clone)]
224pub struct KnowledgeBaseStats {
225    /// The name of the knowledge base
226    pub name: String,
227    /// The version number of the knowledge base
228    pub version: u64,
229    /// Total number of rules in the knowledge base
230    pub total_rules: usize,
231    /// Number of enabled rules
232    pub enabled_rules: usize,
233    /// Number of disabled rules
234    pub disabled_rules: usize,
235    /// Distribution of rules by priority/salience
236    pub priority_distribution: HashMap<i32, usize>,
237}
238
239impl std::fmt::Display for KnowledgeBaseStats {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        writeln!(f, "Knowledge Base: {}", self.name)?;
242        writeln!(f, "Version: {}", self.version)?;
243        writeln!(f, "Total Rules: {}", self.total_rules)?;
244        writeln!(f, "Enabled Rules: {}", self.enabled_rules)?;
245        writeln!(f, "Disabled Rules: {}", self.disabled_rules)?;
246        writeln!(f, "Priority Distribution:")?;
247
248        let mut priorities: Vec<_> = self.priority_distribution.iter().collect();
249        priorities.sort_by(|a, b| b.0.cmp(a.0));
250
251        for (priority, count) in priorities {
252            writeln!(f, "  Priority {}: {} rules", priority, count)?;
253        }
254
255        Ok(())
256    }
257}
258
259/// Extension trait to add GRL export functionality to Rule
260trait RuleGRLExport {
261    fn to_grl(&self) -> String;
262}
263
264impl RuleGRLExport for Rule {
265    fn to_grl(&self) -> String {
266        let mut grl = String::new();
267
268        // Rule declaration
269        grl.push_str(&format!("rule {}", self.name));
270
271        if let Some(ref description) = self.description {
272            grl.push_str(&format!(" \"{}\"", description));
273        }
274
275        if self.salience != 0 {
276            grl.push_str(&format!(" salience {}", self.salience));
277        }
278
279        grl.push_str(" {\n");
280
281        // When clause
282        grl.push_str("    when\n");
283        grl.push_str(&format!("        {}\n", self.conditions.to_grl()));
284
285        // Then clause
286        grl.push_str("    then\n");
287        for action in &self.actions {
288            grl.push_str(&format!("        {};\n", action.to_grl()));
289        }
290
291        grl.push('}');
292
293        if !self.enabled {
294            grl = format!("// DISABLED\n{}", grl);
295        }
296
297        grl
298    }
299}
300
301/// Extension trait for ConditionGroup GRL export
302trait ConditionGroupGRLExport {
303    fn to_grl(&self) -> String;
304}
305
306impl ConditionGroupGRLExport for crate::engine::rule::ConditionGroup {
307    fn to_grl(&self) -> String {
308        match self {
309            crate::engine::rule::ConditionGroup::Single(condition) => {
310                format!(
311                    "{} {} {}",
312                    condition.field,
313                    condition.operator.to_grl(),
314                    condition.value.to_grl()
315                )
316            }
317            crate::engine::rule::ConditionGroup::Compound {
318                left,
319                operator,
320                right,
321            } => {
322                let op_str = match operator {
323                    crate::types::LogicalOperator::And => "&&",
324                    crate::types::LogicalOperator::Or => "||",
325                    crate::types::LogicalOperator::Not => "!",
326                };
327                format!("{} {} {}", left.to_grl(), op_str, right.to_grl())
328            }
329            crate::engine::rule::ConditionGroup::Not(condition) => {
330                format!("!{}", condition.to_grl())
331            }
332        }
333    }
334}
335
336/// Extension trait for Operator GRL export
337trait OperatorGRLExport {
338    fn to_grl(&self) -> &'static str;
339}
340
341impl OperatorGRLExport for crate::types::Operator {
342    fn to_grl(&self) -> &'static str {
343        match self {
344            crate::types::Operator::Equal => "==",
345            crate::types::Operator::NotEqual => "!=",
346            crate::types::Operator::GreaterThan => ">",
347            crate::types::Operator::GreaterThanOrEqual => ">=",
348            crate::types::Operator::LessThan => "<",
349            crate::types::Operator::LessThanOrEqual => "<=",
350            crate::types::Operator::Contains => "contains",
351            crate::types::Operator::NotContains => "not_contains",
352            crate::types::Operator::StartsWith => "starts_with",
353            crate::types::Operator::EndsWith => "ends_with",
354            crate::types::Operator::Matches => "matches",
355        }
356    }
357}
358
359/// Extension trait for Value GRL export
360trait ValueGRLExport {
361    fn to_grl(&self) -> String;
362}
363
364impl ValueGRLExport for Value {
365    fn to_grl(&self) -> String {
366        match self {
367            Value::String(s) => format!("\"{}\"", s),
368            Value::Number(n) => n.to_string(),
369            Value::Integer(i) => i.to_string(),
370            Value::Boolean(b) => b.to_string(),
371            Value::Null => "null".to_string(),
372            Value::Array(_) => "[array]".to_string(),
373            Value::Object(_) => "{object}".to_string(),
374        }
375    }
376}
377
378/// Extension trait for ActionType GRL export
379trait ActionTypeGRLExport {
380    fn to_grl(&self) -> String;
381}
382
383impl ActionTypeGRLExport for crate::types::ActionType {
384    fn to_grl(&self) -> String {
385        match self {
386            crate::types::ActionType::Set { field, value } => {
387                format!("{} = {}", field, value.to_grl())
388            }
389            crate::types::ActionType::Log { message } => {
390                format!("Log(\"{}\")", message)
391            }
392            crate::types::ActionType::MethodCall {
393                object,
394                method,
395                args,
396            } => {
397                let args_str = args
398                    .iter()
399                    .map(|arg| arg.to_grl())
400                    .collect::<Vec<_>>()
401                    .join(", ");
402                format!("{}.{}({})", object, method, args_str)
403            }
404            crate::types::ActionType::Update { object } => {
405                format!("update({})", object)
406            }
407            crate::types::ActionType::Call { function, args } => {
408                let args_str = args
409                    .iter()
410                    .map(|arg| arg.to_grl())
411                    .collect::<Vec<_>>()
412                    .join(", ");
413                format!("{}({})", function, args_str)
414            }
415            crate::types::ActionType::Custom { action_type, .. } => {
416                format!("Custom(\"{}\")", action_type)
417            }
418        }
419    }
420}