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