dd_sds/scoped_ruleset/
mod.rs

1mod bool_set;
2
3use crate::event::{EventVisitor, VisitStringResult};
4use crate::scanner::scope::Scope;
5use crate::scoped_ruleset::bool_set::BoolSet;
6use crate::{Event, Path, PathSegment};
7use ahash::AHashMap;
8
9/// A `ScopedRuleSet` determines which rules will be used to scan each field of an event, and which
10/// paths are considered `excluded`.
11#[derive(Debug)]
12pub struct ScopedRuleSet {
13    tree: RuleTree,
14    // The number of rules stored in this set
15    num_rules: usize,
16    add_implicit_index_wildcards: bool,
17}
18
19impl ScopedRuleSet {
20    /// The scopes for each rule. The indices of the scopes MUST match the rule index.
21    pub fn new(rules_scopes: &[Scope]) -> Self {
22        let mut tree = RuleTree::new();
23
24        for (rule_index, scope) in rules_scopes.iter().enumerate() {
25            match scope {
26                Scope::Include { include, exclude } => {
27                    for path in exclude {
28                        tree.insert_rule_removal(path, rule_index);
29                    }
30
31                    for path in include {
32                        tree.insert_rule_add(path, rule_index);
33                    }
34                }
35                Scope::Exclude(paths) => {
36                    tree.insert_rule_add(&Path::root(), rule_index);
37                    for path in paths {
38                        tree.insert_rule_removal(path, rule_index);
39                    }
40                }
41            }
42        }
43        Self {
44            tree,
45            num_rules: rules_scopes.len(),
46            add_implicit_index_wildcards: false,
47        }
48    }
49
50    pub fn with_implicit_index_wildcards(mut self, value: bool) -> Self {
51        self.add_implicit_index_wildcards = value;
52        self
53    }
54
55    pub fn visit_string_rule_combinations<'path, 'c: 'path>(
56        &'c self,
57        event: &'path mut impl Event,
58        content_visitor: impl ContentVisitor<'path>,
59    ) {
60        let bool_set = if self.add_implicit_index_wildcards {
61            Some(BoolSet::new(self.num_rules))
62        } else {
63            None
64        };
65        let mut visitor = ScopedRuledSetEventVisitor {
66            content_visitor,
67            tree_nodes: vec![ActiveRuleTree {
68                rule_tree: &self.tree,
69                index_wildcard_match: false,
70            }],
71            active_node_counter: vec![NodeCounter {
72                active_tree_count: 1,
73            }],
74            path: Path::root(),
75            bool_set,
76            add_implicit_index_wildcards: self.add_implicit_index_wildcards,
77            // should_keywords_match_event_paths: self.should_keywords_match_event_paths,
78        };
79
80        event.visit_event(&mut visitor)
81    }
82}
83
84pub struct ExclusionCheck<'a> {
85    tree_nodes: &'a [ActiveRuleTree<'a>],
86}
87
88impl ExclusionCheck<'_> {
89    pub fn is_excluded(&self, rule_index: usize) -> bool {
90        for include_node in self.tree_nodes {
91            for change in &include_node.rule_tree.rule_changes {
92                match change {
93                    RuleChange::Add(_) => { /* ignore */ }
94                    RuleChange::Remove(i) => {
95                        if *i == rule_index {
96                            return true;
97                        }
98                    }
99                }
100            }
101        }
102        false
103    }
104}
105
106pub trait ContentVisitor<'path> {
107    fn visit_content<'content_visitor>(
108        &'content_visitor mut self,
109        path: &Path<'path>,
110        content: &str,
111        rules: RuleIndexVisitor,
112        is_excluded: ExclusionCheck<'content_visitor>,
113    ) -> bool;
114}
115
116// This is just a reference to a RuleTree with some additional information
117struct ActiveRuleTree<'a> {
118    rule_tree: &'a RuleTree,
119    // If this tree was pushed because of a wildcard index, indices aren't
120    // allowed to match immediately after, so they are ignored.
121    index_wildcard_match: bool,
122}
123
124struct NodeCounter {
125    // This counts how many trees are currently active, which can
126    // happen due to (implicit) wildcard segments. The last value in this
127    // list is the current number of active trees (n), which is the last
128    // n trees in `tree_nodes`.
129    active_tree_count: usize,
130}
131
132struct ScopedRuledSetEventVisitor<'a, C> {
133    // The struct that will receive content / rules.
134    content_visitor: C,
135
136    // This is a list of parent tree nodes, which is a list of all of the "active" rule changes.
137    // If an "Add" exists for a rule in this list, it will be scanned. If a single "Remove" exists, it will cause the `ExclusionCheck` to return true.
138    tree_nodes: Vec<ActiveRuleTree<'a>>,
139
140    // This is a counter that helps keep track of how many elements we have pushed
141    // In the tree_nodes list and in the true_positive_rule_idx list
142    active_node_counter: Vec<NodeCounter>,
143
144    // The current path being visited
145    path: Path<'a>,
146
147    // A re-usable boolean set to de-duplicate rules
148    bool_set: Option<BoolSet>,
149
150    add_implicit_index_wildcards: bool,
151}
152
153impl<'path, C> EventVisitor<'path> for ScopedRuledSetEventVisitor<'path, C>
154where
155    C: ContentVisitor<'path>,
156{
157    fn push_segment(&mut self, segment: PathSegment<'path>) {
158        // update the tree
159        // The current path may go beyond what is stored in the "include" tree, so the tree is only updated if they are at the same height.
160
161        let num_active_trees = self.active_node_counter.last().unwrap().active_tree_count;
162        let tree_nodes_len = self.tree_nodes.len();
163        let active_trees_range = tree_nodes_len - num_active_trees..tree_nodes_len;
164
165        for tree_index in active_trees_range {
166            if !self.tree_nodes[tree_index].index_wildcard_match || !segment.is_index() {
167                if let Some(child) = self.tree_nodes[tree_index].rule_tree.children.get(&segment) {
168                    self.tree_nodes.push(ActiveRuleTree {
169                        rule_tree: child,
170                        index_wildcard_match: false,
171                    });
172                }
173            }
174
175            if self.add_implicit_index_wildcards && segment.is_index() {
176                // Optionally skip the index (it acts as a wildcard) by
177                // pushing the same tree back onto the stack.
178                self.tree_nodes.push(ActiveRuleTree {
179                    rule_tree: self.tree_nodes[tree_index].rule_tree,
180                    index_wildcard_match: true,
181                });
182            }
183        }
184
185        // The new number of active trees is the number of new trees pushed
186        self.active_node_counter.push(NodeCounter {
187            active_tree_count: self.tree_nodes.len() - tree_nodes_len,
188        });
189
190        self.path.segments.push(segment);
191    }
192
193    fn pop_segment(&mut self) {
194        let node_counter = self.active_node_counter.pop().unwrap();
195        for _ in 0..node_counter.active_tree_count {
196            // The rules from the last node are no longer active, so remove them.
197            let _popped = self.tree_nodes.pop();
198        }
199        self.path.segments.pop();
200    }
201
202    fn visit_string<'s>(&'s mut self, value: &str) -> VisitStringResult<'s, 'path> {
203        let will_mutate = self.content_visitor.visit_content(
204            &self.path,
205            value,
206            RuleIndexVisitor {
207                tree_nodes: &self.tree_nodes,
208                used_rule_set: self.bool_set.as_mut(),
209            },
210            ExclusionCheck {
211                tree_nodes: &self.tree_nodes,
212            },
213        );
214        if let Some(bool_set) = &mut self.bool_set {
215            bool_set.reset();
216        }
217        VisitStringResult {
218            might_mutate: will_mutate,
219            path: &self.path,
220        }
221    }
222}
223
224pub struct RuleIndexVisitor<'a> {
225    tree_nodes: &'a Vec<ActiveRuleTree<'a>>,
226    used_rule_set: Option<&'a mut BoolSet>,
227}
228
229impl RuleIndexVisitor<'_> {
230    /// Visits all rules associated with the current string. This may
231    /// potentially return no rule indices at all.
232    pub fn visit_rule_indices(&mut self, mut visit: impl FnMut(usize)) {
233        // visit rules with an `Include` scope
234        for include_node in self.tree_nodes {
235            if include_node.index_wildcard_match {
236                // This is guaranteed to be a duplicated node. Skip it
237                continue;
238            }
239            for change in &include_node.rule_tree.rule_changes {
240                match change {
241                    RuleChange::Add(rule_index) => {
242                        if let Some(used_rule_set) = &mut self.used_rule_set {
243                            if !used_rule_set.get_and_set(*rule_index) {
244                                (visit)(*rule_index);
245                            }
246                        } else {
247                            (visit)(*rule_index);
248                        }
249                    }
250                    RuleChange::Remove(_) => { /* Nothing to do here */ }
251                }
252            }
253        }
254    }
255}
256
257#[derive(Clone, Copy, PartialEq, Debug)]
258enum RuleChange {
259    Add(usize),
260    Remove(usize),
261}
262
263#[derive(Clone, Debug)]
264struct RuleTree {
265    rule_changes: Vec<RuleChange>,
266    children: AHashMap<PathSegment<'static>, RuleTree>,
267}
268
269impl RuleTree {
270    pub fn new() -> Self {
271        Self {
272            rule_changes: vec![],
273            children: AHashMap::new(),
274        }
275    }
276
277    pub fn insert_rule_add(&mut self, path: &Path<'static>, rule_index: usize) {
278        self.insert_rule_inner(&path.segments, RuleChange::Add(rule_index));
279    }
280
281    pub fn insert_rule_removal(&mut self, path: &Path<'static>, rule_index: usize) {
282        self.insert_rule_inner(&path.segments, RuleChange::Remove(rule_index));
283    }
284
285    fn insert_rule_inner(&mut self, path: &[PathSegment<'static>], rule_change: RuleChange) {
286        if self.rule_changes.contains(&rule_change) {
287            return;
288        }
289        if let Some((first, remaining)) = path.split_first() {
290            let child_tree = self
291                .children
292                .entry(first.clone())
293                .or_insert_with(RuleTree::new);
294            child_tree.insert_rule_inner(remaining, rule_change);
295        } else {
296            // remove any recursive children, since they will be duplicated
297            self.recursively_remove(rule_change);
298            self.rule_changes.push(rule_change);
299        }
300    }
301
302    // Remove the given change from all children (recursive). This is used
303    // to remove duplicates.
304    fn recursively_remove(&mut self, rule_change: RuleChange) {
305        self.rule_changes.retain(|x| *x != rule_change);
306        for child_tree in self.children.values_mut() {
307            child_tree.recursively_remove(rule_change);
308        }
309    }
310}
311
312#[cfg(test)]
313mod test {
314    use crate::simple_event::SimpleEvent;
315
316    use super::*;
317
318    #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
319    struct VisitedPath {
320        path: Path<'static>,
321        content: String,
322        rules: Vec<VisitedRule>,
323    }
324
325    #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
326    struct VisitedRule {
327        rule_index: usize,
328        is_excluded: bool,
329    }
330
331    #[derive(Debug, PartialEq, Eq)]
332    struct Visited {
333        paths: Vec<VisitedPath>,
334    }
335
336    // Visits the event and returns the paths that were visited
337    fn visit_event(event: &mut impl Event, ruleset: &ScopedRuleSet) -> Visited {
338        let mut visited = Visited { paths: vec![] };
339
340        struct RecordingContentVisitor<'a> {
341            visited: &'a mut Visited,
342        }
343
344        impl<'a> ContentVisitor<'a> for RecordingContentVisitor<'a> {
345            fn visit_content<'content_visitor>(
346                &'content_visitor mut self,
347                path: &Path<'a>,
348                content: &str,
349                mut rule_iter: RuleIndexVisitor,
350                exclusion_check: ExclusionCheck<'content_visitor>,
351            ) -> bool {
352                let mut rules = vec![];
353                rule_iter.visit_rule_indices(|rule_index| {
354                    rules.push(VisitedRule {
355                        rule_index,
356                        is_excluded: exclusion_check.is_excluded(rule_index),
357                    });
358                });
359                rules.sort();
360                self.visited.paths.push(VisitedPath {
361                    path: path.into_static(),
362                    content: content.to_string(),
363                    rules,
364                });
365                true
366            }
367        }
368
369        ruleset.visit_string_rule_combinations(
370            event,
371            RecordingContentVisitor {
372                visited: &mut visited,
373            },
374        );
375        visited.paths.sort();
376        visited
377    }
378
379    #[test]
380    fn test_inclusive_scopes() {
381        // Fields are scanned as long as they are a child of any `include` path
382        let ruleset = ScopedRuleSet::new(&[
383            Scope::include(vec![Path::from(vec!["a".into()])]),
384            Scope::include(vec![Path::from(vec!["a".into(), "b".into()])]),
385            Scope::include(vec![]),
386        ]);
387
388        let mut event = SimpleEvent::Map(
389            [
390                (
391                    "a".into(),
392                    SimpleEvent::Map(
393                        [
394                            ("b".into(), SimpleEvent::String("value-ab".into())),
395                            ("c".into(), SimpleEvent::String("value-ac".into())),
396                        ]
397                        .into(),
398                    ),
399                ),
400                ("d".into(), SimpleEvent::String("value-d".into())),
401            ]
402            .into(),
403        );
404
405        let paths = visit_event(&mut event, &ruleset);
406
407        assert_eq!(
408            paths,
409            Visited {
410                paths: vec![
411                    VisitedPath {
412                        path: Path::from(vec!["a".into(), "b".into()]),
413                        content: "value-ab".into(),
414                        rules: vec![
415                            VisitedRule {
416                                rule_index: 0,
417                                is_excluded: false
418                            },
419                            VisitedRule {
420                                rule_index: 1,
421                                is_excluded: false
422                            }
423                        ]
424                    },
425                    VisitedPath {
426                        path: Path::from(vec!["a".into(), "c".into()]),
427                        content: "value-ac".into(),
428                        rules: vec![VisitedRule {
429                            rule_index: 0,
430                            is_excluded: false
431                        }]
432                    },
433                    VisitedPath {
434                        path: Path::from(vec!["d".into()]),
435                        content: "value-d".into(),
436                        rules: vec![]
437                    }
438                ],
439            }
440        );
441    }
442
443    #[test]
444    fn test_inclusive_scopes_array() {
445        // Fields are scanned as long as they are a child of any `include` path
446        let ruleset = ScopedRuleSet::new(&[
447            Scope::include(vec![Path::from(vec![0.into()])]),
448            Scope::include(vec![Path::from(vec![1.into(), 0.into()])]),
449            Scope::include(vec![Path::from(vec![2.into(), 0.into()])]),
450        ]);
451
452        let mut event = SimpleEvent::List(vec![
453            SimpleEvent::String("value-0".into()),
454            SimpleEvent::String("value-1".into()),
455            SimpleEvent::List(vec![SimpleEvent::String("value-2-0".into())]),
456            SimpleEvent::String("value-3".into()),
457        ]);
458
459        let paths = visit_event(&mut event, &ruleset);
460
461        assert_eq!(
462            paths,
463            Visited {
464                paths: vec![
465                    VisitedPath {
466                        path: Path::from(vec![0.into()]),
467                        content: "value-0".into(),
468                        rules: vec![VisitedRule {
469                            rule_index: 0,
470                            is_excluded: false
471                        }]
472                    },
473                    VisitedPath {
474                        path: Path::from(vec![1.into()]),
475                        content: "value-1".into(),
476                        rules: vec![]
477                    },
478                    VisitedPath {
479                        path: Path::from(vec![2.into(), 0.into()]),
480                        content: "value-2-0".into(),
481                        rules: vec![VisitedRule {
482                            rule_index: 2,
483                            is_excluded: false
484                        }]
485                    },
486                    VisitedPath {
487                        path: Path::from(vec![3.into()]),
488                        content: "value-3".into(),
489                        rules: vec![]
490                    }
491                ],
492            }
493        );
494    }
495
496    #[test]
497    fn test_exclusive_scopes() {
498        // All fields are scanned, but fields that are children of any `exclude` path are marked as excluded
499        let ruleset = ScopedRuleSet::new(&[
500            Scope::exclude(vec![Path::from(vec!["a".into()])]),
501            Scope::exclude(vec![Path::from(vec!["a".into(), "b".into()])]),
502            // matches everything (it will always be excluded)
503            Scope::all(),
504        ]);
505
506        let mut event = SimpleEvent::Map(
507            [
508                (
509                    "a".into(),
510                    SimpleEvent::Map(
511                        [
512                            ("b".into(), SimpleEvent::String("value-ab".into())),
513                            ("c".into(), SimpleEvent::String("value-ac".into())),
514                        ]
515                        .into(),
516                    ),
517                ),
518                ("d".into(), SimpleEvent::String("value-d".into())),
519                ("e".into(), SimpleEvent::List(vec![])),
520            ]
521            .into(),
522        );
523
524        let paths = visit_event(&mut event, &ruleset);
525
526        assert_eq!(
527            paths,
528            Visited {
529                paths: vec![
530                    VisitedPath {
531                        path: Path::from(vec!["a".into(), "b".into()]),
532                        content: "value-ab".into(),
533                        rules: vec![
534                            VisitedRule {
535                                rule_index: 0,
536                                is_excluded: true
537                            },
538                            VisitedRule {
539                                rule_index: 1,
540                                is_excluded: true
541                            },
542                            VisitedRule {
543                                rule_index: 2,
544                                is_excluded: false
545                            }
546                        ]
547                    },
548                    VisitedPath {
549                        path: Path::from(vec!["a".into(), "c".into()]),
550                        content: "value-ac".into(),
551                        rules: vec![
552                            VisitedRule {
553                                rule_index: 0,
554                                is_excluded: true
555                            },
556                            VisitedRule {
557                                rule_index: 1,
558                                is_excluded: false
559                            },
560                            VisitedRule {
561                                rule_index: 2,
562                                is_excluded: false
563                            }
564                        ]
565                    },
566                    VisitedPath {
567                        path: Path::from(vec!["d".into()]),
568                        content: "value-d".into(),
569                        rules: vec![
570                            VisitedRule {
571                                rule_index: 0,
572                                is_excluded: false
573                            },
574                            VisitedRule {
575                                rule_index: 1,
576                                is_excluded: false
577                            },
578                            VisitedRule {
579                                rule_index: 2,
580                                is_excluded: false
581                            }
582                        ]
583                    }
584                ],
585            },
586        );
587    }
588
589    #[test]
590    fn test_exclusive_scopes_array() {
591        // All fields are scanned, but fields that are children of any `exclude` path are marked as excluded
592        let ruleset = ScopedRuleSet::new(&[
593            Scope::exclude(vec![Path::from(vec![0.into()])]),
594            Scope::exclude(vec![Path::from(vec![1.into(), 0.into()])]),
595            Scope::exclude(vec![Path::from(vec![2.into(), 0.into()])]),
596        ]);
597
598        let mut event = SimpleEvent::List(vec![
599            SimpleEvent::String("value-0".into()),
600            SimpleEvent::String("value-1".into()),
601            SimpleEvent::List(vec![SimpleEvent::String("value-2-0".into())]),
602            SimpleEvent::String("value-3".into()),
603        ]);
604
605        let paths = visit_event(&mut event, &ruleset);
606
607        assert_eq!(
608            paths,
609            Visited {
610                paths: vec![
611                    VisitedPath {
612                        path: Path::from(vec![0.into()]),
613                        content: "value-0".into(),
614                        rules: vec![
615                            VisitedRule {
616                                rule_index: 0,
617                                is_excluded: true
618                            },
619                            VisitedRule {
620                                rule_index: 1,
621                                is_excluded: false
622                            },
623                            VisitedRule {
624                                rule_index: 2,
625                                is_excluded: false
626                            }
627                        ]
628                    },
629                    VisitedPath {
630                        path: Path::from(vec![1.into()]),
631                        content: "value-1".into(),
632                        rules: vec![
633                            VisitedRule {
634                                rule_index: 0,
635                                is_excluded: false
636                            },
637                            VisitedRule {
638                                rule_index: 1,
639                                is_excluded: false
640                            },
641                            VisitedRule {
642                                rule_index: 2,
643                                is_excluded: false
644                            }
645                        ]
646                    },
647                    VisitedPath {
648                        path: Path::from(vec![2.into(), 0.into()]),
649                        content: "value-2-0".into(),
650                        rules: vec![
651                            VisitedRule {
652                                rule_index: 0,
653                                is_excluded: false
654                            },
655                            VisitedRule {
656                                rule_index: 1,
657                                is_excluded: false
658                            },
659                            VisitedRule {
660                                rule_index: 2,
661                                is_excluded: true
662                            }
663                        ]
664                    },
665                    VisitedPath {
666                        path: Path::from(vec![3.into()]),
667                        content: "value-3".into(),
668                        rules: vec![
669                            VisitedRule {
670                                rule_index: 0,
671                                is_excluded: false
672                            },
673                            VisitedRule {
674                                rule_index: 1,
675                                is_excluded: false
676                            },
677                            VisitedRule {
678                                rule_index: 2,
679                                is_excluded: false
680                            }
681                        ]
682                    }
683                ],
684            }
685        );
686    }
687
688    #[test]
689    fn test_include_and_exclude() {
690        // Fields that are children of any `include` path are scanned, but they are only marked as
691        // excluded if they are children of any `exclude` path
692        let ruleset = ScopedRuleSet::new(&[Scope::include_and_exclude(
693            vec![Path::from(vec![2.into()])],
694            vec![Path::from(vec![2.into(), 0.into()])],
695        )]);
696
697        let mut event = SimpleEvent::List(vec![
698            SimpleEvent::String("value-0".into()),
699            SimpleEvent::String("value-1".into()),
700            SimpleEvent::List(vec![
701                SimpleEvent::String("value-2-0".into()),
702                SimpleEvent::String("value-2-1".into()),
703            ]),
704            SimpleEvent::String("value-3".into()),
705        ]);
706
707        let paths = visit_event(&mut event, &ruleset);
708
709        assert_eq!(
710            paths,
711            Visited {
712                paths: vec![
713                    VisitedPath {
714                        path: Path::from(vec![0.into()]),
715                        content: "value-0".into(),
716                        rules: vec![]
717                    },
718                    VisitedPath {
719                        path: Path::from(vec![1.into()]),
720                        content: "value-1".into(),
721                        rules: vec![]
722                    },
723                    VisitedPath {
724                        path: Path::from(vec![2.into(), 0.into()]),
725                        content: "value-2-0".into(),
726                        rules: vec![VisitedRule {
727                            rule_index: 0,
728                            is_excluded: true
729                        }]
730                    },
731                    VisitedPath {
732                        path: Path::from(vec![2.into(), 1.into()]),
733                        content: "value-2-1".into(),
734                        rules: vec![VisitedRule {
735                            rule_index: 0,
736                            is_excluded: false
737                        }]
738                    },
739                    VisitedPath {
740                        path: Path::from(vec![3.into()]),
741                        content: "value-3".into(),
742                        rules: vec![]
743                    },
744                ]
745            }
746        );
747    }
748
749    #[test]
750    fn test_include_and_exclude_priority() {
751        // exclude paths have priority, and override any include path. (even if the include is more specific)
752
753        let ruleset = ScopedRuleSet::new(&[Scope::include_and_exclude(
754            // This include is ignored, since an exclude overrides it
755            vec![Path::from(vec![1.into(), 0.into()])],
756            vec![Path::from(vec![1.into()])],
757        )]);
758
759        let mut event = SimpleEvent::List(vec![
760            SimpleEvent::String("value-0".into()),
761            SimpleEvent::List(vec![
762                SimpleEvent::String("value-1-0".into()),
763                SimpleEvent::String("value-1-1".into()),
764            ]),
765        ]);
766
767        let paths = visit_event(&mut event, &ruleset);
768
769        assert_eq!(
770            paths,
771            Visited {
772                paths: vec![
773                    VisitedPath {
774                        path: Path::from(vec![0.into()]),
775                        content: "value-0".into(),
776                        rules: vec![]
777                    },
778                    VisitedPath {
779                        path: Path::from(vec![1.into(), 0.into()]),
780                        content: "value-1-0".into(),
781                        rules: vec![VisitedRule {
782                            rule_index: 0,
783                            is_excluded: true
784                        }]
785                    },
786                    VisitedPath {
787                        path: Path::from(vec![1.into(), 1.into()]),
788                        content: "value-1-1".into(),
789                        rules: vec![]
790                    }
791                ],
792            }
793        );
794    }
795
796    #[test]
797    fn test_include_same_change_multiple_times() {
798        // If multiple "include" scopes cover the same field, it should only be returned once.
799
800        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![
801            Path::from(vec!["a".into()]),
802            Path::from(vec!["a".into(), "b".into()]),
803        ])]);
804
805        let mut event = SimpleEvent::Map(
806            [
807                (
808                    "a".into(),
809                    SimpleEvent::Map(
810                        [
811                            ("b".into(), SimpleEvent::String("value-ab".into())),
812                            ("c".into(), SimpleEvent::String("value-ac".into())),
813                        ]
814                        .into(),
815                    ),
816                ),
817                ("d".into(), SimpleEvent::String("value-d".into())),
818            ]
819            .into(),
820        );
821
822        let paths = visit_event(&mut event, &ruleset);
823
824        assert_eq!(
825            paths,
826            Visited {
827                paths: vec![
828                    VisitedPath {
829                        path: Path::from(vec!["a".into(), "b".into()]),
830                        content: "value-ab".into(),
831                        rules: vec![VisitedRule {
832                            rule_index: 0,
833                            is_excluded: false
834                        }]
835                    },
836                    VisitedPath {
837                        path: Path::from(vec!["a".into(), "c".into()]),
838                        content: "value-ac".into(),
839                        rules: vec![VisitedRule {
840                            rule_index: 0,
841                            is_excluded: false
842                        }]
843                    },
844                    VisitedPath {
845                        path: Path::from(vec!["d".into()]),
846                        content: "value-d".into(),
847                        rules: vec![]
848                    }
849                ],
850            }
851        );
852    }
853
854    #[test]
855    fn test_include_root_multiple_times() {
856        // This makes sure duplicate rule changes (at the root of the tree) are removed
857        let ruleset =
858            ScopedRuleSet::new(&[Scope::include(vec![Path::from(vec![]), Path::from(vec![])])]);
859
860        let mut event = SimpleEvent::Map(
861            [
862                (
863                    "a".into(),
864                    SimpleEvent::Map(
865                        [
866                            ("b".into(), SimpleEvent::String("value-ab".into())),
867                            ("c".into(), SimpleEvent::String("value-ac".into())),
868                        ]
869                        .into(),
870                    ),
871                ),
872                ("d".into(), SimpleEvent::String("value-d".into())),
873            ]
874            .into(),
875        );
876
877        let paths = visit_event(&mut event, &ruleset);
878
879        assert_eq!(
880            paths,
881            Visited {
882                paths: vec![
883                    VisitedPath {
884                        path: Path::from(vec!["a".into(), "b".into()]),
885                        content: "value-ab".into(),
886                        rules: vec![VisitedRule {
887                            rule_index: 0,
888                            is_excluded: false
889                        }]
890                    },
891                    VisitedPath {
892                        path: Path::from(vec!["a".into(), "c".into()]),
893                        content: "value-ac".into(),
894                        rules: vec![VisitedRule {
895                            rule_index: 0,
896                            is_excluded: false
897                        }]
898                    },
899                    VisitedPath {
900                        path: Path::from(vec!["d".into()]),
901                        content: "value-d".into(),
902                        rules: vec![VisitedRule {
903                            rule_index: 0,
904                            is_excluded: false
905                        }]
906                    }
907                ],
908            }
909        );
910    }
911
912    #[test]
913    fn test_include_same_change_multiple_times_reversed() {
914        // If multiple "include" scopes cover the same field, it should only be returned once.
915        // This one specifically tests when a more generic path is added _after_ a specific path.
916        // (The more specific one should be removed from the tree)
917
918        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![
919            Path::from(vec!["a".into(), "b".into()]),
920            Path::from(vec!["a".into()]),
921        ])]);
922
923        let mut event = SimpleEvent::Map(
924            [
925                (
926                    "a".into(),
927                    SimpleEvent::Map(
928                        [
929                            ("b".into(), SimpleEvent::String("value-ab".into())),
930                            ("c".into(), SimpleEvent::String("value-ac".into())),
931                        ]
932                        .into(),
933                    ),
934                ),
935                ("d".into(), SimpleEvent::String("value-d".into())),
936            ]
937            .into(),
938        );
939
940        let paths = visit_event(&mut event, &ruleset);
941
942        assert_eq!(
943            paths,
944            Visited {
945                paths: vec![
946                    VisitedPath {
947                        path: Path::from(vec!["a".into(), "b".into()]),
948                        content: "value-ab".into(),
949                        rules: vec![VisitedRule {
950                            rule_index: 0,
951                            is_excluded: false
952                        }]
953                    },
954                    VisitedPath {
955                        path: Path::from(vec!["a".into(), "c".into()]),
956                        content: "value-ac".into(),
957                        rules: vec![VisitedRule {
958                            rule_index: 0,
959                            is_excluded: false
960                        }]
961                    },
962                    VisitedPath {
963                        path: Path::from(vec!["d".into()]),
964                        content: "value-d".into(),
965                        rules: vec![]
966                    }
967                ],
968            }
969        );
970    }
971
972    #[test]
973    fn test_fields_should_act_as_wildcard_on_lists() {
974        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![Path::from(vec![
975            "a".into(),
976            "b".into(),
977        ])])])
978        .with_implicit_index_wildcards(true);
979
980        let mut event = SimpleEvent::Map(
981            [(
982                "a".into(),
983                SimpleEvent::List(vec![SimpleEvent::Map(
984                    [("b".into(), SimpleEvent::String("value-a-0-b".to_string()))].into(),
985                )]),
986            )]
987            .into(),
988        );
989
990        let paths = visit_event(&mut event, &ruleset);
991
992        assert_eq!(
993            paths,
994            Visited {
995                paths: vec![VisitedPath {
996                    path: Path::from(vec!["a".into(), 0.into(), "b".into()]),
997                    content: "value-a-0-b".into(),
998                    // Rule 0 matches this path even though there is a "0" index between the "a" and "b" field
999                    rules: vec![VisitedRule {
1000                        rule_index: 0,
1001                        is_excluded: false
1002                    }]
1003                },],
1004            }
1005        );
1006    }
1007
1008    #[test]
1009    fn test_exclude_implicit_wildcard_path() {
1010        // This makes sure that exclusions are applied even when a path matches due to an
1011        // implicit array wildcard.
1012
1013        let ab_path = Path::from(vec!["a".into(), "b".into()]);
1014        let ruleset = ScopedRuleSet::new(&[Scope::include_and_exclude(
1015            vec![ab_path.clone()],
1016            vec![ab_path],
1017        )])
1018        .with_implicit_index_wildcards(true);
1019
1020        let mut event = SimpleEvent::Map(
1021            [(
1022                "a".into(),
1023                SimpleEvent::List(vec![SimpleEvent::Map(
1024                    [("b".into(), SimpleEvent::String("value-a-0-b".to_string()))].into(),
1025                )]),
1026            )]
1027            .into(),
1028        );
1029
1030        let paths = visit_event(&mut event, &ruleset);
1031
1032        assert_eq!(
1033            paths,
1034            Visited {
1035                paths: vec![VisitedPath {
1036                    path: Path::from(vec!["a".into(), 0.into(), "b".into()]),
1037                    content: "value-a-0-b".into(),
1038                    // Rule 0 matches this path even though there is a "0" index between the "a" and "b" field
1039                    rules: vec![VisitedRule {
1040                        rule_index: 0,
1041                        is_excluded: true
1042                    }]
1043                },],
1044            }
1045        );
1046    }
1047
1048    #[test]
1049    fn test_included_scope_both_implicit_and_explicit_index() {
1050        let a_0_c_path = Path::from(vec!["a".into(), 0.into(), "c".into()]);
1051        let ab_path = Path::from(vec!["a".into(), "b".into()]);
1052        let a_1_d_path = Path::from(vec!["a".into(), 1.into(), "d".into()]);
1053
1054        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![a_0_c_path, ab_path, a_1_d_path])])
1055            .with_implicit_index_wildcards(true);
1056
1057        let mut event = SimpleEvent::Map(
1058            [(
1059                "a".into(),
1060                SimpleEvent::List(vec![
1061                    SimpleEvent::Map(
1062                        [
1063                            ("b".into(), SimpleEvent::String("value-a-0-b".to_string())),
1064                            ("c".into(), SimpleEvent::String("value-a-0-c".to_string())),
1065                            ("d".into(), SimpleEvent::String("value-a-0-d".to_string())),
1066                        ]
1067                        .into(),
1068                    ),
1069                    SimpleEvent::Map(
1070                        [
1071                            ("b".into(), SimpleEvent::String("value-a-1-b".to_string())),
1072                            ("c".into(), SimpleEvent::String("value-a-1-c".to_string())),
1073                            ("d".into(), SimpleEvent::String("value-a-1-d".to_string())),
1074                        ]
1075                        .into(),
1076                    ),
1077                ]),
1078            )]
1079            .into(),
1080        );
1081
1082        let paths = visit_event(&mut event, &ruleset);
1083
1084        assert_eq!(
1085            paths,
1086            Visited {
1087                paths: vec![
1088                    VisitedPath {
1089                        path: Path::from(vec!["a".into(), 0.into(), "b".into()]),
1090                        content: "value-a-0-b".into(),
1091                        rules: vec![VisitedRule {
1092                            rule_index: 0,
1093                            is_excluded: false
1094                        }]
1095                    },
1096                    VisitedPath {
1097                        path: Path::from(vec!["a".into(), 0.into(), "c".into()]),
1098                        content: "value-a-0-c".into(),
1099                        rules: vec![VisitedRule {
1100                            rule_index: 0,
1101                            is_excluded: false
1102                        }]
1103                    },
1104                    VisitedPath {
1105                        path: Path::from(vec!["a".into(), 0.into(), "d".into()]),
1106                        content: "value-a-0-d".into(),
1107                        rules: vec![]
1108                    },
1109                    VisitedPath {
1110                        path: Path::from(vec!["a".into(), 1.into(), "b".into()]),
1111                        content: "value-a-1-b".into(),
1112                        rules: vec![VisitedRule {
1113                            rule_index: 0,
1114                            is_excluded: false
1115                        }]
1116                    },
1117                    VisitedPath {
1118                        path: Path::from(vec!["a".into(), 1.into(), "c".into()]),
1119                        content: "value-a-1-c".into(),
1120                        rules: vec![]
1121                    },
1122                    VisitedPath {
1123                        path: Path::from(vec!["a".into(), 1.into(), "d".into()]),
1124                        content: "value-a-1-d".into(),
1125                        rules: vec![VisitedRule {
1126                            rule_index: 0,
1127                            is_excluded: false
1128                        }]
1129                    },
1130                ],
1131            }
1132        );
1133    }
1134
1135    #[test]
1136    fn test_duplicate_rules_are_filtered_out() {
1137        // Internally there may be multiple "active" trees which could end up storing duplicate rules.
1138        // Those duplicates must be filtered out.
1139
1140        // Both of these scopes match the path "a[0].b" (one with an explicit index, one with an implicit wildcard index)
1141        let a_b_path = Path::from(vec!["a".into(), "b".into()]);
1142        let a_0_b_path = Path::from(vec!["a".into(), 0.into(), "b".into()]);
1143
1144        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![a_b_path, a_0_b_path])])
1145            .with_implicit_index_wildcards(true);
1146
1147        let mut event = SimpleEvent::Map(
1148            [(
1149                "a".into(),
1150                SimpleEvent::List(vec![SimpleEvent::Map(
1151                    [("b".into(), SimpleEvent::String("value-a-0-b".to_string()))].into(),
1152                )]),
1153            )]
1154            .into(),
1155        );
1156
1157        let paths = visit_event(&mut event, &ruleset);
1158
1159        assert_eq!(
1160            paths,
1161            Visited {
1162                paths: vec![VisitedPath {
1163                    path: Path::from(vec!["a".into(), 0.into(), "b".into()]),
1164                    content: "value-a-0-b".into(),
1165                    rules: vec![VisitedRule {
1166                        rule_index: 0,
1167                        is_excluded: false
1168                    }]
1169                },],
1170            }
1171        );
1172    }
1173
1174    #[test]
1175    fn test_deeply_nested_implicit_wildcard_index() {
1176        // A wildcard index can skip multiple levels of indexing, not just 1
1177
1178        let a_b_path = Path::from(vec!["a".into(), "b".into()]);
1179        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![a_b_path])])
1180            .with_implicit_index_wildcards(true);
1181
1182        let mut event = SimpleEvent::Map(
1183            [(
1184                "a".into(),
1185                SimpleEvent::List(vec![SimpleEvent::List(vec![SimpleEvent::Map(
1186                    [("b".into(), SimpleEvent::String("value-a-0-0-b".to_string()))].into(),
1187                )])]),
1188            )]
1189            .into(),
1190        );
1191
1192        let paths = visit_event(&mut event, &ruleset);
1193
1194        assert_eq!(
1195            paths,
1196            Visited {
1197                paths: vec![VisitedPath {
1198                    path: Path::from(vec!["a".into(), 0.into(), 0.into(), "b".into()]),
1199                    content: "value-a-0-0-b".into(),
1200                    rules: vec![VisitedRule {
1201                        rule_index: 0,
1202                        is_excluded: false
1203                    }]
1204                },],
1205            }
1206        );
1207    }
1208
1209    #[test]
1210    fn test_implicit_index_wildcard_is_disabled_by_default() {
1211        let ruleset = ScopedRuleSet::new(&[Scope::include(vec![Path::from(vec![
1212            "a".into(),
1213            "b".into(),
1214        ])])]);
1215
1216        let mut event = SimpleEvent::Map(
1217            [(
1218                "a".into(),
1219                SimpleEvent::List(vec![SimpleEvent::Map(
1220                    [("b".into(), SimpleEvent::String("value-a-0-b".to_string()))].into(),
1221                )]),
1222            )]
1223            .into(),
1224        );
1225
1226        let paths = visit_event(&mut event, &ruleset);
1227
1228        assert_eq!(
1229            paths,
1230            Visited {
1231                paths: vec![VisitedPath {
1232                    path: Path::from(vec!["a".into(), 0.into(), "b".into()]),
1233                    content: "value-a-0-b".into(),
1234                    // Rule 0 does NOT match, since the implicit index wildcard is disabled
1235                    rules: vec![]
1236                },],
1237            }
1238        );
1239    }
1240}