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 scope_filter: None,
63 extra: serde_yaml_ng::Mapping::new(),
64 }
65 }
66
67 fn ok_builder(_spec: &RuleSpec) -> Result<Box<dyn Rule>> {
68 unreachable!("test should not call this builder")
73 }
74
75 #[test]
76 fn new_registry_has_no_kinds() {
77 let r = RuleRegistry::new();
78 assert_eq!(r.known_kinds().count(), 0);
79 }
80
81 #[test]
82 fn register_inserts_a_kind() {
83 let mut r = RuleRegistry::new();
84 r.register("my_kind", ok_builder);
85 let kinds: Vec<&str> = r.known_kinds().collect();
86 assert_eq!(kinds, vec!["my_kind"]);
87 }
88
89 #[test]
90 fn register_overwrites_existing_kind() {
91 let mut r = RuleRegistry::new();
94 r.register("my_kind", ok_builder);
95 r.register("my_kind", ok_builder);
96 assert_eq!(r.known_kinds().count(), 1);
97 }
98
99 #[test]
100 fn build_rejects_unknown_kind_with_clear_error() {
101 let r = RuleRegistry::new();
102 let err = r.build(&fake_spec("not_real")).unwrap_err();
103 match err {
104 Error::UnknownRuleKind(name) => assert_eq!(name, "not_real"),
105 other => panic!("expected UnknownRuleKind, got {other:?}"),
106 }
107 }
108
109 #[test]
110 fn known_kinds_iterator_lists_all_registered() {
111 let mut r = RuleRegistry::new();
112 r.register("a", ok_builder);
113 r.register("b", ok_builder);
114 r.register("c", ok_builder);
115 let mut kinds: Vec<&str> = r.known_kinds().collect();
116 kinds.sort_unstable();
117 assert_eq!(kinds, vec!["a", "b", "c"]);
118 }
119}