1use serde::{Deserialize, Serialize};
12use serde_json::Value as JsonValue;
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct EqCondition {
17 pub path: String,
19 pub value: JsonValue,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25#[serde(untagged)]
26pub enum Filter {
27 And {
29 #[serde(rename = "$and")]
31 filters: Vec<Filter>,
32 },
33 Or {
35 #[serde(rename = "$or")]
37 filters: Vec<Filter>,
38 },
39 Not {
41 #[serde(rename = "$not")]
43 filter: Box<Filter>,
44 },
45 EqWrapped {
47 #[serde(rename = "$eq")]
49 condition: EqCondition,
50 },
51 Eq {
53 path: String,
55 value: JsonValue,
57 },
58}
59
60impl Filter {
61 pub fn and(filters: Vec<Filter>) -> Self {
63 Self::And { filters }
64 }
65
66 pub fn or(filters: Vec<Filter>) -> Self {
68 Self::Or { filters }
69 }
70
71 #[allow(clippy::should_implement_trait)]
73 pub fn not(filter: Filter) -> Self {
74 Self::Not {
75 filter: Box::new(filter),
76 }
77 }
78
79 pub fn eq(path: impl Into<String>, value: JsonValue) -> Self {
81 Self::Eq {
82 path: path.into(),
83 value,
84 }
85 }
86
87 #[inline]
97 pub fn matches(&self, event: &JsonValue) -> bool {
98 match self {
99 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 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
120 serde_json::from_str(json)
121 }
122
123 pub fn to_json(&self) -> Result<String, serde_json::Error> {
125 serde_json::to_string(self)
126 }
127}
128
129#[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 let idx: usize = segment.parse().ok()?;
157 arr.get(idx)?
158 }
159 _ => return None,
160 };
161 }
162 Some(current)
163}
164
165#[derive(Debug, Default)]
167pub struct FilterBuilder {
168 filters: Vec<Filter>,
169}
170
171impl FilterBuilder {
172 pub fn new() -> Self {
174 Self::default()
175 }
176
177 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 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 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"}))); }
215
216 #[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 #[test]
254 fn matches_handles_modest_depth_on_small_stack() {
255 const DEPTH: usize = 256;
256 let mut f = Filter::eq("x", json!(1));
258 for _ in 0..DEPTH {
259 f = Filter::not(f);
260 }
261
262 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 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 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" })));
372
373 assert!(!filter.matches(&json!({
374 "type": "token",
375 "value": "other", "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 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 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 assert_eq!(json_path_get(&value, "arr.foo"), None);
447 assert_eq!(json_path_get(&value, "arr.100"), None);
449 }
450
451 #[test]
452 fn test_filter_builder_single() {
453 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 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 assert!(!filter.matches(&json!({"any": "value"})));
525 }
526
527 #[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 #[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 let nested_not = Filter::and(vec![Filter::not(leaf.clone())]);
566 assert!(!nested_not.matches(&yes));
567 assert!(nested_not.matches(&no));
568
569 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 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 #[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 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 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 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 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 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 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 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 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}