daa_rules/
lib.rs

1//! # DAA Rules
2//!
3//! A comprehensive rules engine for the Decentralized Autonomous Agents (DAA) system.
4//! Provides policy enforcement, decision automation, and governance capabilities.
5
6use std::collections::HashMap;
7use std::fmt;
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11use uuid::Uuid;
12use chrono::{DateTime, Utc};
13use regex::Regex;
14use async_trait::async_trait;
15
16pub mod engine;
17pub mod conditions;
18pub mod actions;
19pub mod context;
20pub mod storage;
21
22#[cfg(feature = "scripting")]
23pub mod scripting;
24
25#[cfg(feature = "database")]
26pub mod database;
27
28/// Rules engine error types
29#[derive(Error, Debug)]
30pub enum RulesError {
31    #[error("Rule not found: {0}")]
32    RuleNotFound(String),
33    
34    #[error("Invalid rule definition: {0}")]
35    InvalidRule(String),
36    
37    #[error("Condition evaluation failed: {0}")]
38    ConditionEvaluation(String),
39    
40    #[error("Action execution failed: {0}")]
41    ActionExecution(String),
42    
43    #[error("Context error: {0}")]
44    Context(String),
45    
46    #[error("Storage error: {0}")]
47    Storage(String),
48    
49    #[error("Scripting error: {0}")]
50    Scripting(String),
51    
52    #[error("Validation error: {0}")]
53    Validation(String),
54    
55    #[error("Parsing error: {0}")]
56    Parsing(String),
57}
58
59pub type Result<T> = std::result::Result<T, RulesError>;
60
61/// Rule definition
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct Rule {
64    /// Unique rule identifier
65    pub id: String,
66    
67    /// Human-readable rule name
68    pub name: String,
69    
70    /// Rule description
71    pub description: String,
72    
73    /// Rule conditions that must be met
74    pub conditions: Vec<RuleCondition>,
75    
76    /// Actions to execute when conditions are met
77    pub actions: Vec<RuleAction>,
78    
79    /// Rule priority (higher number = higher priority)
80    pub priority: u32,
81    
82    /// Whether the rule is enabled
83    pub enabled: bool,
84    
85    /// Rule creation timestamp
86    pub created_at: DateTime<Utc>,
87    
88    /// Rule last modified timestamp
89    pub updated_at: DateTime<Utc>,
90    
91    /// Rule metadata
92    pub metadata: HashMap<String, String>,
93}
94
95impl Rule {
96    /// Create a new rule
97    pub fn new(
98        id: String,
99        name: String,
100        conditions: Vec<RuleCondition>,
101        actions: Vec<RuleAction>,
102    ) -> Self {
103        let now = Utc::now();
104        
105        Self {
106            id,
107            name,
108            description: String::new(),
109            conditions,
110            actions,
111            priority: 0,
112            enabled: true,
113            created_at: now,
114            updated_at: now,
115            metadata: HashMap::new(),
116        }
117    }
118
119    /// Create a new rule with generated ID
120    pub fn new_with_generated_id(
121        name: String,
122        conditions: Vec<RuleCondition>,
123        actions: Vec<RuleAction>,
124    ) -> Self {
125        Self::new(Uuid::new_v4().to_string(), name, conditions, actions)
126    }
127
128    /// Check if rule is valid
129    pub fn is_valid(&self) -> Result<()> {
130        if self.id.is_empty() {
131            return Err(RulesError::InvalidRule("Rule ID cannot be empty".to_string()));
132        }
133
134        if self.name.is_empty() {
135            return Err(RulesError::InvalidRule("Rule name cannot be empty".to_string()));
136        }
137
138        if self.conditions.is_empty() {
139            return Err(RulesError::InvalidRule("Rule must have at least one condition".to_string()));
140        }
141
142        if self.actions.is_empty() {
143            return Err(RulesError::InvalidRule("Rule must have at least one action".to_string()));
144        }
145
146        // Validate conditions
147        for condition in &self.conditions {
148            condition.validate()?;
149        }
150
151        // Validate actions
152        for action in &self.actions {
153            action.validate()?;
154        }
155
156        Ok(())
157    }
158}
159
160/// Rule condition definition
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub enum RuleCondition {
163    /// Simple equality check
164    Equals {
165        field: String,
166        value: String,
167    },
168    
169    /// Inequality check
170    NotEquals {
171        field: String,
172        value: String,
173    },
174    
175    /// Greater than comparison
176    GreaterThan {
177        field: String,
178        value: f64,
179    },
180    
181    /// Less than comparison
182    LessThan {
183        field: String,
184        value: f64,
185    },
186    
187    /// Pattern matching with regex
188    Matches {
189        field: String,
190        pattern: String,
191    },
192    
193    /// Field existence check
194    Exists {
195        field: String,
196    },
197    
198    /// Value in list check
199    In {
200        field: String,
201        values: Vec<String>,
202    },
203    
204    /// Time-based condition
205    TimeCondition {
206        field: String,
207        operator: TimeOperator,
208        value: DateTime<Utc>,
209    },
210    
211    /// Complex logical condition
212    And {
213        conditions: Vec<RuleCondition>,
214    },
215    
216    /// Complex logical condition
217    Or {
218        conditions: Vec<RuleCondition>,
219    },
220    
221    /// Negation condition
222    Not {
223        condition: Box<RuleCondition>,
224    },
225    
226    /// Custom condition with parameters
227    Custom {
228        condition_type: String,
229        parameters: HashMap<String, String>,
230    },
231}
232
233impl RuleCondition {
234    /// Validate the condition
235    pub fn validate(&self) -> Result<()> {
236        match self {
237            RuleCondition::Matches { pattern, .. } => {
238                Regex::new(pattern)
239                    .map_err(|e| RulesError::InvalidRule(format!("Invalid regex pattern: {}", e)))?;
240            }
241            RuleCondition::And { conditions } | RuleCondition::Or { conditions } => {
242                if conditions.is_empty() {
243                    return Err(RulesError::InvalidRule("Logical conditions must have at least one sub-condition".to_string()));
244                }
245                for condition in conditions {
246                    condition.validate()?;
247                }
248            }
249            RuleCondition::Not { condition } => {
250                condition.validate()?;
251            }
252            _ => {} // Other conditions are always valid
253        }
254        Ok(())
255    }
256}
257
258/// Time comparison operators
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub enum TimeOperator {
261    Before,
262    After,
263    Between { end: DateTime<Utc> },
264}
265
266/// Rule action definition
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub enum RuleAction {
269    /// Set a field value
270    SetField {
271        field: String,
272        value: String,
273    },
274    
275    /// Log a message
276    Log {
277        level: LogLevel,
278        message: String,
279    },
280    
281    /// Send notification
282    Notify {
283        recipient: String,
284        message: String,
285        channel: NotificationChannel,
286    },
287    
288    /// Execute script
289    #[cfg(feature = "scripting")]
290    Script {
291        script_type: String,
292        script: String,
293    },
294    
295    /// Trigger external webhook
296    Webhook {
297        url: String,
298        method: String,
299        headers: HashMap<String, String>,
300        body: String,
301    },
302    
303    /// Modify context
304    ModifyContext {
305        modifications: HashMap<String, String>,
306    },
307    
308    /// Abort execution
309    Abort {
310        reason: String,
311    },
312    
313    /// Custom action with parameters
314    Custom {
315        action_type: String,
316        parameters: HashMap<String, String>,
317    },
318}
319
320impl RuleAction {
321    /// Validate the action
322    pub fn validate(&self) -> Result<()> {
323        match self {
324            RuleAction::Webhook { url, .. } => {
325                if url.is_empty() {
326                    return Err(RulesError::InvalidRule("Webhook URL cannot be empty".to_string()));
327                }
328            }
329            _ => {} // Other actions are always valid
330        }
331        Ok(())
332    }
333}
334
335/// Log levels for logging actions
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub enum LogLevel {
338    Trace,
339    Debug,
340    Info,
341    Warn,
342    Error,
343}
344
345/// Notification channels
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub enum NotificationChannel {
348    Email,
349    Slack,
350    Discord,
351    Webhook,
352    Internal,
353}
354
355/// Result of rule execution
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub enum RuleResult {
358    /// Rule conditions were met and actions executed successfully
359    Allow,
360    
361    /// Rule conditions were met but execution was denied
362    Deny(String),
363    
364    /// Rule execution resulted in modifications
365    Modified(HashMap<String, String>),
366    
367    /// Rule execution was skipped (conditions not met)
368    Skipped,
369    
370    /// Rule execution failed
371    Failed(String),
372}
373
374impl fmt::Display for RuleResult {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            RuleResult::Allow => write!(f, "Allow"),
378            RuleResult::Deny(reason) => write!(f, "Deny: {}", reason),
379            RuleResult::Modified(changes) => write!(f, "Modified: {:?}", changes),
380            RuleResult::Skipped => write!(f, "Skipped"),
381            RuleResult::Failed(error) => write!(f, "Failed: {}", error),
382        }
383    }
384}
385
386/// Execution context for rules
387pub use context::ExecutionContext;
388
389/// Rules engine
390pub use engine::RuleEngine;
391
392/// Re-export key types for convenience
393pub use conditions::ConditionEvaluator;
394pub use actions::ActionExecutor;
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399
400    #[test]
401    fn test_rule_creation() {
402        let rule = Rule::new_with_generated_id(
403            "Test Rule".to_string(),
404            vec![RuleCondition::Equals {
405                field: "status".to_string(),
406                value: "active".to_string(),
407            }],
408            vec![RuleAction::Log {
409                level: LogLevel::Info,
410                message: "Rule triggered".to_string(),
411            }],
412        );
413
414        assert!(!rule.id.is_empty());
415        assert_eq!(rule.name, "Test Rule");
416        assert!(rule.enabled);
417        assert_eq!(rule.conditions.len(), 1);
418        assert_eq!(rule.actions.len(), 1);
419    }
420
421    #[test]
422    fn test_rule_validation() {
423        let rule = Rule::new_with_generated_id(
424            "Valid Rule".to_string(),
425            vec![RuleCondition::Equals {
426                field: "test".to_string(),
427                value: "value".to_string(),
428            }],
429            vec![RuleAction::Log {
430                level: LogLevel::Info,
431                message: "test".to_string(),
432            }],
433        );
434
435        assert!(rule.is_valid().is_ok());
436    }
437
438    #[test]
439    fn test_invalid_rule_validation() {
440        let rule = Rule::new(
441            String::new(), // Empty ID should be invalid
442            "Invalid Rule".to_string(),
443            vec![RuleCondition::Equals {
444                field: "test".to_string(),
445                value: "value".to_string(),
446            }],
447            vec![RuleAction::Log {
448                level: LogLevel::Info,
449                message: "test".to_string(),
450            }],
451        );
452
453        assert!(rule.is_valid().is_err());
454    }
455
456    #[test]
457    fn test_condition_validation() {
458        let valid_condition = RuleCondition::Matches {
459            field: "email".to_string(),
460            pattern: r"^[^@]+@[^@]+\.[^@]+$".to_string(),
461        };
462        assert!(valid_condition.validate().is_ok());
463
464        let invalid_condition = RuleCondition::Matches {
465            field: "email".to_string(),
466            pattern: "[".to_string(), // Invalid regex
467        };
468        assert!(invalid_condition.validate().is_err());
469    }
470
471    #[test]
472    fn test_rule_result_display() {
473        assert_eq!(RuleResult::Allow.to_string(), "Allow");
474        assert_eq!(RuleResult::Deny("test".to_string()).to_string(), "Deny: test");
475        assert_eq!(RuleResult::Skipped.to_string(), "Skipped");
476    }
477}