Skip to main content

net/consumer/
filter.rs

1//! JSON predicate filtering for event consumption.
2//!
3//! The filter engine supports:
4//! - Logical operators: `$and`, `$or`, `$not`
5//! - Dot-path field access: `"foo.bar.baz"`
6//! - Equality matching (values must match exactly)
7//!
8//! Filtering is performed **after retrieval** from the adapter,
9//! not pushed down to the storage layer.
10
11use serde::{Deserialize, Serialize};
12use serde_json::Value as JsonValue;
13
14/// Inner equality condition (path + value).
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct EqCondition {
17    /// Dot-separated path to the field (e.g., "foo.bar.baz").
18    pub path: String,
19    /// Value to match against.
20    pub value: JsonValue,
21}
22
23/// A filter predicate for matching events.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25#[serde(untagged)]
26pub enum Filter {
27    /// Logical AND: all filters must match.
28    And {
29        /// List of filters that must all match.
30        #[serde(rename = "$and")]
31        filters: Vec<Filter>,
32    },
33    /// Logical OR: at least one filter must match.
34    Or {
35        /// List of filters where at least one must match.
36        #[serde(rename = "$or")]
37        filters: Vec<Filter>,
38    },
39    /// Logical NOT: the inner filter must not match.
40    Not {
41        /// The filter to negate.
42        #[serde(rename = "$not")]
43        filter: Box<Filter>,
44    },
45    /// Equality match with $eq wrapper: `{ "$eq": { "path": "...", "value": ... } }`
46    EqWrapped {
47        /// The equality condition.
48        #[serde(rename = "$eq")]
49        condition: EqCondition,
50    },
51    /// Equality match (shorthand): `{ "path": "...", "value": ... }`
52    Eq {
53        /// Dot-separated path to the field (e.g., "foo.bar.baz").
54        path: String,
55        /// Value to match against.
56        value: JsonValue,
57    },
58}
59
60impl Filter {
61    /// Create an AND filter.
62    pub fn and(filters: Vec<Filter>) -> Self {
63        Self::And { filters }
64    }
65
66    /// Create an OR filter.
67    pub fn or(filters: Vec<Filter>) -> Self {
68        Self::Or { filters }
69    }
70
71    /// Create a NOT filter.
72    #[allow(clippy::should_implement_trait)]
73    pub fn not(filter: Filter) -> Self {
74        Self::Not {
75            filter: Box::new(filter),
76        }
77    }
78
79    /// Create an equality filter.
80    pub fn eq(path: impl Into<String>, value: JsonValue) -> Self {
81        Self::Eq {
82            path: path.into(),
83            value,
84        }
85    }
86
87    /// Check if an event matches this filter.
88    ///
89    /// Empty `And` children are rejected as "matches nothing" rather
90    /// than "matches everything" — `.all()` on an empty iterator
91    /// returns `true`, which would silently turn an externally-
92    /// supplied filter JSON like `{"and": []}` into a universal
93    /// pass-through. Empty `Or` naturally returns `false` via
94    /// `.any()` on an empty iterator and keeps its documented
95    /// "matches nothing" behavior.
96    #[inline]
97    pub fn matches(&self, event: &JsonValue) -> bool {
98        match self {
99            // Single-element fast path: skip the iterator + closure
100            // setup and recurse directly. `And { filters: [f] }` and
101            // `Or { filters: [f] }` are common after deserializing
102            // small filter trees and were otherwise paying iter+all/any
103            // overhead per event.
104            Self::And { filters } if filters.len() == 1 => filters[0].matches(event),
105            Self::Or { filters } if filters.len() == 1 => filters[0].matches(event),
106            Self::And { filters } => {
107                !filters.is_empty() && filters.iter().all(|f| f.matches(event))
108            }
109            Self::Or { filters } => filters.iter().any(|f| f.matches(event)),
110            Self::Not { filter } => !filter.matches(event),
111            Self::EqWrapped { condition } => {
112                json_path_get(event, &condition.path) == Some(&condition.value)
113            }
114            Self::Eq { path, value } => json_path_get(event, path) == Some(value),
115        }
116    }
117
118    /// Parse a filter from JSON.
119    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
120        serde_json::from_str(json)
121    }
122
123    /// Convert the filter to JSON.
124    pub fn to_json(&self) -> Result<String, serde_json::Error> {
125        serde_json::to_string(self)
126    }
127}
128
129/// Efficient dot-path accessor for JSON values.
130///
131/// Given a path like `"foo.bar.baz"`, returns `value["foo"]["bar"]["baz"]`.
132///
133/// # Examples
134///
135/// ```
136/// use serde_json::json;
137/// use net::consumer::filter::json_path_get;
138///
139/// let value = json!({"user": {"name": "Alice", "age": 30}});
140/// assert_eq!(json_path_get(&value, "user.name"), Some(&json!("Alice")));
141/// assert_eq!(json_path_get(&value, "user.age"), Some(&json!(30)));
142/// assert_eq!(json_path_get(&value, "user.missing"), None);
143/// ```
144#[inline]
145pub fn json_path_get<'a>(value: &'a JsonValue, path: &str) -> Option<&'a JsonValue> {
146    if path.is_empty() {
147        return Some(value);
148    }
149
150    let mut current = value;
151    for segment in path.split('.') {
152        current = match current {
153            JsonValue::Object(map) => map.get(segment)?,
154            JsonValue::Array(arr) => {
155                // Support numeric indexing for arrays
156                let idx: usize = segment.parse().ok()?;
157                arr.get(idx)?
158            }
159            _ => return None,
160        };
161    }
162    Some(current)
163}
164
165/// Filter builder for fluent API.
166#[derive(Debug, Default)]
167pub struct FilterBuilder {
168    filters: Vec<Filter>,
169}
170
171impl FilterBuilder {
172    /// Create a new filter builder.
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    /// Add an equality condition.
178    pub fn eq(mut self, path: impl Into<String>, value: JsonValue) -> Self {
179        self.filters.push(Filter::eq(path, value));
180        self
181    }
182
183    /// Build an AND filter from accumulated conditions.
184    pub fn build_and(self) -> Filter {
185        if self.filters.len() == 1 {
186            self.filters.into_iter().next().unwrap()
187        } else {
188            Filter::and(self.filters)
189        }
190    }
191
192    /// Build an OR filter from accumulated conditions.
193    pub fn build_or(self) -> Filter {
194        if self.filters.len() == 1 {
195            self.filters.into_iter().next().unwrap()
196        } else {
197            Filter::or(self.filters)
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use serde_json::json;
206
207    #[test]
208    fn test_eq_filter() {
209        let filter = Filter::eq("type", json!("token"));
210
211        assert!(filter.matches(&json!({"type": "token", "value": "hello"})));
212        assert!(!filter.matches(&json!({"type": "message", "value": "hello"})));
213        assert!(!filter.matches(&json!({"value": "hello"}))); // Missing field
214    }
215
216    /// `Filter::from_json` is reachable from any FFI / SDK path
217    /// that accepts an externally-supplied filter. A deeply
218    /// nested adversarial input must NOT crash the consumer
219    /// thread via stack overflow. We rely on `serde_json`'s
220    /// recursion limit (default 128) to reject the JSON form;
221    /// this test pins that the limit is in force, so a future
222    /// switch to a non-recursive deserializer doesn't silently
223    /// open a DoS vector. Constructed depth (10_000) is well
224    /// past any plausible user filter and well past serde_json's
225    /// limit.
226    #[test]
227    fn from_json_rejects_adversarially_nested_filter() {
228        let depth = 10_000usize;
229        let mut json = String::with_capacity(depth * 8 + 32);
230        for _ in 0..depth {
231            json.push_str(r#"{"$not":"#);
232        }
233        json.push_str(r#"{"path":"x","value":1}"#);
234        for _ in 0..depth {
235            json.push('}');
236        }
237
238        let parsed = Filter::from_json(&json);
239        assert!(
240            parsed.is_err(),
241            "depth-{depth} filter JSON must be rejected by serde_json's recursion limit"
242        );
243    }
244
245    /// Programmatic construction bypasses `from_json` and can
246    /// nest arbitrarily — but that's a Rust-API-only path, not a
247    /// DoS surface. We verify here that `matches` handles a
248    /// modest depth (256 — the same `recursion_limit` set in
249    /// `lib.rs:55`) without overflow even on a small thread
250    /// stack. A future change that materially deepens recursion
251    /// per frame (e.g. wrapping in `Box::pin`) would surface
252    /// here.
253    #[test]
254    fn matches_handles_modest_depth_on_small_stack() {
255        const DEPTH: usize = 256;
256        // Build (depth-many) `Not` wrappers around an Eq leaf.
257        let mut f = Filter::eq("x", json!(1));
258        for _ in 0..DEPTH {
259            f = Filter::not(f);
260        }
261
262        // 256 KiB is well below typical defaults; if `matches`
263        // were to use materially more than ~1 KiB per frame this
264        // would overflow.
265        let result = std::thread::Builder::new()
266            .stack_size(256 * 1024)
267            .spawn(move || f.matches(&json!({"x": 1})))
268            .expect("spawn small-stack thread")
269            .join()
270            .expect("matches() must not panic at depth 256 on a small stack");
271
272        // Even number of `Not` wraps → unchanged truth value.
273        assert!(result, "depth-256 nested Not over true Eq should be true");
274    }
275
276    #[test]
277    fn test_nested_path() {
278        let filter = Filter::eq("user.profile.name", json!("Alice"));
279
280        assert!(filter.matches(&json!({
281            "user": {
282                "profile": {
283                    "name": "Alice",
284                    "age": 30
285                }
286            }
287        })));
288
289        assert!(!filter.matches(&json!({
290            "user": {
291                "profile": {
292                    "name": "Bob"
293                }
294            }
295        })));
296    }
297
298    #[test]
299    fn test_array_indexing() {
300        let filter = Filter::eq("items.0.name", json!("first"));
301
302        assert!(filter.matches(&json!({
303            "items": [
304                {"name": "first"},
305                {"name": "second"}
306            ]
307        })));
308
309        assert!(!filter.matches(&json!({
310            "items": [
311                {"name": "other"}
312            ]
313        })));
314    }
315
316    #[test]
317    fn test_and_filter() {
318        let filter = Filter::and(vec![
319            Filter::eq("type", json!("token")),
320            Filter::eq("index", json!(0)),
321        ]);
322
323        assert!(filter.matches(&json!({"type": "token", "index": 0})));
324        assert!(!filter.matches(&json!({"type": "token", "index": 1})));
325        assert!(!filter.matches(&json!({"type": "message", "index": 0})));
326    }
327
328    #[test]
329    fn test_or_filter() {
330        let filter = Filter::or(vec![
331            Filter::eq("type", json!("token")),
332            Filter::eq("type", json!("message")),
333        ]);
334
335        assert!(filter.matches(&json!({"type": "token"})));
336        assert!(filter.matches(&json!({"type": "message"})));
337        assert!(!filter.matches(&json!({"type": "error"})));
338    }
339
340    #[test]
341    fn test_not_filter() {
342        let filter = Filter::not(Filter::eq("type", json!("error")));
343
344        assert!(filter.matches(&json!({"type": "token"})));
345        assert!(filter.matches(&json!({"type": "message"})));
346        assert!(!filter.matches(&json!({"type": "error"})));
347    }
348
349    #[test]
350    fn test_complex_filter() {
351        // Match tokens that are either "hello" or "world" but not from user "bot"
352        let filter = Filter::and(vec![
353            Filter::eq("type", json!("token")),
354            Filter::or(vec![
355                Filter::eq("value", json!("hello")),
356                Filter::eq("value", json!("world")),
357            ]),
358            Filter::not(Filter::eq("user", json!("bot"))),
359        ]);
360
361        assert!(filter.matches(&json!({
362            "type": "token",
363            "value": "hello",
364            "user": "alice"
365        })));
366
367        assert!(!filter.matches(&json!({
368            "type": "token",
369            "value": "hello",
370            "user": "bot"  // Excluded by NOT
371        })));
372
373        assert!(!filter.matches(&json!({
374            "type": "token",
375            "value": "other",  // Not hello or world
376            "user": "alice"
377        })));
378    }
379
380    #[test]
381    fn test_filter_builder() {
382        let filter = FilterBuilder::new()
383            .eq("type", json!("token"))
384            .eq("active", json!(true))
385            .build_and();
386
387        assert!(filter.matches(&json!({"type": "token", "active": true})));
388        assert!(!filter.matches(&json!({"type": "token", "active": false})));
389    }
390
391    #[test]
392    fn test_filter_serialization() {
393        let filter = Filter::and(vec![
394            Filter::eq("type", json!("token")),
395            Filter::not(Filter::eq("error", json!(true))),
396        ]);
397
398        let json = filter.to_json().unwrap();
399        let parsed: Filter = Filter::from_json(&json).unwrap();
400
401        // Should behave the same after round-trip
402        let event = json!({"type": "token", "error": false});
403        assert_eq!(filter.matches(&event), parsed.matches(&event));
404    }
405
406    #[test]
407    fn test_json_path_get() {
408        let value = json!({
409            "a": {
410                "b": {
411                    "c": 42
412                }
413            },
414            "arr": [1, 2, 3],
415            "nested_arr": [{"x": 10}, {"x": 20}]
416        });
417
418        assert_eq!(json_path_get(&value, "a.b.c"), Some(&json!(42)));
419        assert_eq!(json_path_get(&value, "arr.1"), Some(&json!(2)));
420        assert_eq!(json_path_get(&value, "nested_arr.0.x"), Some(&json!(10)));
421        assert_eq!(json_path_get(&value, "missing"), None);
422        assert_eq!(json_path_get(&value, "a.b.missing"), None);
423        assert_eq!(json_path_get(&value, ""), Some(&value));
424    }
425
426    #[test]
427    fn test_json_path_get_primitive() {
428        // Trying to access path on primitive value
429        let value = json!(42);
430        assert_eq!(json_path_get(&value, "foo"), None);
431
432        let value = json!("string");
433        assert_eq!(json_path_get(&value, "bar"), None);
434
435        let value = json!(true);
436        assert_eq!(json_path_get(&value, "baz"), None);
437
438        let value = json!(null);
439        assert_eq!(json_path_get(&value, "qux"), None);
440    }
441
442    #[test]
443    fn test_json_path_get_invalid_array_index() {
444        let value = json!({"arr": [1, 2, 3]});
445        // Non-numeric index on array
446        assert_eq!(json_path_get(&value, "arr.foo"), None);
447        // Out of bounds
448        assert_eq!(json_path_get(&value, "arr.100"), None);
449    }
450
451    #[test]
452    fn test_filter_builder_single() {
453        // Single filter should not wrap in AND/OR
454        let filter = FilterBuilder::new().eq("type", json!("token")).build_and();
455
456        assert!(matches!(filter, Filter::Eq { .. }));
457
458        let filter = FilterBuilder::new().eq("type", json!("token")).build_or();
459
460        assert!(matches!(filter, Filter::Eq { .. }));
461    }
462
463    #[test]
464    fn test_filter_builder_multiple_or() {
465        let filter = FilterBuilder::new()
466            .eq("type", json!("a"))
467            .eq("type", json!("b"))
468            .build_or();
469
470        assert!(filter.matches(&json!({"type": "a"})));
471        assert!(filter.matches(&json!({"type": "b"})));
472        assert!(!filter.matches(&json!({"type": "c"})));
473    }
474
475    #[test]
476    fn test_filter_clone() {
477        let filter = Filter::and(vec![
478            Filter::eq("a", json!(1)),
479            Filter::not(Filter::eq("b", json!(2))),
480        ]);
481
482        let cloned = filter.clone();
483        let event = json!({"a": 1, "b": 3});
484        assert_eq!(filter.matches(&event), cloned.matches(&event));
485    }
486
487    #[test]
488    fn test_filter_debug() {
489        let filter = Filter::eq("type", json!("token"));
490        let debug = format!("{:?}", filter);
491        assert!(debug.contains("Eq"));
492        assert!(debug.contains("type"));
493    }
494
495    #[test]
496    fn test_filter_partial_eq() {
497        let f1 = Filter::eq("type", json!("token"));
498        let f2 = Filter::eq("type", json!("token"));
499        let f3 = Filter::eq("type", json!("other"));
500
501        assert_eq!(f1, f2);
502        assert_ne!(f1, f3);
503    }
504
505    #[test]
506    fn test_empty_and_filter() {
507        // Regression (LOW, BUGS.md): empty `And` used to match
508        // everything via `.all()` on an empty iterator returning
509        // `true`. A filter JSON like `{"and": []}` reaching the
510        // matcher would silently become a universal pass-through.
511        // Now empty `And` matches nothing, consistent with the
512        // conservative "an empty filter isn't a filter" choice.
513        let filter = Filter::and(vec![]);
514        assert!(
515            !filter.matches(&json!({"any": "value"})),
516            "empty And must not match — was silently universal-pass before"
517        );
518    }
519
520    #[test]
521    fn test_empty_or_filter() {
522        let filter = Filter::or(vec![]);
523        // Empty OR should match nothing
524        assert!(!filter.matches(&json!({"any": "value"})));
525    }
526
527    /// Single-element `And` / `Or` must produce the same result as
528    /// the inner filter alone — the fast path in `matches()` recurses
529    /// directly without the iterator+closure setup, but it has to be
530    /// semantically identical to the iter-based path.
531    #[test]
532    fn test_single_element_and_or_match_inner_filter() {
533        let inner = Filter::eq("k", json!("v"));
534        let single_and = Filter::and(vec![inner.clone()]);
535        let single_or = Filter::or(vec![inner.clone()]);
536
537        let yes = json!({"k": "v"});
538        let no = json!({"k": "other"});
539
540        for ev in &[yes, no] {
541            assert_eq!(
542                single_and.matches(ev),
543                inner.matches(ev),
544                "single-element And must match inner: {ev}",
545            );
546            assert_eq!(
547                single_or.matches(ev),
548                inner.matches(ev),
549                "single-element Or must match inner: {ev}",
550            );
551        }
552    }
553
554    /// Fast path must recurse correctly when the single child is
555    /// itself a composite filter (Not, nested And/Or, Eq, etc.) — the
556    /// straight-line `filters[0].matches(event)` call has to dispatch
557    /// the same way the slow path's closure would.
558    #[test]
559    fn test_single_element_fast_path_recurses_into_composite() {
560        let leaf = Filter::eq("k", json!("v"));
561        let yes = json!({"k": "v"});
562        let no = json!({"k": "other"});
563
564        // And{[Not{leaf}]}
565        let nested_not = Filter::and(vec![Filter::not(leaf.clone())]);
566        assert!(!nested_not.matches(&yes));
567        assert!(nested_not.matches(&no));
568
569        // Or{[And{[leaf]}]} — both layers hit the fast path.
570        let nested_double = Filter::or(vec![Filter::and(vec![leaf.clone()])]);
571        assert!(nested_double.matches(&yes));
572        assert!(!nested_double.matches(&no));
573
574        // Or{[And{[leaf, leaf2]}]} — outer hits the fast path, inner
575        // falls through to the iterator path. Verifies the two paths
576        // compose correctly.
577        let leaf2 = Filter::eq("x", json!(1));
578        let mixed = Filter::or(vec![Filter::and(vec![leaf.clone(), leaf2.clone()])]);
579        assert!(mixed.matches(&json!({"k": "v", "x": 1})));
580        assert!(!mixed.matches(&json!({"k": "v", "x": 2})));
581        assert!(!mixed.matches(&json!({"k": "other", "x": 1})));
582    }
583
584    /// Regression: multi-element `And` / `Or` must keep using the
585    /// iterator path (not silently fall into the single-element
586    /// shortcut). Guards against a future refactor of the fast-path
587    /// guard.
588    #[test]
589    fn test_multi_element_and_or_uses_slow_path() {
590        let f1 = Filter::eq("k", json!("v"));
591        let f2 = Filter::eq("x", json!(1));
592
593        let and = Filter::and(vec![f1.clone(), f2.clone()]);
594        assert!(and.matches(&json!({"k": "v", "x": 1})));
595        assert!(!and.matches(&json!({"k": "v", "x": 2})));
596        assert!(!and.matches(&json!({"k": "other", "x": 1})));
597
598        let or = Filter::or(vec![f1.clone(), f2.clone()]);
599        assert!(or.matches(&json!({"k": "v", "x": 99})));
600        assert!(or.matches(&json!({"k": "nope", "x": 1})));
601        assert!(!or.matches(&json!({"k": "nope", "x": 2})));
602    }
603
604    #[test]
605    fn test_filter_builder_default() {
606        let builder = FilterBuilder::default();
607        let debug = format!("{:?}", builder);
608        assert!(debug.contains("FilterBuilder"));
609    }
610
611    #[test]
612    fn test_eq_wrapped_filter_deserialization() {
613        // Test $eq wrapper format: { "$eq": { "path": "type", "value": "token" } }
614        let json_str = r#"{"$eq": {"path": "type", "value": "token"}}"#;
615        let filter: Filter = serde_json::from_str(json_str).unwrap();
616
617        assert!(filter.matches(&json!({"type": "token", "data": "hello"})));
618        assert!(!filter.matches(&json!({"type": "message", "data": "hello"})));
619    }
620
621    #[test]
622    fn test_eq_wrapped_with_nested_path() {
623        // Test $eq with nested path
624        let json_str = r#"{"$eq": {"path": "user.role", "value": "admin"}}"#;
625        let filter: Filter = serde_json::from_str(json_str).unwrap();
626
627        assert!(filter.matches(&json!({"user": {"role": "admin"}})));
628        assert!(!filter.matches(&json!({"user": {"role": "user"}})));
629    }
630
631    #[test]
632    fn test_eq_wrapped_with_numeric_value() {
633        // Test $eq with numeric value
634        let json_str = r#"{"$eq": {"path": "count", "value": 42}}"#;
635        let filter: Filter = serde_json::from_str(json_str).unwrap();
636
637        assert!(filter.matches(&json!({"count": 42})));
638        assert!(!filter.matches(&json!({"count": 41})));
639    }
640
641    #[test]
642    fn test_eq_wrapped_with_boolean_value() {
643        // Test $eq with boolean value
644        let json_str = r#"{"$eq": {"path": "active", "value": true}}"#;
645        let filter: Filter = serde_json::from_str(json_str).unwrap();
646
647        assert!(filter.matches(&json!({"active": true})));
648        assert!(!filter.matches(&json!({"active": false})));
649    }
650
651    #[test]
652    fn test_eq_wrapped_in_and() {
653        // Test $eq wrapped inside $and
654        let json_str = r#"{"$and": [{"$eq": {"path": "type", "value": "token"}}, {"$eq": {"path": "index", "value": 0}}]}"#;
655        let filter: Filter = serde_json::from_str(json_str).unwrap();
656
657        assert!(filter.matches(&json!({"type": "token", "index": 0})));
658        assert!(!filter.matches(&json!({"type": "token", "index": 1})));
659        assert!(!filter.matches(&json!({"type": "message", "index": 0})));
660    }
661
662    #[test]
663    fn test_eq_wrapped_in_or() {
664        // Test $eq wrapped inside $or
665        let json_str = r#"{"$or": [{"$eq": {"path": "type", "value": "token"}}, {"$eq": {"path": "type", "value": "message"}}]}"#;
666        let filter: Filter = serde_json::from_str(json_str).unwrap();
667
668        assert!(filter.matches(&json!({"type": "token"})));
669        assert!(filter.matches(&json!({"type": "message"})));
670        assert!(!filter.matches(&json!({"type": "error"})));
671    }
672
673    #[test]
674    fn test_eq_wrapped_in_not() {
675        // Test $eq wrapped inside $not
676        let json_str = r#"{"$not": {"$eq": {"path": "type", "value": "error"}}}"#;
677        let filter: Filter = serde_json::from_str(json_str).unwrap();
678
679        assert!(filter.matches(&json!({"type": "token"})));
680        assert!(filter.matches(&json!({"type": "message"})));
681        assert!(!filter.matches(&json!({"type": "error"})));
682    }
683
684    #[test]
685    fn test_both_eq_formats_work() {
686        // Test that both shorthand and wrapped formats work
687        let shorthand = r#"{"path": "type", "value": "token"}"#;
688        let wrapped = r#"{"$eq": {"path": "type", "value": "token"}}"#;
689
690        let filter1: Filter = serde_json::from_str(shorthand).unwrap();
691        let filter2: Filter = serde_json::from_str(wrapped).unwrap();
692
693        let event = json!({"type": "token"});
694        assert!(filter1.matches(&event));
695        assert!(filter2.matches(&event));
696
697        let event2 = json!({"type": "other"});
698        assert!(!filter1.matches(&event2));
699        assert!(!filter2.matches(&event2));
700    }
701}