ricecoder_hooks/executor/
condition.rs

1//! Condition evaluation for hook execution
2
3use crate::error::{HooksError, Result};
4use crate::types::{Condition, EventContext};
5use tracing::{debug, warn};
6
7/// Evaluates conditions against event context
8///
9/// Conditions allow hooks to be executed conditionally based on event context values.
10/// For now, this is a placeholder implementation that always returns true.
11/// Future implementations can support more complex condition expressions.
12pub struct ConditionEvaluator;
13
14impl ConditionEvaluator {
15    /// Evaluate a condition against event context
16    ///
17    /// # Arguments
18    ///
19    /// * `condition` - The condition to evaluate
20    /// * `context` - The event context to evaluate against
21    ///
22    /// # Returns
23    ///
24    /// * `Ok(true)` - Condition is met, hook should execute
25    /// * `Ok(false)` - Condition is not met, hook should be skipped
26    /// * `Err` - Error evaluating condition
27    pub fn evaluate(condition: &Condition, context: &EventContext) -> Result<bool> {
28        debug!(
29            expression = %condition.expression,
30            context_keys = ?condition.context_keys,
31            "Evaluating condition"
32        );
33
34        // Verify that all required context keys are present
35        for key in &condition.context_keys {
36            if context.data.get(key).is_none() && context.metadata.get(key).is_none() {
37                warn!(
38                    key = %key,
39                    "Required context key not found for condition evaluation"
40                );
41                return Err(HooksError::InvalidConfiguration(format!(
42                    "Required context key '{}' not found",
43                    key
44                )));
45            }
46        }
47
48        // For now, always return true (conditions are evaluated but always pass)
49        // Future implementations can support:
50        // - Simple comparisons: file_path.ends_with('.rs')
51        // - Pattern matching: file_path matches '*.rs'
52        // - Logical operators: AND, OR, NOT
53        // - Nested conditions
54        Ok(true)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use serde_json::json;
62
63    fn create_test_context() -> EventContext {
64        EventContext {
65            data: json!({
66                "file_path": "/path/to/file.rs",
67                "size": 1024,
68            }),
69            metadata: json!({
70                "user": "alice",
71                "project": "my-project",
72            }),
73        }
74    }
75
76    #[test]
77    fn test_evaluate_condition_with_valid_context() {
78        let condition = Condition {
79            expression: "file_path.ends_with('.rs')".to_string(),
80            context_keys: vec!["file_path".to_string()],
81        };
82        let context = create_test_context();
83
84        let result = ConditionEvaluator::evaluate(&condition, &context).unwrap();
85
86        assert!(result);
87    }
88
89    #[test]
90    fn test_evaluate_condition_with_missing_context_key() {
91        let condition = Condition {
92            expression: "missing_key == 'value'".to_string(),
93            context_keys: vec!["missing_key".to_string()],
94        };
95        let context = create_test_context();
96
97        let result = ConditionEvaluator::evaluate(&condition, &context);
98
99        assert!(result.is_err());
100    }
101
102    #[test]
103    fn test_evaluate_condition_with_metadata_key() {
104        let condition = Condition {
105            expression: "user == 'alice'".to_string(),
106            context_keys: vec!["user".to_string()],
107        };
108        let context = create_test_context();
109
110        let result = ConditionEvaluator::evaluate(&condition, &context).unwrap();
111
112        assert!(result);
113    }
114
115    #[test]
116    fn test_evaluate_condition_with_multiple_keys() {
117        let condition = Condition {
118            expression: "file_path.ends_with('.rs') && user == 'alice'".to_string(),
119            context_keys: vec!["file_path".to_string(), "user".to_string()],
120        };
121        let context = create_test_context();
122
123        let result = ConditionEvaluator::evaluate(&condition, &context).unwrap();
124
125        assert!(result);
126    }
127}