Skip to main content

mnesis_store/
step.rs

1//! The catch-up→live phase marker for typed subscription consumption (#250).
2
3/// One item of a phase-aware subscription cursor: a decoded event, or the
4/// **caught-up** boundary marker emitted once when replay finishes and the
5/// cursor switches to live tailing.
6///
7/// Everything yielded before [`CaughtUp`](Step::CaughtUp) is replay (catch-up
8/// over the backlog); everything after is live. `T` is the item payload —
9/// `Decoded<E>` per-stream, `(AllPosition, StreamKey, Decoded<E>)` for `$all`.
10///
11/// Exhaustive (no `#[non_exhaustive]`, per project rule): the two variants are
12/// frozen at 1.0. A lag signal (`FellBehind`) is intentionally omitted — the
13/// live loop does not distinguish a lagging live consumer from a caught-up one,
14/// and a consumer can observe lag from positions.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Step<T> {
17    /// A delivered event (replay or live — the phase is told by the preceding
18    /// [`CaughtUp`](Step::CaughtUp)).
19    Event(T),
20    /// The backlog is drained; subsequent items are live. Emitted exactly once.
21    CaughtUp,
22}
23
24impl<T> Step<T> {
25    /// The event payload, or `None` for [`CaughtUp`](Step::CaughtUp).
26    #[must_use]
27    pub fn event(self) -> Option<T> {
28        match self {
29            Self::Event(t) => Some(t),
30            Self::CaughtUp => None,
31        }
32    }
33
34    /// `true` iff this is the [`CaughtUp`](Step::CaughtUp) marker.
35    #[must_use]
36    pub const fn is_caught_up(&self) -> bool {
37        matches!(self, Self::CaughtUp)
38    }
39
40    /// Map the carried payload, leaving [`CaughtUp`](Step::CaughtUp) untouched.
41    /// The phase marker flows through every transform (drop-the-tag in
42    /// `subscribe`, decode in `.decoded()`) so a boundary is never lost.
43    #[must_use]
44    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Step<U> {
45        match self {
46            Self::Event(t) => Step::Event(f(t)),
47            Self::CaughtUp => Step::CaughtUp,
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn event_and_caught_up_accessors() {
58        assert_eq!(Step::Event(7u8).event(), Some(7));
59        assert_eq!(Step::<u8>::CaughtUp.event(), None);
60        assert!(Step::<u8>::CaughtUp.is_caught_up());
61        assert!(!Step::Event(7u8).is_caught_up());
62    }
63
64    #[test]
65    fn map_transforms_event_and_passes_caught_up_through() {
66        assert_eq!(
67            Step::Event(3u8).map(|n| u32::from(n) * 2),
68            Step::Event(6u32)
69        );
70        assert_eq!(Step::<u8>::CaughtUp.map(u32::from), Step::<u32>::CaughtUp);
71    }
72}