Skip to main content

rsigma_eval/event/
json.rs

1use std::borrow::Cow;
2
3use serde_json::Value;
4
5use super::{Event, EventValue};
6
7/// Maximum nesting depth for recursive JSON traversal.
8const MAX_NESTING_DEPTH: usize = 64;
9
10/// Zero-copy event backed by `serde_json::Value`.
11///
12/// Supports both borrowed (`&Value`) and owned (`Value`) backing via `Cow`.
13/// This is the primary implementation for JSON/NDJSON input.
14///
15/// Flat keys are checked first: `"actor.id"` as a single key takes precedence
16/// over `{"actor": {"id": ...}}` nested traversal.
17#[derive(Debug)]
18pub struct JsonEvent<'a> {
19    inner: Cow<'a, Value>,
20}
21
22impl<'a> JsonEvent<'a> {
23    /// Wrap a borrowed JSON value as an event.
24    pub fn borrow(v: &'a Value) -> Self {
25        Self {
26            inner: Cow::Borrowed(v),
27        }
28    }
29
30    /// Wrap an owned JSON value as an event.
31    pub fn owned(v: Value) -> Self {
32        Self {
33            inner: Cow::Owned(v),
34        }
35    }
36}
37
38impl<'a> From<&'a Value> for JsonEvent<'a> {
39    fn from(v: &'a Value) -> Self {
40        Self::borrow(v)
41    }
42}
43
44impl From<Value> for JsonEvent<'static> {
45    fn from(v: Value) -> Self {
46        Self::owned(v)
47    }
48}
49
50impl<'a> Event for JsonEvent<'a> {
51    /// Get a field value by name, supporting dot-notation for nested access.
52    ///
53    /// Checks for a flat key first (exact match), then falls back to
54    /// dot-separated traversal. When a path segment yields an array,
55    /// each element is tried and the first match is returned (OR semantics).
56    fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
57        let value: &Value = &self.inner;
58
59        if let Some(obj) = value.as_object()
60            && let Some(v) = obj.get(path)
61        {
62            return Some(EventValue::from(v));
63        }
64
65        if path.contains('.') {
66            let parts: Vec<&str> = path.split('.').collect();
67            return traverse_json(value, &parts).map(EventValue::from);
68        }
69
70        None
71    }
72
73    /// Check if any string value in the event satisfies a predicate.
74    ///
75    /// Short-circuits on the first match, avoiding the allocation of
76    /// collecting all string values into a `Vec`.
77    fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
78        any_string_value_json(&self.inner, pred, MAX_NESTING_DEPTH)
79    }
80
81    /// Iterate over all string values in the event (for keyword detection).
82    ///
83    /// Recursively walks the entire event object and yields every string
84    /// value found, including inside nested objects and arrays. Traversal
85    /// is capped at 64 levels of nesting to prevent stack overflow.
86    fn all_string_values(&self) -> Vec<Cow<'_, str>> {
87        let mut values = Vec::new();
88        collect_string_values_json(&self.inner, &mut values, MAX_NESTING_DEPTH);
89        values
90    }
91
92    fn to_json(&self) -> Value {
93        self.inner.as_ref().clone()
94    }
95}
96
97/// Recursively traverse a JSON value following dot-notation path segments.
98///
99/// When a segment resolves to an array, each element is tried and the first
100/// match for the remaining path is returned.
101fn traverse_json<'a>(current: &'a Value, parts: &[&str]) -> Option<&'a Value> {
102    if parts.is_empty() {
103        return Some(current);
104    }
105
106    let (head, rest) = (parts[0], &parts[1..]);
107
108    match current {
109        Value::Object(map) => {
110            let next = map.get(head)?;
111            traverse_json(next, rest)
112        }
113        Value::Array(arr) => {
114            for item in arr {
115                if let Some(v) = traverse_json(item, parts) {
116                    return Some(v);
117                }
118            }
119            None
120        }
121        _ => None,
122    }
123}
124
125fn any_string_value_json(v: &Value, pred: &dyn Fn(&str) -> bool, depth: usize) -> bool {
126    if depth == 0 {
127        return false;
128    }
129    match v {
130        Value::String(s) => pred(s.as_str()),
131        Value::Object(map) => map
132            .values()
133            .any(|val| any_string_value_json(val, pred, depth - 1)),
134        Value::Array(arr) => arr
135            .iter()
136            .any(|val| any_string_value_json(val, pred, depth - 1)),
137        _ => false,
138    }
139}
140
141fn collect_string_values_json<'a>(v: &'a Value, out: &mut Vec<Cow<'a, str>>, depth: usize) {
142    if depth == 0 {
143        return;
144    }
145    match v {
146        Value::String(s) => out.push(Cow::Borrowed(s.as_str())),
147        Value::Object(map) => {
148            for val in map.values() {
149                collect_string_values_json(val, out, depth - 1);
150            }
151        }
152        Value::Array(arr) => {
153            for val in arr {
154                collect_string_values_json(val, out, depth - 1);
155            }
156        }
157        _ => {}
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use serde_json::json;
165
166    #[test]
167    fn json_flat_field() {
168        let v = json!({"CommandLine": "whoami", "User": "admin"});
169        let event = JsonEvent::borrow(&v);
170        assert_eq!(
171            event.get_field("CommandLine"),
172            Some(EventValue::Str(Cow::Borrowed("whoami")))
173        );
174    }
175
176    #[test]
177    fn json_nested_field() {
178        let v = json!({"actor": {"id": "user123", "type": "User"}});
179        let event = JsonEvent::borrow(&v);
180        assert_eq!(
181            event.get_field("actor.id"),
182            Some(EventValue::Str(Cow::Borrowed("user123")))
183        );
184    }
185
186    #[test]
187    fn json_flat_key_precedence() {
188        let v = json!({"actor.id": "flat_value", "actor": {"id": "nested_value"}});
189        let event = JsonEvent::borrow(&v);
190        assert_eq!(
191            event.get_field("actor.id"),
192            Some(EventValue::Str(Cow::Borrowed("flat_value")))
193        );
194    }
195
196    #[test]
197    fn json_missing_field() {
198        let v = json!({"foo": "bar"});
199        let event = JsonEvent::borrow(&v);
200        assert_eq!(event.get_field("missing"), None);
201    }
202
203    #[test]
204    fn json_null_field() {
205        let v = json!({"foo": null});
206        let event = JsonEvent::borrow(&v);
207        assert_eq!(event.get_field("foo"), Some(EventValue::Null));
208    }
209
210    #[test]
211    fn json_array_traversal() {
212        let v = json!({"a": {"b": [{"c": "found"}, {"c": "other"}]}});
213        let event = JsonEvent::borrow(&v);
214        assert_eq!(
215            event.get_field("a.b.c"),
216            Some(EventValue::Str(Cow::Borrowed("found")))
217        );
218    }
219
220    #[test]
221    fn json_array_traversal_no_match() {
222        let v = json!({"a": {"b": [{"x": 1}, {"y": 2}]}});
223        let event = JsonEvent::borrow(&v);
224        assert_eq!(event.get_field("a.b.c"), None);
225    }
226
227    #[test]
228    fn json_array_traversal_deep() {
229        let v = json!({
230            "events": [
231                {"actors": [{"name": "alice"}, {"name": "bob"}]},
232                {"actors": [{"name": "charlie"}]}
233            ]
234        });
235        let event = JsonEvent::borrow(&v);
236        assert_eq!(
237            event.get_field("events.actors.name"),
238            Some(EventValue::Str(Cow::Borrowed("alice")))
239        );
240    }
241
242    #[test]
243    fn json_array_at_root_level() {
244        let v = json!({"process": [{"command_line": "whoami"}, {"command_line": "id"}]});
245        let event = JsonEvent::borrow(&v);
246        assert_eq!(
247            event.get_field("process.command_line"),
248            Some(EventValue::Str(Cow::Borrowed("whoami")))
249        );
250    }
251
252    #[test]
253    fn json_array_returns_array_value() {
254        let v = json!({"a": {"tags": ["t1", "t2"]}});
255        let event = JsonEvent::borrow(&v);
256        let result = event.get_field("a.tags");
257        assert!(matches!(result, Some(EventValue::Array(_))));
258    }
259
260    #[test]
261    fn json_flat_key_wins_over_array_traversal() {
262        let v = json!({"a.b.c": "flat", "a": {"b": [{"c": "nested"}]}});
263        let event = JsonEvent::borrow(&v);
264        assert_eq!(
265            event.get_field("a.b.c"),
266            Some(EventValue::Str(Cow::Borrowed("flat")))
267        );
268    }
269
270    #[test]
271    fn json_all_string_values() {
272        let v = json!({
273            "a": "hello",
274            "b": 42,
275            "c": {"d": "world", "e": true},
276            "f": ["one", "two"]
277        });
278        let event = JsonEvent::borrow(&v);
279        let values = event.all_string_values();
280        let strs: Vec<&str> = values.iter().map(|c| c.as_ref()).collect();
281        assert!(strs.contains(&"hello"));
282        assert!(strs.contains(&"world"));
283        assert!(strs.contains(&"one"));
284        assert!(strs.contains(&"two"));
285        assert_eq!(values.len(), 4);
286    }
287
288    #[test]
289    fn json_to_json_roundtrip() {
290        let v = json!({"a": 1, "b": "hello", "c": [1, 2]});
291        let event = JsonEvent::borrow(&v);
292        assert_eq!(event.to_json(), v);
293    }
294
295    #[test]
296    fn json_owned_works() {
297        let v = json!({"key": "value"});
298        let event = JsonEvent::owned(v.clone());
299        assert_eq!(
300            event.get_field("key"),
301            Some(EventValue::Str(Cow::Borrowed("value")))
302        );
303        assert_eq!(event.to_json(), v);
304    }
305}