1use std::path::PathBuf;
2
3use thiserror::Error;
4
5pub type Result<T, E = Error> = std::result::Result<T, E>;
6
7#[derive(Debug, Error)]
8pub enum Error {
9 #[error("I/O error at {path}: {source}")]
10 Io {
11 path: PathBuf,
12 #[source]
13 source: std::io::Error,
14 },
15
16 #[error("walk error: {0}")]
17 Walk(#[from] ignore::Error),
18
19 #[error("invalid glob {pattern:?}: {source}")]
20 Glob {
21 pattern: String,
22 #[source]
23 source: globset::Error,
24 },
25
26 #[error("YAML parse error: {0}")]
27 Yaml(#[from] serde_yaml_ng::Error),
28
29 #[error("unknown rule kind {0:?}")]
30 UnknownRuleKind(String),
31
32 #[error("rule {rule_id:?}: {message}")]
33 RuleConfig { rule_id: String, message: String },
34
35 #[error("{0}")]
36 Other(String),
37}
38
39impl Error {
40 pub fn rule_config(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
41 Self::RuleConfig {
42 rule_id: rule_id.into(),
43 message: message.into(),
44 }
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn rule_config_constructor_accepts_strings_and_str_refs() {
54 let e1 = Error::rule_config("foo", "bad");
55 let e2 = Error::rule_config(String::from("foo"), String::from("bad"));
56 assert_eq!(e1.to_string(), e2.to_string());
57 }
58
59 #[test]
60 fn rule_config_display_includes_rule_id_and_message() {
61 let e = Error::rule_config("my-rule", "missing field");
62 let s = e.to_string();
63 assert!(s.contains("my-rule"), "missing rule id: {s}");
64 assert!(s.contains("missing field"), "missing message: {s}");
65 }
66
67 #[test]
68 fn unknown_rule_kind_display_quotes_the_kind() {
69 let e = Error::UnknownRuleKind("not_a_real_kind".into());
70 assert!(e.to_string().contains("not_a_real_kind"));
71 }
72
73 #[test]
74 fn glob_error_display_includes_pattern() {
75 let bad = globset::Glob::new("[unterminated").unwrap_err();
76 let e = Error::Glob {
77 pattern: "[unterminated".into(),
78 source: bad,
79 };
80 let s = e.to_string();
81 assert!(s.contains("[unterminated"), "missing pattern: {s}");
82 }
83
84 #[test]
85 fn yaml_error_propagates_via_from_impl() {
86 let parse: std::result::Result<i32, _> = serde_yaml_ng::from_str("not: yaml: [");
90 let yaml_err = parse.unwrap_err();
91 let our_err: Error = yaml_err.into();
92 assert!(matches!(our_err, Error::Yaml(_)));
93 }
94
95 #[test]
96 fn other_variant_carries_arbitrary_text() {
97 let e = Error::Other("something went sideways".into());
98 assert_eq!(e.to_string(), "something went sideways");
99 }
100}