use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::effect::{EffectContext, EffectFailure};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Verification<T> {
Confirmed(T),
NotApplied,
Inconclusive,
Conflict {
details: String,
},
}
pub type VerificationFuture<T> =
Pin<Box<dyn Future<Output = Result<Verification<T>, EffectFailure>> + Send>>;
pub trait Verifier<T>: Send + Sync + 'static {
fn check(&self, ctx: EffectContext) -> Option<VerificationFuture<T>>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoVerification;
impl<T> Verifier<T> for NoVerification {
fn check(&self, _ctx: EffectContext) -> Option<VerificationFuture<T>> {
None
}
}
#[derive(Clone, Copy, Debug)]
pub struct VerifyWith<F>(pub(crate) F);
impl<T, F, Fut> Verifier<T> for VerifyWith<F>
where
F: Fn(EffectContext) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Verification<T>, EffectFailure>> + Send + 'static,
{
fn check(&self, ctx: EffectContext) -> Option<VerificationFuture<T>> {
Some(Box::pin((self.0)(ctx)))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "mode")]
pub enum VerificationMode {
#[default]
None,
Authoritative,
EventuallyConsistent {
settle: Duration,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NotFoundReading {
NotApplied,
TooEarly {
wait: Duration,
},
}
impl VerificationMode {
pub fn read_not_found(self, elapsed: Duration) -> NotFoundReading {
match self {
Self::Authoritative => NotFoundReading::NotApplied,
Self::EventuallyConsistent { settle } if elapsed >= settle => {
NotFoundReading::NotApplied
}
Self::EventuallyConsistent { settle } => NotFoundReading::TooEarly {
wait: settle.saturating_sub(elapsed),
},
Self::None => NotFoundReading::TooEarly {
wait: Duration::ZERO,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eventually_consistent_lookups_wait_out_the_settle_delay() {
let mode = VerificationMode::EventuallyConsistent {
settle: Duration::from_secs(5),
};
assert_eq!(
mode.read_not_found(Duration::from_secs(2)),
NotFoundReading::TooEarly {
wait: Duration::from_secs(3)
}
);
assert_eq!(
mode.read_not_found(Duration::from_secs(5)),
NotFoundReading::NotApplied
);
assert_eq!(
VerificationMode::None.read_not_found(Duration::MAX),
NotFoundReading::TooEarly {
wait: Duration::ZERO
}
);
}
}