ito_domain/schemas/
workflow_state.rs1use super::workflow::WorkflowDefinition;
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct WorkflowExecution {
12 pub workflow: WorkflowDefinition,
14
15 pub status: ExecutionStatus,
17
18 pub started_at: String,
20
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub completed_at: Option<String>,
24
25 pub current_wave_index: usize,
27
28 pub waves: Vec<WaveExecution>,
30
31 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
33 pub variables: BTreeMap<String, String>,
34}
35
36impl WorkflowExecution {
37 pub fn validate(&self) -> Result<(), String> {
39 self.workflow.validate()?;
40
41 if self.started_at.trim().is_empty() {
42 return Err("execution.started_at must not be empty".to_string());
43 }
44 if let Some(ts) = &self.completed_at
45 && ts.trim().is_empty()
46 {
47 return Err("execution.completed_at must not be empty".to_string());
48 }
49 if !self.waves.is_empty() && self.current_wave_index >= self.waves.len() {
50 return Err(format!(
51 "execution.current_wave_index out of bounds: {} (len {})",
52 self.current_wave_index,
53 self.waves.len()
54 ));
55 }
56
57 for wave in &self.waves {
58 wave.validate()?;
59 }
60 for (k, v) in &self.variables {
61 if k.trim().is_empty() {
62 return Err("execution.variables has empty key".to_string());
63 }
64 if v.trim().is_empty() {
65 return Err(format!("execution.variables has empty value for '{k}'"));
66 }
67 }
68
69 Ok(())
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct WaveExecution {
76 pub wave: super::workflow::WaveDefinition,
78
79 pub status: ExecutionStatus,
81
82 pub tasks: Vec<TaskExecution>,
84}
85
86impl WaveExecution {
87 pub fn validate(&self) -> Result<(), String> {
89 self.wave.validate()?;
90 for task in &self.tasks {
91 task.validate()?;
92 }
93 Ok(())
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct TaskExecution {
100 pub task: super::workflow::TaskDefinition,
102
103 pub status: ExecutionStatus,
105
106 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub started_at: Option<String>,
109
110 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub completed_at: Option<String>,
113
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub error: Option<String>,
117
118 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub output_content: Option<String>,
121}
122
123impl TaskExecution {
124 pub fn validate(&self) -> Result<(), String> {
126 self.task.validate()?;
127 if let Some(ts) = &self.started_at
128 && ts.trim().is_empty()
129 {
130 return Err(format!(
131 "execution.task.started_at must not be empty ({})",
132 self.task.id
133 ));
134 }
135
136 if let Some(ts) = &self.completed_at
137 && ts.trim().is_empty()
138 {
139 return Err(format!(
140 "execution.task.completed_at must not be empty ({})",
141 self.task.id
142 ));
143 }
144
145 if let Some(e) = &self.error
146 && e.trim().is_empty()
147 {
148 return Err(format!(
149 "execution.task.error must not be empty ({})",
150 self.task.id
151 ));
152 }
153
154 if let Some(out) = &self.output_content
155 && out.trim().is_empty()
156 {
157 return Err(format!(
158 "execution.task.output_content must not be empty ({})",
159 self.task.id
160 ));
161 }
162 Ok(())
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum ExecutionStatus {
170 Pending,
172 Running,
174 Complete,
176 Failed,
178 Skipped,
180}