evidentsource_core/domain/
selectors.rs

1//! Event selectors for querying events.
2//!
3//! This module provides types for building event queries based on
4//! event attributes like stream, subject, and event type.
5
6use super::error::IdentifierError;
7use super::identifiers::{EventSubject, EventType, StreamName};
8
9/// An event attribute for exact matching.
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum EventAttribute {
12    /// Match events in a specific stream.
13    Stream(StreamName),
14    /// Match events with a specific subject (or no subject if None).
15    Subject(Option<EventSubject>),
16    /// Match events of a specific type.
17    EventType(EventType),
18}
19
20/// An event attribute prefix for starts-with matching.
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum EventAttributePrefix {
23    /// Match streams starting with prefix.
24    Stream(StreamName),
25    /// Match subjects starting with prefix.
26    Subject(EventSubject),
27    /// Match event types starting with prefix.
28    EventType(EventType),
29}
30
31/// A selector for matching events.
32///
33/// Selectors can express equality checks, prefix matching, and logical
34/// combinations (AND/OR) of other selectors.
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub enum EventSelector {
37    /// Match events where an attribute equals a value.
38    Equals(EventAttribute),
39
40    /// Match events where an attribute starts with a prefix.
41    StartsWith(EventAttributePrefix),
42
43    /// Match events matching both selectors.
44    And {
45        left: Box<EventSelector>,
46        right: Box<EventSelector>,
47    },
48
49    /// Match events matching either selector.
50    Or {
51        left: Box<EventSelector>,
52        right: Box<EventSelector>,
53    },
54}
55
56/// Flattened selector node for WIT transfer.
57///
58/// This represents a node in the index-based format required by WIT,
59/// which doesn't support recursive types.
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub enum FlatSelectorNode {
62    /// Match events where an attribute equals a value.
63    Equals(EventAttribute),
64    /// Match events where an attribute starts with a prefix.
65    StartsWith(EventAttributePrefix),
66    /// Match events matching both selectors (indices into the node list).
67    And(u32, u32),
68    /// Match events matching either selector (indices into the node list).
69    Or(u32, u32),
70}
71
72/// Direction for iterating through query results.
73///
74/// # Example
75///
76/// ```
77/// use evidentsource_core::domain::{QueryDirection, QueryOptions};
78///
79/// // Get the 10 most recent events
80/// let opts = QueryOptions::new()
81///     .direction(QueryDirection::Reverse)
82///     .limit(10);
83/// ```
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
85pub enum QueryDirection {
86    /// Iterate from oldest to newest (by revision).
87    #[default]
88    Forward,
89    /// Iterate from newest to oldest (by revision).
90    Reverse,
91}
92
93/// Options for configuring event queries.
94///
95/// Use the builder-style methods to configure direction and limits.
96///
97/// # Example
98///
99/// ```
100/// use evidentsource_core::domain::{QueryDirection, QueryOptions};
101///
102/// // Default options (forward, no limit)
103/// let opts = QueryOptions::default();
104///
105/// // Get first 100 events in reverse order
106/// let opts = QueryOptions::new()
107///     .direction(QueryDirection::Reverse)
108///     .limit(100);
109///
110/// // Shorthand for reverse
111/// let opts = QueryOptions::new().reverse().limit(50);
112/// ```
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
114pub struct QueryOptions {
115    /// Direction for iterating through results.
116    direction: QueryDirection,
117    /// Maximum number of results to return.
118    limit: Option<u64>,
119}
120
121impl QueryOptions {
122    /// Create new query options with default values.
123    pub fn new() -> Self {
124        Self::default()
125    }
126
127    /// Set the query direction.
128    pub fn direction(mut self, direction: QueryDirection) -> Self {
129        self.direction = direction;
130        self
131    }
132
133    /// Set the direction to forward (oldest to newest).
134    pub fn forward(mut self) -> Self {
135        self.direction = QueryDirection::Forward;
136        self
137    }
138
139    /// Set the direction to reverse (newest to oldest).
140    pub fn reverse(mut self) -> Self {
141        self.direction = QueryDirection::Reverse;
142        self
143    }
144
145    /// Set the maximum number of results to return.
146    pub fn limit(mut self, limit: u64) -> Self {
147        self.limit = Some(limit);
148        self
149    }
150
151    /// Get the configured direction.
152    pub fn get_direction(&self) -> QueryDirection {
153        self.direction
154    }
155
156    /// Get the configured limit, if any.
157    pub fn get_limit(&self) -> Option<u64> {
158        self.limit
159    }
160}
161
162impl EventSelector {
163    /// Create a selector matching events in a specific stream.
164    pub fn stream_equals(stream: impl AsRef<str>) -> Result<Self, IdentifierError> {
165        let s = StreamName::new(stream.as_ref())?;
166        Ok(EventSelector::Equals(EventAttribute::Stream(s)))
167    }
168
169    /// Create a selector matching events with a specific subject.
170    pub fn subject_equals(subject: impl AsRef<str>) -> Result<Self, IdentifierError> {
171        let s = EventSubject::new(subject.as_ref())?;
172        Ok(EventSelector::Equals(EventAttribute::Subject(Some(s))))
173    }
174
175    /// Create a selector matching events with no subject.
176    pub fn no_subject() -> Self {
177        EventSelector::Equals(EventAttribute::Subject(None))
178    }
179
180    /// Create a selector matching events of a specific type.
181    pub fn event_type_equals(event_type: impl AsRef<str>) -> Result<Self, IdentifierError> {
182        let et = EventType::new(event_type.as_ref())?;
183        Ok(EventSelector::Equals(EventAttribute::EventType(et)))
184    }
185
186    /// Create a selector matching streams starting with a prefix.
187    pub fn stream_starts_with(prefix: impl AsRef<str>) -> Result<Self, IdentifierError> {
188        let s = StreamName::new(prefix.as_ref())?;
189        Ok(EventSelector::StartsWith(EventAttributePrefix::Stream(s)))
190    }
191
192    /// Create a selector matching subjects starting with a prefix.
193    pub fn subject_starts_with(prefix: impl AsRef<str>) -> Result<Self, IdentifierError> {
194        let s = EventSubject::new(prefix.as_ref())?;
195        Ok(EventSelector::StartsWith(EventAttributePrefix::Subject(s)))
196    }
197
198    /// Create a selector matching event types starting with a prefix.
199    pub fn event_type_starts_with(prefix: impl AsRef<str>) -> Result<Self, IdentifierError> {
200        let et = EventType::new(prefix.as_ref())?;
201        Ok(EventSelector::StartsWith(EventAttributePrefix::EventType(
202            et,
203        )))
204    }
205
206    /// Combine this selector with another using AND logic.
207    pub fn and(self, other: EventSelector) -> Self {
208        EventSelector::And {
209            left: Box::new(self),
210            right: Box::new(other),
211        }
212    }
213
214    /// Combine this selector with another using OR logic.
215    pub fn or(self, other: EventSelector) -> Self {
216        EventSelector::Or {
217            left: Box::new(self),
218            right: Box::new(other),
219        }
220    }
221
222    /// Check if a prospective event matches this selector.
223    ///
224    /// This is used for client-side filtering of speculated events.
225    pub fn matches_prospective(&self, event: &super::ProspectiveEvent) -> bool {
226        match self {
227            EventSelector::Equals(attr) => match attr {
228                EventAttribute::Stream(expected) => event.stream() == expected.as_str(),
229                EventAttribute::Subject(expected) => match (event.subject(), expected) {
230                    (Some(actual), Some(exp)) => actual == exp.as_str(),
231                    (None, None) => true,
232                    _ => false,
233                },
234                EventAttribute::EventType(expected) => event.event_type() == expected.as_str(),
235            },
236            EventSelector::StartsWith(prefix) => match prefix {
237                EventAttributePrefix::Stream(p) => event.stream().starts_with(p.as_str()),
238                EventAttributePrefix::Subject(p) => event
239                    .subject()
240                    .map(|s| s.starts_with(p.as_str()))
241                    .unwrap_or(false),
242                EventAttributePrefix::EventType(p) => event.event_type().starts_with(p.as_str()),
243            },
244            EventSelector::And { left, right } => {
245                left.matches_prospective(event) && right.matches_prospective(event)
246            }
247            EventSelector::Or { left, right } => {
248                left.matches_prospective(event) || right.matches_prospective(event)
249            }
250        }
251    }
252
253    /// Check if a stored event matches this selector.
254    ///
255    /// For stored events, the stream is extracted from the source URI.
256    pub fn matches(&self, event: &super::Event) -> bool {
257        match self {
258            EventSelector::Equals(attr) => match attr {
259                EventAttribute::Stream(expected) => {
260                    // Extract stream from source URI
261                    event
262                        .stream()
263                        .map(|s| s == expected.as_str())
264                        .unwrap_or(false)
265                }
266                EventAttribute::Subject(expected) => match (event.subject(), expected) {
267                    (Some(actual), Some(exp)) => actual == exp.as_str(),
268                    (None, None) => true,
269                    _ => false,
270                },
271                EventAttribute::EventType(expected) => event.event_type() == expected.as_str(),
272            },
273            EventSelector::StartsWith(prefix) => match prefix {
274                EventAttributePrefix::Stream(p) => event
275                    .stream()
276                    .map(|s| s.starts_with(p.as_str()))
277                    .unwrap_or(false),
278                EventAttributePrefix::Subject(p) => event
279                    .subject()
280                    .map(|s| s.starts_with(p.as_str()))
281                    .unwrap_or(false),
282                EventAttributePrefix::EventType(p) => event.event_type().starts_with(p.as_str()),
283            },
284            EventSelector::And { left, right } => left.matches(event) && right.matches(event),
285            EventSelector::Or { left, right } => left.matches(event) || right.matches(event),
286        }
287    }
288
289    /// Flatten this selector tree into an index-based representation.
290    ///
291    /// Returns the list of nodes with the root at the last index.
292    /// This format is required for WIT transfer since WIT doesn't support
293    /// recursive types.
294    pub fn flatten(&self) -> Vec<FlatSelectorNode> {
295        let mut nodes = Vec::new();
296        self.flatten_into(&mut nodes);
297        nodes
298    }
299
300    fn flatten_into(&self, nodes: &mut Vec<FlatSelectorNode>) -> u32 {
301        match self {
302            EventSelector::Equals(attr) => {
303                let idx = nodes.len() as u32;
304                nodes.push(FlatSelectorNode::Equals(attr.clone()));
305                idx
306            }
307            EventSelector::StartsWith(prefix) => {
308                let idx = nodes.len() as u32;
309                nodes.push(FlatSelectorNode::StartsWith(prefix.clone()));
310                idx
311            }
312            EventSelector::And { left, right } => {
313                let left_idx = left.flatten_into(nodes);
314                let right_idx = right.flatten_into(nodes);
315                let idx = nodes.len() as u32;
316                nodes.push(FlatSelectorNode::And(left_idx, right_idx));
317                idx
318            }
319            EventSelector::Or { left, right } => {
320                let left_idx = left.flatten_into(nodes);
321                let right_idx = right.flatten_into(nodes);
322                let idx = nodes.len() as u32;
323                nodes.push(FlatSelectorNode::Or(left_idx, right_idx));
324                idx
325            }
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333
334    #[test]
335    fn test_simple_selector() {
336        let selector = EventSelector::stream_equals("my-stream").unwrap();
337
338        assert!(matches!(
339            selector,
340            EventSelector::Equals(EventAttribute::Stream(s)) if s.as_str() == "my-stream"
341        ));
342    }
343
344    #[test]
345    fn test_and_selector() {
346        let selector = EventSelector::stream_equals("my-stream")
347            .unwrap()
348            .and(EventSelector::event_type_equals("my.event").unwrap());
349
350        assert!(matches!(selector, EventSelector::And { .. }));
351    }
352
353    #[test]
354    fn test_or_selector() {
355        let selector = EventSelector::event_type_equals("type1")
356            .unwrap()
357            .or(EventSelector::event_type_equals("type2").unwrap());
358
359        assert!(matches!(selector, EventSelector::Or { .. }));
360    }
361
362    #[test]
363    fn test_no_subject() {
364        let selector = EventSelector::no_subject();
365        assert!(matches!(
366            selector,
367            EventSelector::Equals(EventAttribute::Subject(None))
368        ));
369    }
370
371    #[test]
372    fn test_flatten_simple() {
373        let selector = EventSelector::event_type_equals("my.event").unwrap();
374        let nodes = selector.flatten();
375
376        assert_eq!(nodes.len(), 1);
377        assert!(matches!(
378            nodes[0],
379            FlatSelectorNode::Equals(EventAttribute::EventType(_))
380        ));
381    }
382
383    #[test]
384    fn test_flatten_and() {
385        let selector = EventSelector::subject_equals("sub-123")
386            .unwrap()
387            .and(EventSelector::event_type_equals("my.event").unwrap());
388
389        let nodes = selector.flatten();
390
391        assert_eq!(nodes.len(), 3);
392        // First two nodes are the leaf nodes
393        assert!(matches!(
394            nodes[0],
395            FlatSelectorNode::Equals(EventAttribute::Subject(_))
396        ));
397        assert!(matches!(
398            nodes[1],
399            FlatSelectorNode::Equals(EventAttribute::EventType(_))
400        ));
401        // Last node is the And combinator referencing indices 0 and 1
402        assert!(matches!(nodes[2], FlatSelectorNode::And(0, 1)));
403    }
404
405    #[test]
406    fn test_flatten_complex() {
407        // (A AND B) OR C
408        let selector = EventSelector::subject_equals("sub")
409            .unwrap()
410            .and(EventSelector::event_type_equals("type1").unwrap())
411            .or(EventSelector::event_type_equals("type2").unwrap());
412
413        let nodes = selector.flatten();
414
415        assert_eq!(nodes.len(), 5);
416        // Nodes: subject, type1, And(0,1), type2, Or(2,3)
417        assert!(matches!(nodes[4], FlatSelectorNode::Or(2, 3)));
418    }
419
420    #[test]
421    fn test_query_direction_default() {
422        let dir = QueryDirection::default();
423        assert_eq!(dir, QueryDirection::Forward);
424    }
425
426    #[test]
427    fn test_query_options_default() {
428        let opts = QueryOptions::default();
429        assert_eq!(opts.get_direction(), QueryDirection::Forward);
430        assert_eq!(opts.get_limit(), None);
431    }
432
433    #[test]
434    fn test_query_options_builder() {
435        let opts = QueryOptions::new()
436            .direction(QueryDirection::Reverse)
437            .limit(100);
438
439        assert_eq!(opts.get_direction(), QueryDirection::Reverse);
440        assert_eq!(opts.get_limit(), Some(100));
441    }
442
443    #[test]
444    fn test_query_options_convenience_methods() {
445        let opts = QueryOptions::new().reverse().limit(50);
446        assert_eq!(opts.get_direction(), QueryDirection::Reverse);
447        assert_eq!(opts.get_limit(), Some(50));
448
449        let opts = QueryOptions::new().forward();
450        assert_eq!(opts.get_direction(), QueryDirection::Forward);
451    }
452}