Skip to content

Commit 79cd8ea

Browse files
committed
Merge #108: refactor: [#107] centralize UserOutput via dependency injection
75af108 refactor: [#107] extract DestroyCommandController with private step methods (Jose Celano) 9730bd0 refactor: [#107] apply GitHub Copilot PR review suggestions (Jose Celano) b49bcc0 refactor: [#107] simplify error handling with From trait for ProgressReporterError (Jose Celano) cb10deb refactor: [#107] replace mutex panics with proper error handling (Jose Celano) f0704ec refactor: [#107] extract test setup helper in factory module (Jose Celano) b9212ac refactor: [#107] extract test setup helper to reduce duplication (Jose Celano) 38878bb refactor: [#107] remove unnecessary report_error wrapper function (Jose Celano) d06f3ed refactor: [#107] eliminate duplicate CommandContext creation (Jose Celano) c5de285 refactor: [#107] centralize UserOutput via dependency injection (Jose Celano) Pull request description: ## Summary This PR centralizes `UserOutput` management via dependency injection, providing better control over output formatting and reducing duplication throughout the codebase. ## Changes ### Core Changes - ✅ Add `AppContainer` for centralized dependency management (`src/bootstrap/container.rs`) - ✅ Update all command handlers to accept `&Arc<Mutex<UserOutput>>` - ✅ Remove `UserOutput` cloning throughout command execution - ✅ Ensure thread-safe shared ownership of output stream ### Documentation - ✅ Update all documentation examples to use `Arc<Mutex<UserOutput>>` - ✅ Fix all doc tests to compile with new API (234 passing) ### Testing - ✅ Update all tests to use the new pattern - ✅ All pre-commit checks passing ## Benefits 1. **Centralized Control**: Single point of creation for UserOutput 2. **Thread Safety**: Proper Arc<Mutex<>> wrapper ensures safe concurrent access 3. **Reduced Duplication**: Eliminates cloning throughout the codebase 4. **Better Testing**: Easier to inject mock outputs for testing ## Files Changed (20 files) - `src/bootstrap/app.rs` - Updated to use AppContainer - `src/bootstrap/container.rs` - **NEW** - Dependency injection container - `src/bootstrap/mod.rs` - Module updates - `src/presentation/commands/context.rs` - Updated API and docs - `src/presentation/commands/create/*` - Updated create command handlers - `src/presentation/commands/destroy/*` - Updated destroy command handlers - `src/presentation/commands/factory.rs` - Updated factory methods - `src/presentation/commands/mod.rs` - Updated command execution - `src/presentation/progress.rs` - Updated progress reporting - Tests updated across all command modules ## Status - [x] All tests passing - [x] Documentation updated - [x] Pre-commit checks passing - [x] Additional refactors planned (keeping as draft) ## Related Closes #107 ACKs for top commit: josecelano: ACK 75af108 Tree-SHA512: eeeb255bbf2f30fae9f0f1b68a94ba9cae326c74a47476a747831500b1e918ef234a30a3bf0545a891cb3d8ee1579e371628f418175559c85acd35ed73e71c73
2 parents 9b51e11 + 75af108 commit 79cd8ea

File tree

20 files changed

+1178
-417
lines changed

20 files changed

+1178
-417
lines changed

src/bootstrap/app.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ use crate::{bootstrap, presentation};
2727
/// This function serves as the application bootstrap, handling:
2828
/// 1. CLI argument parsing (delegated to presentation layer)
2929
/// 2. Logging initialization using `LoggingConfig`
30-
/// 3. Command execution (delegated to presentation layer)
31-
/// 4. Error handling and exit code management
30+
/// 3. Service container creation for dependency injection
31+
/// 4. Command execution (delegated to presentation layer)
32+
/// 5. Error handling and exit code management
3233
///
3334
/// # Panics
3435
///
@@ -54,10 +55,15 @@ pub fn run() {
5455
"Application started"
5556
);
5657

58+
// Initialize service container for dependency injection
59+
let container = bootstrap::Container::new();
60+
5761
match cli.command {
5862
Some(command) => {
59-
if let Err(e) = presentation::execute(command, &cli.global.working_dir) {
60-
presentation::handle_error(&e);
63+
if let Err(e) =
64+
presentation::execute(command, &cli.global.working_dir, &container.user_output())
65+
{
66+
presentation::handle_error(&e, &container.user_output());
6167
std::process::exit(1);
6268
}
6369
}

src/bootstrap/container.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//! Application Service Container
2+
//!
3+
//! This module provides centralized initialization of application-wide services
4+
//! that need consistent configuration across the entire application.
5+
6+
use std::sync::{Arc, Mutex};
7+
8+
use crate::presentation::commands::constants::DEFAULT_VERBOSITY;
9+
use crate::presentation::user_output::UserOutput;
10+
11+
/// Application service container
12+
///
13+
/// Holds shared services initialized during application bootstrap.
14+
/// Services are wrapped in `Arc<Mutex<T>>` for thread-safe shared ownership
15+
/// with interior mutability across the application.
16+
///
17+
/// # Example
18+
///
19+
/// ```rust
20+
/// use torrust_tracker_deployer_lib::bootstrap::container::Container;
21+
///
22+
/// let container = Container::new();
23+
/// let user_output = container.user_output();
24+
/// user_output.lock().unwrap().success("Operation completed");
25+
/// ```
26+
#[derive(Clone)]
27+
pub struct Container {
28+
user_output: Arc<Mutex<UserOutput>>,
29+
}
30+
31+
impl Container {
32+
/// Create a new container with initialized services
33+
///
34+
/// Uses `DEFAULT_VERBOSITY` for user output. In the future, this may
35+
/// accept a verbosity parameter from CLI flags.
36+
#[must_use]
37+
pub fn new() -> Self {
38+
let user_output = Arc::new(Mutex::new(UserOutput::new(DEFAULT_VERBOSITY)));
39+
40+
Self { user_output }
41+
}
42+
43+
/// Get shared reference to user output service
44+
///
45+
/// Returns an `Arc<Mutex<UserOutput>>` that can be cheaply cloned and shared
46+
/// across threads and function calls. Lock the mutex to access the user output.
47+
///
48+
/// # Example
49+
///
50+
/// ```rust
51+
/// use torrust_tracker_deployer_lib::bootstrap::container::Container;
52+
///
53+
/// let container = Container::new();
54+
/// let user_output = container.user_output();
55+
/// user_output.lock().unwrap().success("Operation completed");
56+
/// ```
57+
#[must_use]
58+
pub fn user_output(&self) -> Arc<Mutex<UserOutput>> {
59+
Arc::clone(&self.user_output)
60+
}
61+
}
62+
63+
impl Default for Container {
64+
fn default() -> Self {
65+
Self::new()
66+
}
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
73+
#[test]
74+
fn it_should_create_container_with_user_output() {
75+
let container = Container::new();
76+
let user_output = container.user_output();
77+
78+
// Verify we can get the user_output service
79+
assert!(Arc::strong_count(&user_output) >= 1);
80+
}
81+
82+
#[test]
83+
fn it_should_return_cloned_arc_on_user_output_access() {
84+
let container = Container::new();
85+
let user_output1 = container.user_output();
86+
let user_output2 = container.user_output();
87+
88+
// Both should point to the same UserOutput instance
89+
assert!(Arc::ptr_eq(&user_output1, &user_output2));
90+
}
91+
92+
#[test]
93+
fn it_should_be_clonable() {
94+
let container1 = Container::new();
95+
let container2 = container1.clone();
96+
97+
let user_output1 = container1.user_output();
98+
let user_output2 = container2.user_output();
99+
100+
// Cloned containers should share the same UserOutput
101+
assert!(Arc::ptr_eq(&user_output1, &user_output2));
102+
}
103+
}

src/bootstrap/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
//! Bootstrap Module
22
//!
33
//! This module contains application initialization and bootstrap concerns.
4-
//! It handles application lifecycle, logging setup, and help display.
4+
//! It handles application lifecycle, logging setup, help display, and service container.
55
//!
66
//! ## Modules
77
//!
88
//! - `app` - Main application bootstrap and entry point logic
9+
//! - `container` - Application service container for dependency injection
910
//! - `help` - Help and usage information display
1011
//! - `logging` - Logging configuration and initialization
1112
1213
pub mod app;
14+
pub mod container;
1315
pub mod help;
1416
pub mod logging;
1517

1618
// Re-export commonly used types for convenience
19+
pub use container::Container;
1720
pub use logging::{LogFormat, LogOutput, LoggingBuilder, LoggingConfig};

0 commit comments

Comments
 (0)