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}