mecha10_cli/dev/
lifecycle_adapter.rs1use crate::types::project::{LifecycleConfig, ModeConfig, ProjectConfig};
13use anyhow::Result;
14use std::collections::{HashMap, HashSet};
15
16pub struct CliLifecycleManager {
21 current_mode: String,
23
24 running_nodes: HashSet<String>,
26
27 mode_config: HashMap<String, ModeConfig>,
29}
30
31#[derive(Debug)]
33#[allow(dead_code)] pub struct ModeTransitionDiff {
35 pub start: Vec<String>,
37
38 pub stop: Vec<String>,
40}
41
42impl CliLifecycleManager {
43 pub fn from_project_config(config: &ProjectConfig) -> Option<Self> {
47 let lifecycle = config.lifecycle.as_ref()?;
48
49 Some(Self {
50 current_mode: lifecycle.default_mode.clone(),
51 running_nodes: HashSet::new(),
52 mode_config: lifecycle.modes.clone(),
53 })
54 }
55
56 pub fn current_mode(&self) -> &str {
58 &self.current_mode
59 }
60
61 pub fn nodes_for_current_mode(&self) -> Vec<String> {
63 self.mode_config
64 .get(&self.current_mode)
65 .map(|config| config.nodes.clone())
66 .unwrap_or_default()
67 }
68
69 #[allow(dead_code)] pub fn calculate_mode_diff(&self, target_mode: &str) -> Result<ModeTransitionDiff> {
74 let target_config = self
76 .mode_config
77 .get(target_mode)
78 .ok_or_else(|| anyhow::anyhow!("Mode '{}' not found", target_mode))?;
79
80 let target_nodes: HashSet<_> = target_config.nodes.iter().map(|s| s.as_str()).collect();
82
83 let stop_nodes: HashSet<_> = target_config.stop_nodes.iter().map(|s| s.as_str()).collect();
85
86 let start: Vec<_> = target_nodes
88 .iter()
89 .filter(|n| !self.running_nodes.contains(**n))
90 .map(|s| s.to_string())
91 .collect();
92
93 let stop: Vec<_> = self
95 .running_nodes
96 .iter()
97 .filter(|n| !target_nodes.contains(n.as_str()) || stop_nodes.contains(n.as_str()))
98 .cloned()
99 .collect();
100
101 Ok(ModeTransitionDiff { start, stop })
102 }
103
104 pub fn mark_nodes_running(&mut self, nodes: &[String]) {
106 for node in nodes {
107 self.running_nodes.insert(node.clone());
108 }
109 }
110
111 #[allow(dead_code)] pub fn mark_nodes_stopped(&mut self, nodes: &[String]) {
114 for node in nodes {
115 self.running_nodes.remove(node);
116 }
117 }
118
119 #[allow(dead_code)] pub fn change_mode(&mut self, target_mode: &str) -> Result<ModeTransitionDiff> {
125 let diff = self.calculate_mode_diff(target_mode)?;
126 self.current_mode = target_mode.to_string();
127 Ok(diff)
128 }
129
130 #[allow(dead_code)] pub fn available_modes(&self) -> Vec<&str> {
133 self.mode_config.keys().map(|s| s.as_str()).collect()
134 }
135
136 #[allow(dead_code)] pub fn validate(lifecycle: &LifecycleConfig, available_nodes: &[String]) -> Result<()> {
143 if !lifecycle.modes.contains_key(&lifecycle.default_mode) {
145 return Err(anyhow::anyhow!(
146 "Default mode '{}' not found in modes",
147 lifecycle.default_mode
148 ));
149 }
150
151 let node_set: HashSet<_> = available_nodes.iter().map(|s| s.as_str()).collect();
153
154 for (mode_name, mode_config) in &lifecycle.modes {
155 for node in &mode_config.nodes {
156 if !node_set.contains(node.as_str()) {
157 return Err(anyhow::anyhow!(
158 "Mode '{}' references unknown node '{}'",
159 mode_name,
160 node
161 ));
162 }
163 }
164
165 for node in &mode_config.stop_nodes {
166 if !node_set.contains(node.as_str()) {
167 return Err(anyhow::anyhow!(
168 "Mode '{}' stop_nodes references unknown node '{}'",
169 mode_name,
170 node
171 ));
172 }
173 }
174 }
175
176 Ok(())
177 }
178}