@@ -25,7 +25,159 @@ use super::errors::DestroySubcommandError;
2525const DESTROY_WORKFLOW_STEPS : usize = 3 ;
2626
2727// ============================================================================
28- // PRESENTATION LAYER CONTROLLER
28+ // HIGH-LEVEL API (EXECUTION CONTEXT PATTERN)
29+ // ============================================================================
30+
31+ /// Handle destroy command using `ExecutionContext` pattern
32+ ///
33+ /// This function provides a clean interface for destroying deployment environments,
34+ /// integrating with the `ExecutionContext` pattern for dependency injection.
35+ ///
36+ /// # Arguments
37+ ///
38+ /// * `environment_name` - Name of the environment to destroy
39+ /// * `working_dir` - Working directory path for operations
40+ /// * `context` - Execution context providing access to services
41+ ///
42+ /// # Returns
43+ ///
44+ /// * `Ok(())` - Environment destroyed successfully
45+ /// * `Err(DestroySubcommandError)` - Destroy operation failed
46+ ///
47+ /// # Errors
48+ ///
49+ /// Returns `DestroySubcommandError` when:
50+ /// * Environment name is invalid or contains special characters
51+ /// * Working directory is not accessible or doesn't exist
52+ /// * Environment is not found in the working directory
53+ /// * Infrastructure destruction fails (OpenTofu/LXD errors)
54+ /// * File system operations fail (permission errors, disk space)
55+ ///
56+ /// # Examples
57+ ///
58+ /// ```rust
59+ /// use std::path::Path;
60+ /// use std::sync::Arc;
61+ /// use torrust_tracker_deployer_lib::presentation::controllers::destroy;
62+ /// use torrust_tracker_deployer_lib::presentation::dispatch::context::ExecutionContext;
63+ /// use torrust_tracker_deployer_lib::bootstrap::container::Container;
64+ /// use torrust_tracker_deployer_lib::presentation::user_output::VerbosityLevel;
65+ ///
66+ /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
67+ /// let container = Arc::new(Container::new(VerbosityLevel::Normal));
68+ /// let context = ExecutionContext::new(container);
69+ /// let working_dir = Path::new("./test");
70+ ///
71+ /// destroy::handle("my-env", working_dir, &context)?;
72+ /// # Ok(())
73+ /// # }
74+ /// ```
75+ #[ allow( clippy:: result_large_err) ] // Error contains detailed context for user guidance
76+ pub fn handle (
77+ environment_name : & str ,
78+ working_dir : & std:: path:: Path ,
79+ context : & crate :: presentation:: dispatch:: context:: ExecutionContext ,
80+ ) -> Result < ( ) , DestroySubcommandError > {
81+ handle_destroy_command (
82+ environment_name,
83+ working_dir,
84+ context. repository_factory ( ) ,
85+ context. clock ( ) ,
86+ & context. user_output ( ) ,
87+ )
88+ }
89+
90+ // ============================================================================
91+ // INTERMEDIATE API (DIRECT DEPENDENCY INJECTION)
92+ // ============================================================================
93+
94+ /// Handle the destroy command
95+ ///
96+ /// This is a thin wrapper over `DestroyCommandController` that serves as
97+ /// the public entry point for the destroy command.
98+ ///
99+ /// # Arguments
100+ ///
101+ /// * `environment_name` - The name of the environment to destroy
102+ /// * `working_dir` - Root directory for environment data storage
103+ /// * `repository_factory` - Factory for creating environment repositories
104+ /// * `clock` - Clock service for timing operations
105+ /// * `user_output` - Shared user output service for consistent output formatting
106+ ///
107+ /// # Errors
108+ ///
109+ /// Returns an error if:
110+ /// - Environment name is invalid (format validation fails)
111+ /// - Environment cannot be loaded from repository
112+ /// - Infrastructure teardown fails
113+ /// - Progress reporting encounters a poisoned mutex
114+ ///
115+ /// All errors include detailed context and actionable troubleshooting guidance.
116+ ///
117+ /// # Returns
118+ ///
119+ /// Returns `Ok(())` on success, or a `DestroySubcommandError` on failure.
120+ ///
121+ /// # Example
122+ ///
123+ /// Using with Container and `ExecutionContext` (recommended):
124+ ///
125+ /// ```rust
126+ /// use std::path::Path;
127+ /// use std::sync::Arc;
128+ /// use torrust_tracker_deployer_lib::bootstrap::Container;
129+ /// use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext;
130+ /// use torrust_tracker_deployer_lib::presentation::controllers::destroy;
131+ /// use torrust_tracker_deployer_lib::presentation::user_output::VerbosityLevel;
132+ ///
133+ /// let container = Container::new(VerbosityLevel::Normal);
134+ /// let context = ExecutionContext::new(Arc::new(container));
135+ ///
136+ /// if let Err(e) = destroy::handle("test-env", Path::new("."), &context) {
137+ /// eprintln!("Destroy failed: {e}");
138+ /// eprintln!("Help: {}", e.help());
139+ /// }
140+ /// ```
141+ ///
142+ /// Direct usage (for testing or specialized scenarios):
143+ ///
144+ /// ```rust
145+ /// use std::path::Path;
146+ /// use std::sync::{Arc, Mutex};
147+ /// use torrust_tracker_deployer_lib::presentation::controllers::destroy;
148+ /// use torrust_tracker_deployer_lib::presentation::user_output::{UserOutput, VerbosityLevel};
149+ /// use torrust_tracker_deployer_lib::infrastructure::persistence::repository_factory::RepositoryFactory;
150+ /// use torrust_tracker_deployer_lib::presentation::commands::constants::DEFAULT_LOCK_TIMEOUT;
151+ /// use torrust_tracker_deployer_lib::shared::SystemClock;
152+ ///
153+ /// let user_output = Arc::new(Mutex::new(UserOutput::new(VerbosityLevel::Normal)));
154+ /// let repository_factory = Arc::new(RepositoryFactory::new(DEFAULT_LOCK_TIMEOUT));
155+ /// let clock = Arc::new(SystemClock);
156+ /// if let Err(e) = destroy::handle_destroy_command("test-env", Path::new("."), repository_factory, clock, &user_output) {
157+ /// eprintln!("Destroy failed: {e}");
158+ /// eprintln!("Help: {}", e.help());
159+ /// }
160+ /// ```
161+ #[ allow( clippy:: result_large_err) ] // Error contains detailed context for user guidance
162+ #[ allow( clippy:: needless_pass_by_value) ] // Arc parameters are moved to constructor for ownership
163+ pub fn handle_destroy_command (
164+ environment_name : & str ,
165+ working_dir : & std:: path:: Path ,
166+ repository_factory : Arc < RepositoryFactory > ,
167+ clock : Arc < dyn Clock > ,
168+ user_output : & Arc < Mutex < UserOutput > > ,
169+ ) -> Result < ( ) , DestroySubcommandError > {
170+ DestroyCommandController :: new (
171+ working_dir. to_path_buf ( ) ,
172+ repository_factory,
173+ clock,
174+ user_output. clone ( ) ,
175+ )
176+ . execute ( environment_name)
177+ }
178+
179+ // ============================================================================
180+ // PRESENTATION LAYER CONTROLLER (IMPLEMENTATION DETAILS)
29181// ============================================================================
30182
31183/// Presentation layer controller for destroy command workflow
@@ -179,162 +331,6 @@ impl DestroyCommandController {
179331 }
180332}
181333
182- // ============================================================================
183- // PUBLIC ENTRY POINT
184- // ============================================================================
185-
186- /// Handle the destroy command
187- ///
188- /// This is a thin wrapper over `DestroyCommandController` that serves as
189- /// the public entry point for the destroy command.
190- ///
191- /// # Arguments
192- ///
193- /// * `environment_name` - The name of the environment to destroy
194- /// * `working_dir` - Root directory for environment data storage
195- /// * `repository_factory` - Factory for creating environment repositories
196- /// * `clock` - Clock service for timing operations
197- /// * `user_output` - Shared user output service for consistent output formatting
198- ///
199- /// # Errors
200- ///
201- /// Returns an error if:
202- /// - Environment name is invalid (format validation fails)
203- /// - Environment cannot be loaded from repository
204- /// - Infrastructure teardown fails
205- /// - Progress reporting encounters a poisoned mutex
206- ///
207- /// All errors include detailed context and actionable troubleshooting guidance.
208- ///
209- /// # Returns
210- ///
211- /// Returns `Ok(())` on success, or a `DestroySubcommandError` on failure.
212- ///
213- /// # Example
214- ///
215- /// Using with Container and `ExecutionContext` (recommended):
216- ///
217- /// ```rust
218- /// use std::path::Path;
219- /// use std::sync::Arc;
220- /// use torrust_tracker_deployer_lib::bootstrap::Container;
221- /// use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext;
222- /// use torrust_tracker_deployer_lib::presentation::controllers::destroy;
223- /// use torrust_tracker_deployer_lib::presentation::user_output::VerbosityLevel;
224- ///
225- /// let container = Container::new(VerbosityLevel::Normal);
226- /// let context = ExecutionContext::new(Arc::new(container));
227- ///
228- /// if let Err(e) = destroy::handle("test-env", Path::new("."), &context) {
229- /// eprintln!("Destroy failed: {e}");
230- /// eprintln!("Help: {}", e.help());
231- /// }
232- /// ```
233- ///
234- /// Direct usage (for testing or specialized scenarios):
235- ///
236- /// ```rust
237- /// use std::path::Path;
238- /// use std::sync::{Arc, Mutex};
239- /// use torrust_tracker_deployer_lib::presentation::controllers::destroy;
240- /// use torrust_tracker_deployer_lib::presentation::user_output::{UserOutput, VerbosityLevel};
241- /// use torrust_tracker_deployer_lib::infrastructure::persistence::repository_factory::RepositoryFactory;
242- /// use torrust_tracker_deployer_lib::presentation::commands::constants::DEFAULT_LOCK_TIMEOUT;
243- /// use torrust_tracker_deployer_lib::shared::SystemClock;
244- ///
245- /// let user_output = Arc::new(Mutex::new(UserOutput::new(VerbosityLevel::Normal)));
246- /// let repository_factory = Arc::new(RepositoryFactory::new(DEFAULT_LOCK_TIMEOUT));
247- /// let clock = Arc::new(SystemClock);
248- /// if let Err(e) = destroy::handle_destroy_command("test-env", Path::new("."), repository_factory, clock, &user_output) {
249- /// eprintln!("Destroy failed: {e}");
250- /// eprintln!("Help: {}", e.help());
251- /// }
252- /// ```
253- #[ allow( clippy:: result_large_err) ] // Error contains detailed context for user guidance
254- #[ allow( clippy:: needless_pass_by_value) ] // Arc parameters are moved to constructor for ownership
255- pub fn handle_destroy_command (
256- environment_name : & str ,
257- working_dir : & std:: path:: Path ,
258- repository_factory : Arc < RepositoryFactory > ,
259- clock : Arc < dyn Clock > ,
260- user_output : & Arc < Mutex < UserOutput > > ,
261- ) -> Result < ( ) , DestroySubcommandError > {
262- DestroyCommandController :: new (
263- working_dir. to_path_buf ( ) ,
264- repository_factory,
265- clock,
266- user_output. clone ( ) ,
267- )
268- . execute ( environment_name)
269- }
270-
271- // ============================================================================
272- // EXECUTION CONTEXT API
273- // ============================================================================
274-
275- /// Handle destroy command using `ExecutionContext` pattern
276- ///
277- /// This function provides a clean interface for destroying deployment environments,
278- /// integrating with the `ExecutionContext` pattern for dependency injection.
279- ///
280- /// # Arguments
281- ///
282- /// * `environment_name` - Name of the environment to destroy
283- /// * `working_dir` - Working directory path for operations
284- /// * `context` - Execution context providing access to services
285- ///
286- /// # Returns
287- ///
288- /// * `Ok(())` - Environment destroyed successfully
289- /// * `Err(DestroySubcommandError)` - Destroy operation failed
290- ///
291- /// # Errors
292- ///
293- /// Returns `DestroySubcommandError` when:
294- /// * Environment name is invalid or contains special characters
295- /// * Working directory is not accessible or doesn't exist
296- /// * Environment is not found in the working directory
297- /// * Infrastructure destruction fails (OpenTofu/LXD errors)
298- /// * File system operations fail (permission errors, disk space)
299- ///
300- /// # Examples
301- ///
302- /// ```rust
303- /// use std::path::Path;
304- /// use std::sync::Arc;
305- /// use torrust_tracker_deployer_lib::presentation::controllers::destroy;
306- /// use torrust_tracker_deployer_lib::presentation::dispatch::context::ExecutionContext;
307- /// use torrust_tracker_deployer_lib::bootstrap::container::Container;
308- /// use torrust_tracker_deployer_lib::presentation::user_output::VerbosityLevel;
309- ///
310- /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
311- /// let container = Arc::new(Container::new(VerbosityLevel::Normal));
312- /// let context = ExecutionContext::new(container);
313- /// let working_dir = Path::new("./test");
314- ///
315- /// destroy::handle("my-env", working_dir, &context)?;
316- /// # Ok(())
317- /// # }
318- /// ```
319- #[ allow( clippy:: result_large_err) ] // Error contains detailed context for user guidance
320- pub fn handle (
321- environment_name : & str ,
322- working_dir : & std:: path:: Path ,
323- context : & crate :: presentation:: dispatch:: context:: ExecutionContext ,
324- ) -> Result < ( ) , DestroySubcommandError > {
325- handle_destroy_command (
326- environment_name,
327- working_dir,
328- context. repository_factory ( ) ,
329- context. clock ( ) ,
330- & context. user_output ( ) ,
331- )
332- }
333-
334- // ============================================================================
335- // TESTS
336- // ============================================================================
337-
338334#[ cfg( test) ]
339335mod tests {
340336 use super :: * ;
0 commit comments