Skip to content

Commit 2f6a868

Browse files
committed
refactor: move handle_error to dedicated error module
- Create new src/presentation/error.rs module for error handling - Move handle_error function from dispatch to error module - Update module exports and imports for better separation of concerns - Clean up dispatch module to focus on command routing - Improve architecture with dedicated error handling space
1 parent 1fe82de commit 2f6a868

File tree

5 files changed

+117
-199
lines changed

5 files changed

+117
-199
lines changed

src/bootstrap/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub fn run() {
6666
if let Err(e) =
6767
presentation::dispatch::route_command(command, &cli.global.working_dir, &context)
6868
{
69-
presentation::handle_error(&e, &context.user_output());
69+
presentation::error::handle_error(&e, &context.user_output());
7070
std::process::exit(1);
7171
}
7272
}

src/presentation/commands/mod.rs

Lines changed: 4 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
//! Command Handlers Module
22
//!
3-
//! This module provides unified command execution and error handling for all CLI commands.
4-
//! It serves as the central dispatch point for command execution and provides consistent
5-
//! error handling across all commands.
6-
7-
use std::sync::{Arc, Mutex};
8-
9-
use crate::presentation::errors::CommandError;
10-
use crate::presentation::input::cli::Commands;
11-
use crate::presentation::user_output::UserOutput;
3+
//! This module provides command execution and error handling for CLI commands.
4+
//!
5+
//! **Note**: The main execution logic has been moved to the Dispatch Layer.
6+
//! See `crate::presentation::dispatch` for the current command routing implementation.
127
138
// Re-export command modules
149
pub mod constants;
@@ -25,187 +20,3 @@ pub mod tests;
2520
// pub mod provision;
2621
// pub mod configure;
2722
// pub mod release;
28-
29-
/// Execute the given command
30-
///
31-
/// **DEPRECATED**: This function is deprecated in favor of the new Dispatch Layer.
32-
/// Use `crate::presentation::dispatch::route_command` instead.
33-
///
34-
/// This function will be removed in a future version. The new dispatch layer
35-
/// provides better separation of concerns and cleaner architecture.
36-
///
37-
/// # Migration Guide
38-
///
39-
/// Old code:
40-
/// ```rust,ignore
41-
/// use std::sync::{Arc, Mutex};
42-
/// use crate::presentation::{commands, user_output::UserOutput};
43-
///
44-
/// let user_output = Arc::new(Mutex::new(UserOutput::new(/* ... */)));
45-
/// commands::execute(command, working_dir, &user_output)?;
46-
/// ```
47-
///
48-
/// New code:
49-
/// ```rust,ignore
50-
/// use std::sync::Arc;
51-
/// use crate::bootstrap::Container;
52-
/// use crate::presentation::dispatch::{route_command, ExecutionContext};
53-
///
54-
/// let container = Arc::new(Container::new());
55-
/// let context = ExecutionContext::new(container);
56-
/// route_command(command, working_dir, &context)?;
57-
/// ```
58-
///
59-
/// This function serves as the central dispatcher for all CLI commands.
60-
/// It matches the command type and delegates execution to the appropriate
61-
/// command handler module.
62-
///
63-
/// # Arguments
64-
///
65-
/// * `command` - The parsed CLI command to execute
66-
/// * `working_dir` - Working directory for environment data storage
67-
/// * `user_output` - Shared user output service for consistent output formatting
68-
///
69-
/// # Returns
70-
///
71-
/// Returns `Ok(())` on successful execution, or a `CommandError` if execution fails.
72-
/// The error contains detailed context and actionable troubleshooting information.
73-
///
74-
/// # Errors
75-
///
76-
/// Returns an error if command execution fails.
77-
///
78-
/// # Example
79-
///
80-
/// ```rust
81-
/// use clap::Parser;
82-
/// use torrust_tracker_deployer_lib::presentation::{input::cli, commands, user_output};
83-
/// use std::{path::Path, sync::{Arc, Mutex}};
84-
///
85-
/// let cli = cli::Cli::parse();
86-
/// if let Some(command) = cli.command {
87-
/// let working_dir = Path::new(".");
88-
/// let user_output = Arc::new(Mutex::new(user_output::UserOutput::new(user_output::VerbosityLevel::Normal)));
89-
/// let result = commands::execute(command, working_dir, &user_output);
90-
/// match result {
91-
/// Ok(_) => println!("Command executed successfully"),
92-
/// Err(e) => commands::handle_error(&e, &user_output),
93-
/// }
94-
/// }
95-
/// ```
96-
#[deprecated(
97-
since = "0.1.0",
98-
note = "Use `crate::presentation::dispatch::route_command` instead"
99-
)]
100-
///
101-
/// ```rust
102-
/// use clap::Parser;
103-
/// use torrust_tracker_deployer_lib::presentation::{input::cli, commands, user_output};
104-
/// use std::{path::Path, sync::{Arc, Mutex}};
105-
///
106-
/// let cli = cli::Cli::parse();
107-
/// if let Some(command) = cli.command {
108-
/// let working_dir = Path::new(".");
109-
/// let user_output = Arc::new(Mutex::new(user_output::UserOutput::new(user_output::VerbosityLevel::Normal)));
110-
/// let result = commands::execute(command, working_dir, &user_output);
111-
/// match result {
112-
/// Ok(_) => println!("Command executed successfully"),
113-
/// Err(e) => commands::handle_error(&e, &user_output),
114-
/// }
115-
/// }
116-
/// ```
117-
pub fn execute(
118-
command: Commands,
119-
working_dir: &std::path::Path,
120-
user_output: &Arc<Mutex<UserOutput>>,
121-
) -> Result<(), CommandError> {
122-
match command {
123-
Commands::Create { action } => {
124-
create::handle_create_command(action, working_dir, user_output)?;
125-
Ok(())
126-
}
127-
Commands::Destroy { environment } => {
128-
destroy::handle_destroy_command(&environment, working_dir, user_output)?;
129-
Ok(())
130-
} // Future commands will be added here:
131-
//
132-
// Commands::Provision { environment, provider } => {
133-
// provision::handle(&environment, &provider)?;
134-
// Ok(())
135-
// }
136-
//
137-
// Commands::Configure { environment } => {
138-
// configure::handle(&environment)?;
139-
// Ok(())
140-
// }
141-
//
142-
// Commands::Release { environment, version } => {
143-
// release::handle(&environment, &version)?;
144-
// Ok(())
145-
// }
146-
}
147-
}
148-
149-
/// Handle command errors with consistent user output
150-
///
151-
/// This function provides standardized error output for all command failures.
152-
/// It displays the error message and detailed troubleshooting information
153-
/// to help users resolve issues.
154-
///
155-
/// # Arguments
156-
///
157-
/// * `error` - The command error to handle and display
158-
/// * `user_output` - Shared user output service for consistent output formatting
159-
///
160-
/// # Example
161-
///
162-
/// ```rust
163-
/// use clap::Parser;
164-
/// use torrust_tracker_deployer_lib::presentation::{commands, input::cli, errors, user_output};
165-
/// use torrust_tracker_deployer_lib::presentation::commands::destroy::DestroySubcommandError;
166-
/// use torrust_tracker_deployer_lib::domain::environment::name::EnvironmentNameError;
167-
/// use std::sync::{Arc, Mutex};
168-
///
169-
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
170-
/// // Example of handling a command error (simulated for testing)
171-
/// let name_error = EnvironmentNameError::InvalidFormat {
172-
/// attempted_name: "invalid_name".to_string(),
173-
/// reason: "contains invalid characters: _".to_string(),
174-
/// valid_examples: vec!["dev".to_string(), "staging".to_string()],
175-
/// };
176-
/// let sample_error = errors::CommandError::Destroy(
177-
/// Box::new(DestroySubcommandError::InvalidEnvironmentName {
178-
/// name: "invalid_name".to_string(),
179-
/// source: name_error,
180-
/// })
181-
/// );
182-
/// let user_output = Arc::new(Mutex::new(user_output::UserOutput::new(user_output::VerbosityLevel::Normal)));
183-
/// commands::handle_error(&sample_error, &user_output);
184-
/// # Ok(())
185-
/// # }
186-
/// ```
187-
pub fn handle_error(error: &CommandError, user_output: &Arc<Mutex<UserOutput>>) {
188-
let help_text = error.help();
189-
190-
if let Ok(mut output) = user_output.lock() {
191-
output.error(&format!("{error}"));
192-
output.blank_line();
193-
output.info_block("For detailed troubleshooting:", &[help_text]);
194-
} else {
195-
// Cannot acquire lock - print to stderr directly as fallback
196-
//
197-
// RATIONALE: Plain text formatting without emojis/styling is intentional.
198-
// When the mutex is poisoned, we're in a degraded error state where another
199-
// thread has panicked. Using plain eprintln! ensures maximum compatibility
200-
// and avoids any additional complexity that could fail in this critical path.
201-
// The goal here is reliability over aesthetics - get the error message to
202-
// the user no matter what, even if it's not pretty.
203-
eprintln!("ERROR: {error}");
204-
eprintln!();
205-
eprintln!("CRITICAL: Failed to acquire user output lock.");
206-
eprintln!("This indicates a panic occurred in another thread.");
207-
eprintln!();
208-
eprintln!("For detailed troubleshooting:");
209-
eprintln!("{help_text}");
210-
}
211-
}

