1use std::collections::btree_map::Iter;
10use std::collections::BTreeMap;
11use std::fmt::{Display, Formatter};
12
13use Entry::*;
14
15use crate::{Rule, Set};
16
17#[derive(Clone, Debug)]
18pub struct RuleEntry {
19 pub id: usize,
20 pub text: String,
21 pub origin: Origin,
22 pub valid: bool,
23 pub msg: Option<String>,
24 _fk: usize,
25}
26
27#[derive(Clone, Debug)]
28pub struct SetEntry {
29 pub name: String,
30 pub text: String,
31 pub origin: Origin,
32 pub valid: bool,
33 pub msg: Option<String>,
34 _fk: usize,
35}
36
37#[derive(Clone, Debug)]
38pub struct CommentEntry {
39 pub text: String,
40 pub origin: Origin,
41 _fk: usize,
42}
43
44#[derive(Clone, Debug)]
49pub enum Entry {
50 ValidRule(Rule),
52 RuleWithWarning(Rule, String),
53 Invalid { text: String, error: String },
54
55 ValidSet(Set),
57 SetWithWarning(Set, String),
58 InvalidSet { text: String, error: String },
59
60 Comment(String),
62}
63
64impl Display for Entry {
65 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66 let txt = match self {
67 ValidRule(r) | RuleWithWarning(r, _) => r.to_string(),
68 ValidSet(r) | SetWithWarning(r, _) => r.to_string(),
69 Invalid { text, .. } => text.clone(),
70 InvalidSet { text, .. } => text.clone(),
71 Comment(text) => format!("#{}", text),
72 };
73 f.write_fmt(format_args!("{}", txt))
74 }
75}
76
77impl Entry {
78 fn diagnostic_messages(&self) -> Option<String> {
79 match self {
80 RuleWithWarning(_, w) | SetWithWarning(_, w) => Some(w.clone()),
81 Invalid { error, .. } | InvalidSet { error, .. } => Some(error.clone()),
82 _ => None,
83 }
84 }
85}
86
87fn is_valid(def: &Entry) -> bool {
88 !matches!(def, Invalid { .. } | InvalidSet { .. })
89}
90
91fn is_rule(def: &Entry) -> bool {
92 matches!(def, ValidRule(_) | RuleWithWarning(..) | Invalid { .. })
93}
94
95fn is_set(def: &Entry) -> bool {
96 matches!(def, ValidSet(_) | SetWithWarning(..) | InvalidSet { .. })
97}
98
99fn is_comment(def: &Entry) -> bool {
100 matches!(def, Comment(_))
101}
102
103type Origin = String;
104type DbEntry = (Origin, Entry);
105
106#[derive(Clone, Debug, Default)]
109pub struct DB {
110 model: BTreeMap<usize, DbEntry>,
111 rules: BTreeMap<usize, RuleEntry>,
112 sets: BTreeMap<usize, SetEntry>,
113 comments: BTreeMap<usize, CommentEntry>,
114}
115
116impl From<Vec<(Origin, Entry)>> for DB {
117 fn from(s: Vec<(String, Entry)>) -> Self {
118 DB::from_sources(s)
119 }
120}
121
122impl DB {
123 pub(crate) fn from_sources(defs: Vec<(Origin, Entry)>) -> Self {
125 let model: BTreeMap<usize, DbEntry> = defs
126 .into_iter()
127 .enumerate()
128 .map(|(i, (source, d))| (i, (source, d)))
129 .collect();
130
131 let rules: BTreeMap<usize, RuleEntry> = model
132 .iter()
133 .filter(|(_fk, (_, e))| is_rule(e))
134 .enumerate()
135 .map(|(id, (fk, (o, e)))| RuleEntry {
136 id: id + 1,
137 text: e.to_string(),
138 origin: o.clone(),
139 valid: is_valid(e),
140 msg: e.diagnostic_messages(),
141 _fk: *fk,
142 })
143 .map(|e| (e.id, e))
144 .collect();
145
146 let sets: BTreeMap<usize, SetEntry> = model
147 .iter()
148 .enumerate()
149 .map(|(fk, v)| (v, fk))
150 .filter(|((_, (_, m)), _)| is_set(m))
151 .map(|((id, (o, e)), fk)| {
152 (
153 *id,
154 SetEntry {
155 name: "_".to_string(),
157 text: e.to_string(),
158 origin: o.clone(),
159 valid: is_valid(e),
160 msg: e.diagnostic_messages(),
161 _fk: fk,
162 },
163 )
164 })
165 .collect();
166
167 let comments = model
168 .iter()
169 .enumerate()
170 .map(|(fk, v)| (v, fk))
171 .filter(|((_, (_, m)), _)| is_comment(m))
172 .map(|((id, (o, e)), fk)| {
173 (
174 *id,
175 CommentEntry {
176 text: e.to_string(),
177 origin: o.clone(),
178 _fk: fk,
179 },
180 )
181 })
182 .collect();
183
184 Self {
185 model,
186 rules,
187 sets,
188 comments,
189 }
190 }
191
192 pub fn len(&self) -> usize {
194 self.model.len()
195 }
196
197 pub fn is_empty(&self) -> bool {
199 self.model.is_empty()
200 }
201
202 pub fn rule(&self, num: usize) -> Option<&RuleEntry> {
204 self.rules.get(&num)
205 }
206
207 pub fn rule_rev(&self, fk: usize) -> Option<&RuleEntry> {
209 self.rules.iter().find(|(_, e)| e._fk == fk).map(|(_, e)| e)
210 }
211
212 pub fn rules(&self) -> Vec<&RuleEntry> {
214 self.rules.values().collect()
215 }
216
217 pub fn sets(&self) -> Vec<&SetEntry> {
219 self.sets.values().collect()
220 }
221
222 pub fn comments(&self) -> Vec<&CommentEntry> {
224 self.comments.values().collect()
225 }
226
227 pub fn entry(&self, num: usize) -> Option<&Entry> {
228 self.model.get(&num).map(|(_, e)| e)
229 }
230
231 pub fn iter(&self) -> Iter<'_, usize, DbEntry> {
233 self.model.iter()
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use crate::{Decision, Object, Permission, Subject};
240
241 use super::*;
242
243 impl From<Rule> for Entry {
244 fn from(r: Rule) -> Self {
245 ValidRule(r)
246 }
247 }
248
249 impl DB {
250 fn from_source(origin: Origin, defs: Vec<Entry>) -> Self {
251 DB::from_sources(defs.into_iter().map(|d| (origin.clone(), d)).collect())
252 }
253 }
254
255 impl Entry {
256 pub fn unwrap(&self) -> Rule {
257 match self {
258 ValidRule(val) => val.clone(),
259 RuleWithWarning(val, _) => val.clone(),
260 _ => {
261 panic!("called unwrap on an invalid rule or set def")
262 }
263 }
264 }
265 }
266
267 fn any_all_all(decision: Decision) -> Entry {
268 Rule::new(Subject::all(), Permission::Any, Object::all(), decision).into()
269 }
270
271 #[test]
272 fn default_db_is_empty() {
273 assert!(DB::default().is_empty());
274 }
275
276 #[test]
277 fn db_create() {
278 let r1: Entry = Rule::new(
279 Subject::all(),
280 Permission::Any,
281 Object::all(),
282 Decision::Allow,
283 )
284 .into();
285 let source = "foo.rules".to_string();
286 let db: DB = vec![(source, r1)].into();
287 assert!(!db.is_empty());
288 assert!(db.rule(1).is_some());
289 }
290
291 #[test]
292 fn db_create_single_source() {
293 let r1 = any_all_all(Decision::Allow);
294 let r2 = any_all_all(Decision::Deny);
295
296 let source = "/foo/bar.rules";
297 let db: DB = DB::from_source(source.to_string(), vec![r1, r2]);
298 assert_eq!(db.rule(1).unwrap().origin, source);
299 assert_eq!(db.rule(2).unwrap().origin, source);
300 }
301
302 #[test]
303 fn db_create_each_source() {
304 let r1 = any_all_all(Decision::Allow);
305 let r2 = any_all_all(Decision::Deny);
306
307 let source1 = "/foo.rules";
308 let source2 = "/bar.rules";
309 let db: DB = DB::from_sources(vec![(source1.to_string(), r1), (source2.to_string(), r2)]);
310 assert_eq!(db.rule(1).unwrap().origin, source1);
311 assert_eq!(db.rule(2).unwrap().origin, source2);
312 }
313
314 #[test]
315 fn maintain_order() {
316 let source = "foo.rules".to_string();
317 let subjs = vec!["fee", "fi", "fo", "fum", "this", "is", "such", "fun"];
318 let rules: Vec<(String, Entry)> = subjs
319 .iter()
320 .map(|s| {
321 (
322 source.clone(),
323 Rule::new(
324 Subject::from_exe(s),
325 Permission::Any,
326 Object::all(),
327 Decision::Allow,
328 )
329 .into(),
330 )
331 })
332 .collect();
333
334 let db: DB = rules.into();
335 assert!(!db.is_empty());
336 assert_eq!(db.len(), 8);
337
338 for s in subjs.iter().enumerate() {
339 assert_eq!(db.entry(s.0).unwrap().unwrap().subj.exe().unwrap(), *s.1);
340 }
341 }
342
343 #[test]
344 fn test_prefixed_comment() {
345 assert!(Comment("sometext".to_string()).to_string().starts_with('#'))
346 }
347}