cli_testing_specialist/
error.rs

1use colored::Colorize;
2use std::path::PathBuf;
3use thiserror::Error;
4
5/// Result type alias for cli-testing-specialist
6pub type Result<T> = std::result::Result<T, CliTestError>;
7
8/// Error types for CLI testing operations
9#[derive(Error, Debug)]
10pub enum CliTestError {
11    /// Binary file not found at specified path
12    #[error("Binary not found: {0}")]
13    BinaryNotFound(PathBuf),
14
15    /// Binary file exists but is not executable
16    #[error("Binary not executable: {0}")]
17    BinaryNotExecutable(PathBuf),
18
19    /// Failed to execute the binary
20    #[error("Failed to execute binary: {0}")]
21    ExecutionFailed(String),
22
23    /// Help output is invalid or cannot be parsed
24    #[error("Invalid help output")]
25    InvalidHelpOutput,
26
27    /// Failed to parse option from help text
28    #[error("Failed to parse option: {0}")]
29    OptionParseError(String),
30
31    /// Template rendering failed
32    #[error("Template rendering failed: {0}")]
33    TemplateError(String),
34
35    /// BATS test execution failed
36    #[error("BATS execution failed: {0}")]
37    BatsExecutionFailed(String),
38
39    /// Report generation failed
40    #[error("Report generation failed: {0}")]
41    ReportError(String),
42
43    /// Configuration error
44    #[error("Configuration error: {0}")]
45    Config(String),
46
47    /// Validation error
48    #[error("Validation error: {0}")]
49    Validation(String),
50
51    /// I/O error occurred
52    #[error(transparent)]
53    IoError(#[from] std::io::Error),
54
55    /// JSON serialization/deserialization error
56    #[error(transparent)]
57    Json(#[from] serde_json::Error),
58
59    /// YAML serialization/deserialization error
60    #[error(transparent)]
61    Yaml(#[from] serde_yaml::Error),
62}
63
64// Re-export as Error for convenience
65pub use CliTestError as Error;
66
67impl CliTestError {
68    /// Get detailed error message for logging (may contain sensitive info)
69    ///
70    /// This method provides verbose error details suitable for logging
71    /// but should NOT be displayed directly to end users.
72    pub fn detailed_message(&self) -> String {
73        match self {
74            Self::BinaryNotFound(path) => {
75                format!("Binary not found at path: {}", path.display())
76            }
77            Self::BinaryNotExecutable(path) => {
78                format!("Binary at {} is not executable", path.display())
79            }
80            Self::ExecutionFailed(msg) => {
81                format!("Binary execution failed: {}", msg)
82            }
83            Self::InvalidHelpOutput => {
84                "Help output could not be parsed - ensure binary supports --help".to_string()
85            }
86            Self::OptionParseError(details) => {
87                format!("Failed to parse option: {}", details)
88            }
89            Self::TemplateError(msg) => {
90                format!("Template rendering error: {}", msg)
91            }
92            Self::BatsExecutionFailed(msg) => {
93                format!("BATS test execution failed: {}", msg)
94            }
95            Self::ReportError(msg) => {
96                format!("Report generation error: {}", msg)
97            }
98            Self::Config(msg) => {
99                format!("Configuration error: {}", msg)
100            }
101            Self::Validation(msg) => {
102                format!("Validation error: {}", msg)
103            }
104            Self::IoError(e) => {
105                format!("I/O error: {}", e)
106            }
107            Self::Json(e) => {
108                format!("JSON error: {}", e)
109            }
110            Self::Yaml(e) => {
111                format!("YAML error: {}", e)
112            }
113        }
114    }
115
116    /// Get user-friendly colored error message with helpful suggestions
117    ///
118    /// This method formats errors with colors and provides actionable suggestions
119    /// for users to resolve common issues.
120    pub fn user_message(&self) -> String {
121        match self {
122            Self::BinaryNotFound(path) => {
123                format!(
124                    "{} {}\n{} {}",
125                    "Error:".red().bold(),
126                    format!("Binary not found: {}", path.display()).white(),
127                    "Suggestion:".yellow().bold(),
128                    "Check that the path is correct and the file exists".white()
129                )
130            }
131            Self::BinaryNotExecutable(path) => {
132                format!(
133                    "{} {}\n{} {}",
134                    "Error:".red().bold(),
135                    format!("Binary is not executable: {}", path.display()).white(),
136                    "Suggestion:".yellow().bold(),
137                    format!("Try: chmod +x {}", path.display()).white()
138                )
139            }
140            Self::ExecutionFailed(msg) => {
141                format!(
142                    "{} {}\n{} {}",
143                    "Error:".red().bold(),
144                    format!("Failed to execute binary: {}", msg).white(),
145                    "Suggestion:".yellow().bold(),
146                    "Verify the binary runs correctly with --help flag".white()
147                )
148            }
149            Self::InvalidHelpOutput => {
150                format!(
151                    "{} {}\n{} {}",
152                    "Error:".red().bold(),
153                    "Help output could not be parsed".white(),
154                    "Suggestion:".yellow().bold(),
155                    "Ensure the binary supports --help and produces valid output".white()
156                )
157            }
158            Self::OptionParseError(details) => {
159                format!(
160                    "{} {}\n{} {}",
161                    "Error:".red().bold(),
162                    format!("Failed to parse option: {}", details).white(),
163                    "Suggestion:".yellow().bold(),
164                    "Check if the help text follows standard CLI conventions".white()
165                )
166            }
167            Self::TemplateError(msg) => {
168                format!(
169                    "{} {}\n{} {}",
170                    "Error:".red().bold(),
171                    format!("Template rendering failed: {}", msg).white(),
172                    "Suggestion:".yellow().bold(),
173                    "Verify template syntax and variable bindings".white()
174                )
175            }
176            Self::BatsExecutionFailed(msg) => {
177                format!(
178                    "{} {}\n{} {}",
179                    "Error:".red().bold(),
180                    format!("BATS test execution failed: {}", msg).white(),
181                    "Suggestion:".yellow().bold(),
182                    "Install BATS: brew install bats-core or apt-get install bats".white()
183                )
184            }
185            Self::ReportError(msg) => {
186                format!(
187                    "{} {}\n{} {}",
188                    "Error:".red().bold(),
189                    format!("Report generation failed: {}", msg).white(),
190                    "Suggestion:".yellow().bold(),
191                    "Check output directory permissions and disk space".white()
192                )
193            }
194            Self::Config(msg) => {
195                format!(
196                    "{} {}\n{} {}",
197                    "Error:".red().bold(),
198                    format!("Configuration error: {}", msg).white(),
199                    "Suggestion:".yellow().bold(),
200                    "Review your configuration file syntax and required fields".white()
201                )
202            }
203            Self::Validation(msg) => {
204                format!(
205                    "{} {}\n{} {}",
206                    "Error:".red().bold(),
207                    format!("Validation error: {}", msg).white(),
208                    "Suggestion:".yellow().bold(),
209                    "Ensure all required parameters are provided".white()
210                )
211            }
212            Self::IoError(e) => {
213                format!(
214                    "{} {}\n{} {}",
215                    "Error:".red().bold(),
216                    format!("I/O error: {}", e).white(),
217                    "Suggestion:".yellow().bold(),
218                    "Check file permissions and disk space".white()
219                )
220            }
221            Self::Json(e) => {
222                format!(
223                    "{} {}\n{} {}",
224                    "Error:".red().bold(),
225                    format!("JSON error: {}", e).white(),
226                    "Suggestion:".yellow().bold(),
227                    "Validate JSON syntax using a JSON linter".white()
228                )
229            }
230            Self::Yaml(e) => {
231                format!(
232                    "{} {}\n{} {}",
233                    "Error:".red().bold(),
234                    format!("YAML error: {}", e).white(),
235                    "Suggestion:".yellow().bold(),
236                    "Check YAML indentation and syntax".white()
237                )
238            }
239        }
240    }
241
242    /// Print error with color to stderr
243    pub fn print_error(&self) {
244        eprintln!("{}", self.user_message());
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_binary_not_found_error() {
254        let path = PathBuf::from("/nonexistent/binary");
255        let error = CliTestError::BinaryNotFound(path.clone());
256        assert!(error.to_string().contains("Binary not found"));
257    }
258
259    #[test]
260    fn test_binary_not_executable_error() {
261        let path = PathBuf::from("/bin/not-executable");
262        let error = CliTestError::BinaryNotExecutable(path);
263        assert!(error.to_string().contains("not executable"));
264    }
265
266    #[test]
267    fn test_execution_failed_error() {
268        let error = CliTestError::ExecutionFailed("timeout".to_string());
269        assert!(error.to_string().contains("Failed to execute"));
270    }
271
272    #[test]
273    fn test_detailed_message_contains_more_info() {
274        let path = PathBuf::from("/test/binary");
275        let error = CliTestError::BinaryNotFound(path);
276        let detailed = error.detailed_message();
277
278        // Detailed message should contain full path
279        assert!(detailed.contains("/test/binary"));
280    }
281}