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