Skip to main content

rsigma_eval/event/
kv.rs

1use std::borrow::Cow;
2
3use serde_json::Value;
4
5use super::{Event, EventValue};
6
7/// Flat key-value event (e.g., from logfmt, syslog structured data).
8///
9/// No nested access (no dot-notation traversal), no arrays.
10/// All values are strings.
11#[derive(Debug, Clone)]
12pub struct KvEvent {
13    fields: Vec<(String, String)>,
14}
15
16impl KvEvent {
17    pub fn new(fields: Vec<(String, String)>) -> Self {
18        Self { fields }
19    }
20
21    pub fn fields(&self) -> &[(String, String)] {
22        &self.fields
23    }
24}
25
26impl Event for KvEvent {
27    fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
28        self.fields
29            .iter()
30            .find(|(k, _)| k == path)
31            .map(|(_, v)| EventValue::Str(Cow::Borrowed(v.as_str())))
32    }
33
34    fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
35        self.fields.iter().any(|(_, v)| pred(v.as_str()))
36    }
37
38    fn all_string_values(&self) -> Vec<Cow<'_, str>> {
39        self.fields
40            .iter()
41            .map(|(_, v)| Cow::Borrowed(v.as_str()))
42            .collect()
43    }
44
45    fn to_json(&self) -> Value {
46        let map: serde_json::Map<String, Value> = self
47            .fields
48            .iter()
49            .map(|(k, v)| (k.clone(), Value::String(v.clone())))
50            .collect();
51        Value::Object(map)
52    }
53
54    fn field_keys(&self) -> Vec<Cow<'_, str>> {
55        self.fields
56            .iter()
57            .map(|(k, _)| Cow::Borrowed(k.as_str()))
58            .collect()
59    }
60
61    fn top_level_keys(&self) -> Option<Vec<Cow<'_, str>>> {
62        Some(
63            self.fields
64                .iter()
65                .map(|(k, _)| Cow::Borrowed(k.as_str()))
66                .collect(),
67        )
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use serde_json::json;
75
76    #[test]
77    fn kv_get_field() {
78        let event = KvEvent::new(vec![
79            ("host".into(), "web01".into()),
80            ("status".into(), "200".into()),
81        ]);
82        assert_eq!(
83            event.get_field("host"),
84            Some(EventValue::Str(Cow::Borrowed("web01")))
85        );
86        assert_eq!(event.get_field("missing"), None);
87    }
88
89    #[test]
90    fn kv_all_string_values() {
91        let event = KvEvent::new(vec![("a".into(), "one".into()), ("b".into(), "two".into())]);
92        let vals = event.all_string_values();
93        assert_eq!(vals.len(), 2);
94    }
95
96    #[test]
97    fn kv_to_json() {
98        let event = KvEvent::new(vec![("key".into(), "val".into())]);
99        let j = event.to_json();
100        assert_eq!(j, json!({"key": "val"}));
101    }
102
103    #[test]
104    fn kv_field_keys() {
105        let event = KvEvent::new(vec![
106            ("host".into(), "web01".into()),
107            ("status".into(), "200".into()),
108        ]);
109        let keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
110        assert_eq!(keys, vec!["host", "status"]);
111    }
112}