1use std::fmt;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug)]
10pub enum Error {
11 Git(String),
13
14 Config(String),
16
17 InvalidPath(String),
19
20 Io(std::io::Error),
22
23 Runtime(String),
25
26 Pattern(String),
28
29 Http(String),
31
32 Workflow(String),
34
35 WorkflowTimeout(String),
37
38 RateLimitExceeded(String),
40
41 Recovery(String),
43
44 Yaml(String),
46
47 EventParse(String),
49
50 ShallowExhausted(String),
52
53 Other(String),
55}
56
57impl fmt::Display for Error {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Error::Git(msg) => write!(f, "Git error: {}", msg),
61 Error::Config(msg) => write!(f, "Configuration error: {}", msg),
62 Error::InvalidPath(path) => write!(f, "Invalid path: {}", path),
63 Error::Io(err) => write!(f, "I/O error: {}", err),
64 Error::Runtime(msg) => write!(f, "Runtime error: {}", msg),
65 Error::Pattern(msg) => write!(f, "Pattern error: {}", msg),
66 Error::Http(msg) => write!(f, "HTTP error: {}", msg),
67 Error::Workflow(msg) => write!(f, "Workflow error: {}", msg),
68 Error::WorkflowTimeout(msg) => write!(f, "Workflow timeout: {}", msg),
69 Error::RateLimitExceeded(msg) => write!(f, "Rate limit exceeded: {}", msg),
70 Error::Recovery(msg) => write!(f, "Recovery error: {}", msg),
71 Error::Yaml(msg) => write!(f, "YAML error: {}", msg),
72 Error::EventParse(msg) => write!(f, "Event parse error: {}", msg),
73 Error::ShallowExhausted(msg) => write!(f, "Shallow clone exhausted: {}", msg),
74 Error::Other(msg) => write!(f, "Error: {}", msg),
75 }
76 }
77}
78
79impl std::error::Error for Error {
80 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81 match self {
82 Error::Io(err) => Some(err),
83 _ => None,
84 }
85 }
86}
87
88impl From<std::io::Error> for Error {
89 fn from(err: std::io::Error) -> Self {
90 Error::Io(err)
91 }
92}
93
94impl From<git2::Error> for Error {
95 fn from(err: git2::Error) -> Self {
96 Error::Git(err.to_string())
97 }
98}
99
100impl From<globset::Error> for Error {
101 fn from(err: globset::Error) -> Self {
102 Error::Pattern(err.to_string())
103 }
104}
105
106impl From<reqwest::Error> for Error {
107 fn from(err: reqwest::Error) -> Self {
108 Error::Http(err.to_string())
109 }
110}
111
112impl From<serde_json::Error> for Error {
113 fn from(err: serde_json::Error) -> Self {
114 Error::Other(format!("JSON error: {}", err))
115 }
116}
117
118impl From<serde_norway::Error> for Error {
119 fn from(err: serde_norway::Error) -> Self {
120 Error::Yaml(err.to_string())
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128#[repr(u8)]
129pub enum ErrorKind {
130 Git,
132 InvalidPath,
134 Config,
136 Io,
138 Runtime,
140 Pattern,
142 Http,
144 Workflow,
146 WorkflowTimeout,
148 RateLimitExceeded,
150 Recovery,
152 Yaml,
154 EventParse,
156 ShallowExhausted,
158 Other,
160}
161
162impl Error {
163 #[inline]
165 pub const fn kind(&self) -> ErrorKind {
166 match self {
167 Error::Git(_) => ErrorKind::Git,
168 Error::InvalidPath(_) => ErrorKind::InvalidPath,
169 Error::Config(_) => ErrorKind::Config,
170 Error::Io(_) => ErrorKind::Io,
171 Error::Runtime(_) => ErrorKind::Runtime,
172 Error::Pattern(_) => ErrorKind::Pattern,
173 Error::Http(_) => ErrorKind::Http,
174 Error::Workflow(_) => ErrorKind::Workflow,
175 Error::WorkflowTimeout(_) => ErrorKind::WorkflowTimeout,
176 Error::RateLimitExceeded(_) => ErrorKind::RateLimitExceeded,
177 Error::Recovery(_) => ErrorKind::Recovery,
178 Error::Yaml(_) => ErrorKind::Yaml,
179 Error::EventParse(_) => ErrorKind::EventParse,
180 Error::ShallowExhausted(_) => ErrorKind::ShallowExhausted,
181 Error::Other(_) => ErrorKind::Other,
182 }
183 }
184
185 #[inline]
187 pub fn message(&self) -> &str {
188 match self {
189 Error::Git(msg)
190 | Error::Config(msg)
191 | Error::InvalidPath(msg)
192 | Error::Runtime(msg)
193 | Error::Pattern(msg)
194 | Error::Http(msg)
195 | Error::Workflow(msg)
196 | Error::WorkflowTimeout(msg)
197 | Error::RateLimitExceeded(msg)
198 | Error::Recovery(msg)
199 | Error::Yaml(msg)
200 | Error::EventParse(msg)
201 | Error::ShallowExhausted(msg)
202 | Error::Other(msg) => msg,
203 Error::Io(_) => "I/O error",
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_error_kind_is_copy() {
214 let err = Error::Git("test".to_string());
215 let k = err.kind();
216 let k2 = k; assert_eq!(k, k2);
218 }
219
220 #[test]
221 fn test_error_kind_zero_alloc() {
222 assert_eq!(std::mem::size_of::<ErrorKind>(), 1);
224 }
225
226 #[test]
227 fn test_error_message_borrows() {
228 let err = Error::Config("bad config".to_string());
229 let msg: &str = err.message();
230 assert_eq!(msg, "bad config");
231 }
233
234 #[test]
235 fn test_all_error_variants_have_kind() {
236 let cases: Vec<(Error, ErrorKind)> = vec![
237 (Error::Git("g".into()), ErrorKind::Git),
238 (Error::InvalidPath("p".into()), ErrorKind::InvalidPath),
239 (Error::Config("c".into()), ErrorKind::Config),
240 (Error::Io(std::io::Error::other("io")), ErrorKind::Io),
241 (Error::Runtime("r".into()), ErrorKind::Runtime),
242 (Error::Pattern("pat".into()), ErrorKind::Pattern),
243 (Error::Http("h".into()), ErrorKind::Http),
244 (Error::Workflow("w".into()), ErrorKind::Workflow),
245 (
246 Error::WorkflowTimeout("wt".into()),
247 ErrorKind::WorkflowTimeout,
248 ),
249 (
250 Error::RateLimitExceeded("rl".into()),
251 ErrorKind::RateLimitExceeded,
252 ),
253 (Error::Recovery("rec".into()), ErrorKind::Recovery),
254 (Error::Yaml("y".into()), ErrorKind::Yaml),
255 (Error::EventParse("ep".into()), ErrorKind::EventParse),
256 (
257 Error::ShallowExhausted("se".into()),
258 ErrorKind::ShallowExhausted,
259 ),
260 (Error::Other("o".into()), ErrorKind::Other),
261 ];
262
263 for (err, expected_kind) in cases {
264 assert_eq!(err.kind(), expected_kind, "Mismatch for {:?}", err);
265 }
266 }
267
268 #[test]
269 fn test_error_kind_repr_u8() {
270 assert_eq!(std::mem::size_of::<ErrorKind>(), 1);
271 }
272
273 #[test]
274 fn test_error_messages_never_contain_token_patterns() {
275 let token_patterns = ["ghp_", "gho_", "ghs_", "github_pat_", "Bearer "];
278 let errors: Vec<Error> = vec![
279 Error::Git("git error".into()),
280 Error::Config("config error".into()),
281 Error::Http("http error".into()),
282 Error::Workflow("workflow error".into()),
283 Error::Runtime("runtime error".into()),
284 Error::RateLimitExceeded("rate limit exceeded".into()),
285 ];
286
287 for err in &errors {
288 let msg = err.message();
289 let display = format!("{}", err);
290 let debug = format!("{:?}", err);
291 for pattern in &token_patterns {
292 assert!(
293 !msg.contains(pattern),
294 "Error message contains token pattern '{}': {}",
295 pattern,
296 msg
297 );
298 assert!(
299 !display.contains(pattern),
300 "Error Display contains token pattern '{}': {}",
301 pattern,
302 display
303 );
304 assert!(
305 !debug.contains(pattern),
306 "Error Debug contains token pattern '{}': {}",
307 pattern,
308 debug
309 );
310 }
311 }
312 }
313}