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