age.rs
raw
use std::fmt;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use serde::{Deserialize, Deserializer};
/// How long data must have gone unused before `clean` will delete it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MinAge(Duration);
impl MinAge {
pub const fn new(threshold: Duration) -> Self {
Self(threshold)
}
pub const fn as_duration(self) -> Duration {
self.0
}
/// Whether data last used at `last_used` has gone untouched long enough to delete.
///
/// Data with a `last_used` in the future is never eligible: that means the
/// clock moved backwards, and guessing at a real age from a broken reading
/// is how you delete something that is actually in use.
pub fn is_eligible(self, last_used: SystemTime, now: SystemTime) -> bool {
match now.duration_since(last_used) {
Ok(age) => age >= self.0,
Err(_) => false,
}
}
}
impl FromStr for MinAge {
type Err = humantime::DurationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
humantime::parse_duration(s).map(MinAge)
}
}
impl fmt::Display for MinAge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", humantime::format_duration(self.0))
}
}
impl<'de> Deserialize<'de> for MinAge {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let raw = String::deserialize(deserializer)?;
raw.parse().map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
const DAY: Duration = Duration::from_secs(60 * 60 * 24);
#[test]
fn parses_humantime_units() {
assert_eq!("3d".parse::<MinAge>().unwrap(), MinAge::new(3 * DAY));
assert_eq!(
"90s".parse::<MinAge>().unwrap(),
MinAge::new(Duration::from_secs(90))
);
assert_eq!(
"1h 30m".parse::<MinAge>().unwrap(),
MinAge::new(Duration::from_secs(5400))
);
}
#[test]
fn rejects_nonsense() {
assert!("soon".parse::<MinAge>().is_err());
assert!("".parse::<MinAge>().is_err());
}
#[test]
fn eligibility_is_inclusive_at_the_threshold() {
let now = SystemTime::UNIX_EPOCH + 10 * DAY;
let three_days = MinAge::new(3 * DAY);
assert!(three_days.is_eligible(now - 3 * DAY, now));
assert!(three_days.is_eligible(now - 4 * DAY, now));
assert!(!three_days.is_eligible(now - 2 * DAY, now));
}
#[test]
fn zero_threshold_takes_everything_present_or_past() {
let now = SystemTime::UNIX_EPOCH + 10 * DAY;
assert!(MinAge::new(Duration::ZERO).is_eligible(now, now));
assert!(MinAge::new(Duration::ZERO).is_eligible(now - DAY, now));
}
#[test]
fn future_timestamps_are_never_eligible() {
let now = SystemTime::UNIX_EPOCH + 10 * DAY;
assert!(!MinAge::new(Duration::ZERO).is_eligible(now + DAY, now));
assert!(!MinAge::new(3 * DAY).is_eligible(now + 100 * DAY, now));
}
}