Skip to main content

torrust_tracker_deployer_lib/presentation/cli/dispatch/
context.rs

1//! Execution Context
2//!
3//! This module provides the `ExecutionContext` wrapper around the Container for
4//! dependency injection in command handlers. It offers a clean interface for
5//! accessing services needed during command execution.
6//!
7//! ## Purpose
8//!
9//! The `ExecutionContext` serves as an abstraction layer between the Container
10//! (which holds raw services) and command handlers (which need typed access).
11//! This separation provides:
12//!
13//! - **Clean Interface**: Command handlers get strongly-typed service access
14//! - **Thread Safety**: All services are properly wrapped for concurrent access
15//! - **Future-Proofing**: Easy to add new services without changing handler signatures
16//! - **Testing Support**: Easy to inject test doubles through Container
17//!
18//! ## Design
19//!
20//! ```text
21//! Container (bootstrap) → ExecutionContext (dispatch) → Command Handlers
22//!
23//! Raw services        Clean typed access      Business logic
24//! ```
25//!
26//! ## Usage Example
27//!
28//! ```ignore
29//! use torrust_tracker_deployer_lib::bootstrap::Container;
30//! use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
31//! use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
32//! use std::sync::Arc;
33//! use std::path::Path;
34//!
35//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
36//! // Create execution context from container
37//! let container = Container::new(VerbosityLevel::Normal, Path::new("."));
38//! let context = ExecutionContext::new(Arc::new(container), global_args);
39//!
40//! // Command handlers access services through context
41//! let user_output = context.user_output();
42//! user_output.lock().borrow_mut().progress("Processing...");
43//! # Ok(())
44//! # }
45//! ```
46
47use std::cell::RefCell;
48use std::sync::Arc;
49
50use parking_lot::ReentrantMutex;
51
52use crate::bootstrap::Container;
53use crate::infrastructure::persistence::file_repository_factory::FileRepositoryFactory;
54use crate::presentation::cli::input::cli::args::GlobalArgs;
55use crate::presentation::cli::input::cli::OutputFormat;
56use crate::presentation::cli::views::UserOutput;
57use crate::shared::clock::Clock;
58
59/// ### Design Consideration: Shared State Access
60///
61/// Currently, there is no shared mutable state in the system that requires `Arc<Mutex<T>>`
62/// patterns. However, if shared state is needed in the future, it can be added to the
63/// Container and accessed through standard Rust concurrency patterns:
64///
65/// # Examples
66///
67/// ```ignore
68/// use std::sync::Arc;
69/// use std::path::Path;
70/// use torrust_tracker_deployer_lib::bootstrap::Container;
71/// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
72/// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
73///
74/// let container = Arc::new(Container::new(VerbosityLevel::Normal, Path::new(".")));
75/// let context = ExecutionContext::new(container, global_args);
76///
77/// // Access user output service
78/// let user_output = context.user_output();
79/// user_output.lock().borrow_mut().success("Operation completed");
80/// ```
81#[derive(Clone)]
82pub struct ExecutionContext {
83    container: Arc<Container>,
84    global_args: GlobalArgs,
85}
86
87impl ExecutionContext {
88    /// Create a new execution context from a container
89    ///
90    /// # Arguments
91    ///
92    /// * `container` - Application service container with initialized services
93    /// * `global_args` - Global CLI arguments (logging config, output format, etc.)
94    ///
95    /// # Examples
96    ///
97    /// ```ignore
98    /// use torrust_tracker_deployer_lib::bootstrap::Container;
99    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
100    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
101    /// use torrust_tracker_deployer_lib::presentation::cli::input::cli::args::GlobalArgs;
102    /// use torrust_tracker_deployer_lib::bootstrap::logging::{LogFormat, LogOutput};
103    /// use torrust_tracker_deployer_lib::presentation::cli::input::cli::OutputFormat;
104    /// use std::sync::Arc;
105    /// use std::path::PathBuf;
106    ///
107    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
108    /// let container = Container::new(VerbosityLevel::Normal, &PathBuf::from("."));
109    /// let global_args = GlobalArgs {
110    ///     log_file_format: LogFormat::Compact,
111    ///     log_stderr_format: LogFormat::Pretty,
112    ///     log_output: LogOutput::FileOnly,
113    ///     log_dir: PathBuf::from("./data/logs"),
114    ///     working_dir: PathBuf::from("."),
115    ///     output_format: OutputFormat::Text,
116    ///     verbosity: 0,
117    /// };
118    /// let context = ExecutionContext::new(Arc::new(container), global_args);
119    /// # Ok(())
120    /// # }
121    /// ```
122    #[must_use]
123    pub fn new(container: Arc<Container>, global_args: GlobalArgs) -> Self {
124        Self {
125            container,
126            global_args,
127        }
128    }
129
130    /// Get reference to the underlying container
131    ///
132    /// Provides access to the raw container for cases where direct access
133    /// to container methods is needed.
134    ///
135    /// # Examples
136    ///
137    /// ```ignore
138    /// use std::path::Path;
139    /// use torrust_tracker_deployer_lib::bootstrap::Container;
140    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
141    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
142    /// use std::sync::Arc;
143    ///
144    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
145    /// let container = Container::new(VerbosityLevel::Normal, Path::new("."));
146    /// let context = ExecutionContext::new(Arc::new(container), global_args);
147    ///
148    /// let container_ref = context.container();
149    /// // Use container_ref as needed
150    /// # Ok(())
151    /// # }
152    /// ```
153    #[must_use]
154    pub fn container(&self) -> &Arc<Container> {
155        &self.container
156    }
157
158    /// Get shared reference to user output service
159    ///
160    /// Returns the user output service for displaying messages, progress,
161    /// and results to users. The service is wrapped in `Arc<Mutex<T>>` for
162    /// thread-safe shared access.
163    ///
164    /// # Examples
165    ///
166    /// ```ignore
167    /// use torrust_tracker_deployer_lib::bootstrap::Container;
168    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
169    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
170    /// use std::sync::Arc;
171    /// use std::path::Path;
172    ///
173    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
174    /// let container = Container::new(VerbosityLevel::Normal, Path::new("."));
175    /// let context = ExecutionContext::new(Arc::new(container), global_args);
176    ///
177    /// let user_output = context.user_output();
178    /// user_output.lock().borrow_mut().success("Operation completed");
179    /// # Ok(())
180    /// # }
181    /// ```
182    #[must_use]
183    pub fn user_output(&self) -> Arc<ReentrantMutex<RefCell<UserOutput>>> {
184        self.container.user_output()
185    }
186
187    /// Get shared reference to repository factory service
188    ///
189    /// Returns the repository factory service for creating environment
190    /// repositories. The service is wrapped in `Arc<T>` for shared access.
191    ///
192    /// # Examples
193    ///
194    /// ```ignore
195    /// use torrust_tracker_deployer_lib::bootstrap::Container;
196    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
197    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
198    /// use std::sync::Arc;
199    /// use std::path::Path;
200    ///
201    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
202    /// let container = Container::new(VerbosityLevel::Normal, Path::new("."));
203    /// let context = ExecutionContext::new(Arc::new(container), global_args);
204    ///
205    /// let file_repository_factory = context.file_repository_factory();
206    /// // Use file_repository_factory to create repositories
207    /// # Ok(())
208    /// # }
209    /// ```
210    #[must_use]
211    pub fn file_repository_factory(&self) -> Arc<FileRepositoryFactory> {
212        self.container.file_repository_factory()
213    }
214
215    /// Get shared reference to environment repository
216    ///
217    /// Returns the environment repository for persistence operations.
218    /// The repository is wrapped in `Arc<dyn EnvironmentRepository>` for shared access.
219    ///
220    /// # Examples
221    ///
222    /// ```ignore
223    /// use torrust_tracker_deployer_lib::bootstrap::Container;
224    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
225    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
226    /// use std::sync::Arc;
227    /// use std::path::Path;
228    ///
229    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
230    /// let container = Container::new(VerbosityLevel::Normal, Path::new("."));
231    /// let context = ExecutionContext::new(Arc::new(container), global_args);
232    ///
233    /// let repository = context.repository();
234    /// // Use repository for environment persistence
235    /// # Ok(())
236    /// # }
237    /// ```
238    #[must_use]
239    pub fn repository(
240        &self,
241    ) -> Arc<dyn crate::domain::environment::repository::EnvironmentRepository + Send + Sync> {
242        self.container.repository()
243    }
244
245    /// Get shared reference to clock service
246    ///
247    /// Returns the clock service for time-related operations.
248    /// The service is wrapped in `Arc<dyn Clock>` for shared access.
249    ///
250    /// # Examples
251    ///
252    /// ```ignore
253    /// use torrust_tracker_deployer_lib::bootstrap::Container;
254    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
255    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
256    /// use std::sync::Arc;
257    /// use std::path::Path;
258    ///
259    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
260    /// let container = Container::new(VerbosityLevel::Normal, Path::new("."));
261    /// let context = ExecutionContext::new(Arc::new(container), global_args);
262    ///
263    /// let clock = context.clock();
264    /// // Use clock for time operations
265    /// # Ok(())
266    /// # }
267    /// ```
268    #[must_use]
269    pub fn clock(&self) -> Arc<dyn Clock> {
270        self.container.clock()
271    }
272
273    /// Get the output format from global CLI arguments
274    ///
275    /// Returns the user-specified output format (Text or Json) for command results.
276    /// This allows controllers to format their output appropriately based on user preference.
277    ///
278    /// # Examples
279    ///
280    /// ```ignore
281    /// use torrust_tracker_deployer_lib::bootstrap::Container;
282    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
283    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
284    /// use torrust_tracker_deployer_lib::presentation::cli::input::cli::args::GlobalArgs;
285    /// use torrust_tracker_deployer_lib::presentation::cli::input::cli::OutputFormat;
286    /// use torrust_tracker_deployer_lib::bootstrap::logging::{LogFormat, LogOutput};
287    /// use std::sync::Arc;
288    /// use std::path::PathBuf;
289    ///
290    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
291    /// let container = Container::new(VerbosityLevel::Normal, &PathBuf::from("."));
292    /// let global_args = GlobalArgs {
293    ///     log_file_format: LogFormat::Compact,
294    ///     log_stderr_format: LogFormat::Pretty,
295    ///     log_output: LogOutput::FileOnly,
296    ///     log_dir: PathBuf::from("./data/logs"),
297    ///     working_dir: PathBuf::from("."),
298    ///     output_format: OutputFormat::Json,
299    ///     verbosity: 0,
300    /// };
301    /// let context = ExecutionContext::new(Arc::new(container), global_args);
302    ///
303    /// let format = context.output_format();
304    /// match format {
305    ///     OutputFormat::Text => println!("Human-readable text"),
306    ///     OutputFormat::Json => println!("{{\"result\": \"json\"}}"),
307    /// }
308    /// # Ok(())
309    /// # }
310    /// ```
311    #[must_use]
312    pub fn output_format(&self) -> OutputFormat {
313        self.global_args.output_format
314    }
315
316    /// Get the working directory from global CLI arguments
317    ///
318    /// Returns the working directory path specified by the user (or default ".").
319    /// This is where environment data will be stored (data/ and build/ subdirectories).
320    ///
321    /// # Examples
322    ///
323    /// ```ignore
324    /// use torrust_tracker_deployer_lib::bootstrap::Container;
325    /// use torrust_tracker_deployer_lib::presentation::cli::views::VerbosityLevel;
326    /// use torrust_tracker_deployer_lib::presentation::cli::dispatch::ExecutionContext;
327    /// use torrust_tracker_deployer_lib::presentation::cli::input::cli::args::GlobalArgs;
328    /// use torrust_tracker_deployer_lib::presentation::cli::input::cli::OutputFormat;
329    /// use torrust_tracker_deployer_lib::bootstrap::logging::{LogFormat, LogOutput};
330    /// use std::sync::Arc;
331    /// use std::path::PathBuf;
332    ///
333    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
334    /// let container = Container::new(VerbosityLevel::Normal, &PathBuf::from("."));
335    /// let global_args = GlobalArgs {
336    ///     log_file_format: LogFormat::Compact,
337    ///     log_stderr_format: LogFormat::Pretty,
338    ///     log_output: LogOutput::FileOnly,
339    ///     log_dir: PathBuf::from("./data/logs"),
340    ///     working_dir: PathBuf::from("/tmp/test-workspace"),
341    ///     output_format: OutputFormat::Text,
342    ///     verbosity: 0,
343    /// };
344    /// let context = ExecutionContext::new(Arc::new(container), global_args);
345    ///
346    /// let working_dir = context.working_dir();
347    /// println!("Working directory: {}", working_dir.display());
348    /// # Ok(())
349    /// # }
350    /// ```
351    #[must_use]
352    pub fn working_dir(&self) -> &std::path::Path {
353        &self.global_args.working_dir
354    }
355}