1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Flow {
11 pub name: String,
13 pub services: HashMap<String, Service>,
15 pub stages: HashMap<String, Stage>,
17}
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct Stage {
22 #[serde(default)]
24 pub services: Vec<String>,
25 #[serde(default)]
27 pub variables: HashMap<String, String>,
28}
29
30#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32pub struct Service {
33 pub image: Option<String>,
34 pub version: Option<String>,
35 pub command: Option<String>,
36 #[serde(default)]
37 pub ports: Vec<Port>,
38 #[serde(default)]
39 pub environment: HashMap<String, String>,
40 #[serde(default)]
41 pub volumes: Vec<Volume>,
42 #[serde(default)]
43 pub depends_on: Vec<String>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Port {
49 pub host: u16,
50 pub container: u16,
51 #[serde(default = "default_protocol")]
52 pub protocol: Protocol,
53 pub host_ip: Option<String>,
54}
55
56#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
58#[serde(rename_all = "lowercase")]
59pub enum Protocol {
60 #[default]
61 Tcp,
62 Udp,
63}
64
65fn default_protocol() -> Protocol {
66 Protocol::Tcp
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Volume {
72 pub host: PathBuf,
73 pub container: PathBuf,
74 #[serde(default)]
75 pub read_only: bool,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Process {
84 pub id: String,
86 pub flow_name: String,
88 pub stage_name: String,
90 pub service_name: String,
92 pub container_id: Option<String>,
94 pub pid: Option<u32>,
96 pub state: ProcessState,
98 pub started_at: i64,
100 pub stopped_at: Option<i64>,
102 pub image: String,
104 pub memory_usage: Option<u64>,
106 pub cpu_usage: Option<f64>,
108 pub ports: Vec<Port>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114#[serde(rename_all = "lowercase")]
115pub enum ProcessState {
116 Starting,
118 Running,
120 Stopping,
122 Stopped,
124 Paused,
126 Failed,
128 Restarting,
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_flow_creation() {
138 let mut services = HashMap::new();
139 services.insert(
140 "api".to_string(),
141 Service {
142 image: Some("myapp:1.0.0".to_string()),
143 ..Default::default()
144 },
145 );
146
147 let mut stages = HashMap::new();
148 stages.insert(
149 "local".to_string(),
150 Stage {
151 services: vec!["api".to_string()],
152 variables: HashMap::new(),
153 },
154 );
155
156 let flow = Flow {
157 name: "my-project".to_string(),
158 services,
159 stages,
160 };
161
162 assert_eq!(flow.name, "my-project");
163 assert_eq!(flow.services.len(), 1);
164 assert_eq!(flow.stages.len(), 1);
165 assert!(flow.services.contains_key("api"));
166 assert!(flow.stages.contains_key("local"));
167 }
168
169 #[test]
170 fn test_flow_to_flowconfig_conversion() {
171 let mut services = HashMap::new();
172 services.insert("db".to_string(), Service::default());
173
174 let mut stages = HashMap::new();
175 stages.insert("dev".to_string(), Stage::default());
176
177 let flow = Flow {
178 name: "test-flow".to_string(),
179 services: services.clone(),
180 stages: stages.clone(),
181 };
182
183 assert_eq!(flow.services.len(), 1);
184 assert_eq!(flow.stages.len(), 1);
185 assert!(flow.services.contains_key("db"));
186 assert!(flow.stages.contains_key("dev"));
187 }
188
189 #[test]
190 fn test_process_creation() {
191 let process = Process {
192 id: "proc-123".to_string(),
193 flow_name: "my-flow".to_string(),
194 stage_name: "local".to_string(),
195 service_name: "api".to_string(),
196 container_id: Some("container-abc".to_string()),
197 pid: Some(1234),
198 state: ProcessState::Running,
199 started_at: 1704067200,
200 stopped_at: None,
201 image: "myapp:1.0.0".to_string(),
202 memory_usage: Some(256_000_000),
203 cpu_usage: Some(5.5),
204 ports: vec![],
205 };
206
207 assert_eq!(process.id, "proc-123");
208 assert_eq!(process.flow_name, "my-flow");
209 assert_eq!(process.state, ProcessState::Running);
210 assert_eq!(process.pid, Some(1234));
211 assert!(process.stopped_at.is_none());
212 }
213
214 #[test]
215 fn test_process_state_transitions() {
216 let states = vec![
217 ProcessState::Starting,
218 ProcessState::Running,
219 ProcessState::Stopping,
220 ProcessState::Stopped,
221 ProcessState::Paused,
222 ProcessState::Failed,
223 ProcessState::Restarting,
224 ];
225
226 for state in states {
227 let process = Process {
228 id: "test".to_string(),
229 flow_name: "test".to_string(),
230 stage_name: "test".to_string(),
231 service_name: "test".to_string(),
232 container_id: None,
233 pid: None,
234 state: state.clone(),
235 started_at: 0,
236 stopped_at: None,
237 image: "test".to_string(),
238 memory_usage: None,
239 cpu_usage: None,
240 ports: vec![],
241 };
242
243 assert_eq!(process.state, state);
244 }
245 }
246
247 #[test]
248 fn test_process_with_resource_usage() {
249 let process = Process {
250 id: "proc-456".to_string(),
251 flow_name: "resource-test".to_string(),
252 stage_name: "local".to_string(),
253 service_name: "db".to_string(),
254 container_id: Some("container-xyz".to_string()),
255 pid: Some(5678),
256 state: ProcessState::Running,
257 started_at: 1704067200,
258 stopped_at: None,
259 image: "postgres:16".to_string(),
260 memory_usage: Some(512_000_000), cpu_usage: Some(10.5), ports: vec![Port {
263 host: 5432,
264 container: 5432,
265 protocol: Protocol::Tcp,
266 host_ip: None,
267 }],
268 };
269
270 assert_eq!(process.memory_usage, Some(512_000_000));
271 assert_eq!(process.cpu_usage, Some(10.5));
272 assert_eq!(process.ports.len(), 1);
273 assert_eq!(process.ports[0].host, 5432);
274 }
275
276 #[test]
277 fn test_process_serialization() {
278 let process = Process {
279 id: "proc-789".to_string(),
280 flow_name: "serialize-test".to_string(),
281 stage_name: "local".to_string(),
282 service_name: "api".to_string(),
283 container_id: Some("container-123".to_string()),
284 pid: Some(9999),
285 state: ProcessState::Running,
286 started_at: 1704067200,
287 stopped_at: None,
288 image: "myapp:latest".to_string(),
289 memory_usage: Some(128_000_000),
290 cpu_usage: Some(2.5),
291 ports: vec![],
292 };
293
294 let json = serde_json::to_string(&process).unwrap();
296 assert!(json.contains("proc-789"));
297 assert!(json.contains("serialize-test"));
298
299 let deserialized: Process = serde_json::from_str(&json).unwrap();
301 assert_eq!(deserialized.id, process.id);
302 assert_eq!(deserialized.flow_name, process.flow_name);
303 assert_eq!(deserialized.state, process.state);
304 }
305}