intel/src/stale.rs
//! How old an advisory database may get before a scan against it says so, and
//! before it stops being an answer at all.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Warn after `warn_after_days`, block after `block_after_days`, both counted
/// from the snapshot date the bundle carries (its `created_at`), not from the
/// day it was imported: a month-old bundle carried in today is a month old.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct StalenessRule {
pub warn_after_days: u32,
pub block_after_days: u32,
}
impl Default for StalenessRule {
fn default() -> Self {
Self {
warn_after_days: 7,
block_after_days: 30,
}
}
}
/// The verdict on one database's age.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Staleness {
Fresh,
Warn,
Block,
}
impl Staleness {
pub fn as_str(self) -> &'static str {
match self {
Staleness::Fresh => "fresh",
Staleness::Warn => "warn",
Staleness::Block => "block",
}
}
}
impl StalenessRule {
/// Refuse a rule whose block comes before its warning.
pub fn validate(&self) -> anyhow::Result<()> {
anyhow::ensure!(
self.block_after_days >= self.warn_after_days,
"intel staleness: block_after_days ({}) is before warn_after_days ({})",
self.block_after_days,
self.warn_after_days
);
Ok(())
}
/// Whole days from `snapshot` to `now`. A snapshot dated in the future (a
/// skewed builder clock) counts as age 0, not as negative.
pub fn age_days(snapshot: DateTime<Utc>, now: DateTime<Utc>) -> i64 {
(now - snapshot).num_days().max(0)
}
pub fn judge(&self, snapshot: DateTime<Utc>, now: DateTime<Utc>) -> Staleness {
let age = Self::age_days(snapshot, now);
if age >= i64::from(self.block_after_days) {
Staleness::Block
} else if age >= i64::from(self.warn_after_days) {
Staleness::Warn
} else {
Staleness::Fresh
}
}
}