Skip to content

Commit a764bcf

Browse files
committed
refactor: [#162] decouple template and environment error types with unified CreateCommandError
- Create dedicated CreateEnvironmentTemplateCommandError for template commands - Implement unified CreateCommandError wrapper with transparent error delegation - Update template handler to use template-specific error type with proper boxing - Modify router to map errors correctly via CreateCommandError variants - Update all tests to handle unified error pattern matching - Preserve help() method delegation and error chain integrity - Fix clippy issues: add backticks around UserOutput, use std::io::Error::other This completes the error architecture decoupling requested, giving each command its own error type while maintaining a unified interface for the create command.
1 parent e725f82 commit a764bcf

File tree

9 files changed

+270
-21
lines changed

9 files changed

+270
-21
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! Unified Create Command Errors
2+
//!
3+
//! This module defines a unified error type that encompasses all create subcommand errors,
4+
//! providing a single interface for environment and template command errors.
5+
6+
use thiserror::Error;
7+
8+
use super::subcommands::{
9+
environment::CreateEnvironmentCommandError, template::CreateEnvironmentTemplateCommandError,
10+
};
11+
12+
/// Unified error type for all create subcommands
13+
///
14+
/// This error type provides a unified interface for errors that can occur during
15+
/// any create subcommand execution (environment creation or template generation).
16+
/// It wraps the specific command errors while preserving their context and help methods.
17+
#[derive(Debug, Error)]
18+
pub enum CreateCommandError {
19+
/// Environment creation errors
20+
#[error(transparent)]
21+
Environment(#[from] CreateEnvironmentCommandError),
22+
23+
/// Template generation errors
24+
#[error(transparent)]
25+
Template(#[from] CreateEnvironmentTemplateCommandError),
26+
}
27+
28+
impl CreateCommandError {
29+
/// Get detailed troubleshooting guidance for this error
30+
///
31+
/// This method delegates to the specific command error's help method,
32+
/// providing context-appropriate troubleshooting guidance.
33+
#[must_use]
34+
pub fn help(&self) -> &'static str {
35+
match self {
36+
Self::Environment(err) => err.help(),
37+
Self::Template(err) => err.help(),
38+
}
39+
}
40+
}

src/presentation/controllers/create/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//! - `router` - Main command router routing between subcommands
1717
//! - `subcommands` - Individual subcommand implementations (environment, template)
1818
//! - `environment` - Contains environment creation logic, error types, and config loading
19+
//! - `errors` - Unified error types for all create subcommands
1920
//!
2021
//! ## Usage Example
2122
//!
@@ -38,12 +39,15 @@
3839
//! }
3940
//! ```
4041
42+
pub mod errors;
4143
pub mod router;
4244
pub mod subcommands;
4345

4446
#[cfg(test)]
4547
mod tests;
4648

4749
// Re-export commonly used types for convenience
50+
pub use errors::CreateCommandError;
4851
pub use router::route_command;
4952
pub use subcommands::environment::{ConfigFormat, ConfigLoader, CreateEnvironmentCommandError};
53+
pub use subcommands::template::CreateEnvironmentTemplateCommandError;

src/presentation/controllers/create/router.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ use std::path::Path;
88
use crate::presentation::dispatch::ExecutionContext;
99
use crate::presentation::input::cli::commands::CreateAction;
1010

11-
use super::subcommands;
12-
use super::subcommands::environment::CreateEnvironmentCommandError;
11+
use super::{errors::CreateCommandError, subcommands};
1312

1413
/// Route the create command to its appropriate subcommand
1514
///
@@ -23,7 +22,7 @@ use super::subcommands::environment::CreateEnvironmentCommandError;
2322
///
2423
/// # Returns
2524
///
26-
/// Returns `Ok(())` on success, or a `CreateEnvironmentCommandError` on failure.
25+
/// Returns `Ok(())` on success, or a `CreateCommandError` on failure.
2726
///
2827
/// # Errors
2928
///
@@ -33,14 +32,16 @@ pub fn route_command(
3332
action: CreateAction,
3433
working_dir: &Path,
3534
context: &ExecutionContext,
36-
) -> Result<(), CreateEnvironmentCommandError> {
35+
) -> Result<(), CreateCommandError> {
3736
match action {
3837
CreateAction::Environment { env_file } => {
3938
subcommands::handle_environment_creation(&env_file, working_dir, context)
39+
.map_err(CreateCommandError::Environment)
4040
}
4141
CreateAction::Template { output_path } => {
4242
let template_path = output_path.unwrap_or_else(CreateAction::default_template_path);
4343
subcommands::handle_template_generation(&template_path, context)
44+
.map_err(CreateCommandError::Template)
4445
}
4546
}
4647
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,188 @@
1+
//! Template Command Errors
2+
//!
3+
//! This module defines error types specific to template generation commands.
4+
//! These errors provide detailed context and actionable guidance for template-related failures.
15
6+
use std::path::PathBuf;
7+
use thiserror::Error;
8+
9+
/// Errors that can occur during template generation commands
10+
///
11+
/// This error type covers all failure scenarios that can occur during template
12+
/// generation operations, including file I/O errors, validation failures, and
13+
/// system-level issues.
14+
///
15+
/// All variants include structured context to aid in debugging and provide
16+
/// actionable guidance to users through the `.help()` method.
17+
#[derive(Debug, Error)]
18+
pub enum CreateEnvironmentTemplateCommandError {
19+
/// Failed to acquire lock on `UserOutput` for displaying progress/results
20+
///
21+
/// This error occurs when the `UserOutput` mutex is poisoned, typically indicating
22+
/// that another thread panicked while holding the output lock. This is a system-level
23+
/// error that should be rare in normal operation.
24+
#[error(
25+
"Failed to acquire output lock for displaying template generation progress
26+
Tip: This indicates a system error - try restarting the command"
27+
)]
28+
UserOutputLockFailed,
29+
30+
/// Template file generation failed
31+
///
32+
/// This error occurs when the underlying template generation operation fails,
33+
/// which could be due to file system permissions, disk space, or template
34+
/// processing errors.
35+
#[error(
36+
"Failed to generate configuration template at '{path}': {source}
37+
Tip: Check directory permissions and available disk space"
38+
)]
39+
TemplateGenerationFailed {
40+
/// Path where template generation was attempted
41+
path: PathBuf,
42+
/// The underlying error that caused template generation to fail
43+
#[source]
44+
source: Box<dyn std::error::Error + Send + Sync>,
45+
},
46+
}
47+
48+
impl CreateEnvironmentTemplateCommandError {
49+
/// Get detailed troubleshooting guidance for this error
50+
///
51+
/// This method provides comprehensive troubleshooting steps that can be
52+
/// displayed to users when they need more help resolving the error.
53+
///
54+
/// # Example
55+
///
56+
/// ```rust
57+
/// use torrust_tracker_deployer_lib::presentation::controllers::create::subcommands::template::CreateEnvironmentTemplateCommandError;
58+
///
59+
/// fn handle_error(error: CreateEnvironmentTemplateCommandError) {
60+
/// eprintln!("Error: {error}");
61+
/// eprintln!("\nTroubleshooting:\n{}", error.help());
62+
/// }
63+
/// ```
64+
#[must_use]
65+
pub fn help(&self) -> &'static str {
66+
match self {
67+
Self::UserOutputLockFailed => {
68+
"User Output Lock Failed - Detailed Troubleshooting:
69+
70+
1. This is a rare system-level error that occurs when another thread panics
71+
while displaying output to the user.
72+
73+
2. Try the following steps:
74+
- Restart the command
75+
- If the error persists, restart your terminal
76+
- Check for any background processes that might interfere
77+
78+
3. If the problem continues:
79+
- Check system resources (memory, disk space)
80+
- Consider running with --verbose for more detailed error information
81+
- Report the issue if it's reproducible
82+
83+
For more information, see the troubleshooting guide."
84+
}
85+
86+
Self::TemplateGenerationFailed { .. } => {
87+
"Template Generation Failed - Detailed Troubleshooting:
88+
89+
1. Check directory permissions:
90+
- Ensure write permissions for the target directory
91+
- Verify parent directory exists
92+
- Check ownership of the target path
93+
94+
2. Verify available resources:
95+
- Check available disk space: df -h (Unix) or dir (Windows)
96+
- Ensure sufficient memory is available
97+
98+
3. File system issues:
99+
- Check if the path contains invalid characters
100+
- Verify the path length is within system limits
101+
- Ensure no other process is using the target file
102+
103+
4. Template processing issues:
104+
- Check if required template data is available
105+
- Verify template format is valid
106+
- Look for any corrupted template files
107+
108+
If the problem persists, run with --verbose for detailed logs and report
109+
the issue with system details."
110+
}
111+
}
112+
}
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use super::*;
118+
use std::error::Error;
119+
use std::path::PathBuf;
120+
121+
#[test]
122+
fn it_should_display_user_output_lock_failed_error() {
123+
let error = CreateEnvironmentTemplateCommandError::UserOutputLockFailed;
124+
let message = error.to_string();
125+
126+
assert!(message.contains("Failed to acquire output lock"));
127+
assert!(message.contains("Tip: This indicates a system error"));
128+
}
129+
130+
#[test]
131+
fn it_should_display_template_generation_failed_error() {
132+
let path = PathBuf::from("/test/template.json");
133+
let source: Box<dyn std::error::Error + Send + Sync> = Box::new(std::io::Error::new(
134+
std::io::ErrorKind::PermissionDenied,
135+
"Access denied",
136+
));
137+
138+
let error = CreateEnvironmentTemplateCommandError::TemplateGenerationFailed {
139+
path: path.clone(),
140+
source,
141+
};
142+
let message = error.to_string();
143+
144+
assert!(message.contains("Failed to generate configuration template"));
145+
assert!(message.contains("/test/template.json"));
146+
assert!(message.contains("Tip: Check directory permissions"));
147+
}
148+
149+
#[test]
150+
fn it_should_have_help_for_all_variants() {
151+
let errors = vec![
152+
CreateEnvironmentTemplateCommandError::UserOutputLockFailed,
153+
CreateEnvironmentTemplateCommandError::TemplateGenerationFailed {
154+
path: PathBuf::from("/test"),
155+
source: Box::new(std::io::Error::other("test")),
156+
},
157+
];
158+
159+
for error in errors {
160+
let help = error.help();
161+
assert!(!help.is_empty(), "Help text should not be empty");
162+
assert!(
163+
help.contains("Troubleshooting") || help.len() > 50,
164+
"Help should contain actionable guidance"
165+
);
166+
}
167+
}
168+
169+
#[test]
170+
fn it_should_preserve_error_chain() {
171+
let source_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
172+
let error = CreateEnvironmentTemplateCommandError::TemplateGenerationFailed {
173+
path: PathBuf::from("/test"),
174+
source: Box::new(source_error),
175+
};
176+
177+
// Check that source error is preserved
178+
assert!(error.source().is_some(), "Source error should be preserved");
179+
180+
// Check error chain
181+
let chain = std::error::Error::source(&error);
182+
assert!(chain.is_some(), "Error chain should exist");
183+
184+
if let Some(source) = chain {
185+
assert!(source.to_string().contains("File not found"));
186+
}
187+
}
188+
}

