use alloc::vec::Vec;
use core::future::Future;
use core::num::{NonZeroU32, NonZeroU64};
use mnesis::{Id, Version};
use crate::codec::{Decode, Encode, OwningCodec};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Hydrated<S, P> {
Absent,
Stale {
stored_schema: NonZeroU32,
},
Found {
position: P,
state: S,
},
}
impl<S, P> Hydrated<S, P> {
#[must_use]
pub fn into_found(self) -> Option<(P, S)> {
match self {
Self::Found { position, state } => Some((position, state)),
Self::Absent | Self::Stale { .. } => None,
}
}
}
pub trait SnapshotStore<S, P>: Send + Sync {
type Error: core::error::Error + Send + Sync + 'static;
fn hydrate(
&self,
id: &impl Id,
schema_version: NonZeroU32,
) -> impl Future<Output = Result<Hydrated<S, P>, Self::Error>> + Send;
fn commit(
&self,
id: &impl Id,
schema_version: NonZeroU32,
position: P,
state: &S,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
}
impl<S, P, T> SnapshotStore<S, P> for &T
where
S: Send + Sync,
P: Send,
T: SnapshotStore<S, P>,
{
type Error = T::Error;
fn hydrate(
&self,
id: &impl Id,
schema_version: NonZeroU32,
) -> impl Future<Output = Result<Hydrated<S, P>, Self::Error>> + Send {
(**self).hydrate(id, schema_version)
}
fn commit(
&self,
id: &impl Id,
schema_version: NonZeroU32,
position: P,
state: &S,
) -> impl Future<Output = Result<(), Self::Error>> + Send {
(**self).commit(id, schema_version, position, state)
}
}
pub trait PersistTrigger: Send + Sync {
fn should_persist(
&self,
old_version: Option<Version>,
new_version: Version,
event_names: impl Iterator<Item: AsRef<str>>,
) -> bool;
}
#[derive(Debug, Clone, Copy)]
pub struct EveryNEvents(pub NonZeroU64);
impl PersistTrigger for EveryNEvents {
fn should_persist(
&self,
old_version: Option<Version>,
new_version: Version,
_event_names: impl Iterator<Item: AsRef<str>>,
) -> bool {
let n = self.0.get();
let old_bucket = old_version.map_or(0, |v| v.as_u64() / n);
let new_bucket = new_version.as_u64() / n;
new_bucket > old_bucket
}
}
#[derive(Debug, Clone)]
pub struct AfterEventTypes {
types: Vec<&'static str>,
}
impl AfterEventTypes {
#[must_use]
pub fn new(types: &[&'static str]) -> Self {
Self {
types: types.to_vec(),
}
}
}
impl PersistTrigger for AfterEventTypes {
fn should_persist(
&self,
_old_version: Option<Version>,
_new_version: Version,
mut event_names: impl Iterator<Item: AsRef<str>>,
) -> bool {
event_names.any(|name| self.types.iter().any(|t| *t == name.as_ref()))
}
}
pub struct CodecSnapshotStore<SS, C> {
store: SS,
codec: C,
}
impl<SS, C> CodecSnapshotStore<SS, C> {
#[must_use]
pub const fn new(store: SS, codec: C) -> Self {
Self { store, codec }
}
}
impl<S, P, SS, C> SnapshotStore<S, P> for CodecSnapshotStore<SS, C>
where
S: Send + Sync + 'static,
P: Send,
SS: SnapshotStore<Vec<u8>, P>,
C: Encode<S> + OwningCodec<S>,
{
type Error =
CodecSnapshotStoreError<SS::Error, <C as Encode<S>>::Error, <C as Decode<S>>::Error>;
async fn hydrate(
&self,
id: &impl Id,
schema_version: NonZeroU32,
) -> Result<Hydrated<S, P>, Self::Error> {
let (position, bytes) = match self
.store
.hydrate(id, schema_version)
.await
.map_err(CodecSnapshotStoreError::Store)?
{
Hydrated::Absent => return Ok(Hydrated::Absent),
Hydrated::Stale { stored_schema } => return Ok(Hydrated::Stale { stored_schema }),
Hydrated::Found { position, state } => (position, state),
};
let label = id.to_label();
let env = crate::envelope::PersistedEnvelope::for_decode(label.as_str(), &bytes)
.map_err(CodecSnapshotStoreError::EnvelopeSynthesis)?;
let state =
<C as Decode<S>>::decode(&self.codec, &env).map_err(CodecSnapshotStoreError::Decode)?;
Ok(Hydrated::Found { position, state })
}
async fn commit(
&self,
id: &impl Id,
schema_version: NonZeroU32,
position: P,
state: &S,
) -> Result<(), Self::Error> {
let bytes = <C as Encode<S>>::encode(&self.codec, state)
.map_err(CodecSnapshotStoreError::Encode)?;
let bytes_vec = bytes.to_vec();
self.store
.commit(id, schema_version, position, &bytes_vec)
.await
.map_err(CodecSnapshotStoreError::Store)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CodecSnapshotStoreError<S, EncErr, DecErr> {
#[error(transparent)]
Store(S),
#[error(transparent)]
Encode(EncErr),
#[error(transparent)]
Decode(DecErr),
#[error("envelope synthesis error: {0}")]
EnvelopeSynthesis(#[source] crate::envelope::ForDecodeError),
}