1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Event trace view for querying the sequence of events in a chain.
use std::collections::HashSet;
use crate::{Event, Label, Topic};
use super::{EventChain, EventEntry, EventMatcher};
/// Event trace view for querying the sequence of events in the chain.
#[derive(Debug)]
pub struct EventTrace<'a, E: Event, T: Topic<E>> {
pub(super) chain: &'a EventChain<E, T>,
}
impl<E: Event + Label, T: Topic<E>> EventTrace<'_, E, T> {
/// Returns all unique events in this chain (unordered).
pub fn all(&self) -> Vec<&EventEntry<E, T>> {
let mut seen_ids = HashSet::new();
self.chain
.chain_entries()
.filter(|e| seen_ids.insert(e.id()))
.collect()
}
/// Returns events in order of occurrence (BFS from root).
///
/// Each unique event appears once, in the order it was reached
/// during chain traversal.
pub fn ordered(&self) -> Vec<&EventEntry<E, T>> {
let mut seen_ids = HashSet::new();
self.chain
.ordered_entries()
.into_iter()
.filter(|e| seen_ids.insert(e.id()))
.collect()
}
/// Returns true if the chain contains an event matching the given matcher.
pub fn contains(&self, matcher: impl Into<EventMatcher<E, T>>) -> bool {
let matcher = matcher.into();
self.chain.chain_entries().any(|e| matcher.matches(e))
}
/// Returns true if any event path exactly matches all matchers (same length and order).
///
/// Each path follows the parent-child tree from root to leaf. For branching
/// chains, returns true if any single branch matches.
pub fn exact<M>(&self, matchers: &[M]) -> bool
where
M: Into<EventMatcher<E, T>> + Clone,
{
if matchers.is_empty() {
return true;
}
let matchers: Vec<_> = matchers.iter().cloned().map(|m| m.into()).collect();
let paths = self.chain.event_paths();
paths.iter().any(|path| {
path.len() == matchers.len()
&& path
.iter()
.zip(matchers.iter())
.all(|(entry, matcher)| matcher.matches(entry))
})
}
/// Returns true if events matching the matchers appear consecutively in any event path.
///
/// Each path follows the parent-child tree from root to leaf. For branching
/// chains, returns true if any single branch contains the contiguous segment.
pub fn segment<M>(&self, matchers: &[M]) -> bool
where
M: Into<EventMatcher<E, T>> + Clone,
{
if matchers.is_empty() {
return true;
}
let matchers: Vec<_> = matchers.iter().cloned().map(|m| m.into()).collect();
let paths = self.chain.event_paths();
paths
.iter()
.any(|path| Self::contains_contiguous(path, &matchers))
}
/// Returns true if events matching the matchers appear in order in any event path (gaps allowed).
///
/// Each path follows the parent-child tree from root to leaf. For branching
/// chains, returns true if any single branch passes through the matchers.
pub fn passes_through<M>(&self, matchers: &[M]) -> bool
where
M: Into<EventMatcher<E, T>> + Clone,
{
if matchers.is_empty() {
return true;
}
let matchers: Vec<_> = matchers.iter().cloned().map(|m| m.into()).collect();
let paths = self.chain.event_paths();
paths
.iter()
.any(|path| Self::contains_subsequence(path, &matchers))
}
/// Returns the number of distinct event paths in the chain.
pub fn path_count(&self) -> usize {
self.chain.event_paths().len()
}
fn contains_contiguous(path: &[&EventEntry<E, T>], matchers: &[EventMatcher<E, T>]) -> bool {
if matchers.len() > path.len() {
return false;
}
path.windows(matchers.len()).any(|window| {
window
.iter()
.zip(matchers.iter())
.all(|(entry, matcher)| matcher.matches(entry))
})
}
fn contains_subsequence(path: &[&EventEntry<E, T>], matchers: &[EventMatcher<E, T>]) -> bool {
let mut matcher_idx = 0;
for entry in path {
if matcher_idx >= matchers.len() {
break;
}
if matchers[matcher_idx].matches(entry) {
matcher_idx += 1;
}
}
matcher_idx == matchers.len()
}
}