torrust_tracker_deployer_lib/presentation/cli/controllers/purge/
handler.rs1use std::cell::RefCell;
7use std::sync::Arc;
8
9use parking_lot::ReentrantMutex;
10
11use crate::application::command_handlers::purge::handler::PurgeCommandHandler;
12use crate::domain::environment::name::EnvironmentName;
13use crate::presentation::cli::input::cli::OutputFormat;
14use crate::presentation::cli::views::commands::purge::{JsonView, PurgeDetailsData, TextView};
15use crate::presentation::cli::views::progress::ProgressReporter;
16use crate::presentation::cli::views::Render;
17use crate::presentation::cli::views::UserOutput;
18
19use super::errors::PurgeSubcommandError;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23enum PurgeStep {
24 ValidateEnvironment,
25 ConfirmOperation,
26 PurgeLocalData,
27}
28
29impl PurgeStep {
30 const ALL: &'static [Self] = &[
32 Self::ValidateEnvironment,
33 Self::ConfirmOperation,
34 Self::PurgeLocalData,
35 ];
36
37 const fn count() -> usize {
39 Self::ALL.len()
40 }
41
42 fn description(self) -> &'static str {
44 match self {
45 Self::ValidateEnvironment => "Validating environment",
46 Self::ConfirmOperation => "Confirming operation",
47 Self::PurgeLocalData => "Purging local data",
48 }
49 }
50}
51
52pub struct PurgeCommandController {
71 handler: PurgeCommandHandler,
72 progress: ProgressReporter,
73}
74
75impl PurgeCommandController {
76 #[allow(clippy::needless_pass_by_value)] pub fn new(
82 handler: PurgeCommandHandler,
83 user_output: Arc<ReentrantMutex<RefCell<UserOutput>>>,
84 ) -> Self {
85 let progress = ProgressReporter::new(user_output, PurgeStep::count());
86
87 Self { handler, progress }
88 }
89
90 #[allow(clippy::result_large_err)]
117 #[allow(clippy::unused_async)] pub async fn execute(
119 &mut self,
120 environment_name: &str,
121 force: bool,
122 output_format: OutputFormat,
123 ) -> Result<(), PurgeSubcommandError> {
124 let env_name = self.validate_environment_name(environment_name)?;
125
126 if !force {
128 self.progress
129 .start_step(PurgeStep::ConfirmOperation.description())?;
130
131 self.show_confirmation_prompt(environment_name);
133
134 if !Self::read_user_confirmation()? {
136 self.progress.complete_step(None)?;
137 return Err(PurgeSubcommandError::UserCancelled);
138 }
139
140 self.progress.complete_step(None)?;
141 }
142
143 self.progress
145 .start_step(PurgeStep::PurgeLocalData.description())?;
146 self.handler.execute(&env_name).map_err(|source| {
147 PurgeSubcommandError::PurgeOperationFailed {
148 name: environment_name.to_string(),
149 source,
150 }
151 })?;
152 self.progress.complete_step(None)?;
153
154 self.complete_workflow(environment_name, output_format)?;
155
156 Ok(())
157 }
158
159 #[allow(clippy::result_large_err)]
164 fn validate_environment_name(
165 &mut self,
166 name: &str,
167 ) -> Result<EnvironmentName, PurgeSubcommandError> {
168 self.progress
169 .start_step(PurgeStep::ValidateEnvironment.description())?;
170
171 let env_name = EnvironmentName::new(name.to_string()).map_err(|source| {
172 PurgeSubcommandError::InvalidEnvironmentName {
173 name: name.to_string(),
174 source,
175 }
176 })?;
177
178 self.progress.complete_step(None)?;
179
180 Ok(env_name)
181 }
182
183 #[allow(clippy::result_large_err)]
188 fn complete_workflow(
189 &mut self,
190 environment_name: &str,
191 output_format: OutputFormat,
192 ) -> Result<(), PurgeSubcommandError> {
193 let data = PurgeDetailsData::from_environment_name(environment_name);
194 match output_format {
195 OutputFormat::Text => {
196 self.progress.complete(&TextView::render(&data)?)?;
197 }
198 OutputFormat::Json => {
199 self.progress.result(&JsonView::render(&data)?)?;
200 }
201 }
202 Ok(())
203 }
204
205 fn show_confirmation_prompt(&mut self, environment_name: &str) {
210 let warning = format!(
211 "⚠️ WARNING: This will permanently delete all local data for '{environment_name}':\n\
212 • data/{environment_name}/ directory\n\
213 • build/{environment_name}/ directory\n\
214 • Environment registry entry\n\
215 \n\
216 This operation CANNOT be undone!\n"
217 );
218
219 self.progress.output().lock().borrow_mut().warn(&warning);
220
221 self.progress
222 .output()
223 .lock()
224 .borrow_mut()
225 .progress("Are you sure you want to continue? (y/N): ");
226 }
227
228 #[allow(clippy::result_large_err)]
232 fn read_user_confirmation() -> Result<bool, PurgeSubcommandError> {
233 use std::io::{self, BufRead};
234
235 let stdin = io::stdin();
236 let mut line = String::new();
237
238 stdin
239 .lock()
240 .read_line(&mut line)
241 .map_err(|source| PurgeSubcommandError::IoError {
242 operation: "reading user confirmation".to_string(),
243 source,
244 })?;
245
246 let response = line.trim().to_lowercase();
247 Ok(response == "y" || response == "yes")
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 }