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