src/presentation/dispatch/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
//! # Ok(())
119119
//! # }
120120
//! ```
121+
121122
// Command routing module
122123
pub mod router;
123124

src/presentation/error.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! Error Handling Module - Presentation Layer
2+
//!
3+
//! This module provides error handling functionality for the presentation layer,
4+
//! specifically focusing on displaying errors to users in a helpful and actionable way.
5+
//!
6+
//! ## Purpose
7+
//!
8+
//! The error handling module is responsible for:
9+
//! - **User-Friendly Error Display**: Converting internal errors to readable messages
10+
//! - **Actionable Guidance**: Providing specific steps users can take to resolve issues
11+
//! - **Fallback Handling**: Ensuring error messages are displayed even in degraded states
12+
//! - **Consistent Formatting**: Maintaining consistent error output across all commands
13+
//!
14+
//! ## Design Principles
15+
//!
16+
//! - **Observability**: All errors include sufficient context for debugging
17+
//! - **Actionability**: Error messages tell users how to fix problems
18+
//! - **Reliability**: Error handling itself must not fail
19+
//! - **Consistency**: All errors follow the same display patterns
20+
//!
21+
//! ## Module Integration
22+
//!
23+
//! This module integrates with:
24+
//! - **`CommandError` Types** - Uses structured error types from `presentation::errors`
25+
//! - **`UserOutput` Service** - Leverages user output for consistent formatting
26+
//! - **Help System** - Displays detailed troubleshooting via `.help()` method
27+
//!
28+
//! ## Usage
29+
//!
30+
//! ```rust
31+
//! use std::sync::{Arc, Mutex};
32+
//! use torrust_tracker_deployer_lib::presentation::{error, user_output};
33+
//! use torrust_tracker_deployer_lib::presentation::errors::CommandError;
34+
//!
35+
//! # fn example(error: CommandError, user_output: Arc<Mutex<user_output::UserOutput>>) {
36+
//! // Display error with detailed troubleshooting
37+
//! error::handle_error(&error, &user_output);
38+
//! # }
39+
//! ```
40+
41+
use std::sync::{Arc, Mutex};
42+
43+
use crate::presentation::errors::CommandError;
44+
use crate::presentation::user_output::UserOutput;
45+
46+
/// Handle command errors with consistent user output
47+
///
48+
/// This function provides standardized error output for all command failures.
49+
/// It displays the error message and detailed troubleshooting information
50+
/// to help users resolve issues.
51+
///
52+
/// # Arguments
53+
///
54+
/// * `error` - The command error to handle and display
55+
/// * `user_output` - Shared user output service for consistent output formatting
56+
///
57+
/// # Example
58+
///
59+
/// ```rust
60+
/// use clap::Parser;
61+
/// use torrust_tracker_deployer_lib::presentation::{error, input::cli, errors, user_output};
62+
/// use torrust_tracker_deployer_lib::presentation::commands::destroy::DestroySubcommandError;
63+
/// use torrust_tracker_deployer_lib::domain::environment::name::EnvironmentNameError;
64+
/// use std::sync::{Arc, Mutex};
65+
///
66+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
67+
/// // Example of handling a command error (simulated for testing)
68+
/// let name_error = EnvironmentNameError::InvalidFormat {
69+
/// attempted_name: "invalid_name".to_string(),
70+
/// reason: "contains invalid characters: _".to_string(),
71+
/// valid_examples: vec!["dev".to_string(), "staging".to_string()],
72+
/// };
73+
/// let sample_error = errors::CommandError::Destroy(
74+
/// Box::new(DestroySubcommandError::InvalidEnvironmentName {
75+
/// name: "invalid_name".to_string(),
76+
/// source: name_error,
77+
/// })
78+
/// );
79+
/// let user_output = Arc::new(Mutex::new(user_output::UserOutput::new(user_output::VerbosityLevel::Normal)));
80+
/// error::handle_error(&sample_error, &user_output);
81+
/// # Ok(())
82+
/// # }
83+
/// ```
84+
pub fn handle_error(error: &CommandError, user_output: &Arc<Mutex<UserOutput>>) {
85+
let help_text = error.help();
86+
87+
if let Ok(mut output) = user_output.lock() {
88+
output.error(&format!("{error}"));
89+
output.blank_line();
90+
output.info_block("For detailed troubleshooting:", &[help_text]);
91+
} else {
92+
// Cannot acquire lock - print to stderr directly as fallback
93+
//
94+
// RATIONALE: Plain text formatting without emojis/styling is intentional.
95+
// When the mutex is poisoned, we're in a degraded error state where another
96+
// thread has panicked. Using plain eprintln! ensures maximum compatibility
97+
// and avoids any additional complexity that could fail in this critical path.
98+
// The goal here is reliability over aesthetics - get the error message to
99+
// the user no matter what, even if it's not pretty.
100+
eprintln!("ERROR: {error}");
101+
eprintln!();
102+
eprintln!("CRITICAL: Failed to acquire user output lock.");
103+
eprintln!("This indicates a panic occurred in another thread.");
104+
eprintln!();
105+
eprintln!("For detailed troubleshooting:");
106+
eprintln!("{help_text}");
107+
}
108+
}

src/presentation/mod.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
// Core presentation modules
4646
pub mod commands;
4747
pub mod dispatch;
48+
pub mod error;
4849
pub mod errors;
4950
pub mod input;
5051
pub mod progress;
@@ -53,12 +54,9 @@ pub mod user_output;
5354
// Re-export commonly used presentation types for convenience
5455
pub use commands::create::CreateSubcommandError;
5556
pub use commands::destroy::DestroySubcommandError;
56-
pub use commands::handle_error;
5757

58-
// Deprecated: Use dispatch layer instead
59-
#[deprecated(since = "0.1.0", note = "Use `dispatch::route_command` instead")]
60-
#[allow(deprecated)]
61-
pub use commands::execute;
58+
// Re-export error handling function from error module
59+
pub use error::handle_error;
6260

6361
pub use errors::CommandError;
6462
pub use input::{Cli, Commands, GlobalArgs};

0 commit comments

Comments
 (0)