Skip to main content

rules/
category.rs

1use std::fmt;
2
3/// Built-in diagnostic categories for registered rules.
4///
5/// Custom and guard rules use freeform strings (e.g. `"guard:s3_versioning"`)
6/// and do not use this enum. Public APIs expose categories as plain strings.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum Category {
9    Schema,
10    Structure,
11    Intrinsic,
12    BestPractice,
13    Resource,
14    Security,
15    Parameter,
16    Reference,
17    Deprecation,
18    General,
19}
20
21impl Category {
22    pub const fn as_str(&self) -> &'static str {
23        match self {
24            Category::Schema => "Schema",
25            Category::Structure => "Structure",
26            Category::Intrinsic => "Intrinsic Function",
27            Category::BestPractice => "Best Practice",
28            Category::Resource => "Resource",
29            Category::Security => "Security",
30            Category::Parameter => "Parameter",
31            Category::Reference => "Reference",
32            Category::Deprecation => "Deprecation",
33            Category::General => "General",
34        }
35    }
36}
37
38impl std::str::FromStr for Category {
39    type Err = String;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        match s.to_lowercase().as_str() {
43            "schema" => Ok(Category::Schema),
44            "structure" => Ok(Category::Structure),
45            "intrinsic function" => Ok(Category::Intrinsic),
46            "best practice" | "best-practice" | "best_practice" => Ok(Category::BestPractice),
47            "resource" => Ok(Category::Resource),
48            "security" => Ok(Category::Security),
49            "parameter" => Ok(Category::Parameter),
50            "reference" => Ok(Category::Reference),
51            "deprecation" => Ok(Category::Deprecation),
52            "general" => Ok(Category::General),
53            _ => Err(format!("Invalid category: {}", s)),
54        }
55    }
56}
57
58impl fmt::Display for Category {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.write_str(self.as_str())
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::str::FromStr;
68
69    #[test]
70    fn as_str_round_trips_through_from_str_for_all_variants() {
71        let all = [
72            Category::Schema,
73            Category::Structure,
74            Category::Intrinsic,
75            Category::BestPractice,
76            Category::Resource,
77            Category::Security,
78            Category::Parameter,
79            Category::Reference,
80            Category::Deprecation,
81            Category::General,
82        ];
83        for cat in all {
84            assert_eq!(Category::from_str(cat.as_str()).unwrap(), cat, "round-trip failed for {:?}", cat);
85        }
86    }
87
88    #[test]
89    fn from_str_parses_case_insensitively() {
90        assert_eq!(Category::from_str("SCHEMA").unwrap(), Category::Schema);
91        assert_eq!(Category::from_str("Best Practice").unwrap(), Category::BestPractice);
92        assert_eq!(Category::from_str("BEST PRACTICE").unwrap(), Category::BestPractice);
93    }
94
95    #[test]
96    fn from_str_errors_on_freeform_category_string() {
97        let err = Category::from_str("guard:test").unwrap_err();
98        assert_eq!(err, "Invalid category: guard:test");
99    }
100}