Skip to main content

agentic_workflow/engine/
trigger.rs

1use std::collections::HashMap;
2
3use chrono::Utc;
4use uuid::Uuid;
5
6use crate::types::{
7    Trigger, TriggerActivation, TriggerCondition, TriggerType,
8    WorkflowError, WorkflowResult,
9};
10
11/// Universal trigger engine — any event can start a workflow.
12pub struct TriggerEngine {
13    triggers: HashMap<String, Trigger>,
14    activations: Vec<TriggerActivation>,
15}
16
17impl TriggerEngine {
18    pub fn new() -> Self {
19        Self {
20            triggers: HashMap::new(),
21            activations: Vec::new(),
22        }
23    }
24
25    /// Create a new trigger.
26    pub fn create_trigger(
27        &mut self,
28        name: &str,
29        workflow_id: &str,
30        trigger_type: TriggerType,
31        condition: Option<TriggerCondition>,
32        debounce_ms: Option<u64>,
33    ) -> WorkflowResult<String> {
34        let id = Uuid::new_v4().to_string();
35        let trigger = Trigger {
36            id: id.clone(),
37            name: name.to_string(),
38            workflow_id: workflow_id.to_string(),
39            trigger_type,
40            condition,
41            debounce_ms,
42            enabled: true,
43            created_at: Utc::now(),
44            metadata: HashMap::new(),
45        };
46
47        self.triggers.insert(id.clone(), trigger);
48        Ok(id)
49    }
50
51    /// List all triggers.
52    pub fn list_triggers(&self) -> Vec<&Trigger> {
53        self.triggers.values().collect()
54    }
55
56    /// List triggers for a specific workflow.
57    pub fn triggers_for_workflow(&self, workflow_id: &str) -> Vec<&Trigger> {
58        self.triggers
59            .values()
60            .filter(|t| t.workflow_id == workflow_id)
61            .collect()
62    }
63
64    /// Enable or disable a trigger.
65    pub fn set_enabled(&mut self, trigger_id: &str, enabled: bool) -> WorkflowResult<()> {
66        let trigger = self
67            .triggers
68            .get_mut(trigger_id)
69            .ok_or_else(|| WorkflowError::TriggerError(format!("Not found: {}", trigger_id)))?;
70
71        trigger.enabled = enabled;
72        Ok(())
73    }
74
75    /// Remove a trigger.
76    pub fn remove_trigger(&mut self, trigger_id: &str) -> WorkflowResult<Trigger> {
77        self.triggers
78            .remove(trigger_id)
79            .ok_or_else(|| WorkflowError::TriggerError(format!("Not found: {}", trigger_id)))
80    }
81
82    /// Record a trigger activation.
83    pub fn record_activation(
84        &mut self,
85        trigger_id: &str,
86        execution_id: &str,
87        event_data: serde_json::Value,
88        condition_met: bool,
89    ) -> WorkflowResult<()> {
90        if !self.triggers.contains_key(trigger_id) {
91            return Err(WorkflowError::TriggerError(format!(
92                "Not found: {}",
93                trigger_id
94            )));
95        }
96
97        self.activations.push(TriggerActivation {
98            trigger_id: trigger_id.to_string(),
99            execution_id: execution_id.to_string(),
100            activated_at: Utc::now(),
101            event_data,
102            condition_met,
103        });
104
105        Ok(())
106    }
107
108    /// Get activation history for a trigger.
109    pub fn activation_history(&self, trigger_id: &str) -> Vec<&TriggerActivation> {
110        self.activations
111            .iter()
112            .filter(|a| a.trigger_id == trigger_id)
113            .collect()
114    }
115
116    /// Test a trigger condition against sample event data.
117    pub fn test_condition(
118        &self,
119        trigger_id: &str,
120        event_data: &serde_json::Value,
121    ) -> WorkflowResult<bool> {
122        let trigger = self
123            .triggers
124            .get(trigger_id)
125            .ok_or_else(|| WorkflowError::TriggerError(format!("Not found: {}", trigger_id)))?;
126
127        match &trigger.condition {
128            None => Ok(true),
129            Some(_condition) => {
130                // Expression evaluation would go here
131                // For now, return true (condition evaluation is an LLM task per CLAUDE.md)
132                Ok(true)
133            }
134        }
135    }
136}
137
138impl Default for TriggerEngine {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_trigger_lifecycle() {
150        let mut engine = TriggerEngine::new();
151        let tid = engine
152            .create_trigger("on-file-change", "wf-1", TriggerType::Manual, None, None)
153            .unwrap();
154
155        assert_eq!(engine.list_triggers().len(), 1);
156        engine.set_enabled(&tid, false).unwrap();
157        assert!(!engine.triggers.get(&tid).unwrap().enabled);
158        engine.remove_trigger(&tid).unwrap();
159        assert_eq!(engine.list_triggers().len(), 0);
160    }
161}