Skip to main content

wfe_core/models/
workflow_definition.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4
5use super::error_behavior::ErrorBehavior;
6
7/// A compiled workflow definition ready for execution.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct WorkflowDefinition {
10    pub id: String,
11    pub version: u32,
12    pub description: Option<String>,
13    pub steps: Vec<WorkflowStep>,
14    pub default_error_behavior: ErrorBehavior,
15    #[serde(default, with = "super::option_duration_millis")]
16    pub default_error_retry_interval: Option<Duration>,
17}
18
19impl WorkflowDefinition {
20    pub fn new(id: impl Into<String>, version: u32) -> Self {
21        Self {
22            id: id.into(),
23            version,
24            description: None,
25            steps: Vec::new(),
26            default_error_behavior: ErrorBehavior::default(),
27            default_error_retry_interval: None,
28        }
29    }
30}
31
32/// A single step in a workflow definition.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct WorkflowStep {
35    pub id: usize,
36    pub name: Option<String>,
37    pub external_id: Option<String>,
38    pub step_type: String,
39    pub children: Vec<usize>,
40    pub outcomes: Vec<StepOutcome>,
41    pub error_behavior: Option<ErrorBehavior>,
42    pub compensation_step_id: Option<usize>,
43    pub do_compensate: bool,
44    #[serde(default)]
45    pub saga: bool,
46    /// Serializable configuration for primitive steps (e.g. event_name, duration).
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub step_config: Option<serde_json::Value>,
49}
50
51impl WorkflowStep {
52    pub fn new(id: usize, step_type: impl Into<String>) -> Self {
53        Self {
54            id,
55            name: None,
56            external_id: None,
57            step_type: step_type.into(),
58            children: Vec::new(),
59            outcomes: Vec::new(),
60            error_behavior: None,
61            compensation_step_id: None,
62            do_compensate: false,
63            saga: false,
64            step_config: None,
65        }
66    }
67}
68
69/// Routing outcome from a step.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct StepOutcome {
72    pub next_step: usize,
73    pub label: Option<String>,
74    pub value: Option<serde_json::Value>,
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use pretty_assertions::assert_eq;
81
82    #[test]
83    fn definition_defaults() {
84        let def = WorkflowDefinition::new("test-workflow", 1);
85        assert_eq!(def.id, "test-workflow");
86        assert_eq!(def.version, 1);
87        assert!(def.steps.is_empty());
88        assert_eq!(def.default_error_behavior, ErrorBehavior::default());
89        assert!(def.default_error_retry_interval.is_none());
90    }
91
92    #[test]
93    fn step_defaults() {
94        let step = WorkflowStep::new(0, "MyStep");
95        assert_eq!(step.id, 0);
96        assert_eq!(step.step_type, "MyStep");
97        assert!(step.children.is_empty());
98        assert!(step.outcomes.is_empty());
99        assert!(step.error_behavior.is_none());
100        assert!(step.compensation_step_id.is_none());
101    }
102
103    #[test]
104    fn definition_serde_round_trip() {
105        let mut def = WorkflowDefinition::new("wf", 3);
106        let mut step = WorkflowStep::new(0, "StepA");
107        step.outcomes.push(StepOutcome {
108            next_step: 1,
109            label: Some("next".into()),
110            value: None,
111        });
112        def.steps.push(step);
113        def.steps.push(WorkflowStep::new(1, "StepB"));
114
115        let json = serde_json::to_string(&def).unwrap();
116        let deserialized: WorkflowDefinition = serde_json::from_str(&json).unwrap();
117        assert_eq!(def.id, deserialized.id);
118        assert_eq!(def.steps.len(), deserialized.steps.len());
119        assert_eq!(def.steps[0].outcomes[0].next_step, 1);
120    }
121}