1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4
5use super::condition::StepCondition;
6use super::error_behavior::ErrorBehavior;
7use super::service::ServiceDefinition;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20pub struct SharedVolume {
21 pub mount_path: String,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub size: Option<String>,
29}
30
31impl Default for SharedVolume {
32 fn default() -> Self {
33 Self {
34 mount_path: "/workspace".to_string(),
35 size: None,
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct WorkflowDefinition {
43 pub id: String,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub name: Option<String>,
51 pub version: u32,
53 pub description: Option<String>,
55 pub steps: Vec<WorkflowStep>,
57 pub default_error_behavior: ErrorBehavior,
59 #[serde(default, with = "super::option_duration_millis")]
60 pub default_error_retry_interval: Option<Duration>,
62 #[serde(default, skip_serializing_if = "Vec::is_empty")]
64 pub services: Vec<ServiceDefinition>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub shared_volume: Option<SharedVolume>,
71}
72
73impl WorkflowDefinition {
74 pub fn new(id: impl Into<String>, version: u32) -> Self {
75 Self {
76 id: id.into(),
77 name: None,
78 version,
79 description: None,
80 steps: Vec::new(),
81 default_error_behavior: ErrorBehavior::default(),
82 default_error_retry_interval: None,
83 services: Vec::new(),
84 shared_volume: None,
85 }
86 }
87
88 pub fn display_name(&self) -> &str {
90 self.name.as_deref().unwrap_or(&self.id)
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct WorkflowStep {
97 pub id: usize,
99 pub name: Option<String>,
101 pub external_id: Option<String>,
103 pub step_type: String,
105 pub children: Vec<usize>,
107 pub outcomes: Vec<StepOutcome>,
109 pub error_behavior: Option<ErrorBehavior>,
111 pub compensation_step_id: Option<usize>,
113 pub do_compensate: bool,
115 #[serde(default)]
116 pub saga: bool,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub step_config: Option<serde_json::Value>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub when: Option<StepCondition>,
124}
125
126impl WorkflowStep {
127 pub fn new(id: usize, step_type: impl Into<String>) -> Self {
128 Self {
129 id,
130 name: None,
131 external_id: None,
132 step_type: step_type.into(),
133 children: Vec::new(),
134 outcomes: Vec::new(),
135 error_behavior: None,
136 compensation_step_id: None,
137 do_compensate: false,
138 saga: false,
139 step_config: None,
140 when: None,
141 }
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct StepOutcome {
148 pub next_step: usize,
150 pub label: Option<String>,
152 pub value: Option<serde_json::Value>,
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use pretty_assertions::assert_eq;
160
161 #[test]
162 fn definition_defaults() {
163 let def = WorkflowDefinition::new("test-workflow", 1);
164 assert_eq!(def.id, "test-workflow");
165 assert_eq!(def.version, 1);
166 assert!(def.steps.is_empty());
167 assert_eq!(def.default_error_behavior, ErrorBehavior::default());
168 assert!(def.default_error_retry_interval.is_none());
169 }
170
171 #[test]
172 fn step_defaults() {
173 let step = WorkflowStep::new(0, "MyStep");
174 assert_eq!(step.id, 0);
175 assert_eq!(step.step_type, "MyStep");
176 assert!(step.children.is_empty());
177 assert!(step.outcomes.is_empty());
178 assert!(step.error_behavior.is_none());
179 assert!(step.compensation_step_id.is_none());
180 }
181
182 #[test]
183 fn definition_serde_round_trip() {
184 let mut def = WorkflowDefinition::new("wf", 3);
185 let mut step = WorkflowStep::new(0, "StepA");
186 step.outcomes.push(StepOutcome {
187 next_step: 1,
188 label: Some("next".into()),
189 value: None,
190 });
191 def.steps.push(step);
192 def.steps.push(WorkflowStep::new(1, "StepB"));
193
194 let json = serde_json::to_string(&def).unwrap();
195 let deserialized: WorkflowDefinition = serde_json::from_str(&json).unwrap();
196 assert_eq!(def.id, deserialized.id);
197 assert_eq!(def.steps.len(), deserialized.steps.len());
198 assert_eq!(def.steps[0].outcomes[0].next_step, 1);
199 }
200}