src/presentation/controllers/create/subcommands/template/handler.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
use std::path::Path;
77

88
use crate::application::command_handlers::create::config::EnvironmentCreationConfig;
9-
use crate::presentation::controllers::create::subcommands::environment::CreateEnvironmentCommandError;
109
use crate::presentation::dispatch::ExecutionContext;
1110

11+
use super::errors::CreateEnvironmentTemplateCommandError;
12+
1213
/// Handle template generation
1314
///
1415
/// This function generates a configuration template file with placeholder values
@@ -21,7 +22,7 @@ use crate::presentation::dispatch::ExecutionContext;
2122
///
2223
/// # Returns
2324
///
24-
/// Returns `Ok(())` on success, or a `CreateEnvironmentCommandError` on failure.
25+
/// Returns `Ok(())` on success, or a `CreateEnvironmentTemplateCommandError` on failure.
2526
///
2627
/// # Errors
2728
///
@@ -35,12 +36,12 @@ use crate::presentation::dispatch::ExecutionContext;
3536
pub fn handle_template_generation(
3637
output_path: &Path,
3738
context: &ExecutionContext,
38-
) -> Result<(), CreateEnvironmentCommandError> {
39+
) -> Result<(), CreateEnvironmentTemplateCommandError> {
3940
// Lock user output for progress messages
4041
let user_output = context.user_output();
4142
let mut output = user_output
4243
.lock()
43-
.map_err(|_| CreateEnvironmentCommandError::UserOutputLockFailed)?;
44+
.map_err(|_| CreateEnvironmentTemplateCommandError::UserOutputLockFailed)?;
4445

4546
output.progress("Generating configuration template...");
4647

@@ -52,7 +53,10 @@ pub fn handle_template_generation(
5253
EnvironmentCreationConfig::generate_template_file(output_path)
5354
.await
5455
.map_err(
55-
|source| CreateEnvironmentCommandError::TemplateGenerationFailed { source },
56+
|source| CreateEnvironmentTemplateCommandError::TemplateGenerationFailed {
57+
path: output_path.to_path_buf(),
58+
source: Box::new(source),
59+
},
5660
)
5761
})?;
5862

src/presentation/controllers/create/subcommands/template/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ pub mod handler;
88

99
// Re-export the main handler function
1010
pub use handler::handle_template_generation;
11+
12+
// Re-export error types
13+
pub use errors::CreateEnvironmentTemplateCommandError;

0 commit comments

Comments
 (0)