1use std::borrow::Cow;
2
3use rsigma_parser::fieldpath::{first_unescaped, unescape_brackets};
4use serde_json::Value;
5
6use super::{Event, EventValue};
7
8const MAX_NESTING_DEPTH: usize = 64;
10
11#[derive(Debug)]
19pub struct JsonEvent<'a> {
20 inner: Cow<'a, Value>,
21}
22
23impl<'a> JsonEvent<'a> {
24 pub fn borrow(v: &'a Value) -> Self {
26 Self {
27 inner: Cow::Borrowed(v),
28 }
29 }
30
31 pub fn owned(v: Value) -> Self {
33 Self {
34 inner: Cow::Owned(v),
35 }
36 }
37}
38
39impl<'a> From<&'a Value> for JsonEvent<'a> {
40 fn from(v: &'a Value) -> Self {
41 Self::borrow(v)
42 }
43}
44
45impl From<Value> for JsonEvent<'static> {
46 fn from(v: Value) -> Self {
47 Self::owned(v)
48 }
49}
50
51impl<'a> Event for JsonEvent<'a> {
52 fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
61 let value: &Value = &self.inner;
62
63 if let Some(obj) = value.as_object() {
64 if let Some(v) = obj.get(path) {
65 return Some(EventValue::from(v));
66 }
67
68 if path.contains('.') || path.contains('[') || path.contains('\\') {
69 if let Some(root) = path_root_key(path)
72 && !obj.contains_key(root.as_ref())
73 {
74 return None;
75 }
76 let ops = parse_path_ops(path);
77 let mut collected: Vec<EventValue<'_>> = Vec::new();
78 collect_by_ops(value, &ops, &mut collected);
79 return match collected.len() {
80 0 => None,
81 1 => collected.pop(),
82 _ => Some(EventValue::Array(collected)),
83 };
84 }
85
86 return None;
87 }
88
89 if path.contains('.') || path.contains('[') || path.contains('\\') {
90 let ops = parse_path_ops(path);
91 let mut collected: Vec<EventValue<'_>> = Vec::new();
92 collect_by_ops(value, &ops, &mut collected);
93 return match collected.len() {
94 0 => None,
95 1 => collected.pop(),
96 _ => Some(EventValue::Array(collected)),
97 };
98 }
99
100 None
101 }
102
103 fn top_level_keys(&self) -> Option<Vec<Cow<'_, str>>> {
104 match self.inner.as_ref() {
105 Value::Object(map) => Some(map.keys().map(|k| Cow::Borrowed(k.as_str())).collect()),
106 _ => None,
109 }
110 }
111
112 fn visit_top_level_keys(&self, visit: &mut dyn FnMut(&str)) -> bool {
113 match self.inner.as_ref() {
114 Value::Object(map) => {
115 for key in map.keys() {
116 visit(key.as_str());
117 }
118 true
119 }
120 _ => false,
121 }
122 }
123
124 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
129 any_string_value_json(&self.inner, pred, MAX_NESTING_DEPTH)
130 }
131
132 fn all_string_values(&self) -> Vec<Cow<'_, str>> {
139 let mut values = Vec::new();
140 collect_string_values_json(&self.inner, &mut values, MAX_NESTING_DEPTH);
141 values
142 }
143
144 fn visit_string_values(&self, visit: &mut dyn FnMut(&str)) {
145 visit_string_values_json(&self.inner, visit, MAX_NESTING_DEPTH);
146 }
147
148 fn to_json(&self) -> Value {
149 self.inner.as_ref().clone()
150 }
151
152 fn field_keys(&self) -> Vec<Cow<'_, str>> {
163 let mut out = Vec::new();
164 collect_field_keys(&self.inner, "", &mut out, MAX_NESTING_DEPTH);
165 out
166 }
167}
168
169fn path_root_key(path: &str) -> Option<Cow<'_, str>> {
174 let segment = match first_unescaped(path, b'.') {
175 Some(0) => return None,
176 Some(pos) => &path[..pos],
177 None => path,
178 };
179 if segment.is_empty() {
180 return None;
181 }
182 let name = match first_unescaped(segment, b'[') {
183 Some(0) => return None,
184 Some(pos) => &segment[..pos],
185 None => segment,
186 };
187 if name.is_empty() {
188 return None;
189 }
190 Some(unescape_brackets(name))
191}
192
193enum PathOp<'a> {
195 Key(Cow<'a, str>),
198 Index(i64),
201}
202
203pub(crate) fn resolve_array_index(index: i64, len: usize) -> Option<usize> {
206 if index >= 0 {
207 usize::try_from(index).ok().filter(|&i| i < len)
208 } else {
209 usize::try_from(index.unsigned_abs())
210 .ok()
211 .and_then(|abs| len.checked_sub(abs))
212 }
213}
214
215fn parse_path_ops(path: &str) -> Vec<PathOp<'_>> {
220 let mut ops = Vec::new();
221 for part in path.split('.') {
222 match first_unescaped(part, b'[') {
223 Some(bpos) if parse_index_groups(&part[bpos..]).is_some() => {
224 let name = &part[..bpos];
225 if !name.is_empty() {
226 ops.push(PathOp::Key(unescape_brackets(name)));
227 }
228 for idx in parse_index_groups(&part[bpos..]).expect("checked") {
229 ops.push(PathOp::Index(idx));
230 }
231 }
232 _ => ops.push(PathOp::Key(unescape_brackets(part))),
235 }
236 }
237 ops
238}
239
240fn parse_index_groups(s: &str) -> Option<Vec<i64>> {
243 let mut out = Vec::new();
244 let mut rem = s;
245 while !rem.is_empty() {
246 let rest = rem.strip_prefix('[')?;
247 let close = rest.find(']')?;
248 let idx: i64 = rest[..close].parse().ok()?;
249 out.push(idx);
250 rem = &rest[close + 1..];
251 }
252 Some(out)
253}
254
255fn collect_by_ops<'a>(current: &'a Value, ops: &[PathOp<'_>], out: &mut Vec<EventValue<'a>>) {
262 let Some((op, rest)) = ops.split_first() else {
263 out.push(EventValue::from(current));
264 return;
265 };
266
267 match op {
268 PathOp::Key(key) => match current {
269 Value::Object(map) => {
270 if let Some(next) = map.get(key.as_ref()) {
271 collect_by_ops(next, rest, out);
272 }
273 }
274 Value::Array(arr) => {
275 for item in arr {
276 collect_by_ops(item, ops, out);
277 }
278 }
279 _ => {}
280 },
281 PathOp::Index(i) => {
282 if let Value::Array(arr) = current
283 && let Some(idx) = resolve_array_index(*i, arr.len())
284 && let Some(next) = arr.get(idx)
285 {
286 collect_by_ops(next, rest, out);
287 }
288 }
289 }
290}
291
292fn any_string_value_json(v: &Value, pred: &dyn Fn(&str) -> bool, depth: usize) -> bool {
293 if depth == 0 {
294 return false;
295 }
296 match v {
297 Value::String(s) => pred(s.as_str()),
298 Value::Number(n) => pred(&n.to_string()),
299 Value::Object(map) => map
300 .values()
301 .any(|val| any_string_value_json(val, pred, depth - 1)),
302 Value::Array(arr) => arr
303 .iter()
304 .any(|val| any_string_value_json(val, pred, depth - 1)),
305 _ => false,
306 }
307}
308
309fn collect_field_keys<'a>(v: &'a Value, prefix: &str, out: &mut Vec<Cow<'a, str>>, depth: usize) {
310 if depth == 0 {
311 return;
312 }
313 if let Value::Object(map) = v {
314 for (k, child) in map {
315 let path = if prefix.is_empty() {
316 k.clone()
317 } else {
318 format!("{prefix}.{k}")
319 };
320 match child {
321 Value::Object(_) => collect_field_keys(child, &path, out, depth - 1),
328 _ => out.push(Cow::Owned(path)),
329 }
330 }
331 }
332}
333
334fn collect_string_values_json<'a>(v: &'a Value, out: &mut Vec<Cow<'a, str>>, depth: usize) {
335 if depth == 0 {
336 return;
337 }
338 match v {
339 Value::String(s) => out.push(Cow::Borrowed(s.as_str())),
340 Value::Number(n) => out.push(Cow::Owned(n.to_string())),
341 Value::Object(map) => {
342 for val in map.values() {
343 collect_string_values_json(val, out, depth - 1);
344 }
345 }
346 Value::Array(arr) => {
347 for val in arr {
348 collect_string_values_json(val, out, depth - 1);
349 }
350 }
351 _ => {}
352 }
353}
354
355fn visit_string_values_json(v: &Value, visit: &mut dyn FnMut(&str), depth: usize) {
356 if depth == 0 {
357 return;
358 }
359 match v {
360 Value::String(s) => visit(s.as_str()),
361 Value::Number(n) => visit(&n.to_string()),
362 Value::Object(map) => {
363 for val in map.values() {
364 visit_string_values_json(val, visit, depth - 1);
365 }
366 }
367 Value::Array(arr) => {
368 for val in arr {
369 visit_string_values_json(val, visit, depth - 1);
370 }
371 }
372 _ => {}
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use serde_json::json;
380
381 #[test]
382 fn json_flat_field() {
383 let v = json!({"CommandLine": "whoami", "User": "admin"});
384 let event = JsonEvent::borrow(&v);
385 assert_eq!(
386 event.get_field("CommandLine"),
387 Some(EventValue::Str(Cow::Borrowed("whoami")))
388 );
389 }
390
391 #[test]
392 fn json_nested_field() {
393 let v = json!({"actor": {"id": "user123", "type": "User"}});
394 let event = JsonEvent::borrow(&v);
395 assert_eq!(
396 event.get_field("actor.id"),
397 Some(EventValue::Str(Cow::Borrowed("user123")))
398 );
399 }
400
401 #[test]
402 fn json_flat_key_precedence() {
403 let v = json!({"actor.id": "flat_value", "actor": {"id": "nested_value"}});
404 let event = JsonEvent::borrow(&v);
405 assert_eq!(
406 event.get_field("actor.id"),
407 Some(EventValue::Str(Cow::Borrowed("flat_value")))
408 );
409 }
410
411 #[test]
412 fn json_missing_field() {
413 let v = json!({"foo": "bar"});
414 let event = JsonEvent::borrow(&v);
415 assert_eq!(event.get_field("missing"), None);
416 }
417
418 #[test]
419 fn json_dotted_miss_skips_absent_root() {
420 let v = json!({"message": "x", "product": "windows"});
421 let event = JsonEvent::borrow(&v);
422 assert_eq!(event.get_field("Event.EventData.CommandLine"), None);
423 assert_eq!(event.get_field("actor.id"), None);
424 assert_eq!(event.get_field("name[0].x"), None);
425 }
426
427 #[test]
428 fn json_top_level_keys_object() {
429 let v = json!({"message": "x", "product": "windows"});
430 let event = JsonEvent::borrow(&v);
431 let mut keys: Vec<String> = event
432 .top_level_keys()
433 .unwrap()
434 .into_iter()
435 .map(|k| k.into_owned())
436 .collect();
437 keys.sort();
438 assert_eq!(keys, vec!["message", "product"]);
439 }
440
441 #[test]
442 fn json_top_level_keys_non_object_unknown() {
443 let v = json!([{"a": 1}]);
444 let event = JsonEvent::borrow(&v);
445 assert_eq!(event.top_level_keys(), None);
446 }
447
448 #[test]
449 fn path_root_key_segments() {
450 assert_eq!(path_root_key("CommandLine").as_deref(), Some("CommandLine"));
451 assert_eq!(path_root_key("actor.id").as_deref(), Some("actor"));
452 assert_eq!(path_root_key("name[0].x").as_deref(), Some("name"));
453 assert_eq!(path_root_key(".id"), None);
454 assert_eq!(path_root_key("[0].x"), None);
455 }
456
457 #[test]
458 fn json_null_field() {
459 let v = json!({"foo": null});
460 let event = JsonEvent::borrow(&v);
461 assert_eq!(event.get_field("foo"), Some(EventValue::Null));
462 }
463
464 #[test]
465 fn json_array_traversal() {
466 let v = json!({"a": {"b": [{"c": "found"}, {"c": "other"}]}});
469 let event = JsonEvent::borrow(&v);
470 assert_eq!(
471 event.get_field("a.b.c"),
472 Some(EventValue::Array(vec![
473 EventValue::Str(Cow::Borrowed("found")),
474 EventValue::Str(Cow::Borrowed("other")),
475 ]))
476 );
477 }
478
479 #[test]
480 fn json_array_traversal_no_match() {
481 let v = json!({"a": {"b": [{"x": 1}, {"y": 2}]}});
482 let event = JsonEvent::borrow(&v);
483 assert_eq!(event.get_field("a.b.c"), None);
484 }
485
486 #[test]
487 fn json_array_traversal_deep() {
488 let v = json!({
489 "events": [
490 {"actors": [{"name": "alice"}, {"name": "bob"}]},
491 {"actors": [{"name": "charlie"}]}
492 ]
493 });
494 let event = JsonEvent::borrow(&v);
495 assert_eq!(
497 event.get_field("events.actors.name"),
498 Some(EventValue::Array(vec![
499 EventValue::Str(Cow::Borrowed("alice")),
500 EventValue::Str(Cow::Borrowed("bob")),
501 EventValue::Str(Cow::Borrowed("charlie")),
502 ]))
503 );
504 }
505
506 #[test]
507 fn json_array_at_root_level() {
508 let v = json!({"process": [{"command_line": "whoami"}, {"command_line": "id"}]});
509 let event = JsonEvent::borrow(&v);
510 assert_eq!(
511 event.get_field("process.command_line"),
512 Some(EventValue::Array(vec![
513 EventValue::Str(Cow::Borrowed("whoami")),
514 EventValue::Str(Cow::Borrowed("id")),
515 ]))
516 );
517 }
518
519 #[test]
520 fn json_array_returns_array_value() {
521 let v = json!({"a": {"tags": ["t1", "t2"]}});
522 let event = JsonEvent::borrow(&v);
523 let result = event.get_field("a.tags");
524 assert!(matches!(result, Some(EventValue::Array(_))));
525 }
526
527 #[test]
528 fn json_flat_key_wins_over_array_traversal() {
529 let v = json!({"a.b.c": "flat", "a": {"b": [{"c": "nested"}]}});
530 let event = JsonEvent::borrow(&v);
531 assert_eq!(
532 event.get_field("a.b.c"),
533 Some(EventValue::Str(Cow::Borrowed("flat")))
534 );
535 }
536
537 #[test]
538 fn json_all_string_values() {
539 let v = json!({
540 "a": "hello",
541 "b": 42,
542 "c": {"d": "world", "e": true},
543 "f": ["one", "two"]
544 });
545 let event = JsonEvent::borrow(&v);
546 let values = event.all_string_values();
547 let strs: Vec<&str> = values.iter().map(|c| c.as_ref()).collect();
548 assert!(strs.contains(&"hello"));
549 assert!(strs.contains(&"world"));
550 assert!(strs.contains(&"one"));
551 assert!(strs.contains(&"two"));
552 assert!(strs.contains(&"42"));
553 assert_eq!(values.len(), 5);
554 }
555
556 #[test]
557 fn json_to_json_roundtrip() {
558 let v = json!({"a": 1, "b": "hello", "c": [1, 2]});
559 let event = JsonEvent::borrow(&v);
560 assert_eq!(event.to_json(), v);
561 }
562
563 #[test]
564 fn json_owned_works() {
565 let v = json!({"key": "value"});
566 let event = JsonEvent::owned(v.clone());
567 assert_eq!(
568 event.get_field("key"),
569 Some(EventValue::Str(Cow::Borrowed("value")))
570 );
571 assert_eq!(event.to_json(), v);
572 }
573
574 #[test]
575 fn json_field_keys_flat() {
576 let v = json!({"CommandLine": "x", "User": "y"});
577 let event = JsonEvent::borrow(&v);
578 let mut keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
579 keys.sort();
580 assert_eq!(keys, vec!["CommandLine", "User"]);
581 }
582
583 #[test]
584 fn json_field_keys_nested_leaves_only() {
585 let v = json!({"actor": {"id": "u1", "type": "User"}, "verb": "login"});
589 let event = JsonEvent::borrow(&v);
590 let mut keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
591 keys.sort();
592 assert_eq!(keys, vec!["actor.id", "actor.type", "verb"]);
593 }
594
595 #[test]
596 fn json_field_keys_deeply_nested_leaves_only() {
597 let v = json!({"a": {"b": {"c": 1}}, "flat": "x"});
598 let event = JsonEvent::borrow(&v);
599 let mut keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
600 keys.sort();
601 assert_eq!(keys, vec!["a.b.c", "flat"]);
602 }
603
604 #[test]
605 fn json_field_keys_array_parent_only() {
606 let v = json!({"events": [{"id": 1}, {"id": 2}]});
607 let event = JsonEvent::borrow(&v);
608 let keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
609 assert_eq!(keys, vec!["events"]);
611 }
612
613 #[test]
614 fn json_field_keys_top_level_non_object_empty() {
615 let v = json!("just a string");
616 let event = JsonEvent::owned(v);
617 assert!(event.field_keys().is_empty());
618 }
619
620 #[test]
621 fn json_traversal_with_consecutive_dots_does_not_panic() {
622 let v = json!({"a": {"b": "x"}});
629 let event = JsonEvent::borrow(&v);
630 assert_eq!(event.get_field("a..b"), None);
631 }
632
633 #[test]
634 fn json_traversal_with_trailing_dot_does_not_panic() {
635 let v = json!({"a": {"b": "x"}});
640 let event = JsonEvent::borrow(&v);
641 assert_eq!(event.get_field("a.b."), None);
642 }
643}