Skip to content

Commit 68ee69b

Browse files
committed
feat: add Scanner::rules method to list rules
This is motivated by the future python bindings which allows inspecting rules in a scanner, but this can be useful in Rust as well.
1 parent 3522484 commit 68ee69b

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

boreal/src/scanner/mod.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,70 @@ impl Scanner {
361361
pub fn get_string_symbol(&self, symbol: StringSymbol) -> &str {
362362
self.inner.bytes_pool.get_str(symbol)
363363
}
364+
365+
/// List rules contained in this scanner.
366+
#[must_use]
367+
pub fn rules(&self) -> RulesIter {
368+
RulesIter {
369+
global_rules: self.inner.global_rules.iter(),
370+
rules: self.inner.rules.iter(),
371+
namespaces: &self.inner.namespaces,
372+
}
373+
}
374+
}
375+
376+
/// Iterator on the rules of a scanner.
377+
#[derive(Debug)]
378+
pub struct RulesIter<'scanner> {
379+
global_rules: std::slice::Iter<'scanner, Rule>,
380+
rules: std::slice::Iter<'scanner, Rule>,
381+
namespaces: &'scanner [Option<String>],
382+
}
383+
384+
impl<'scanner> Iterator for RulesIter<'scanner> {
385+
type Item = RuleDetails<'scanner>;
386+
387+
fn next(&mut self) -> Option<Self::Item> {
388+
let (rule, is_global) = match self.global_rules.next() {
389+
Some(rule) => (rule, true),
390+
None => (self.rules.next()?, false),
391+
};
392+
393+
Some(RuleDetails {
394+
name: &rule.name,
395+
namespace: self
396+
.namespaces
397+
.get(rule.namespace_index)
398+
.and_then(|v| v.as_deref()),
399+
tags: &rule.tags,
400+
metadatas: &rule.metadatas,
401+
is_global,
402+
is_private: rule.is_private,
403+
})
404+
}
405+
}
406+
407+
/// Details on a rule contained in a scanner
408+
#[derive(Debug)]
409+
#[non_exhaustive]
410+
pub struct RuleDetails<'scanner> {
411+
/// Name of the rule.
412+
pub name: &'scanner str,
413+
414+
/// Namespace containing the rule. None if in the default namespace.
415+
pub namespace: Option<&'scanner str>,
416+
417+
/// Tags associated with the rule.
418+
pub tags: &'scanner [String],
419+
420+
/// Metadata associated with the rule.
421+
pub metadatas: &'scanner [Metadata],
422+
423+
/// Is the rule global
424+
pub is_global: bool,
425+
426+
/// Is the rule private
427+
pub is_private: bool,
364428
}
365429

366430
#[derive(Debug)]
@@ -1548,5 +1612,18 @@ mod tests {
15481612
entrypoint: None,
15491613
params: &ScanParams::default(),
15501614
});
1615+
test_type_traits_non_clonable(RulesIter {
1616+
global_rules: [].iter(),
1617+
rules: [].iter(),
1618+
namespaces: &[],
1619+
});
1620+
test_type_traits_non_clonable(RuleDetails {
1621+
name: "",
1622+
namespace: None,
1623+
tags: &[],
1624+
metadatas: &[],
1625+
is_global: false,
1626+
is_private: false,
1627+
});
15511628
}
15521629
}

boreal/tests/it/api.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::io::{Seek, Write};
44
use std::sync::atomic::{AtomicBool, Ordering};
55

66
use boreal::compiler::CompilerBuilder;
7+
use boreal::MetadataValue;
78

89
// An import is reused in the same namespace
910
#[test]
@@ -93,3 +94,92 @@ rule a {
9394
assert!(!FIRST.load(Ordering::SeqCst));
9495
assert!(SECOND.load(Ordering::SeqCst));
9596
}
97+
98+
#[test]
99+
fn test_scanner_list_rules() {
100+
let mut compiler = boreal::Compiler::new();
101+
102+
compiler
103+
.add_rules_str(
104+
r#"
105+
global rule g {
106+
condition: true
107+
}
108+
private rule p: tag {
109+
meta:
110+
b = true
111+
condition: true
112+
}
113+
"#,
114+
)
115+
.unwrap();
116+
compiler
117+
.add_rules_str_in_namespace(
118+
r#"
119+
private global rule pg: tag1 tag2 {
120+
meta:
121+
s = "str"
122+
i = -23
123+
condition: true
124+
}
125+
126+
rule r: tag {
127+
condition: true
128+
}
129+
"#,
130+
"namespace",
131+
)
132+
.unwrap();
133+
134+
let scanner = compiler.into_scanner();
135+
let rules: Vec<_> = scanner.rules().collect();
136+
137+
assert_eq!(rules.len(), 4);
138+
139+
let r0 = &rules[0];
140+
assert_eq!(r0.name, "g");
141+
assert_eq!(r0.namespace, None);
142+
assert_eq!(r0.tags.len(), 0);
143+
assert!(r0.is_global);
144+
assert!(!r0.is_private);
145+
assert_eq!(r0.metadatas.len(), 0);
146+
147+
let r1 = &rules[1];
148+
assert_eq!(r1.name, "pg");
149+
assert_eq!(r1.namespace, Some("namespace"));
150+
assert_eq!(r1.tags, &["tag1", "tag2"]);
151+
assert!(r1.is_global);
152+
assert!(r1.is_private);
153+
assert_eq!(r1.metadatas.len(), 2);
154+
assert_eq!(scanner.get_string_symbol(r1.metadatas[0].name), "s");
155+
match r1.metadatas[0].value {
156+
MetadataValue::Bytes(b) => assert_eq!(scanner.get_bytes_symbol(b), b"str"),
157+
_ => panic!("invalid metadata {:?}", r1.metadatas[0]),
158+
};
159+
assert_eq!(scanner.get_string_symbol(r1.metadatas[1].name), "i");
160+
match r1.metadatas[1].value {
161+
MetadataValue::Integer(i) => assert_eq!(i, -23),
162+
_ => panic!("invalid metadata {:?}", r1.metadatas[1]),
163+
};
164+
165+
let r2 = &rules[2];
166+
assert_eq!(r2.name, "p");
167+
assert_eq!(r2.namespace, None);
168+
assert_eq!(r2.tags, &["tag"]);
169+
assert!(!r2.is_global);
170+
assert!(r2.is_private);
171+
assert_eq!(r2.metadatas.len(), 1);
172+
assert_eq!(scanner.get_string_symbol(r2.metadatas[0].name), "b");
173+
match r2.metadatas[0].value {
174+
MetadataValue::Boolean(b) => assert!(b),
175+
_ => panic!("invalid metadata {:?}", r1.metadatas[0]),
176+
};
177+
178+
let r3 = &rules[3];
179+
assert_eq!(r3.name, "r");
180+
assert_eq!(r3.namespace, Some("namespace"));
181+
assert_eq!(r3.tags, &["tag"]);
182+
assert!(!r3.is_global);
183+
assert!(!r3.is_private);
184+
assert_eq!(r3.metadatas.len(), 0);
185+
}

0 commit comments

Comments
 (0)