use serde_json::Value;
#[derive(Debug)]
pub struct Event<'a> {
inner: &'a Value,
}
impl<'a> Event<'a> {
pub fn from_value(value: &'a Value) -> Self {
Event { inner: value }
}
pub fn get_field(&self, path: &str) -> Option<&'a Value> {
if let Some(obj) = self.inner.as_object()
&& let Some(v) = obj.get(path)
{
return Some(v);
}
if path.contains('.') {
let parts: Vec<&str> = path.split('.').collect();
return traverse(self.inner, &parts);
}
None
}
pub fn all_string_values(&self) -> Vec<&'a str> {
let mut values = Vec::new();
collect_string_values(self.inner, &mut values, MAX_NESTING_DEPTH);
values
}
pub fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
any_string_value_rec(self.inner, pred, MAX_NESTING_DEPTH)
}
pub fn as_value(&self) -> &'a Value {
self.inner
}
}
fn traverse<'a>(current: &'a Value, parts: &[&str]) -> Option<&'a Value> {
if parts.is_empty() {
return Some(current);
}
let (head, rest) = (parts[0], &parts[1..]);
match current {
Value::Object(map) => {
let next = map.get(head)?;
traverse(next, rest)
}
Value::Array(arr) => {
for item in arr {
if let Some(v) = traverse(item, parts) {
return Some(v);
}
}
None
}
_ => None,
}
}
const MAX_NESTING_DEPTH: usize = 64;
fn any_string_value_rec(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_rec(val, pred, depth - 1)),
Value::Array(arr) => arr
.iter()
.any(|val| any_string_value_rec(val, pred, depth - 1)),
_ => false,
}
}
fn collect_string_values<'a>(v: &'a Value, out: &mut Vec<&'a str>, depth: usize) {
if depth == 0 {
return;
}
match v {
Value::String(s) => out.push(s.as_str()),
Value::Object(map) => {
for val in map.values() {
collect_string_values(val, out, depth - 1);
}
}
Value::Array(arr) => {
for val in arr {
collect_string_values(val, out, depth - 1);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_flat_field() {
let v = json!({"CommandLine": "whoami", "User": "admin"});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("CommandLine"),
Some(&Value::String("whoami".into()))
);
}
#[test]
fn test_nested_field() {
let v = json!({"actor": {"id": "user123", "type": "User"}});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("actor.id"),
Some(&Value::String("user123".into()))
);
}
#[test]
fn test_flat_key_precedence() {
let v = json!({"actor.id": "flat_value", "actor": {"id": "nested_value"}});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("actor.id"),
Some(&Value::String("flat_value".into()))
);
}
#[test]
fn test_missing_field() {
let v = json!({"foo": "bar"});
let event = Event::from_value(&v);
assert_eq!(event.get_field("missing"), None);
}
#[test]
fn test_array_traversal() {
let v = json!({"a": {"b": [{"c": "found"}, {"c": "other"}]}});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("a.b.c"),
Some(&Value::String("found".into()))
);
}
#[test]
fn test_array_traversal_no_match() {
let v = json!({"a": {"b": [{"x": 1}, {"y": 2}]}});
let event = Event::from_value(&v);
assert_eq!(event.get_field("a.b.c"), None);
}
#[test]
fn test_array_traversal_deep() {
let v = json!({
"events": [
{"actors": [{"name": "alice"}, {"name": "bob"}]},
{"actors": [{"name": "charlie"}]}
]
});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("events.actors.name"),
Some(&Value::String("alice".into()))
);
}
#[test]
fn test_array_at_root_level() {
let v = json!({"process": [{"command_line": "whoami"}, {"command_line": "id"}]});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("process.command_line"),
Some(&Value::String("whoami".into()))
);
}
#[test]
fn test_array_returns_array_value() {
let v = json!({"a": {"tags": ["t1", "t2"]}});
let event = Event::from_value(&v);
assert_eq!(event.get_field("a.tags"), Some(&json!(["t1", "t2"])));
}
#[test]
fn test_flat_key_still_wins_over_array_traversal() {
let v = json!({"a.b.c": "flat", "a": {"b": [{"c": "nested"}]}});
let event = Event::from_value(&v);
assert_eq!(
event.get_field("a.b.c"),
Some(&Value::String("flat".into()))
);
}
#[test]
fn test_all_string_values() {
let v = json!({
"a": "hello",
"b": 42,
"c": {"d": "world", "e": true},
"f": ["one", "two"]
});
let event = Event::from_value(&v);
let values = event.all_string_values();
assert!(values.contains(&"hello"));
assert!(values.contains(&"world"));
assert!(values.contains(&"one"));
assert!(values.contains(&"two"));
assert_eq!(values.len(), 4);
}
}