rust_rule_engine/engine/
knowledge_base.rs

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