use crate::{
error::{MemoryError, Result},
event::StoredEvent,
id::{EventId, StreamId},
};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet};
pub trait Projection<E>: Default {
fn prepare_replay(&mut self, _events: &[StoredEvent<E>]) {}
fn apply(&mut self, event: &StoredEvent<E>) -> Result<()>;
fn apply_owned(&mut self, event: StoredEvent<E>) -> Result<()> {
self.apply(&event)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplayCursor {
pub global_position: Option<u64>,
pub stream_versions: BTreeMap<StreamId, u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProjectionSnapshot<P> {
pub cursor: ReplayCursor,
pub projection: P,
}
pub fn replay<E, P>(events: &[StoredEvent<E>]) -> Result<P>
where
P: Projection<E>,
{
replay_tracked(events).map(|(projection, _)| projection)
}
pub fn replay_owned<E, P>(events: Vec<StoredEvent<E>>) -> Result<P>
where
P: Projection<E>,
{
apply_owned_sequence(P::default(), ReplayCursor::default(), events, true)
.map(|(projection, _)| projection)
}
pub fn replay_tracked<E, P>(events: &[StoredEvent<E>]) -> Result<(P, ReplayCursor)>
where
P: Projection<E>,
{
apply_sequence(P::default(), ReplayCursor::default(), events, true)
}
pub fn resume<E, P>(
snapshot: ProjectionSnapshot<P>,
tail: &[StoredEvent<E>],
) -> Result<(P, ReplayCursor)>
where
P: Projection<E>,
{
apply_sequence(snapshot.projection, snapshot.cursor, tail, false)
}
fn apply_sequence<E, P>(
mut projection: P,
mut cursor: ReplayCursor,
events: &[StoredEvent<E>],
require_zero_start: bool,
) -> Result<(P, ReplayCursor)>
where
P: Projection<E>,
{
projection.prepare_replay(events);
let mut event_ids = HashSet::<EventId>::with_capacity(events.len());
for event in events {
let (expected_position, expected_version) =
validate_next(&cursor, &mut event_ids, event, require_zero_start)?;
projection.apply(event)?;
cursor
.stream_versions
.insert(event.metadata.stream_id.clone(), expected_version);
cursor.global_position = Some(expected_position);
}
Ok((projection, cursor))
}
fn apply_owned_sequence<E, P>(
mut projection: P,
mut cursor: ReplayCursor,
events: Vec<StoredEvent<E>>,
require_zero_start: bool,
) -> Result<(P, ReplayCursor)>
where
P: Projection<E>,
{
projection.prepare_replay(&events);
let mut event_ids = HashSet::<EventId>::with_capacity(events.len());
for event in events {
let (expected_position, expected_version) =
validate_next(&cursor, &mut event_ids, &event, require_zero_start)?;
let stream = event.metadata.stream_id.clone();
projection.apply_owned(event)?;
cursor.stream_versions.insert(stream, expected_version);
cursor.global_position = Some(expected_position);
}
Ok((projection, cursor))
}
fn validate_next<E>(
cursor: &ReplayCursor,
event_ids: &mut HashSet<EventId>,
event: &StoredEvent<E>,
require_zero_start: bool,
) -> Result<(u64, u64)> {
if !event_ids.insert(event.metadata.id.clone()) {
return Err(MemoryError::DuplicateEvent {
id: event.metadata.id.to_string(),
});
}
let expected_position = cursor.global_position.map_or(Ok(0), |position| {
position.checked_add(1).ok_or(MemoryError::CapacityOverflow)
})?;
if require_zero_start && cursor.global_position.is_none() && expected_position != 0 {
return Err(MemoryError::InvalidReplay {
reason: "complete replay must start at zero".to_owned(),
});
}
if event.metadata.global_position != expected_position {
return Err(MemoryError::InvalidReplay {
reason: format!(
"global position {}, expected {expected_position}",
event.metadata.global_position
),
});
}
let expected_version = cursor
.stream_versions
.get(&event.metadata.stream_id)
.map_or(Ok(0), |version| {
version.checked_add(1).ok_or(MemoryError::CapacityOverflow)
})?;
if event.metadata.stream_version != expected_version {
return Err(MemoryError::InvalidReplay {
reason: format!(
"stream {} version {}, expected {expected_version}",
event.metadata.stream_id, event.metadata.stream_version
),
});
}
Ok((expected_position, expected_version))
}