Skip to main content

wfe_core/models/
execution_result.rs

1use std::time::Duration;
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::poll_config::PollEndpointConfig;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct ExecutionResult {
10    /// Whether the workflow should proceed to the next step.
11    pub proceed: bool,
12    /// Outcome value for decision-based routing.
13    pub outcome_value: Option<serde_json::Value>,
14    /// Duration to sleep before re-executing.
15    #[serde(default, with = "super::option_duration_millis")]
16    pub sleep_for: Option<Duration>,
17    /// Step-specific state to persist between executions.
18    pub persistence_data: Option<serde_json::Value>,
19    /// Event name to wait for.
20    pub event_name: Option<String>,
21    /// Event key to match.
22    pub event_key: Option<String>,
23    /// Only consider events published after this time.
24    pub event_as_of: Option<DateTime<Utc>>,
25    /// Values to branch execution on (for ForEach/Parallel).
26    pub branch_values: Option<Vec<serde_json::Value>>,
27    /// Poll endpoint configuration for external service polling.
28    pub poll_endpoint: Option<PollEndpointConfig>,
29    /// Output data to merge into workflow.data after step completion.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub output_data: Option<serde_json::Value>,
32}
33
34impl ExecutionResult {
35    /// Continue to the next step.
36    pub fn next() -> Self {
37        Self {
38            proceed: true,
39            ..Default::default()
40        }
41    }
42
43    /// Continue with an outcome value for decision routing.
44    pub fn outcome(value: impl Into<serde_json::Value>) -> Self {
45        Self {
46            proceed: true,
47            outcome_value: Some(value.into()),
48            ..Default::default()
49        }
50    }
51
52    /// Pause execution and persist step-specific data.
53    pub fn persist(data: serde_json::Value) -> Self {
54        Self {
55            proceed: false,
56            persistence_data: Some(data),
57            ..Default::default()
58        }
59    }
60
61    /// Create child branches for parallel/foreach execution.
62    pub fn branch(values: Vec<serde_json::Value>, persistence_data: Option<serde_json::Value>) -> Self {
63        Self {
64            proceed: false,
65            branch_values: Some(values),
66            persistence_data,
67            ..Default::default()
68        }
69    }
70
71    /// Sleep for a duration before re-executing.
72    pub fn sleep(duration: Duration, persistence_data: Option<serde_json::Value>) -> Self {
73        Self {
74            proceed: false,
75            sleep_for: Some(duration),
76            persistence_data,
77            ..Default::default()
78        }
79    }
80
81    /// Wait for an external event.
82    pub fn wait_for_event(
83        event_name: impl Into<String>,
84        event_key: impl Into<String>,
85        as_of: DateTime<Utc>,
86    ) -> Self {
87        Self {
88            proceed: false,
89            event_name: Some(event_name.into()),
90            event_key: Some(event_key.into()),
91            event_as_of: Some(as_of),
92            ..Default::default()
93        }
94    }
95
96    /// Poll an external endpoint until a condition is met.
97    pub fn poll_endpoint(config: PollEndpointConfig) -> Self {
98        Self {
99            proceed: false,
100            poll_endpoint: Some(config),
101            ..Default::default()
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use pretty_assertions::assert_eq;
110
111    #[test]
112    fn next_proceeds_with_no_data() {
113        let result = ExecutionResult::next();
114        assert!(result.proceed);
115        assert!(result.outcome_value.is_none());
116        assert!(result.sleep_for.is_none());
117        assert!(result.persistence_data.is_none());
118        assert!(result.event_name.is_none());
119        assert!(result.branch_values.is_none());
120        assert!(result.poll_endpoint.is_none());
121    }
122
123    #[test]
124    fn outcome_proceeds_with_value() {
125        let result = ExecutionResult::outcome(serde_json::json!(42));
126        assert!(result.proceed);
127        assert_eq!(result.outcome_value, Some(serde_json::json!(42)));
128    }
129
130    #[test]
131    fn persist_does_not_proceed() {
132        let data = serde_json::json!({"counter": 5});
133        let result = ExecutionResult::persist(data.clone());
134        assert!(!result.proceed);
135        assert_eq!(result.persistence_data, Some(data));
136    }
137
138    #[test]
139    fn branch_creates_child_values() {
140        let values = vec![serde_json::json!(1), serde_json::json!(2), serde_json::json!(3)];
141        let result = ExecutionResult::branch(values.clone(), None);
142        assert!(!result.proceed);
143        assert_eq!(result.branch_values, Some(values));
144    }
145
146    #[test]
147    fn sleep_sets_duration() {
148        let result = ExecutionResult::sleep(Duration::from_secs(30), None);
149        assert!(!result.proceed);
150        assert_eq!(result.sleep_for, Some(Duration::from_secs(30)));
151    }
152
153    #[test]
154    fn wait_for_event_sets_event_fields() {
155        let now = Utc::now();
156        let result = ExecutionResult::wait_for_event("order.completed", "order-123", now);
157        assert!(!result.proceed);
158        assert_eq!(result.event_name.as_deref(), Some("order.completed"));
159        assert_eq!(result.event_key.as_deref(), Some("order-123"));
160        assert_eq!(result.event_as_of, Some(now));
161    }
162
163    #[test]
164    fn poll_endpoint_sets_config() {
165        use super::super::poll_config::*;
166        use std::collections::HashMap;
167
168        let config = PollEndpointConfig {
169            url: "https://api.example.com/status".into(),
170            method: HttpMethod::Get,
171            headers: HashMap::new(),
172            body: None,
173            interval: Duration::from_secs(10),
174            timeout: Duration::from_secs(300),
175            condition: PollCondition::StatusCode(200),
176        };
177        let result = ExecutionResult::poll_endpoint(config.clone());
178        assert!(!result.proceed);
179        assert_eq!(result.poll_endpoint, Some(config));
180    }
181
182    #[test]
183    fn serde_round_trip() {
184        let result = ExecutionResult::sleep(Duration::from_secs(30), Some(serde_json::json!({"x": 1})));
185        let json = serde_json::to_string(&result).unwrap();
186        let deserialized: ExecutionResult = serde_json::from_str(&json).unwrap();
187        assert_eq!(result.proceed, deserialized.proceed);
188        assert_eq!(result.sleep_for, deserialized.sleep_for);
189        assert_eq!(result.persistence_data, deserialized.persistence_data);
190    }
191}