rust_rule_engine/engine/
knowledge_base.rs

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