1use colored::Colorize;
2use std::path::PathBuf;
3use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, CliTestError>;
7
8#[derive(Error, Debug)]
10pub enum CliTestError {
11 #[error("Binary not found: {0}")]
13 BinaryNotFound(PathBuf),
14
15 #[error("Binary not executable: {0}")]
17 BinaryNotExecutable(PathBuf),
18
19 #[error("Failed to execute binary: {0}")]
21 ExecutionFailed(String),
22
23 #[error("Invalid help output")]
25 InvalidHelpOutput,
26
27 #[error("Failed to parse option: {0}")]
29 OptionParseError(String),
30
31 #[error("Template rendering failed: {0}")]
33 TemplateError(String),
34
35 #[error("BATS execution failed: {0}")]
37 BatsExecutionFailed(String),
38
39 #[error("Report generation failed: {0}")]
41 ReportError(String),
42
43 #[error("Configuration error: {0}")]
45 Config(String),
46
47 #[error("Validation error: {0}")]
49 Validation(String),
50
51 #[error(transparent)]
53 IoError(#[from] std::io::Error),
54
55 #[error(transparent)]
57 Json(#[from] serde_json::Error),
58
59 #[error(transparent)]
61 Yaml(#[from] serde_yaml::Error),
62}
63
64pub use CliTestError as Error;
66
67impl CliTestError {
68 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 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 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 assert!(detailed.contains("/test/binary"));
280 }
281}