1use std::borrow::Cow;
11use std::collections::HashMap;
12
13use serde_json::Value;
14
15const MAX_NESTING_DEPTH: usize = 64;
17
18#[derive(Debug, Clone, PartialEq)]
28pub enum EventValue<'a> {
29 Str(Cow<'a, str>),
30 Int(i64),
31 Float(f64),
32 Bool(bool),
33 Null,
34 Array(Vec<EventValue<'a>>),
35 Map(Vec<(Cow<'a, str>, EventValue<'a>)>),
36}
37
38impl<'a> EventValue<'a> {
39 #[inline]
41 pub fn as_str(&self) -> Option<Cow<'_, str>> {
42 match self {
43 EventValue::Str(s) => Some(Cow::Borrowed(s)),
44 EventValue::Int(n) => Some(Cow::Owned(n.to_string())),
45 EventValue::Float(f) => Some(Cow::Owned(f.to_string())),
46 EventValue::Bool(b) => Some(Cow::Borrowed(if *b { "true" } else { "false" })),
47 _ => None,
48 }
49 }
50
51 #[inline]
53 pub fn as_f64(&self) -> Option<f64> {
54 match self {
55 EventValue::Float(f) => Some(*f),
56 EventValue::Int(n) => Some(*n as f64),
57 EventValue::Str(s) => s.parse().ok(),
58 _ => None,
59 }
60 }
61
62 #[inline]
64 pub fn as_i64(&self) -> Option<i64> {
65 match self {
66 EventValue::Int(n) => Some(*n),
67 EventValue::Float(f) => {
68 let truncated = *f as i64;
69 if (truncated as f64 - f).abs() < f64::EPSILON {
70 Some(truncated)
71 } else {
72 None
73 }
74 }
75 EventValue::Str(s) => s.parse().ok(),
76 _ => None,
77 }
78 }
79
80 #[inline]
82 pub fn as_bool(&self) -> Option<bool> {
83 match self {
84 EventValue::Bool(b) => Some(*b),
85 EventValue::Str(s) => match s.to_lowercase().as_str() {
86 "true" | "1" | "yes" => Some(true),
87 "false" | "0" | "no" => Some(false),
88 _ => None,
89 },
90 _ => None,
91 }
92 }
93
94 #[inline]
95 pub fn is_null(&self) -> bool {
96 matches!(self, EventValue::Null)
97 }
98
99 pub fn to_json(&self) -> Value {
101 match self {
102 EventValue::Str(s) => Value::String(s.to_string()),
103 EventValue::Int(n) => Value::Number((*n).into()),
104 EventValue::Float(f) => {
105 serde_json::Number::from_f64(*f).map_or(Value::Null, Value::Number)
106 }
107 EventValue::Bool(b) => Value::Bool(*b),
108 EventValue::Null => Value::Null,
109 EventValue::Array(arr) => Value::Array(arr.iter().map(|v| v.to_json()).collect()),
110 EventValue::Map(entries) => {
111 let map = entries
112 .iter()
113 .map(|(k, v)| (k.to_string(), v.to_json()))
114 .collect();
115 Value::Object(map)
116 }
117 }
118 }
119}
120
121impl<'a> From<&'a Value> for EventValue<'a> {
122 fn from(v: &'a Value) -> Self {
123 match v {
124 Value::String(s) => EventValue::Str(Cow::Borrowed(s.as_str())),
125 Value::Number(n) => {
126 if let Some(i) = n.as_i64() {
127 EventValue::Int(i)
128 } else {
129 EventValue::Float(n.as_f64().unwrap_or(f64::NAN))
130 }
131 }
132 Value::Bool(b) => EventValue::Bool(*b),
133 Value::Null => EventValue::Null,
134 Value::Array(arr) => EventValue::Array(arr.iter().map(EventValue::from).collect()),
135 Value::Object(map) => EventValue::Map(
136 map.iter()
137 .map(|(k, v)| (Cow::Borrowed(k.as_str()), EventValue::from(v)))
138 .collect(),
139 ),
140 }
141 }
142}
143
144pub trait Event {
153 fn get_field(&self, path: &str) -> Option<EventValue<'_>>;
158
159 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool;
162
163 fn all_string_values(&self) -> Vec<Cow<'_, str>>;
165
166 fn to_json(&self) -> Value;
168}
169
170impl<T: Event + ?Sized> Event for &T {
171 fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
172 (**self).get_field(path)
173 }
174
175 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
176 (**self).any_string_value(pred)
177 }
178
179 fn all_string_values(&self) -> Vec<Cow<'_, str>> {
180 (**self).all_string_values()
181 }
182
183 fn to_json(&self) -> Value {
184 (**self).to_json()
185 }
186}
187
188#[derive(Debug)]
200pub struct JsonEvent<'a> {
201 inner: Cow<'a, Value>,
202}
203
204impl<'a> JsonEvent<'a> {
205 pub fn borrow(v: &'a Value) -> Self {
207 Self {
208 inner: Cow::Borrowed(v),
209 }
210 }
211
212 pub fn owned(v: Value) -> Self {
214 Self {
215 inner: Cow::Owned(v),
216 }
217 }
218}
219
220impl<'a> From<&'a Value> for JsonEvent<'a> {
221 fn from(v: &'a Value) -> Self {
222 Self::borrow(v)
223 }
224}
225
226impl From<Value> for JsonEvent<'static> {
227 fn from(v: Value) -> Self {
228 Self::owned(v)
229 }
230}
231
232impl<'a> Event for JsonEvent<'a> {
233 fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
239 let value: &Value = &self.inner;
240
241 if let Some(obj) = value.as_object()
242 && let Some(v) = obj.get(path)
243 {
244 return Some(EventValue::from(v));
245 }
246
247 if path.contains('.') {
248 let parts: Vec<&str> = path.split('.').collect();
249 return traverse_json(value, &parts).map(EventValue::from);
250 }
251
252 None
253 }
254
255 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
260 any_string_value_json(&self.inner, pred, MAX_NESTING_DEPTH)
261 }
262
263 fn all_string_values(&self) -> Vec<Cow<'_, str>> {
269 let mut values = Vec::new();
270 collect_string_values_json(&self.inner, &mut values, MAX_NESTING_DEPTH);
271 values
272 }
273
274 fn to_json(&self) -> Value {
275 self.inner.as_ref().clone()
276 }
277}
278
279fn traverse_json<'a>(current: &'a Value, parts: &[&str]) -> Option<&'a Value> {
286 if parts.is_empty() {
287 return Some(current);
288 }
289
290 let (head, rest) = (parts[0], &parts[1..]);
291
292 match current {
293 Value::Object(map) => {
294 let next = map.get(head)?;
295 traverse_json(next, rest)
296 }
297 Value::Array(arr) => {
298 for item in arr {
299 if let Some(v) = traverse_json(item, parts) {
300 return Some(v);
301 }
302 }
303 None
304 }
305 _ => None,
306 }
307}
308
309fn any_string_value_json(v: &Value, pred: &dyn Fn(&str) -> bool, depth: usize) -> bool {
310 if depth == 0 {
311 return false;
312 }
313 match v {
314 Value::String(s) => pred(s.as_str()),
315 Value::Object(map) => map
316 .values()
317 .any(|val| any_string_value_json(val, pred, depth - 1)),
318 Value::Array(arr) => arr
319 .iter()
320 .any(|val| any_string_value_json(val, pred, depth - 1)),
321 _ => false,
322 }
323}
324
325fn collect_string_values_json<'a>(v: &'a Value, out: &mut Vec<Cow<'a, str>>, depth: usize) {
326 if depth == 0 {
327 return;
328 }
329 match v {
330 Value::String(s) => out.push(Cow::Borrowed(s.as_str())),
331 Value::Object(map) => {
332 for val in map.values() {
333 collect_string_values_json(val, out, depth - 1);
334 }
335 }
336 Value::Array(arr) => {
337 for val in arr {
338 collect_string_values_json(val, out, depth - 1);
339 }
340 }
341 _ => {}
342 }
343}
344
345#[derive(Debug, Clone)]
354pub struct KvEvent {
355 fields: Vec<(String, String)>,
356}
357
358impl KvEvent {
359 pub fn new(fields: Vec<(String, String)>) -> Self {
360 Self { fields }
361 }
362
363 pub fn fields(&self) -> &[(String, String)] {
364 &self.fields
365 }
366}
367
368impl Event for KvEvent {
369 fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
370 self.fields
371 .iter()
372 .find(|(k, _)| k == path)
373 .map(|(_, v)| EventValue::Str(Cow::Borrowed(v.as_str())))
374 }
375
376 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
377 self.fields.iter().any(|(_, v)| pred(v.as_str()))
378 }
379
380 fn all_string_values(&self) -> Vec<Cow<'_, str>> {
381 self.fields
382 .iter()
383 .map(|(_, v)| Cow::Borrowed(v.as_str()))
384 .collect()
385 }
386
387 fn to_json(&self) -> Value {
388 let map: serde_json::Map<String, Value> = self
389 .fields
390 .iter()
391 .map(|(k, v)| (k.clone(), Value::String(v.clone())))
392 .collect();
393 Value::Object(map)
394 }
395}
396
397#[derive(Debug, Clone)]
405pub struct PlainEvent {
406 raw: String,
407}
408
409impl PlainEvent {
410 pub fn new(raw: String) -> Self {
411 Self { raw }
412 }
413
414 pub fn raw(&self) -> &str {
415 &self.raw
416 }
417}
418
419impl Event for PlainEvent {
420 fn get_field(&self, _path: &str) -> Option<EventValue<'_>> {
421 None
422 }
423
424 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
425 pred(&self.raw)
426 }
427
428 fn all_string_values(&self) -> Vec<Cow<'_, str>> {
429 vec![Cow::Borrowed(&self.raw)]
430 }
431
432 fn to_json(&self) -> Value {
433 serde_json::json!({ "_raw": self.raw })
434 }
435}
436
437#[derive(Debug, Clone)]
445pub struct MapEvent<K = String, V = String>
446where
447 K: AsRef<str> + std::fmt::Debug + Clone,
448 V: AsRef<str> + std::fmt::Debug + Clone,
449{
450 inner: HashMap<K, V>,
451}
452
453impl<K, V> MapEvent<K, V>
454where
455 K: AsRef<str> + std::fmt::Debug + Clone,
456 V: AsRef<str> + std::fmt::Debug + Clone,
457{
458 pub fn new(inner: HashMap<K, V>) -> Self {
459 Self { inner }
460 }
461}
462
463impl<K, V> Event for MapEvent<K, V>
464where
465 K: AsRef<str> + std::fmt::Debug + Clone,
466 V: AsRef<str> + std::fmt::Debug + Clone,
467{
468 fn get_field(&self, path: &str) -> Option<EventValue<'_>> {
469 self.inner
470 .iter()
471 .find(|(k, _)| k.as_ref() == path)
472 .map(|(_, v)| EventValue::Str(Cow::Borrowed(v.as_ref())))
473 }
474
475 fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool {
476 self.inner.values().any(|v| pred(v.as_ref()))
477 }
478
479 fn all_string_values(&self) -> Vec<Cow<'_, str>> {
480 self.inner
481 .values()
482 .map(|v| Cow::Borrowed(v.as_ref()))
483 .collect()
484 }
485
486 fn to_json(&self) -> Value {
487 let map: serde_json::Map<String, Value> = self
488 .inner
489 .iter()
490 .map(|(k, v)| {
491 (
492 k.as_ref().to_string(),
493 Value::String(v.as_ref().to_string()),
494 )
495 })
496 .collect();
497 Value::Object(map)
498 }
499}
500
501#[cfg(test)]
506mod tests {
507 use super::*;
508 use serde_json::json;
509
510 #[test]
513 fn json_flat_field() {
514 let v = json!({"CommandLine": "whoami", "User": "admin"});
515 let event = JsonEvent::borrow(&v);
516 assert_eq!(
517 event.get_field("CommandLine"),
518 Some(EventValue::Str(Cow::Borrowed("whoami")))
519 );
520 }
521
522 #[test]
523 fn json_nested_field() {
524 let v = json!({"actor": {"id": "user123", "type": "User"}});
525 let event = JsonEvent::borrow(&v);
526 assert_eq!(
527 event.get_field("actor.id"),
528 Some(EventValue::Str(Cow::Borrowed("user123")))
529 );
530 }
531
532 #[test]
533 fn json_flat_key_precedence() {
534 let v = json!({"actor.id": "flat_value", "actor": {"id": "nested_value"}});
535 let event = JsonEvent::borrow(&v);
536 assert_eq!(
537 event.get_field("actor.id"),
538 Some(EventValue::Str(Cow::Borrowed("flat_value")))
539 );
540 }
541
542 #[test]
543 fn json_missing_field() {
544 let v = json!({"foo": "bar"});
545 let event = JsonEvent::borrow(&v);
546 assert_eq!(event.get_field("missing"), None);
547 }
548
549 #[test]
550 fn json_null_field() {
551 let v = json!({"foo": null});
552 let event = JsonEvent::borrow(&v);
553 assert_eq!(event.get_field("foo"), Some(EventValue::Null));
554 }
555
556 #[test]
557 fn json_array_traversal() {
558 let v = json!({"a": {"b": [{"c": "found"}, {"c": "other"}]}});
559 let event = JsonEvent::borrow(&v);
560 assert_eq!(
561 event.get_field("a.b.c"),
562 Some(EventValue::Str(Cow::Borrowed("found")))
563 );
564 }
565
566 #[test]
567 fn json_array_traversal_no_match() {
568 let v = json!({"a": {"b": [{"x": 1}, {"y": 2}]}});
569 let event = JsonEvent::borrow(&v);
570 assert_eq!(event.get_field("a.b.c"), None);
571 }
572
573 #[test]
574 fn json_array_traversal_deep() {
575 let v = json!({
576 "events": [
577 {"actors": [{"name": "alice"}, {"name": "bob"}]},
578 {"actors": [{"name": "charlie"}]}
579 ]
580 });
581 let event = JsonEvent::borrow(&v);
582 assert_eq!(
583 event.get_field("events.actors.name"),
584 Some(EventValue::Str(Cow::Borrowed("alice")))
585 );
586 }
587
588 #[test]
589 fn json_array_at_root_level() {
590 let v = json!({"process": [{"command_line": "whoami"}, {"command_line": "id"}]});
591 let event = JsonEvent::borrow(&v);
592 assert_eq!(
593 event.get_field("process.command_line"),
594 Some(EventValue::Str(Cow::Borrowed("whoami")))
595 );
596 }
597
598 #[test]
599 fn json_array_returns_array_value() {
600 let v = json!({"a": {"tags": ["t1", "t2"]}});
601 let event = JsonEvent::borrow(&v);
602 let result = event.get_field("a.tags");
603 assert!(matches!(result, Some(EventValue::Array(_))));
604 }
605
606 #[test]
607 fn json_flat_key_wins_over_array_traversal() {
608 let v = json!({"a.b.c": "flat", "a": {"b": [{"c": "nested"}]}});
609 let event = JsonEvent::borrow(&v);
610 assert_eq!(
611 event.get_field("a.b.c"),
612 Some(EventValue::Str(Cow::Borrowed("flat")))
613 );
614 }
615
616 #[test]
617 fn json_all_string_values() {
618 let v = json!({
619 "a": "hello",
620 "b": 42,
621 "c": {"d": "world", "e": true},
622 "f": ["one", "two"]
623 });
624 let event = JsonEvent::borrow(&v);
625 let values = event.all_string_values();
626 let strs: Vec<&str> = values.iter().map(|c| c.as_ref()).collect();
627 assert!(strs.contains(&"hello"));
628 assert!(strs.contains(&"world"));
629 assert!(strs.contains(&"one"));
630 assert!(strs.contains(&"two"));
631 assert_eq!(values.len(), 4);
632 }
633
634 #[test]
635 fn json_to_json_roundtrip() {
636 let v = json!({"a": 1, "b": "hello", "c": [1, 2]});
637 let event = JsonEvent::borrow(&v);
638 assert_eq!(event.to_json(), v);
639 }
640
641 #[test]
642 fn json_owned_works() {
643 let v = json!({"key": "value"});
644 let event = JsonEvent::owned(v.clone());
645 assert_eq!(
646 event.get_field("key"),
647 Some(EventValue::Str(Cow::Borrowed("value")))
648 );
649 assert_eq!(event.to_json(), v);
650 }
651
652 #[test]
655 fn event_value_as_str() {
656 assert_eq!(EventValue::Str(Cow::Borrowed("hi")).as_str().unwrap(), "hi");
657 assert_eq!(EventValue::Int(42).as_str().unwrap(), "42");
658 assert_eq!(EventValue::Float(2.71).as_str().unwrap(), "2.71");
659 assert_eq!(EventValue::Bool(true).as_str().unwrap(), "true");
660 assert!(EventValue::Null.as_str().is_none());
661 }
662
663 #[test]
664 fn event_value_as_f64() {
665 assert_eq!(EventValue::Float(2.71).as_f64(), Some(2.71));
666 assert_eq!(EventValue::Int(42).as_f64(), Some(42.0));
667 assert_eq!(EventValue::Str(Cow::Borrowed("1.5")).as_f64(), Some(1.5));
668 assert!(EventValue::Bool(true).as_f64().is_none());
669 }
670
671 #[test]
672 fn event_value_as_i64() {
673 assert_eq!(EventValue::Int(42).as_i64(), Some(42));
674 assert_eq!(EventValue::Float(42.0).as_i64(), Some(42));
675 assert_eq!(EventValue::Float(42.5).as_i64(), None);
676 assert_eq!(EventValue::Str(Cow::Borrowed("100")).as_i64(), Some(100));
677 }
678
679 #[test]
680 fn event_value_to_json() {
681 assert_eq!(EventValue::Str(Cow::Borrowed("hi")).to_json(), json!("hi"));
682 assert_eq!(EventValue::Int(42).to_json(), json!(42));
683 assert_eq!(EventValue::Bool(true).to_json(), json!(true));
684 assert_eq!(EventValue::Null.to_json(), Value::Null);
685 }
686
687 #[test]
690 fn kv_get_field() {
691 let event = KvEvent::new(vec![
692 ("host".into(), "web01".into()),
693 ("status".into(), "200".into()),
694 ]);
695 assert_eq!(
696 event.get_field("host"),
697 Some(EventValue::Str(Cow::Borrowed("web01")))
698 );
699 assert_eq!(event.get_field("missing"), None);
700 }
701
702 #[test]
703 fn kv_all_string_values() {
704 let event = KvEvent::new(vec![("a".into(), "one".into()), ("b".into(), "two".into())]);
705 let vals = event.all_string_values();
706 assert_eq!(vals.len(), 2);
707 }
708
709 #[test]
710 fn kv_to_json() {
711 let event = KvEvent::new(vec![("key".into(), "val".into())]);
712 let j = event.to_json();
713 assert_eq!(j, json!({"key": "val"}));
714 }
715
716 #[test]
719 fn plain_get_field_always_none() {
720 let event = PlainEvent::new("raw log line".into());
721 assert_eq!(event.get_field("anything"), None);
722 }
723
724 #[test]
725 fn plain_keyword_search() {
726 let event = PlainEvent::new("error: disk full".into());
727 assert!(event.any_string_value(&|s| s.contains("disk")));
728 assert!(!event.any_string_value(&|s| s.contains("memory")));
729 }
730
731 #[test]
732 fn plain_to_json() {
733 let event = PlainEvent::new("hello".into());
734 assert_eq!(event.to_json(), json!({"_raw": "hello"}));
735 }
736
737 #[test]
740 fn map_event_get_field() {
741 let mut m = HashMap::new();
742 m.insert("user".to_string(), "admin".to_string());
743 let event = MapEvent::new(m);
744 assert_eq!(
745 event.get_field("user"),
746 Some(EventValue::Str(Cow::Borrowed("admin")))
747 );
748 assert_eq!(event.get_field("missing"), None);
749 }
750
751 #[test]
752 fn map_event_to_json() {
753 let mut m = HashMap::new();
754 m.insert("k".to_string(), "v".to_string());
755 let event = MapEvent::new(m);
756 assert_eq!(event.to_json(), json!({"k": "v"}));
757 }
758}