Skip to main content

rsigma_eval/
event.rs

1//! Event abstraction for Sigma rule evaluation.
2//!
3//! Provides the [`Event`] trait for generic event access, the [`EventValue`]
4//! enum representing field values, and concrete implementations:
5//! - [`JsonEvent`] — zero-copy wrapper around `serde_json::Value`
6//! - [`KvEvent`] — flat key-value pairs (e.g., from logfmt / syslog)
7//! - [`PlainEvent`] — raw log line (keyword matching only)
8//! - [`MapEvent`] — generic `HashMap<K, V>` adapter
9
10use std::borrow::Cow;
11use std::collections::HashMap;
12
13use serde_json::Value;
14
15/// Maximum nesting depth for recursive JSON traversal.
16const MAX_NESTING_DEPTH: usize = 64;
17
18// =============================================================================
19// EventValue
20// =============================================================================
21
22/// A value retrieved from an event field.
23///
24/// Supports zero-copy borrows from JSON-backed events (`Cow::Borrowed`)
25/// and owned values from non-JSON sources (`Cow::Owned`).
26/// Null is distinct from field-absent (`get_field` returns `None`).
27#[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    /// Coerce to string. Str as-is, Int/Float decimal, Bool "true"/"false".
40    #[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    /// Coerce to f64. Int lossless, Float as-is, Str parsed.
52    #[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    /// Coerce to i64. Int as-is, Float truncated if exact, Str parsed.
63    #[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    /// Coerce to bool. Bool as-is, Str: true/false/1/0/yes/no.
81    #[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    /// Convert to `serde_json::Value`.
100    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
144// =============================================================================
145// Event trait
146// =============================================================================
147
148/// Generic interface for accessing event data during Sigma rule evaluation.
149///
150/// Implementations provide field lookup (with dot-notation), keyword search
151/// over all string values, and serialization to JSON for correlation storage.
152pub trait Event {
153    /// Look up a field by name. Supports dot-notation for nested access.
154    ///
155    /// Returns `None` if the field is absent.
156    /// Returns `Some(EventValue::Null)` if the field exists but is null.
157    fn get_field(&self, path: &str) -> Option<EventValue<'_>>;
158
159    /// Check if any string value anywhere in the event satisfies a predicate.
160    /// Used by keyword detection.
161    fn any_string_value(&self, pred: &dyn Fn(&str) -> bool) -> bool;
162
163    /// Collect all string values in the event.
164    fn all_string_values(&self) -> Vec<Cow<'_, str>>;
165
166    /// Materialize the event as a `serde_json::Value`.
167    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// =============================================================================
189// JsonEvent
190// =============================================================================
191
192/// Zero-copy event backed by `serde_json::Value`.
193///
194/// Supports both borrowed (`&Value`) and owned (`Value`) backing via `Cow`.
195/// This is the primary implementation for JSON/NDJSON input.
196///
197/// Flat keys are checked first: `"actor.id"` as a single key takes precedence
198/// over `{"actor": {"id": ...}}` nested traversal.
199#[derive(Debug)]
200pub struct JsonEvent<'a> {
201    inner: Cow<'a, Value>,
202}
203
204impl<'a> JsonEvent<'a> {
205    /// Wrap a borrowed JSON value as an event.
206    pub fn borrow(v: &'a Value) -> Self {
207        Self {
208            inner: Cow::Borrowed(v),
209        }
210    }
211
212    /// Wrap an owned JSON value as an event.
213    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    /// Get a field value by name, supporting dot-notation for nested access.
234    ///
235    /// Checks for a flat key first (exact match), then falls back to
236    /// dot-separated traversal. When a path segment yields an array,
237    /// each element is tried and the first match is returned (OR semantics).
238    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    /// Check if any string value in the event satisfies a predicate.
256    ///
257    /// Short-circuits on the first match, avoiding the allocation of
258    /// collecting all string values into a `Vec`.
259    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    /// Iterate over all string values in the event (for keyword detection).
264    ///
265    /// Recursively walks the entire event object and yields every string
266    /// value found, including inside nested objects and arrays. Traversal
267    /// is capped at 64 levels of nesting to prevent stack overflow.
268    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
279// -- JsonEvent helpers --------------------------------------------------------
280
281/// Recursively traverse a JSON value following dot-notation path segments.
282///
283/// When a segment resolves to an array, each element is tried and the first
284/// match for the remaining path is returned.
285fn 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// =============================================================================
346// KvEvent
347// =============================================================================
348
349/// Flat key-value event (e.g., from logfmt, syslog structured data).
350///
351/// No nested access (no dot-notation traversal), no arrays.
352/// All values are strings.
353#[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// =============================================================================
398// PlainEvent
399// =============================================================================
400
401/// Raw log line event (keyword matching only).
402///
403/// `get_field` always returns `None`. Useful for keyword-only Sigma rules.
404#[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// =============================================================================
438// MapEvent
439// =============================================================================
440
441/// Generic flat-map event for user-defined key-value stores.
442///
443/// Flat key lookup only (no dot-notation, no nesting).
444#[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// =============================================================================
502// Tests
503// =============================================================================
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use serde_json::json;
509
510    // -- JsonEvent tests ------------------------------------------------------
511
512    #[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    // -- EventValue coercion tests --------------------------------------------
653
654    #[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    // -- KvEvent tests --------------------------------------------------------
688
689    #[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    // -- PlainEvent tests -----------------------------------------------------
717
718    #[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    // -- MapEvent tests -------------------------------------------------------
738
739    #[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}