use std::borrow::Cow;
use rsigma_parser::fieldpath::{first_unescaped, unescape_brackets};
use serde_json::Value;
use super::{Event, EventValue};
const MAX_NESTING_DEPTH: usize = 64;
#[derive(Debug)]
pub struct JsonEvent<'a> {
inner: Cow<'a, Value>,
}
impl<'a> JsonEvent<'a> {
pub fn borrow(v: &'a Value) -> Self {
Self {
inner: Cow::Borrowed(v),
}
}
pub fn owned(v: Value) -> Self {
Self {
inner: Cow::Owned(v),
}
}
}
impl<'a> From<&'a Value> for JsonEvent<'a> {
fn from(v: &'a Value) -> Self {
Self::borrow(v)
}
}
impl From<Value> for JsonEvent<'static> {
fn from(v: Value) -> Self {
Self::owned(v)
}
}
impl<'a> Event for JsonEvent<'a> {
fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
let value: &Value = &self.inner;
if let Some(obj) = value.as_object()
&& let Some(v) = obj.get(path)
{
return Some(EventValue::from(v));
}
if path.contains('.') || path.contains('[') || path.contains('\\') {
let ops = parse_path_ops(path);
let mut collected: Vec<EventValue<'_>> = Vec::new();
collect_by_ops(value, &ops, &mut collected);
return match collected.len() {
0 => None,
1 => collected.pop(),
_ => Some(EventValue::Array(collected)),
};
}
None
}
fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
any_string_value_json(&self.inner, pred, MAX_NESTING_DEPTH)
}
fn all_string_values(&self) -> Vec<Cow<'_, str>> {
let mut values = Vec::new();
collect_string_values_json(&self.inner, &mut values, MAX_NESTING_DEPTH);
values
}
fn to_json(&self) -> Value {
self.inner.as_ref().clone()
}
fn field_keys(&self) -> Vec<Cow<'_, str>> {
let mut out = Vec::new();
collect_field_keys(&self.inner, "", &mut out, MAX_NESTING_DEPTH);
out
}
}
enum PathOp<'a> {
Key(Cow<'a, str>),
Index(i64),
}
pub(crate) fn resolve_array_index(index: i64, len: usize) -> Option<usize> {
if index >= 0 {
usize::try_from(index).ok().filter(|&i| i < len)
} else {
usize::try_from(index.unsigned_abs())
.ok()
.and_then(|abs| len.checked_sub(abs))
}
}
fn parse_path_ops(path: &str) -> Vec<PathOp<'_>> {
let mut ops = Vec::new();
for part in path.split('.') {
match first_unescaped(part, b'[') {
Some(bpos) if parse_index_groups(&part[bpos..]).is_some() => {
let name = &part[..bpos];
if !name.is_empty() {
ops.push(PathOp::Key(unescape_brackets(name)));
}
for idx in parse_index_groups(&part[bpos..]).expect("checked") {
ops.push(PathOp::Index(idx));
}
}
_ => ops.push(PathOp::Key(unescape_brackets(part))),
}
}
ops
}
fn parse_index_groups(s: &str) -> Option<Vec<i64>> {
let mut out = Vec::new();
let mut rem = s;
while !rem.is_empty() {
let rest = rem.strip_prefix('[')?;
let close = rest.find(']')?;
let idx: i64 = rest[..close].parse().ok()?;
out.push(idx);
rem = &rest[close + 1..];
}
Some(out)
}
fn collect_by_ops<'a>(current: &'a Value, ops: &[PathOp<'_>], out: &mut Vec<EventValue<'a>>) {
let Some((op, rest)) = ops.split_first() else {
out.push(EventValue::from(current));
return;
};
match op {
PathOp::Key(key) => match current {
Value::Object(map) => {
if let Some(next) = map.get(key.as_ref()) {
collect_by_ops(next, rest, out);
}
}
Value::Array(arr) => {
for item in arr {
collect_by_ops(item, ops, out);
}
}
_ => {}
},
PathOp::Index(i) => {
if let Value::Array(arr) = current
&& let Some(idx) = resolve_array_index(*i, arr.len())
&& let Some(next) = arr.get(idx)
{
collect_by_ops(next, rest, out);
}
}
}
}
fn any_string_value_json(v: &Value, pred: &dyn Fn(&str) -> bool, depth: usize) -> bool {
if depth == 0 {
return false;
}
match v {
Value::String(s) => pred(s.as_str()),
Value::Object(map) => map
.values()
.any(|val| any_string_value_json(val, pred, depth - 1)),
Value::Array(arr) => arr
.iter()
.any(|val| any_string_value_json(val, pred, depth - 1)),
_ => false,
}
}
fn collect_field_keys<'a>(v: &'a Value, prefix: &str, out: &mut Vec<Cow<'a, str>>, depth: usize) {
if depth == 0 {
return;
}
if let Value::Object(map) = v {
for (k, child) in map {
let path = if prefix.is_empty() {
k.clone()
} else {
format!("{prefix}.{k}")
};
match child {
Value::Object(_) => collect_field_keys(child, &path, out, depth - 1),
_ => out.push(Cow::Owned(path)),
}
}
}
}
fn collect_string_values_json<'a>(v: &'a Value, out: &mut Vec<Cow<'a, str>>, depth: usize) {
if depth == 0 {
return;
}
match v {
Value::String(s) => out.push(Cow::Borrowed(s.as_str())),
Value::Object(map) => {
for val in map.values() {
collect_string_values_json(val, out, depth - 1);
}
}
Value::Array(arr) => {
for val in arr {
collect_string_values_json(val, out, depth - 1);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn json_flat_field() {
let v = json!({"CommandLine": "whoami", "User": "admin"});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("CommandLine"),
Some(EventValue::Str(Cow::Borrowed("whoami")))
);
}
#[test]
fn json_nested_field() {
let v = json!({"actor": {"id": "user123", "type": "User"}});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("actor.id"),
Some(EventValue::Str(Cow::Borrowed("user123")))
);
}
#[test]
fn json_flat_key_precedence() {
let v = json!({"actor.id": "flat_value", "actor": {"id": "nested_value"}});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("actor.id"),
Some(EventValue::Str(Cow::Borrowed("flat_value")))
);
}
#[test]
fn json_missing_field() {
let v = json!({"foo": "bar"});
let event = JsonEvent::borrow(&v);
assert_eq!(event.get_field("missing"), None);
}
#[test]
fn json_null_field() {
let v = json!({"foo": null});
let event = JsonEvent::borrow(&v);
assert_eq!(event.get_field("foo"), Some(EventValue::Null));
}
#[test]
fn json_array_traversal() {
let v = json!({"a": {"b": [{"c": "found"}, {"c": "other"}]}});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("a.b.c"),
Some(EventValue::Array(vec![
EventValue::Str(Cow::Borrowed("found")),
EventValue::Str(Cow::Borrowed("other")),
]))
);
}
#[test]
fn json_array_traversal_no_match() {
let v = json!({"a": {"b": [{"x": 1}, {"y": 2}]}});
let event = JsonEvent::borrow(&v);
assert_eq!(event.get_field("a.b.c"), None);
}
#[test]
fn json_array_traversal_deep() {
let v = json!({
"events": [
{"actors": [{"name": "alice"}, {"name": "bob"}]},
{"actors": [{"name": "charlie"}]}
]
});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("events.actors.name"),
Some(EventValue::Array(vec![
EventValue::Str(Cow::Borrowed("alice")),
EventValue::Str(Cow::Borrowed("bob")),
EventValue::Str(Cow::Borrowed("charlie")),
]))
);
}
#[test]
fn json_array_at_root_level() {
let v = json!({"process": [{"command_line": "whoami"}, {"command_line": "id"}]});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("process.command_line"),
Some(EventValue::Array(vec![
EventValue::Str(Cow::Borrowed("whoami")),
EventValue::Str(Cow::Borrowed("id")),
]))
);
}
#[test]
fn json_array_returns_array_value() {
let v = json!({"a": {"tags": ["t1", "t2"]}});
let event = JsonEvent::borrow(&v);
let result = event.get_field("a.tags");
assert!(matches!(result, Some(EventValue::Array(_))));
}
#[test]
fn json_flat_key_wins_over_array_traversal() {
let v = json!({"a.b.c": "flat", "a": {"b": [{"c": "nested"}]}});
let event = JsonEvent::borrow(&v);
assert_eq!(
event.get_field("a.b.c"),
Some(EventValue::Str(Cow::Borrowed("flat")))
);
}
#[test]
fn json_all_string_values() {
let v = json!({
"a": "hello",
"b": 42,
"c": {"d": "world", "e": true},
"f": ["one", "two"]
});
let event = JsonEvent::borrow(&v);
let values = event.all_string_values();
let strs: Vec<&str> = values.iter().map(|c| c.as_ref()).collect();
assert!(strs.contains(&"hello"));
assert!(strs.contains(&"world"));
assert!(strs.contains(&"one"));
assert!(strs.contains(&"two"));
assert_eq!(values.len(), 4);
}
#[test]
fn json_to_json_roundtrip() {
let v = json!({"a": 1, "b": "hello", "c": [1, 2]});
let event = JsonEvent::borrow(&v);
assert_eq!(event.to_json(), v);
}
#[test]
fn json_owned_works() {
let v = json!({"key": "value"});
let event = JsonEvent::owned(v.clone());
assert_eq!(
event.get_field("key"),
Some(EventValue::Str(Cow::Borrowed("value")))
);
assert_eq!(event.to_json(), v);
}
#[test]
fn json_field_keys_flat() {
let v = json!({"CommandLine": "x", "User": "y"});
let event = JsonEvent::borrow(&v);
let mut keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
keys.sort();
assert_eq!(keys, vec!["CommandLine", "User"]);
}
#[test]
fn json_field_keys_nested_leaves_only() {
let v = json!({"actor": {"id": "u1", "type": "User"}, "verb": "login"});
let event = JsonEvent::borrow(&v);
let mut keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
keys.sort();
assert_eq!(keys, vec!["actor.id", "actor.type", "verb"]);
}
#[test]
fn json_field_keys_deeply_nested_leaves_only() {
let v = json!({"a": {"b": {"c": 1}}, "flat": "x"});
let event = JsonEvent::borrow(&v);
let mut keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
keys.sort();
assert_eq!(keys, vec!["a.b.c", "flat"]);
}
#[test]
fn json_field_keys_array_parent_only() {
let v = json!({"events": [{"id": 1}, {"id": 2}]});
let event = JsonEvent::borrow(&v);
let keys: Vec<String> = event.field_keys().iter().map(|c| c.to_string()).collect();
assert_eq!(keys, vec!["events"]);
}
#[test]
fn json_field_keys_top_level_non_object_empty() {
let v = json!("just a string");
let event = JsonEvent::owned(v);
assert!(event.field_keys().is_empty());
}
#[test]
fn json_traversal_with_consecutive_dots_does_not_panic() {
let v = json!({"a": {"b": "x"}});
let event = JsonEvent::borrow(&v);
assert_eq!(event.get_field("a..b"), None);
}
#[test]
fn json_traversal_with_trailing_dot_does_not_panic() {
let v = json!({"a": {"b": "x"}});
let event = JsonEvent::borrow(&v);
assert_eq!(event.get_field("a.b."), None);
}
}