use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct TtlConfig {
pub duration: Duration,
}
impl TtlConfig {
pub fn new(duration: Duration) -> Self {
assert!(
!duration.is_zero(),
"TTL duration must be greater than zero"
);
Self { duration }
}
}
impl Default for TtlConfig {
fn default() -> Self {
Self::new(Duration::from_secs(2 * 60 * 60))
}
}
#[derive(Debug, Clone)]
pub struct TemplateMetadata {
pub inserted_at: Option<Instant>,
}
impl TemplateMetadata {
pub fn new_with_ttl() -> Self {
Self {
inserted_at: Some(Instant::now()),
}
}
pub fn new_without_ttl() -> Self {
Self { inserted_at: None }
}
pub fn is_expired(&self, config: &TtlConfig) -> bool {
match self.inserted_at {
Some(instant) => instant.elapsed() >= config.duration,
None => false,
}
}
}
impl Default for TemplateMetadata {
fn default() -> Self {
Self::new_without_ttl()
}
}
#[derive(Debug, Clone)]
pub struct TemplateWithTtl<T> {
pub template: T,
pub metadata: TemplateMetadata,
}
impl<T> TemplateWithTtl<T> {
pub fn new_with_ttl(template: T) -> Self {
Self {
template,
metadata: TemplateMetadata::new_with_ttl(),
}
}
pub fn new_without_ttl(template: T) -> Self {
Self {
template,
metadata: TemplateMetadata::new_without_ttl(),
}
}
pub fn new(template: T, ttl_enabled: bool) -> Self {
if ttl_enabled {
Self::new_with_ttl(template)
} else {
Self::new_without_ttl(template)
}
}
pub fn is_expired(&self, config: &TtlConfig) -> bool {
self.metadata.is_expired(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_time_based_expiration() {
let config = TtlConfig::new(Duration::from_millis(100));
let metadata = TemplateMetadata::new_with_ttl();
assert!(!metadata.is_expired(&config));
thread::sleep(Duration::from_millis(500));
assert!(metadata.is_expired(&config));
}
#[test]
fn test_no_ttl_never_expires() {
let config = TtlConfig::new(Duration::from_millis(1));
let metadata = TemplateMetadata::new_without_ttl();
thread::sleep(Duration::from_millis(10));
assert!(!metadata.is_expired(&config));
}
#[test]
fn test_template_with_ttl_wrapper() {
let template = 42u32;
let wrapped = TemplateWithTtl::new_with_ttl(template);
assert_eq!(wrapped.template, 42);
let config = TtlConfig::new(Duration::from_millis(100));
assert!(!wrapped.is_expired(&config));
thread::sleep(Duration::from_millis(150));
assert!(wrapped.is_expired(&config));
}
#[test]
fn test_template_without_ttl() {
let wrapped = TemplateWithTtl::new_without_ttl(42u32);
let config = TtlConfig::new(Duration::from_millis(1));
thread::sleep(Duration::from_millis(10));
assert!(!wrapped.is_expired(&config));
}
#[test]
fn test_conditional_new() {
let with = TemplateWithTtl::new(42u32, true);
assert!(with.metadata.inserted_at.is_some());
let without = TemplateWithTtl::new(42u32, false);
assert!(without.metadata.inserted_at.is_none());
}
#[test]
fn test_default_config() {
let config = TtlConfig::default();
assert_eq!(config.duration, Duration::from_secs(2 * 60 * 60));
}
}