use crate::error::RillError;
pub const SNAPSHOT_FORMAT_VERSION: u32 = 1;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Snapshot<T> {
pub format_version: u32,
pub model: T,
}
impl<T> Snapshot<T> {
pub fn new(model: T) -> Self {
Self {
format_version: SNAPSHOT_FORMAT_VERSION,
model,
}
}
pub fn into_model(self) -> Result<T, RillError> {
if self.format_version != SNAPSHOT_FORMAT_VERSION {
return Err(RillError::IncompatibleStateVersion {
expected: SNAPSHOT_FORMAT_VERSION,
actual: self.format_version,
});
}
Ok(self.model)
}
pub fn into_model_with_validation<F>(self, validate: F) -> Result<T, RillError>
where
F: FnOnce(&T) -> Result<(), RillError>,
{
let model = self.into_model()?;
validate(&model)?;
Ok(model)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stats::Mean;
#[cfg(feature = "serde")]
use crate::traits::OnlineStatistic;
#[cfg(feature = "serde")]
#[test]
fn snapshot_roundtrip() {
let mut mean = Mean::new();
mean.update(1.0).unwrap();
mean.update(2.0).unwrap();
let snap = Snapshot::new(mean);
let json = serde_json::to_string(&snap).unwrap();
let restored: Snapshot<Mean> = serde_json::from_str(&json).unwrap();
let m = restored.into_model().unwrap();
assert!((m.value() - 1.5).abs() < 1e-12);
assert_eq!(m.count(), 2);
}
#[test]
fn incompatible_version_rejected() {
let snap = Snapshot {
format_version: 999,
model: Mean::new(),
};
assert!(snap.into_model().is_err());
}
#[test]
fn application_validation_runs_before_activation() {
let snap = Snapshot::new(Mean::new());
let result = snap.into_model_with_validation(|_| {
Err(RillError::InvalidState(
"application check failed".to_owned(),
))
});
assert!(matches!(result, Err(RillError::InvalidState(_))));
}
}