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}