specfold_core/model/
registry.rs1use super::doctype::{self, Form, KeyKind, TypeDef};
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7#[derive(Debug, Clone, Default, Deserialize, Serialize)]
9#[serde(deny_unknown_fields)]
10pub struct TypeOverride {
11 pub enabled: Option<bool>,
12 pub about: Option<String>,
13 pub key: Option<KeyKind>,
14 pub form: Option<Form>,
15 pub statuses: Option<Vec<String>>,
16 pub initial: Option<String>,
17 pub sealed: Option<bool>,
18 pub fields: Option<Vec<String>>,
19 pub template: Option<String>,
20 pub transitions: Option<BTreeMap<String, Vec<String>>>,
21 pub require_when: Option<BTreeMap<String, Vec<String>>>,
22 pub sections: Option<Vec<String>>,
23}
24
25#[derive(Debug, Clone)]
26pub struct Registry {
27 pub types: Vec<TypeDef>,
28}
29
30fn apply(t: &mut TypeDef, o: &TypeOverride) {
31 if let Some(v) = &o.about {
32 t.about = v.clone();
33 }
34 if let Some(v) = o.key {
35 t.key = v;
36 }
37 if let Some(v) = o.form {
38 t.form = v;
39 }
40 if let Some(v) = &o.statuses {
41 t.statuses = v.clone();
42 }
43 if let Some(v) = &o.initial {
44 t.initial = v.clone();
45 }
46 if let Some(v) = o.sealed {
47 t.sealed = v;
48 }
49 if let Some(v) = &o.fields {
50 t.fields = v.clone();
51 }
52 if let Some(v) = &o.template {
53 t.template = v.clone();
54 }
55 if let Some(v) = &o.transitions {
56 t.transitions = v.clone();
57 }
58 if let Some(v) = &o.require_when {
59 t.require_when = v.clone();
60 }
61 if let Some(v) = &o.sections {
62 t.sections = v.clone();
63 }
64}
65
66pub fn suspect_overrides(overrides: &BTreeMap<String, TypeOverride>) -> Vec<String> {
70 let builtin: Vec<String> = doctype::builtin().into_iter().map(|t| t.name).collect();
71 let mut out = vec![];
72 for (name, o) in overrides {
73 if builtin.contains(name) {
74 continue;
75 }
76 let near = crate::check::rules::support::nearest(name, builtin.iter().map(String::as_str))
77 .or_else(|| builtin.iter().map(String::as_str).find(|b| name.strip_suffix('s') == Some(*b)));
78 if o.enabled == Some(false) {
79 let hint = near.map(|n| format!("; did you mean [types.{n}]?")).unwrap_or_default();
80 out.push(format!("[types.{name}] disables a type that does not exist{hint}"));
81 } else if let Some(n) = near
82 && o.about.is_none()
83 {
84 out.push(format!(
85 "[types.{name}] is not a built-in type, so it creates a new one; did you mean [types.{n}]? (give it an `about` to keep it as a new type)"
86 ));
87 }
88 }
89 out
90}
91
92impl Registry {
93 pub fn new(overrides: &BTreeMap<String, TypeOverride>) -> Self {
94 let mut types = doctype::builtin();
95 for (name, o) in overrides {
96 let pos = types.iter().position(|t| &t.name == name);
97 if o.enabled == Some(false) {
98 if let Some(p) = pos {
99 types.remove(p);
100 }
101 continue;
102 }
103 match pos {
104 Some(p) => apply(&mut types[p], o),
105 None => {
106 let mut t = doctype::custom(name);
107 apply(&mut t, o);
108 types.push(t);
109 }
110 }
111 }
112 Registry { types }
113 }
114
115 pub fn get(&self, name: &str) -> Option<&TypeDef> {
116 self.types.iter().find(|t| t.name == name)
117 }
118
119 pub fn names(&self) -> Vec<&str> {
120 self.types.iter().map(|t| t.name.as_str()).collect()
121 }
122}
123
124#[cfg(test)]
125mod suspect_tests {
126 use super::{TypeOverride, suspect_overrides};
127 use std::collections::BTreeMap;
128
129 #[test]
130 fn near_misses_are_named_and_deliberate_types_are_not() {
131 let mut m = BTreeMap::new();
132 m.insert("adrs".to_string(), TypeOverride { sections: Some(vec!["Decision".into()]), ..Default::default() });
133 m.insert("memo".to_string(), TypeOverride { about: Some("Short notes".into()), ..Default::default() });
134 m.insert("rfc".to_string(), TypeOverride::default());
135 let got = suspect_overrides(&m);
136 assert_eq!(got.len(), 1, "{got:?}");
137 assert!(got[0].contains("did you mean [types.adr]"), "{got:?}");
138 }
139}