weavatrix_memory/projection/
replay.rs1use crate::{
2 error::{MemoryError, Result},
3 event::StoredEvent,
4 id::{EventId, StreamId},
5};
6use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, HashSet};
8
9pub trait Projection<E>: Default {
10 fn prepare_replay(&mut self, _events: &[StoredEvent<E>]) {}
15
16 fn apply(&mut self, event: &StoredEvent<E>) -> Result<()>;
22
23 fn apply_owned(&mut self, event: StoredEvent<E>) -> Result<()> {
30 self.apply(&event)
31 }
32}
33
34#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
35pub struct ReplayCursor {
36 pub global_position: Option<u64>,
37 pub stream_versions: BTreeMap<StreamId, u64>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct ProjectionSnapshot<P> {
42 pub cursor: ReplayCursor,
43 pub projection: P,
44}
45
46pub fn replay<E, P>(events: &[StoredEvent<E>]) -> Result<P>
52where
53 P: Projection<E>,
54{
55 replay_tracked(events).map(|(projection, _)| projection)
56}
57
58pub fn replay_owned<E, P>(events: Vec<StoredEvent<E>>) -> Result<P>
66where
67 P: Projection<E>,
68{
69 apply_owned_sequence(P::default(), ReplayCursor::default(), events, true)
70 .map(|(projection, _)| projection)
71}
72
73pub fn replay_tracked<E, P>(events: &[StoredEvent<E>]) -> Result<(P, ReplayCursor)>
79where
80 P: Projection<E>,
81{
82 apply_sequence(P::default(), ReplayCursor::default(), events, true)
83}
84
85pub fn resume<E, P>(
92 snapshot: ProjectionSnapshot<P>,
93 tail: &[StoredEvent<E>],
94) -> Result<(P, ReplayCursor)>
95where
96 P: Projection<E>,
97{
98 apply_sequence(snapshot.projection, snapshot.cursor, tail, false)
99}
100
101fn apply_sequence<E, P>(
102 mut projection: P,
103 mut cursor: ReplayCursor,
104 events: &[StoredEvent<E>],
105 require_zero_start: bool,
106) -> Result<(P, ReplayCursor)>
107where
108 P: Projection<E>,
109{
110 projection.prepare_replay(events);
111 let mut event_ids = HashSet::<EventId>::with_capacity(events.len());
112 for event in events {
113 let (expected_position, expected_version) =
114 validate_next(&cursor, &mut event_ids, event, require_zero_start)?;
115 projection.apply(event)?;
116 cursor
117 .stream_versions
118 .insert(event.metadata.stream_id.clone(), expected_version);
119 cursor.global_position = Some(expected_position);
120 }
121 Ok((projection, cursor))
122}
123
124fn apply_owned_sequence<E, P>(
125 mut projection: P,
126 mut cursor: ReplayCursor,
127 events: Vec<StoredEvent<E>>,
128 require_zero_start: bool,
129) -> Result<(P, ReplayCursor)>
130where
131 P: Projection<E>,
132{
133 projection.prepare_replay(&events);
134 let mut event_ids = HashSet::<EventId>::with_capacity(events.len());
135 for event in events {
136 let (expected_position, expected_version) =
137 validate_next(&cursor, &mut event_ids, &event, require_zero_start)?;
138 let stream = event.metadata.stream_id.clone();
139 projection.apply_owned(event)?;
140 cursor.stream_versions.insert(stream, expected_version);
141 cursor.global_position = Some(expected_position);
142 }
143 Ok((projection, cursor))
144}
145
146fn validate_next<E>(
147 cursor: &ReplayCursor,
148 event_ids: &mut HashSet<EventId>,
149 event: &StoredEvent<E>,
150 require_zero_start: bool,
151) -> Result<(u64, u64)> {
152 if !event_ids.insert(event.metadata.id.clone()) {
153 return Err(MemoryError::DuplicateEvent {
154 id: event.metadata.id.to_string(),
155 });
156 }
157 let expected_position = cursor.global_position.map_or(Ok(0), |position| {
158 position.checked_add(1).ok_or(MemoryError::CapacityOverflow)
159 })?;
160 if require_zero_start && cursor.global_position.is_none() && expected_position != 0 {
161 return Err(MemoryError::InvalidReplay {
162 reason: "complete replay must start at zero".to_owned(),
163 });
164 }
165 if event.metadata.global_position != expected_position {
166 return Err(MemoryError::InvalidReplay {
167 reason: format!(
168 "global position {}, expected {expected_position}",
169 event.metadata.global_position
170 ),
171 });
172 }
173 let expected_version = cursor
174 .stream_versions
175 .get(&event.metadata.stream_id)
176 .map_or(Ok(0), |version| {
177 version.checked_add(1).ok_or(MemoryError::CapacityOverflow)
178 })?;
179 if event.metadata.stream_version != expected_version {
180 return Err(MemoryError::InvalidReplay {
181 reason: format!(
182 "stream {} version {}, expected {expected_version}",
183 event.metadata.stream_id, event.metadata.stream_version
184 ),
185 });
186 }
187 Ok((expected_position, expected_version))
188}