Skip to main content

torrust_tracker_deployer_lib/presentation/cli/
errors.rs

1//! Presentation Layer Error Types
2//!
3//! This module defines unified error handling for CLI commands following the error
4//! handling conventions documented in docs/contributing/error-handling.md.
5//!
6//! ## Design Principles
7//!
8//! - **Clarity**: Unambiguous error messages with specific context
9//! - **Traceability**: Full error chains preserved for debugging  
10//! - **Actionability**: Clear instructions for resolution
11//! - **Unified Structure**: Single `CommandError` enum containing all command-specific errors
12//!
13//! ## Error Hierarchy
14//!
15//! ```text
16//! CommandError
17//! └── Destroy(DestroyError)       # Destroy command errors
18//! └── Exists(ExistsSubcommandError) # Exists command errors
19//! ```
20
21use thiserror::Error;
22
23use crate::presentation::cli::controllers::{
24    configure::ConfigureSubcommandError, create::CreateCommandError,
25    destroy::DestroySubcommandError, docs::DocsCommandError, exists::ExistsSubcommandError,
26    list::ListSubcommandError, provision::ProvisionSubcommandError, purge::PurgeSubcommandError,
27    register::errors::RegisterSubcommandError, release::ReleaseSubcommandError,
28    render::errors::RenderCommandError, run::RunSubcommandError, show::ShowSubcommandError,
29    test::TestSubcommandError, validate::errors::ValidateSubcommandError,
30};
31
32/// Errors that can occur during CLI command execution
33///
34/// This enum provides a unified interface for all command-specific errors,
35/// following the project's error handling conventions with structured error
36/// types, source preservation, and tiered help system support.
37#[derive(Debug, Error)]
38pub enum CommandError {
39    /// Create command specific errors
40    ///
41    /// Encapsulates all errors that can occur during create operations (environment or template).
42    /// Use `.help()` for detailed troubleshooting steps.
43    #[error("Create command failed: {0}")]
44    Create(Box<CreateCommandError>),
45
46    /// Destroy command specific errors
47    ///
48    /// Encapsulates all errors that can occur during environment destruction.
49    /// Use `.help()` for detailed troubleshooting steps.
50    #[error("Destroy command failed: {0}")]
51    Destroy(Box<DestroySubcommandError>),
52
53    /// Docs command specific errors
54    ///
55    /// Encapsulates all errors that can occur during CLI documentation generation.
56    /// Use `.help()` for detailed troubleshooting steps.
57    #[error("Docs command failed: {0}")]
58    Docs(Box<DocsCommandError>),
59
60    /// Provision command specific errors
61    ///
62    /// Encapsulates all errors that can occur during infrastructure provisioning.
63    /// Use `.help()` for detailed troubleshooting steps.
64    #[error("Provision command failed: {0}")]
65    Provision(Box<ProvisionSubcommandError>),
66
67    /// Configure command specific errors
68    ///
69    /// Encapsulates all errors that can occur during environment configuration.
70    /// Use `.help()` for detailed troubleshooting steps.
71    #[error("Configure command failed: {0}")]
72    Configure(Box<ConfigureSubcommandError>),
73
74    /// Test command specific errors
75    ///
76    /// Encapsulates all errors that can occur during infrastructure validation.
77    /// Use `.help()` for detailed troubleshooting steps.
78    #[error("Test command failed: {0}")]
79    Test(Box<TestSubcommandError>),
80
81    /// Register command specific errors
82    ///
83    /// Encapsulates all errors that can occur during instance registration.
84    /// Use `.help()` for detailed troubleshooting steps.
85    #[error("Register command failed: {0}")]
86    Register(Box<RegisterSubcommandError>),
87
88    /// Release command specific errors
89    ///
90    /// Encapsulates all errors that can occur during software release operations.
91    /// Use `.help()` for detailed troubleshooting steps.
92    #[error("Release command failed: {0}")]
93    Release(Box<ReleaseSubcommandError>),
94
95    /// Render command specific errors
96    ///
97    /// Encapsulates all errors that can occur during artifact generation.
98    /// Use `.help()` for detailed troubleshooting steps.
99    #[error("Render command failed: {0}")]
100    Render(Box<RenderCommandError>),
101
102    /// Run command specific errors
103    ///
104    /// Encapsulates all errors that can occur during stack execution.
105    /// Use `.help()` for detailed troubleshooting steps.
106    #[error("Run command failed: {0}")]
107    Run(Box<RunSubcommandError>),
108
109    /// Show command specific errors
110    ///
111    /// Encapsulates all errors that can occur during environment information display.
112    /// Use `.help()` for detailed troubleshooting steps.
113    #[error("Show command failed: {0}")]
114    Show(Box<ShowSubcommandError>),
115
116    /// Exists command specific errors
117    ///
118    /// Encapsulates all errors that can occur during environment existence check.
119    /// Use `.help()` for detailed troubleshooting steps.
120    #[error("Exists command failed: {0}")]
121    Exists(Box<ExistsSubcommandError>),
122
123    /// List command specific errors
124    ///
125    /// Encapsulates all errors that can occur during environment listing.
126    /// Use `.help()` for detailed troubleshooting steps.
127    #[error("List command failed: {0}")]
128    List(Box<ListSubcommandError>),
129
130    /// Purge command specific errors
131    ///
132    /// Encapsulates all errors that can occur during local environment data removal.
133    /// Use `.help()` for detailed troubleshooting steps.
134    #[error("Purge command failed: {0}")]
135    Purge(Box<PurgeSubcommandError>),
136
137    /// Validate command specific errors
138    ///
139    /// Encapsulates all errors that can occur during configuration validation.
140    /// Use `.help()` for detailed troubleshooting steps.
141    #[error("Validate command failed: {0}")]
142    Validate(Box<ValidateSubcommandError>),
143
144    /// User output lock acquisition failed
145    ///
146    /// Failed to acquire the mutex lock for user output. This typically indicates
147    /// a panic occurred in another thread while holding the lock.
148    #[error("Failed to acquire user output lock - a panic occurred in another thread while displaying output")]
149    UserOutputLockFailed,
150}
151
152impl From<CreateCommandError> for CommandError {
153    fn from(error: CreateCommandError) -> Self {
154        Self::Create(Box::new(error))
155    }
156}
157
158impl From<DestroySubcommandError> for CommandError {
159    fn from(error: DestroySubcommandError) -> Self {
160        Self::Destroy(Box::new(error))
161    }
162}
163
164impl From<DocsCommandError> for CommandError {
165    fn from(error: DocsCommandError) -> Self {
166        Self::Docs(Box::new(error))
167    }
168}
169
170impl From<ProvisionSubcommandError> for CommandError {
171    fn from(error: ProvisionSubcommandError) -> Self {
172        Self::Provision(Box::new(error))
173    }
174}
175
176impl From<ConfigureSubcommandError> for CommandError {
177    fn from(error: ConfigureSubcommandError) -> Self {
178        Self::Configure(Box::new(error))
179    }
180}
181
182impl From<RegisterSubcommandError> for CommandError {
183    fn from(error: RegisterSubcommandError) -> Self {
184        Self::Register(Box::new(error))
185    }
186}
187
188impl From<TestSubcommandError> for CommandError {
189    fn from(error: TestSubcommandError) -> Self {
190        Self::Test(Box::new(error))
191    }
192}
193
194impl From<ReleaseSubcommandError> for CommandError {
195    fn from(error: ReleaseSubcommandError) -> Self {
196        Self::Release(Box::new(error))
197    }
198}
199
200impl From<RenderCommandError> for CommandError {
201    fn from(error: RenderCommandError) -> Self {
202        Self::Render(Box::new(error))
203    }
204}
205
206impl From<RunSubcommandError> for CommandError {
207    fn from(error: RunSubcommandError) -> Self {
208        Self::Run(Box::new(error))
209    }
210}
211
212impl From<ShowSubcommandError> for CommandError {
213    fn from(error: ShowSubcommandError) -> Self {
214        Self::Show(Box::new(error))
215    }
216}
217
218impl From<ExistsSubcommandError> for CommandError {
219    fn from(error: ExistsSubcommandError) -> Self {
220        Self::Exists(Box::new(error))
221    }
222}
223
224impl From<ListSubcommandError> for CommandError {
225    fn from(error: ListSubcommandError) -> Self {
226        Self::List(Box::new(error))
227    }
228}
229
230impl From<PurgeSubcommandError> for CommandError {
231    fn from(error: PurgeSubcommandError) -> Self {
232        Self::Purge(Box::new(error))
233    }
234}
235
236impl From<ValidateSubcommandError> for CommandError {
237    fn from(error: ValidateSubcommandError) -> Self {
238        Self::Validate(Box::new(error))
239    }
240}
241
242impl CommandError {
243    /// Get detailed troubleshooting guidance for this error
244    ///
245    /// This method provides comprehensive troubleshooting steps that can be
246    /// displayed to users when they need more help resolving the error.
247    /// It delegates to the specific command error's help method.
248    ///
249    /// # Example
250    ///
251    /// ```rust
252    /// use clap::Parser;
253    /// use torrust_tracker_deployer_lib::presentation::cli::errors;
254    /// use torrust_tracker_deployer_lib::presentation::cli::controllers::destroy::DestroySubcommandError;
255    /// use torrust_tracker_deployer_lib::application::command_handlers::destroy::DestroyCommandHandlerError;
256    /// use std::path::PathBuf;
257    ///
258    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
259    /// // Create error for demonstration
260    /// let destroy_error = DestroySubcommandError::DestroyOperationFailed {
261    ///     name: "test-env".to_string(),
262    ///     source: DestroyCommandHandlerError::StateCleanupFailed {
263    ///         path: PathBuf::from("/tmp/test"),
264    ///         source: std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied"),
265    ///     },
266    /// };
267    /// let error = errors::CommandError::Destroy(Box::new(destroy_error));
268    ///
269    /// // Get help text
270    /// let help_text = error.help();
271    /// println!("{}", help_text);
272    /// # Ok(())
273    /// # }
274    /// ```
275    #[must_use]
276    pub fn help(&self) -> String {
277        match self {
278            Self::Create(e) => e.help(),
279            Self::Destroy(e) => e.help().to_string(),
280            Self::Docs(e) => e.help(),
281            Self::Provision(e) => e.help().to_string(),
282            Self::Configure(e) => e.help().to_string(),
283            Self::Register(e) => e.help().to_string(),
284            Self::Test(e) => e.as_ref().help().to_string(),
285            Self::Release(e) => e.help().to_string(),
286            Self::Render(e) => e
287                .help()
288                .unwrap_or_else(|| "No additional help available".to_string()),
289            Self::Run(e) => e.help().to_string(),
290            Self::Show(e) => e.help().to_string(),
291            Self::Exists(e) => e.help().to_string(),
292            Self::List(e) => e.help().to_string(),
293            Self::Purge(e) => e.help().to_string(),
294            Self::Validate(e) => e
295                .help()
296                .unwrap_or_else(|| "No additional help available".to_string()),
297            Self::UserOutputLockFailed => "User Output Lock Failed - Detailed Troubleshooting:
298
299This error indicates that a panic occurred in another thread while it was using
300the user output system, leaving the mutex in a \"poisoned\" state.
301
3021. Check for any error messages that appeared before this one
303   - The original panic message should appear earlier in the output
304   - This will indicate what caused the initial failure
305
3062. This is typically caused by:
307   - A bug in the application code that caused a panic
308   - An unhandled error condition that triggered a panic
309   - Resource exhaustion (memory, file handles, etc.)
310
3113. If you can reproduce this issue:
312   - Run with --verbose to see more detailed logging
313   - Report the issue with the full error output and steps to reproduce
314
315This is a serious application error that indicates a bug. Please report it to the developers."
316                .to_string(),
317        }
318    }
319}