use crate::model::{
ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
};
use thiserror::Error;
#[derive(Debug)]
pub struct SnapshotInventoryDeltaView<'a> {
capture: &'a IcManagementRequestRecord,
baseline: &'a IcSnapshotReply<'a>,
observed: &'a IcSnapshotReply<'a>,
candidates: Vec<&'a IcSnapshotInfo>,
}
impl<'a> SnapshotInventoryDeltaView<'a> {
#[must_use]
pub const fn capture(&self) -> &'a IcManagementRequestRecord {
self.capture
}
#[must_use]
pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
self.baseline
}
#[must_use]
pub const fn observed(&self) -> &'a IcSnapshotReply<'a> {
self.observed
}
#[must_use]
pub fn candidates(&self) -> &[&'a IcSnapshotInfo] {
&self.candidates
}
}
pub fn compare<'a>(
capture: &'a IcManagementRequestRecord,
baseline: &'a IcSnapshotReply<'a>,
observed: &'a IcSnapshotReply<'a>,
) -> Result<SnapshotInventoryDeltaView<'a>, SnapshotInventoryDeltaError> {
if capture.method() != IcManagementMethodRecord::TakeCanisterSnapshot {
return Err(SnapshotInventoryDeltaError::WrongCaptureMethod);
}
let candidates = compare_inventories(capture.target(), baseline, observed)?;
Ok(SnapshotInventoryDeltaView {
capture,
baseline,
observed,
candidates,
})
}
pub(crate) fn compare_inventories<'a>(
target: &str,
baseline: &IcSnapshotReply<'_>,
observed: &'a IcSnapshotReply<'_>,
) -> Result<Vec<&'a IcSnapshotInfo>, SnapshotInventoryDeltaError> {
for reply in [baseline, observed] {
if reply.request().method() != IcManagementMethodRecord::ListCanisterSnapshots {
return Err(SnapshotInventoryDeltaError::WrongInventoryMethod);
}
if reply.request().target() != target {
return Err(SnapshotInventoryDeltaError::TargetMismatch);
}
}
let mut candidates = Vec::new();
let mut previous = baseline.snapshots().iter().peekable();
for snapshot in observed.snapshots() {
match previous.peek() {
Some(old) if old.id() < snapshot.id() => {
return Err(SnapshotInventoryDeltaError::LostBaseline);
}
Some(old) if old.id() == snapshot.id() => {
if *old != snapshot {
return Err(SnapshotInventoryDeltaError::ChangedBaselineMetadata);
}
previous.next();
}
_ => candidates.push(snapshot),
}
}
if previous.next().is_some() {
return Err(SnapshotInventoryDeltaError::LostBaseline);
}
Ok(candidates)
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum SnapshotInventoryDeltaError {
#[error("inventory comparison requires a new-snapshot capture request")]
WrongCaptureMethod,
#[error("inventory comparison requires two snapshot-list replies")]
WrongInventoryMethod,
#[error("snapshot inventory target differs from capture target")]
TargetMismatch,
#[error("snapshot inventory lost a baseline identifier")]
LostBaseline,
#[error("snapshot inventory changed baseline metadata")]
ChangedBaselineMetadata,
}
#[cfg(test)]
mod tests;