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
60#[derive(Debug, Clone)]
66pub struct CompiledSegment {
67 field: String,
68 idx: Option<usize>,
69}
70
71impl CompiledSegment {
72 fn from_str(s: &str) -> Self {
73 Self {
74 field: s.to_string(),
75 idx: s.parse().ok(),
76 }
77 }
78}
79
80#[derive(Debug, Clone)]
90pub enum CompiledFilter {
91 And(Vec<CompiledFilter>),
93 Or(Vec<CompiledFilter>),
95 Not(Box<CompiledFilter>),
97 Eq {
99 segments: Vec<CompiledSegment>,
101 value: JsonValue,
103 },
104}
105
106impl CompiledFilter {
107 #[inline]
110 pub fn matches(&self, event: &JsonValue) -> bool {
111 match self {
112 Self::And(filters) if filters.len() == 1 => filters[0].matches(event),
113 Self::Or(filters) if filters.len() == 1 => filters[0].matches(event),
114 Self::And(filters) => !filters.is_empty() && filters.iter().all(|f| f.matches(event)),
115 Self::Or(filters) => filters.iter().any(|f| f.matches(event)),
116 Self::Not(f) => !f.matches(event),
117 Self::Eq { segments, value } => json_path_get_compiled(event, segments) == Some(value),
118 }
119 }
120}
121
122#[inline]
125fn json_path_get_compiled<'a>(
126 value: &'a JsonValue,
127 segments: &[CompiledSegment],
128) -> Option<&'a JsonValue> {
129 if segments.is_empty() {
130 return Some(value);
131 }
132 let mut current = value;
133 for seg in segments {
134 current = match current {
135 JsonValue::Object(map) => map.get(&seg.field)?,
136 JsonValue::Array(arr) => arr.get(seg.idx?)?,
137 _ => return None,
138 };
139 }
140 Some(current)
141}
142
143impl Filter {
144 pub fn and(filters: Vec<Filter>) -> Self {
146 Self::And { filters }
147 }
148
149 pub fn or(filters: Vec<Filter>) -> Self {
151 Self::Or { filters }
152 }
153
154 #[allow(clippy::should_implement_trait)]
156 pub fn not(filter: Filter) -> Self {
157 Self::Not {
158 filter: Box::new(filter),
159 }
160 }
161
162 pub fn eq(path: impl Into<String>, value: JsonValue) -> Self {
164 Self::Eq {
165 path: path.into(),
166 value,
167 }
168 }
169
170 #[inline]
180 pub fn matches(&self, event: &JsonValue) -> bool {
181 match self {
182 Self::And { filters } if filters.len() == 1 => filters[0].matches(event),
188 Self::Or { filters } if filters.len() == 1 => filters[0].matches(event),
189 Self::And { filters } => {
190 !filters.is_empty() && filters.iter().all(|f| f.matches(event))
191 }
192 Self::Or { filters } => filters.iter().any(|f| f.matches(event)),
193 Self::Not { filter } => !filter.matches(event),
194 Self::EqWrapped { condition } => {
195 json_path_get(event, &condition.path) == Some(&condition.value)
196 }
197 Self::Eq { path, value } => json_path_get(event, path) == Some(value),
198 }
199 }
200
201 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
203 serde_json::from_str(json)
204 }
205
206 pub fn to_json(&self) -> Result<String, serde_json::Error> {
208 serde_json::to_string(self)
209 }
210
211 pub fn compile(&self) -> CompiledFilter {
215 match self {
216 Self::And { filters } => {
217 CompiledFilter::And(filters.iter().map(Self::compile).collect())
218 }
219 Self::Or { filters } => CompiledFilter::Or(filters.iter().map(Self::compile).collect()),
220 Self::Not { filter } => CompiledFilter::Not(Box::new(filter.compile())),
221 Self::Eq { path, value } => CompiledFilter::Eq {
222 segments: compile_path(path),
223 value: value.clone(),
224 },
225 Self::EqWrapped { condition } => CompiledFilter::Eq {
226 segments: compile_path(&condition.path),
227 value: condition.value.clone(),
228 },
229 }
230 }
231}
232
233fn compile_path(path: &str) -> Vec<CompiledSegment> {
237 if path.is_empty() {
238 Vec::new()
239 } else {
240 path.split('.').map(CompiledSegment::from_str).collect()
241 }
242}
243
244#[inline]
260pub fn json_path_get<'a>(value: &'a JsonValue, path: &str) -> Option<&'a JsonValue> {
261 if path.is_empty() {
262 return Some(value);
263 }
264
265 let mut current = value;
266 for segment in path.split('.') {
267 current = match current {
268 JsonValue::Object(map) => map.get(segment)?,
269 JsonValue::Array(arr) => {
270 let idx: usize = segment.parse().ok()?;
272 arr.get(idx)?
273 }
274 _ => return None,
275 };
276 }
277 Some(current)
278}
279
280#[derive(Debug, Default)]
282pub struct FilterBuilder {
283 filters: Vec<Filter>,
284}
285
286impl FilterBuilder {
287 pub fn new() -> Self {
289 Self::default()
290 }
291
292 pub fn eq(mut self, path: impl Into<String>, value: JsonValue) -> Self {
294 self.filters.push(Filter::eq(path, value));
295 self
296 }
297
298 #[expect(
300 clippy::unwrap_used,
301 reason = "len == 1 branch guarantees the iterator yields exactly one element"
302 )]
303 pub fn build_and(self) -> Filter {
304 if self.filters.len() == 1 {
305 self.filters.into_iter().next().unwrap()
306 } else {
307 Filter::and(self.filters)
308 }
309 }
310
311 #[expect(
313 clippy::unwrap_used,
314 reason = "len == 1 branch guarantees the iterator yields exactly one element"
315 )]
316 pub fn build_or(self) -> Filter {
317 if self.filters.len() == 1 {
318 self.filters.into_iter().next().unwrap()
319 } else {
320 Filter::or(self.filters)
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use serde_json::json;
329
330 #[test]
331 fn test_eq_filter() {
332 let filter = Filter::eq("type", json!("token"));
333
334 assert!(filter.matches(&json!({"type": "token", "value": "hello"})));
335 assert!(!filter.matches(&json!({"type": "message", "value": "hello"})));
336 assert!(!filter.matches(&json!({"value": "hello"}))); }
338
339 #[test]
350 fn from_json_rejects_adversarially_nested_filter() {
351 let depth = 10_000usize;
352 let mut json = String::with_capacity(depth * 8 + 32);
353 for _ in 0..depth {
354 json.push_str(r#"{"$not":"#);
355 }
356 json.push_str(r#"{"path":"x","value":1}"#);
357 for _ in 0..depth {
358 json.push('}');
359 }
360
361 let parsed = Filter::from_json(&json);
362 assert!(
363 parsed.is_err(),
364 "depth-{depth} filter JSON must be rejected by serde_json's recursion limit"
365 );
366 }
367
368 #[test]
377 fn matches_handles_modest_depth_on_small_stack() {
378 const DEPTH: usize = 256;
379 let mut f = Filter::eq("x", json!(1));
381 for _ in 0..DEPTH {
382 f = Filter::not(f);
383 }
384
385 let result = std::thread::Builder::new()
389 .stack_size(256 * 1024)
390 .spawn(move || f.matches(&json!({"x": 1})))
391 .expect("spawn small-stack thread")
392 .join()
393 .expect("matches() must not panic at depth 256 on a small stack");
394
395 assert!(result, "depth-256 nested Not over true Eq should be true");
397 }
398
399 #[test]
400 fn test_nested_path() {
401 let filter = Filter::eq("user.profile.name", json!("Alice"));
402
403 assert!(filter.matches(&json!({
404 "user": {
405 "profile": {
406 "name": "Alice",
407 "age": 30
408 }
409 }
410 })));
411
412 assert!(!filter.matches(&json!({
413 "user": {
414 "profile": {
415 "name": "Bob"
416 }
417 }
418 })));
419 }
420
421 #[test]
422 fn test_array_indexing() {
423 let filter = Filter::eq("items.0.name", json!("first"));
424
425 assert!(filter.matches(&json!({
426 "items": [
427 {"name": "first"},
428 {"name": "second"}
429 ]
430 })));
431
432 assert!(!filter.matches(&json!({
433 "items": [
434 {"name": "other"}
435 ]
436 })));
437 }
438
439 #[test]
440 fn test_and_filter() {
441 let filter = Filter::and(vec![
442 Filter::eq("type", json!("token")),
443 Filter::eq("index", json!(0)),
444 ]);
445
446 assert!(filter.matches(&json!({"type": "token", "index": 0})));
447 assert!(!filter.matches(&json!({"type": "token", "index": 1})));
448 assert!(!filter.matches(&json!({"type": "message", "index": 0})));
449 }
450
451 #[test]
452 fn test_or_filter() {
453 let filter = Filter::or(vec![
454 Filter::eq("type", json!("token")),
455 Filter::eq("type", json!("message")),
456 ]);
457
458 assert!(filter.matches(&json!({"type": "token"})));
459 assert!(filter.matches(&json!({"type": "message"})));
460 assert!(!filter.matches(&json!({"type": "error"})));
461 }
462
463 #[test]
464 fn test_not_filter() {
465 let filter = Filter::not(Filter::eq("type", json!("error")));
466
467 assert!(filter.matches(&json!({"type": "token"})));
468 assert!(filter.matches(&json!({"type": "message"})));
469 assert!(!filter.matches(&json!({"type": "error"})));
470 }
471
472 #[test]
473 fn test_complex_filter() {
474 let filter = Filter::and(vec![
476 Filter::eq("type", json!("token")),
477 Filter::or(vec![
478 Filter::eq("value", json!("hello")),
479 Filter::eq("value", json!("world")),
480 ]),
481 Filter::not(Filter::eq("user", json!("bot"))),
482 ]);
483
484 assert!(filter.matches(&json!({
485 "type": "token",
486 "value": "hello",
487 "user": "alice"
488 })));
489
490 assert!(!filter.matches(&json!({
491 "type": "token",
492 "value": "hello",
493 "user": "bot" })));
495
496 assert!(!filter.matches(&json!({
497 "type": "token",
498 "value": "other", "user": "alice"
500 })));
501 }
502
503 #[test]
504 fn test_filter_builder() {
505 let filter = FilterBuilder::new()
506 .eq("type", json!("token"))
507 .eq("active", json!(true))
508 .build_and();
509
510 assert!(filter.matches(&json!({"type": "token", "active": true})));
511 assert!(!filter.matches(&json!({"type": "token", "active": false})));
512 }
513
514 #[test]
515 fn test_filter_serialization() {
516 let filter = Filter::and(vec![
517 Filter::eq("type", json!("token")),
518 Filter::not(Filter::eq("error", json!(true))),
519 ]);
520
521 let json = filter.to_json().unwrap();
522 let parsed: Filter = Filter::from_json(&json).unwrap();
523
524 let event = json!({"type": "token", "error": false});
526 assert_eq!(filter.matches(&event), parsed.matches(&event));
527 }
528
529 #[test]
540 fn compiled_filter_matches_raw_filter_semantically() {
541 let raw: Filter = serde_json::from_str(
543 r#"{"$and": [
544 {"path": "user.profile.name", "value": "Alice"},
545 {"$or": [
546 {"path": "items.0", "value": "first"},
547 {"$eq": {"path": "items.1", "value": "second"}}
548 ]},
549 {"$not": {"path": "user.profile.role", "value": "guest"}}
550 ]}"#,
551 )
552 .unwrap();
553 let compiled = raw.compile();
554
555 let events = [
556 serde_json::json!({
558 "user": {"profile": {"name": "Alice", "role": "admin"}},
559 "items": ["first", "second"]
560 }),
561 serde_json::json!({
563 "user": {"profile": {"name": "Bob", "role": "admin"}},
564 "items": ["first", "second"]
565 }),
566 serde_json::json!({
568 "user": {"profile": {"name": "Alice", "role": "guest"}},
569 "items": ["first", "second"]
570 }),
571 serde_json::json!({
573 "user": {"profile": {"name": "Alice", "role": "admin"}}
574 }),
575 serde_json::json!({
577 "user": {"profile": {"name": "Alice", "role": "admin"}},
578 "items": ["first"]
579 }),
580 ];
581
582 for ev in &events {
583 assert_eq!(
584 compiled.matches(ev),
585 raw.matches(ev),
586 "compiled vs raw diverge on {ev:?}",
587 );
588 }
589 }
590
591 #[test]
597 fn compile_caches_array_index_parse_per_segment() {
598 let f = Filter::eq("items.42.foo", serde_json::json!(1));
602 let compiled = f.compile();
603 let CompiledFilter::Eq { segments, .. } = compiled else {
604 panic!("expected CompiledFilter::Eq");
605 };
606 assert_eq!(segments.len(), 3);
607 assert_eq!(segments[0].field, "items");
608 assert!(
609 segments[0].idx.is_none(),
610 "'items' must not pre-parse as usize",
611 );
612 assert_eq!(segments[1].field, "42");
613 assert_eq!(
614 segments[1].idx,
615 Some(42),
616 "'42' must pre-parse as Some(42) — cached integer index",
617 );
618 assert_eq!(segments[2].field, "foo");
619 assert!(segments[2].idx.is_none());
620 }
621
622 #[test]
623 fn test_json_path_get() {
624 let value = json!({
625 "a": {
626 "b": {
627 "c": 42
628 }
629 },
630 "arr": [1, 2, 3],
631 "nested_arr": [{"x": 10}, {"x": 20}]
632 });
633
634 assert_eq!(json_path_get(&value, "a.b.c"), Some(&json!(42)));
635 assert_eq!(json_path_get(&value, "arr.1"), Some(&json!(2)));
636 assert_eq!(json_path_get(&value, "nested_arr.0.x"), Some(&json!(10)));
637 assert_eq!(json_path_get(&value, "missing"), None);
638 assert_eq!(json_path_get(&value, "a.b.missing"), None);
639 assert_eq!(json_path_get(&value, ""), Some(&value));
640 }
641
642 #[test]
643 fn test_json_path_get_primitive() {
644 let value = json!(42);
646 assert_eq!(json_path_get(&value, "foo"), None);
647
648 let value = json!("string");
649 assert_eq!(json_path_get(&value, "bar"), None);
650
651 let value = json!(true);
652 assert_eq!(json_path_get(&value, "baz"), None);
653
654 let value = json!(null);
655 assert_eq!(json_path_get(&value, "qux"), None);
656 }
657
658 #[test]
659 fn test_json_path_get_invalid_array_index() {
660 let value = json!({"arr": [1, 2, 3]});
661 assert_eq!(json_path_get(&value, "arr.foo"), None);
663 assert_eq!(json_path_get(&value, "arr.100"), None);
665 }
666
667 #[test]
668 fn test_filter_builder_single() {
669 let filter = FilterBuilder::new().eq("type", json!("token")).build_and();
671
672 assert!(matches!(filter, Filter::Eq { .. }));
673
674 let filter = FilterBuilder::new().eq("type", json!("token")).build_or();
675
676 assert!(matches!(filter, Filter::Eq { .. }));
677 }
678
679 #[test]
680 fn test_filter_builder_multiple_or() {
681 let filter = FilterBuilder::new()
682 .eq("type", json!("a"))
683 .eq("type", json!("b"))
684 .build_or();
685
686 assert!(filter.matches(&json!({"type": "a"})));
687 assert!(filter.matches(&json!({"type": "b"})));
688 assert!(!filter.matches(&json!({"type": "c"})));
689 }
690
691 #[test]
692 fn test_filter_clone() {
693 let filter = Filter::and(vec![
694 Filter::eq("a", json!(1)),
695 Filter::not(Filter::eq("b", json!(2))),
696 ]);
697
698 let cloned = filter.clone();
699 let event = json!({"a": 1, "b": 3});
700 assert_eq!(filter.matches(&event), cloned.matches(&event));
701 }
702
703 #[test]
704 fn test_filter_debug() {
705 let filter = Filter::eq("type", json!("token"));
706 let debug = format!("{:?}", filter);
707 assert!(debug.contains("Eq"));
708 assert!(debug.contains("type"));
709 }
710
711 #[test]
712 fn test_filter_partial_eq() {
713 let f1 = Filter::eq("type", json!("token"));
714 let f2 = Filter::eq("type", json!("token"));
715 let f3 = Filter::eq("type", json!("other"));
716
717 assert_eq!(f1, f2);
718 assert_ne!(f1, f3);
719 }
720
721 #[test]
722 fn test_empty_and_filter() {
723 let filter = Filter::and(vec![]);
730 assert!(
731 !filter.matches(&json!({"any": "value"})),
732 "empty And must not match — was silently universal-pass before"
733 );
734 }
735
736 #[test]
737 fn test_empty_or_filter() {
738 let filter = Filter::or(vec![]);
739 assert!(!filter.matches(&json!({"any": "value"})));
741 }
742
743 #[test]
748 fn test_single_element_and_or_match_inner_filter() {
749 let inner = Filter::eq("k", json!("v"));
750 let single_and = Filter::and(vec![inner.clone()]);
751 let single_or = Filter::or(vec![inner.clone()]);
752
753 let yes = json!({"k": "v"});
754 let no = json!({"k": "other"});
755
756 for ev in &[yes, no] {
757 assert_eq!(
758 single_and.matches(ev),
759 inner.matches(ev),
760 "single-element And must match inner: {ev}",
761 );
762 assert_eq!(
763 single_or.matches(ev),
764 inner.matches(ev),
765 "single-element Or must match inner: {ev}",
766 );
767 }
768 }
769
770 #[test]
775 fn test_single_element_fast_path_recurses_into_composite() {
776 let leaf = Filter::eq("k", json!("v"));
777 let yes = json!({"k": "v"});
778 let no = json!({"k": "other"});
779
780 let nested_not = Filter::and(vec![Filter::not(leaf.clone())]);
782 assert!(!nested_not.matches(&yes));
783 assert!(nested_not.matches(&no));
784
785 let nested_double = Filter::or(vec![Filter::and(vec![leaf.clone()])]);
787 assert!(nested_double.matches(&yes));
788 assert!(!nested_double.matches(&no));
789
790 let leaf2 = Filter::eq("x", json!(1));
794 let mixed = Filter::or(vec![Filter::and(vec![leaf.clone(), leaf2.clone()])]);
795 assert!(mixed.matches(&json!({"k": "v", "x": 1})));
796 assert!(!mixed.matches(&json!({"k": "v", "x": 2})));
797 assert!(!mixed.matches(&json!({"k": "other", "x": 1})));
798 }
799
800 #[test]
805 fn test_multi_element_and_or_uses_slow_path() {
806 let f1 = Filter::eq("k", json!("v"));
807 let f2 = Filter::eq("x", json!(1));
808
809 let and = Filter::and(vec![f1.clone(), f2.clone()]);
810 assert!(and.matches(&json!({"k": "v", "x": 1})));
811 assert!(!and.matches(&json!({"k": "v", "x": 2})));
812 assert!(!and.matches(&json!({"k": "other", "x": 1})));
813
814 let or = Filter::or(vec![f1.clone(), f2.clone()]);
815 assert!(or.matches(&json!({"k": "v", "x": 99})));
816 assert!(or.matches(&json!({"k": "nope", "x": 1})));
817 assert!(!or.matches(&json!({"k": "nope", "x": 2})));
818 }
819
820 #[test]
821 fn test_filter_builder_default() {
822 let builder = FilterBuilder::default();
823 let debug = format!("{:?}", builder);
824 assert!(debug.contains("FilterBuilder"));
825 }
826
827 #[test]
828 fn test_eq_wrapped_filter_deserialization() {
829 let json_str = r#"{"$eq": {"path": "type", "value": "token"}}"#;
831 let filter: Filter = serde_json::from_str(json_str).unwrap();
832
833 assert!(filter.matches(&json!({"type": "token", "data": "hello"})));
834 assert!(!filter.matches(&json!({"type": "message", "data": "hello"})));
835 }
836
837 #[test]
838 fn test_eq_wrapped_with_nested_path() {
839 let json_str = r#"{"$eq": {"path": "user.role", "value": "admin"}}"#;
841 let filter: Filter = serde_json::from_str(json_str).unwrap();
842
843 assert!(filter.matches(&json!({"user": {"role": "admin"}})));
844 assert!(!filter.matches(&json!({"user": {"role": "user"}})));
845 }
846
847 #[test]
848 fn test_eq_wrapped_with_numeric_value() {
849 let json_str = r#"{"$eq": {"path": "count", "value": 42}}"#;
851 let filter: Filter = serde_json::from_str(json_str).unwrap();
852
853 assert!(filter.matches(&json!({"count": 42})));
854 assert!(!filter.matches(&json!({"count": 41})));
855 }
856
857 #[test]
858 fn test_eq_wrapped_with_boolean_value() {
859 let json_str = r#"{"$eq": {"path": "active", "value": true}}"#;
861 let filter: Filter = serde_json::from_str(json_str).unwrap();
862
863 assert!(filter.matches(&json!({"active": true})));
864 assert!(!filter.matches(&json!({"active": false})));
865 }
866
867 #[test]
868 fn test_eq_wrapped_in_and() {
869 let json_str = r#"{"$and": [{"$eq": {"path": "type", "value": "token"}}, {"$eq": {"path": "index", "value": 0}}]}"#;
871 let filter: Filter = serde_json::from_str(json_str).unwrap();
872
873 assert!(filter.matches(&json!({"type": "token", "index": 0})));
874 assert!(!filter.matches(&json!({"type": "token", "index": 1})));
875 assert!(!filter.matches(&json!({"type": "message", "index": 0})));
876 }
877
878 #[test]
879 fn test_eq_wrapped_in_or() {
880 let json_str = r#"{"$or": [{"$eq": {"path": "type", "value": "token"}}, {"$eq": {"path": "type", "value": "message"}}]}"#;
882 let filter: Filter = serde_json::from_str(json_str).unwrap();
883
884 assert!(filter.matches(&json!({"type": "token"})));
885 assert!(filter.matches(&json!({"type": "message"})));
886 assert!(!filter.matches(&json!({"type": "error"})));
887 }
888
889 #[test]
890 fn test_eq_wrapped_in_not() {
891 let json_str = r#"{"$not": {"$eq": {"path": "type", "value": "error"}}}"#;
893 let filter: Filter = serde_json::from_str(json_str).unwrap();
894
895 assert!(filter.matches(&json!({"type": "token"})));
896 assert!(filter.matches(&json!({"type": "message"})));
897 assert!(!filter.matches(&json!({"type": "error"})));
898 }
899
900 #[test]
901 fn test_both_eq_formats_work() {
902 let shorthand = r#"{"path": "type", "value": "token"}"#;
904 let wrapped = r#"{"$eq": {"path": "type", "value": "token"}}"#;
905
906 let filter1: Filter = serde_json::from_str(shorthand).unwrap();
907 let filter2: Filter = serde_json::from_str(wrapped).unwrap();
908
909 let event = json!({"type": "token"});
910 assert!(filter1.matches(&event));
911 assert!(filter2.matches(&event));
912
913 let event2 = json!({"type": "other"});
914 assert!(!filter1.matches(&event2));
915 assert!(!filter2.matches(&event2));
916 }
917}