1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[allow(dead_code)]
8pub enum ExitCode {
9 Success = 0,
11 ParseError = 1,
13 LintErrors = 2,
15 IoError = 3,
17 InvalidArgs = 4,
19}
20
21#[derive(Debug, Error)]
23pub enum DiscoveryError {
24 #[error("invalid glob pattern '{pattern}': {source}")]
26 InvalidPattern {
27 pattern: String,
29 #[source]
31 source: globset::Error,
32 },
33
34 #[error("failed to read '{path}': {source}")]
36 IoError {
37 path: PathBuf,
39 #[source]
41 source: std::io::Error,
42 },
43
44 #[error("permission denied: '{path}'")]
46 PermissionDenied {
47 path: PathBuf,
49 },
50
51 #[error("broken symbolic link: '{path}'")]
53 BrokenSymlink {
54 path: PathBuf,
56 },
57
58 #[error("path does not exist: '{path}'")]
60 PathNotFound {
61 path: PathBuf,
63 },
64
65 #[error("failed to read file list from stdin: {source}")]
67 StdinError {
68 #[source]
70 source: std::io::Error,
71 },
72
73 #[error("exceeded maximum of {max} paths")]
75 TooManyPaths {
76 max: usize,
78 },
79}
80
81impl ExitCode {
82 pub const fn as_i32(self) -> i32 {
84 self as i32
85 }
86}
87
88pub fn format_error(err: &anyhow::Error, use_color: bool) -> String {
90 use std::fmt::Write;
91 let mut output = String::new();
92
93 #[cfg(feature = "colors")]
94 if use_color {
95 use colored::Colorize;
96 let _ = writeln!(output, "{} {}", "error:".red().bold(), err);
97
98 for (i, cause) in err.chain().skip(1).enumerate() {
100 let _ = writeln!(
101 output,
102 " {}{} {}",
103 "caused by".dimmed(),
104 format!("[{i}]").dimmed(),
105 cause.to_string().dimmed()
106 );
107 }
108 return output;
109 }
110
111 let _ = writeln!(output, "error: {err}");
113
114 for (i, cause) in err.chain().skip(1).enumerate() {
115 let _ = writeln!(output, " caused by[{i}] {cause}");
116 }
117
118 output
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_exit_code_values() {
127 assert_eq!(ExitCode::Success.as_i32(), 0);
128 assert_eq!(ExitCode::ParseError.as_i32(), 1);
129 assert_eq!(ExitCode::LintErrors.as_i32(), 2);
130 assert_eq!(ExitCode::IoError.as_i32(), 3);
131 assert_eq!(ExitCode::InvalidArgs.as_i32(), 4);
132 }
133
134 #[test]
135 fn test_format_error_no_color() {
136 let err = anyhow::anyhow!("test error");
137 let formatted = format_error(&err, false);
138 assert!(formatted.contains("error: test error"));
139 }
140
141 #[test]
142 fn test_format_error_with_chain() {
143 use anyhow::Context;
144 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
145 let err: anyhow::Error = Err::<(), _>(io_err)
147 .context("Failed to read config")
148 .unwrap_err();
149 let formatted = format_error(&err, false);
150 assert!(formatted.contains("Failed to read config"));
151 assert!(formatted.contains("caused by"));
152 }
153}