1use std::collections::HashMap;
2
3use crate::config::RuleSpec;
4use crate::error::{Error, Result};
5use crate::rule::Rule;
6
7pub type RuleBuilder = fn(&RuleSpec) -> Result<Box<dyn Rule>>;
8
9#[derive(Default)]
12pub struct RuleRegistry {
13 builders: HashMap<String, RuleBuilder>,
14}
15
16impl std::fmt::Debug for RuleRegistry {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 f.debug_struct("RuleRegistry")
19 .field("kinds", &self.builders.keys().collect::<Vec<_>>())
20 .finish()
21 }
22}
23
24impl RuleRegistry {
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn register(&mut self, kind: &str, builder: RuleBuilder) {
30 self.builders.insert(kind.to_string(), builder);
31 }
32
33 pub fn build(&self, spec: &RuleSpec) -> Result<Box<dyn Rule>> {
34 let builder = self
35 .builders
36 .get(&spec.kind)
37 .ok_or_else(|| Error::UnknownRuleKind(spec.kind.clone()))?;
38 builder(spec)
39 }
40
41 pub fn known_kinds(&self) -> impl Iterator<Item = &str> {
42 self.builders.keys().map(String::as_str)
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::level::Level;
50
51 fn fake_spec(kind: &str) -> RuleSpec {
52 RuleSpec {
53 id: "t".into(),
54 kind: kind.into(),
55 level: Level::Error,
56 paths: None,
57 message: None,
58 policy_url: None,
59 when: None,
60 fix: None,
61 git_tracked_only: false,
62 extra: serde_yaml_ng::Mapping::new(),
63 }
64 }
65
66 fn ok_builder(_spec: &RuleSpec) -> Result<Box<dyn Rule>> {
67 unreachable!("test should not call this builder")
72 }
73
74 #[test]
75 fn new_registry_has_no_kinds() {
76 let r = RuleRegistry::new();
77 assert_eq!(r.known_kinds().count(), 0);
78 }
79
80 #[test]
81 fn register_inserts_a_kind() {
82 let mut r = RuleRegistry::new();
83 r.register("my_kind", ok_builder);
84 let kinds: Vec<&str> = r.known_kinds().collect();
85 assert_eq!(kinds, vec!["my_kind"]);
86 }
87
88 #[test]
89 fn register_overwrites_existing_kind() {
90 let mut r = RuleRegistry::new();
93 r.register("my_kind", ok_builder);
94 r.register("my_kind", ok_builder);
95 assert_eq!(r.known_kinds().count(), 1);
96 }
97
98 #[test]
99 fn build_rejects_unknown_kind_with_clear_error() {
100 let r = RuleRegistry::new();
101 let err = r.build(&fake_spec("not_real")).unwrap_err();
102 match err {
103 Error::UnknownRuleKind(name) => assert_eq!(name, "not_real"),
104 other => panic!("expected UnknownRuleKind, got {other:?}"),
105 }
106 }
107
108 #[test]
109 fn known_kinds_iterator_lists_all_registered() {
110 let mut r = RuleRegistry::new();
111 r.register("a", ok_builder);
112 r.register("b", ok_builder);
113 r.register("c", ok_builder);
114 let mut kinds: Vec<&str> = r.known_kinds().collect();
115 kinds.sort_unstable();
116 assert_eq!(kinds, vec!["a", "b", "c"]);
117 }
118}