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,
52 pub description: Option<String>,
53 pub steps: Vec<WorkflowStep>,
54 pub default_error_behavior: ErrorBehavior,
55 #[serde(default, with = "super::option_duration_millis")]
56 pub default_error_retry_interval: Option<Duration>,
57 #[serde(default, skip_serializing_if = "Vec::is_empty")]
59 pub services: Vec<ServiceDefinition>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub shared_volume: Option<SharedVolume>,
66}
67
68impl WorkflowDefinition {
69 pub fn new(id: impl Into<String>, version: u32) -> Self {
70 Self {
71 id: id.into(),
72 name: None,
73 version,
74 description: None,
75 steps: Vec::new(),
76 default_error_behavior: ErrorBehavior::default(),
77 default_error_retry_interval: None,
78 services: Vec::new(),
79 shared_volume: None,
80 }
81 }
82
83 pub fn display_name(&self) -> &str {
85 self.name.as_deref().unwrap_or(&self.id)
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct WorkflowStep {
92 pub id: usize,
93 pub name: Option<String>,
94 pub external_id: Option<String>,
95 pub step_type: String,
96 pub children: Vec<usize>,
97 pub outcomes: Vec<StepOutcome>,
98 pub error_behavior: Option<ErrorBehavior>,
99 pub compensation_step_id: Option<usize>,
100 pub do_compensate: bool,
101 #[serde(default)]
102 pub saga: bool,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub step_config: Option<serde_json::Value>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub when: Option<StepCondition>,
109}
110
111impl WorkflowStep {
112 pub fn new(id: usize, step_type: impl Into<String>) -> Self {
113 Self {
114 id,
115 name: None,
116 external_id: None,
117 step_type: step_type.into(),
118 children: Vec::new(),
119 outcomes: Vec::new(),
120 error_behavior: None,
121 compensation_step_id: None,
122 do_compensate: false,
123 saga: false,
124 step_config: None,
125 when: None,
126 }
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct StepOutcome {
133 pub next_step: usize,
134 pub label: Option<String>,
135 pub value: Option<serde_json::Value>,
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use pretty_assertions::assert_eq;
142
143 #[test]
144 fn definition_defaults() {
145 let def = WorkflowDefinition::new("test-workflow", 1);
146 assert_eq!(def.id, "test-workflow");
147 assert_eq!(def.version, 1);
148 assert!(def.steps.is_empty());
149 assert_eq!(def.default_error_behavior, ErrorBehavior::default());
150 assert!(def.default_error_retry_interval.is_none());
151 }
152
153 #[test]
154 fn step_defaults() {
155 let step = WorkflowStep::new(0, "MyStep");
156 assert_eq!(step.id, 0);
157 assert_eq!(step.step_type, "MyStep");
158 assert!(step.children.is_empty());
159 assert!(step.outcomes.is_empty());
160 assert!(step.error_behavior.is_none());
161 assert!(step.compensation_step_id.is_none());
162 }
163
164 #[test]
165 fn definition_serde_round_trip() {
166 let mut def = WorkflowDefinition::new("wf", 3);
167 let mut step = WorkflowStep::new(0, "StepA");
168 step.outcomes.push(StepOutcome {
169 next_step: 1,
170 label: Some("next".into()),
171 value: None,
172 });
173 def.steps.push(step);
174 def.steps.push(WorkflowStep::new(1, "StepB"));
175
176 let json = serde_json::to_string(&def).unwrap();
177 let deserialized: WorkflowDefinition = serde_json::from_str(&json).unwrap();
178 assert_eq!(def.id, deserialized.id);
179 assert_eq!(def.steps.len(), deserialized.steps.len());
180 assert_eq!(def.steps[0].outcomes[0].next_step, 1);
181 }
182}