use std::time::Duration;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CacheMetadata {
pub created_time: Duration,
pub ttl: Option<Duration>,
}
impl CacheMetadata {
pub fn new(ttl: Option<Duration>) -> Self {
Self {
created_time: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(Duration::ZERO),
ttl,
}
}
pub fn with_time(created_time: Duration, ttl: Option<Duration>) -> Self {
Self {
created_time,
ttl,
}
}
pub fn is_expired(&self, now: Duration) -> bool {
if let Some(ttl) = self.ttl {
now >= self.created_time + ttl
} else {
false }
}
pub fn expires_at(&self) -> Option<Duration> {
self.ttl.map(|ttl| self.created_time + ttl)
}
pub fn age(&self, now: Duration) -> Duration {
now.saturating_sub(self.created_time)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CacheEntry<T> {
pub value: T,
pub metadata: CacheMetadata,
}
impl<T> CacheEntry<T> {
pub fn new(value: T, ttl: Option<Duration>) -> Self {
Self {
value,
metadata: CacheMetadata::new(ttl),
}
}
pub fn with_metadata(value: T, metadata: CacheMetadata) -> Self {
Self {
value,
metadata,
}
}
pub fn is_expired(&self, now: Duration) -> bool {
self.metadata.is_expired(now)
}
pub fn expires_at(&self) -> Option<Duration> {
self.metadata.expires_at()
}
pub fn age(&self, now: Duration) -> Duration {
self.metadata.age(now)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_cache_metadata() {
let ttl = Duration::from_secs(60);
let metadata = CacheMetadata::new(Some(ttl));
let now = metadata.created_time;
assert!(!metadata.is_expired(now));
let later = now + ttl + Duration::from_secs(1);
assert!(metadata.is_expired(later));
let expiry = now + ttl;
assert!(metadata.is_expired(expiry));
}
#[test]
fn test_cache_metadata_no_ttl() {
let metadata = CacheMetadata::new(None);
let far_future = metadata.created_time + Duration::from_secs(365 * 24 * 60 * 60);
assert!(!metadata.is_expired(far_future));
}
#[test]
fn test_cache_entry() {
let value = "test_value".to_string();
let ttl = Duration::from_secs(30);
let entry = CacheEntry::new(value.clone(), Some(ttl));
assert_eq!(entry.value, value);
assert_eq!(entry.metadata.ttl, Some(ttl));
let now = entry.metadata.created_time + Duration::from_secs(10);
assert_eq!(entry.age(now), Duration::from_secs(10));
}
}