xerv_core/flow/
trigger.rs

1//! Trigger definition from YAML.
2
3use crate::traits::{TriggerConfig, TriggerType};
4use serde::{Deserialize, Serialize};
5
6/// A trigger definition from YAML.
7///
8/// # Example
9///
10/// ```yaml
11/// triggers:
12///   - id: api_webhook
13///     type: webhook
14///     params:
15///       host: "0.0.0.0"
16///       port: 8080
17///       path: "/api/orders"
18///       method: "POST"
19///
20///   - id: daily_sync
21///     type: cron
22///     params:
23///       schedule: "0 0 * * * *"  # Every hour
24///
25///   - id: file_watcher
26///     type: filesystem
27///     params:
28///       path: "/data/uploads"
29///       recursive: true
30///       events: ["create", "modify"]
31/// ```
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct TriggerDefinition {
34    /// Unique identifier for this trigger.
35    pub id: String,
36
37    /// Trigger type (webhook, cron, filesystem, queue, memory, manual).
38    #[serde(rename = "type")]
39    pub trigger_type: String,
40
41    /// Type-specific parameters.
42    #[serde(default)]
43    pub params: serde_yaml::Value,
44
45    /// Whether the trigger is enabled.
46    #[serde(default = "default_enabled")]
47    pub enabled: bool,
48
49    /// Optional description.
50    #[serde(default)]
51    pub description: Option<String>,
52}
53
54fn default_enabled() -> bool {
55    true
56}
57
58impl TriggerDefinition {
59    /// Create a new trigger definition.
60    pub fn new(id: impl Into<String>, trigger_type: impl Into<String>) -> Self {
61        Self {
62            id: id.into(),
63            trigger_type: trigger_type.into(),
64            params: serde_yaml::Value::Null,
65            enabled: true,
66            description: None,
67        }
68    }
69
70    /// Set parameters.
71    pub fn with_params(mut self, params: serde_yaml::Value) -> Self {
72        self.params = params;
73        self
74    }
75
76    /// Set description.
77    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
78        self.description = Some(desc.into());
79        self
80    }
81
82    /// Disable the trigger.
83    pub fn disabled(mut self) -> Self {
84        self.enabled = false;
85        self
86    }
87
88    /// Parse the trigger type string.
89    pub fn parsed_type(&self) -> Option<TriggerType> {
90        TriggerType::from_str(&self.trigger_type)
91    }
92
93    /// Convert to TriggerConfig for use with trigger factories.
94    pub fn to_trigger_config(&self) -> Option<TriggerConfig> {
95        let trigger_type = self.parsed_type()?;
96        Some(TriggerConfig {
97            id: self.id.clone(),
98            trigger_type,
99            params: self.params.clone(),
100        })
101    }
102
103    /// Get a string parameter.
104    pub fn get_string(&self, key: &str) -> Option<&str> {
105        self.params.get(key).and_then(|v| v.as_str())
106    }
107
108    /// Get an integer parameter.
109    pub fn get_i64(&self, key: &str) -> Option<i64> {
110        self.params.get(key).and_then(|v| v.as_i64())
111    }
112
113    /// Get a boolean parameter.
114    pub fn get_bool(&self, key: &str) -> Option<bool> {
115        self.params.get(key).and_then(|v| v.as_bool())
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn deserialize_webhook_trigger() {
125        let yaml = r#"
126id: api_webhook
127type: webhook
128params:
129  host: "0.0.0.0"
130  port: 8080
131  path: "/orders"
132"#;
133        let trigger: TriggerDefinition = serde_yaml::from_str(yaml).unwrap();
134        assert_eq!(trigger.id, "api_webhook");
135        assert_eq!(trigger.trigger_type, "webhook");
136        assert_eq!(trigger.parsed_type(), Some(TriggerType::Webhook));
137        assert_eq!(trigger.get_string("host"), Some("0.0.0.0"));
138        assert_eq!(trigger.get_i64("port"), Some(8080));
139        assert!(trigger.enabled);
140    }
141
142    #[test]
143    fn deserialize_cron_trigger() {
144        let yaml = r#"
145id: hourly_sync
146type: cron
147params:
148  schedule: "0 0 * * * *"
149description: "Runs every hour"
150"#;
151        let trigger: TriggerDefinition = serde_yaml::from_str(yaml).unwrap();
152        assert_eq!(trigger.id, "hourly_sync");
153        assert_eq!(trigger.trigger_type, "cron");
154        assert_eq!(trigger.parsed_type(), Some(TriggerType::Cron));
155        assert_eq!(trigger.get_string("schedule"), Some("0 0 * * * *"));
156        assert_eq!(trigger.description, Some("Runs every hour".to_string()));
157    }
158
159    #[test]
160    fn to_trigger_config() {
161        let def = TriggerDefinition::new("test", "webhook")
162            .with_params(serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
163
164        let config = def.to_trigger_config().unwrap();
165        assert_eq!(config.id, "test");
166        assert_eq!(config.trigger_type, TriggerType::Webhook);
167    }
168
169    #[test]
170    fn invalid_trigger_type() {
171        let def = TriggerDefinition::new("test", "invalid_type");
172        assert!(def.parsed_type().is_none());
173        assert!(def.to_trigger_config().is_none());
174    }
175
176    #[test]
177    fn disabled_trigger() {
178        let yaml = r#"
179id: disabled_webhook
180type: webhook
181enabled: false
182"#;
183        let trigger: TriggerDefinition = serde_yaml::from_str(yaml).unwrap();
184        assert!(!trigger.enabled);
185    }
186}