use std::time::{SystemTime, UNIX_EPOCH};
use super::ICacheEntryValidity;
pub struct TTLCacheEntryValidity {
cache_ttl_ms: i64,
creation_time_in_millis: i64,
}
impl TTLCacheEntryValidity {
#[must_use]
pub fn new(cache_ttl_ms: i64) -> Self {
Self::new_at(cache_ttl_ms, current_time_millis())
}
fn new_at(cache_ttl_ms: i64, creation_time_in_millis: i64) -> Self {
Self {
cache_ttl_ms,
creation_time_in_millis,
}
}
#[must_use]
pub const fn get_cache_ttl_ms(&self) -> i64 {
self.cache_ttl_ms
}
#[must_use]
pub const fn is_cacheable(&self) -> bool {
true
}
#[must_use]
pub fn is_cache_still_valid(&self) -> bool {
self.is_cache_still_valid_at(current_time_millis())
}
fn is_cache_still_valid_at(&self, current_time_in_millis: i64) -> bool {
current_time_in_millis < self.creation_time_in_millis.wrapping_add(self.cache_ttl_ms)
}
}
impl ICacheEntryValidity for TTLCacheEntryValidity {
fn is_cacheable(&self) -> bool {
Self::is_cacheable(self)
}
fn is_cache_still_valid(&self) -> bool {
Self::is_cache_still_valid(self)
}
}
fn current_time_millis() -> i64 {
system_time_millis(SystemTime::now())
}
fn system_time_millis(time: SystemTime) -> i64 {
match time.duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_millis() as i64,
Err(error) => {
let duration = error.duration();
let whole_millis = duration.as_millis() as i64;
let sub_millisecond = duration.subsec_nanos() % 1_000_000 != 0;
whole_millis
.wrapping_neg()
.wrapping_sub(i64::from(sub_millisecond))
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{TTLCacheEntryValidity, UNIX_EPOCH, system_time_millis};
use crate::cache::ICacheEntryValidity;
#[test]
fn preserves_ttl_and_dynamic_cacheable_contract() {
let validity = TTLCacheEntryValidity::new(60_000);
let dynamic: &dyn ICacheEntryValidity = &validity;
assert_eq!(validity.get_cache_ttl_ms(), 60_000);
assert!(validity.is_cacheable());
assert!(validity.is_cache_still_valid());
assert!(dynamic.is_cacheable());
assert!(dynamic.is_cache_still_valid());
}
#[test]
fn uses_strict_wall_clock_boundary_and_preserves_clock_rollback() {
let validity = TTLCacheEntryValidity::new_at(10, 1_000);
assert!(validity.is_cache_still_valid_at(999));
assert!(validity.is_cache_still_valid_at(1_009));
assert!(!validity.is_cache_still_valid_at(1_010));
assert!(!validity.is_cache_still_valid_at(1_011));
}
#[test]
fn preserves_java_long_overflow_and_non_positive_ttl_behavior() {
let zero = TTLCacheEntryValidity::new_at(0, 100);
let negative = TTLCacheEntryValidity::new_at(-1, 100);
let overflowing = TTLCacheEntryValidity::new_at(i64::MAX, 100);
assert!(!zero.is_cache_still_valid_at(100));
assert!(!negative.is_cache_still_valid_at(100));
assert!(!overflowing.is_cache_still_valid_at(100));
}
#[test]
fn converts_unix_wall_clock_milliseconds_on_both_sides_of_epoch() {
assert_eq!(system_time_millis(UNIX_EPOCH), 0);
assert_eq!(
system_time_millis(UNIX_EPOCH + Duration::from_millis(1_234)),
1_234
);
assert_eq!(
system_time_millis(UNIX_EPOCH - Duration::from_millis(1_234)),
-1_234
);
assert_eq!(
system_time_millis(UNIX_EPOCH - Duration::from_micros(500)),
-1
);
}
}