dynamic_cli/error/
display.rs1use crate::error::{ConfigError, DynamicCliError, ParseError};
7
8#[cfg(feature = "colored-output")]
10mod color {
11 use colored::*;
12
13 pub fn error(s: &str) -> String {
14 s.red().bold().to_string()
15 }
16 pub fn question(s: &str) -> String {
17 s.yellow().bold().to_string()
18 }
19 pub fn bullet(s: &str) -> String {
20 s.cyan().to_string()
21 }
22 pub fn suggestion(s: &str) -> String {
23 s.green().to_string()
24 }
25 pub fn info(s: &str) -> String {
26 s.blue().bold().to_string()
27 }
28 pub fn type_name(s: &str) -> String {
29 s.cyan().to_string()
30 }
31 pub fn arg_name(s: &str) -> String {
32 s.yellow().to_string()
33 }
34 pub fn value(s: &str) -> String {
35 s.red().to_string()
36 }
37 pub fn dimmed(s: &str) -> String {
38 s.dimmed().to_string()
39 }
40}
41
42#[cfg(not(feature = "colored-output"))]
43mod color {
44 pub fn error(s: &str) -> String {
45 s.to_string()
46 }
47 pub fn question(s: &str) -> String {
48 s.to_string()
49 }
50 pub fn bullet(s: &str) -> String {
51 s.to_string()
52 }
53 pub fn suggestion(s: &str) -> String {
54 s.to_string()
55 }
56 pub fn info(s: &str) -> String {
57 s.to_string()
58 }
59 pub fn type_name(s: &str) -> String {
60 s.to_string()
61 }
62 pub fn arg_name(s: &str) -> String {
63 s.to_string()
64 }
65 pub fn value(s: &str) -> String {
66 s.to_string()
67 }
68 pub fn dimmed(s: &str) -> String {
69 s.to_string()
70 }
71}
72
73pub fn display_error(error: &DynamicCliError) {
90 eprintln!("{}", format_error(error));
91}
92
93pub fn format_error(error: &DynamicCliError) -> String {
120 let mut output = String::new();
121
122 output.push_str(&format!("{} ", color::error("Error:")));
124
125 match error {
127 DynamicCliError::Parse(e) => {
128 format_parse_error(&mut output, e);
129 }
130
131 DynamicCliError::Config(e) => {
132 format_config_error(&mut output, e);
133 }
134
135 DynamicCliError::Validation(e) => {
136 output.push_str(&format!("{}\n", e));
137 }
138
139 DynamicCliError::Execution(e) => {
140 output.push_str(&format!("{}\n", e));
141 }
142
143 DynamicCliError::Registry(e) => {
144 output.push_str(&format!("{}\n", e));
145 }
146
147 DynamicCliError::Io(e) => {
148 output.push_str(&format!("{}\n", e));
149 }
150 }
151
152 output
153}
154
155fn format_parse_error(output: &mut String, error: &ParseError) {
157 output.push_str(&format!("{}\n", error));
158
159 match error {
161 ParseError::UnknownCommand { suggestions, .. } if !suggestions.is_empty() => {
162 output.push_str(&format!("\n{} Did you mean:\n", color::question("?")));
163 for suggestion in suggestions {
164 output.push_str(&format!(
165 " {} {}\n",
166 color::bullet("•"),
167 color::suggestion(suggestion)
168 ));
169 }
170 }
171
172 ParseError::UnknownOption { suggestions, .. } if !suggestions.is_empty() => {
173 output.push_str(&format!("\n{} Did you mean:\n", color::question("?")));
174 for suggestion in suggestions {
175 output.push_str(&format!(
176 " {} {}\n",
177 color::bullet("•"),
178 color::suggestion(suggestion)
179 ));
180 }
181 }
182
183 ParseError::TypeParseError {
184 arg_name,
185 expected_type,
186 value,
187 ..
188 } => {
189 output.push_str(&format!(
190 "\n{} Expected type {} for argument {}, got: {}\n",
191 color::info("ℹ"),
192 color::type_name(expected_type),
193 color::arg_name(arg_name),
194 color::value(value)
195 ));
196 }
197
198 _ => {}
199 }
200}
201
202fn format_config_error(output: &mut String, error: &ConfigError) {
204 match error {
205 ConfigError::YamlParse {
206 source,
207 line,
208 column,
209 } => {
210 output.push_str(&format!("{}\n", source));
211 if let (Some(l), Some(c)) = (line, column) {
212 output.push_str(&format!(
213 " {} line {}, column {}\n",
214 color::dimmed("at"),
215 color::arg_name(&l.to_string()),
216 color::arg_name(&c.to_string())
217 ));
218 }
219 }
220
221 ConfigError::JsonParse {
222 source,
223 line,
224 column,
225 } => {
226 output.push_str(&format!("{}\n", source));
227 output.push_str(&format!(
228 " {} line {}, column {}\n",
229 color::dimmed("at"),
230 color::arg_name(&line.to_string()),
231 color::arg_name(&column.to_string())
232 ));
233 }
234
235 ConfigError::InvalidSchema { reason, path } => {
236 output.push_str(&format!("{}\n", reason));
237 if let Some(p) = path {
238 output.push_str(&format!(
239 " {} {}\n",
240 color::dimmed("in"),
241 color::type_name(p)
242 ));
243 }
244 }
245
246 _ => {
247 output.push_str(&format!("{}\n", error));
248 }
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use std::path::PathBuf;
256
257 #[test]
258 fn test_format_error_basic() {
259 let error: DynamicCliError = ConfigError::FileNotFound {
260 path: PathBuf::from("test.yaml"),
261 }
262 .into();
263
264 let formatted = format_error(&error);
265
266 assert!(formatted.contains("Error:"));
268 assert!(formatted.contains("test.yaml"));
269 }
270
271 #[test]
272 fn test_format_parse_error_with_suggestions() {
273 let error: DynamicCliError = ParseError::UnknownCommand {
274 command: "simulat".to_string(),
275 suggestions: vec!["simulate".to_string(), "validation".to_string()],
276 }
277 .into();
278
279 let formatted = format_error(&error);
280
281 assert!(formatted.contains("Unknown command"));
283 assert!(formatted.contains("simulat"));
284
285 assert!(formatted.contains("Did you mean"));
287 assert!(formatted.contains("simulate"));
288 }
289
290 #[test]
291 fn test_format_parse_error_without_suggestions() {
292 let error: DynamicCliError = ParseError::UnknownCommand {
293 command: "xyz".to_string(),
294 suggestions: vec![],
295 }
296 .into();
297
298 let formatted = format_error(&error);
299
300 assert!(formatted.contains("Unknown command"));
302 assert!(formatted.contains("xyz"));
303
304 assert!(!formatted.contains("Did you mean"));
306 }
307
308 #[test]
309 fn test_format_config_error_with_location() {
310 let yaml_error = serde_yaml::from_str::<serde_yaml::Value>("invalid: [")
311 .err()
312 .unwrap();
313
314 let error: DynamicCliError = ConfigError::yaml_parse_with_location(yaml_error).into();
315 let formatted = format_error(&error);
316
317 assert!(formatted.contains("Error:"));
319 }
321
322 #[test]
323 fn test_display_error_does_not_panic() {
324 let error: DynamicCliError = ConfigError::FileNotFound {
326 path: PathBuf::from("test.yaml"),
327 }
328 .into();
329
330 display_error(&error);
332 }
333}