#![allow(dead_code)]
use std::time::{Duration, Instant};
use crate::api::DnsRecord;
pub struct DnsCache {
records: Option<Vec<DnsRecord>>,
last_updated: Option<Instant>,
ttl: Duration,
}
impl DnsCache {
pub fn new(ttl: Duration) -> Self {
Self {
records: None,
last_updated: None,
ttl,
}
}
pub fn with_default_ttl() -> Self {
Self::new(Duration::from_secs(60))
}
pub fn is_valid(&self) -> bool {
match self.last_updated {
Some(updated) => updated.elapsed() < self.ttl,
None => false,
}
}
pub fn get(&self) -> Option<&Vec<DnsRecord>> {
if self.is_valid() {
self.records.as_ref()
} else {
None
}
}
pub fn set(&mut self, records: Vec<DnsRecord>) {
self.records = Some(records);
self.last_updated = Some(Instant::now());
}
pub fn invalidate(&mut self) {
self.records = None;
self.last_updated = None;
}
pub fn age(&self) -> Option<Duration> {
self.last_updated.map(|t| t.elapsed())
}
pub fn remaining_ttl(&self) -> Option<Duration> {
self.last_updated.map(|updated| {
let elapsed = updated.elapsed();
if elapsed < self.ttl {
self.ttl - elapsed
} else {
Duration::ZERO
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
fn make_record(id: &str) -> DnsRecord {
DnsRecord {
id: Some(id.to_string()),
record_type: "A".to_string(),
name: "example.com".to_string(),
content: "127.0.0.1".to_string(),
ttl: Some(300),
proxied: Some(false),
comment: None,
}
}
#[test]
fn test_cache_initially_invalid() {
let cache = DnsCache::with_default_ttl();
assert!(!cache.is_valid());
assert!(cache.get().is_none());
}
#[test]
fn test_cache_set_and_get() {
let mut cache = DnsCache::with_default_ttl();
let records = vec![make_record("1"), make_record("2")];
cache.set(records);
assert!(cache.is_valid());
let cached = cache.get().unwrap();
assert_eq!(cached.len(), 2);
assert_eq!(cached[0].id, Some("1".to_string()));
}
#[test]
fn test_cache_expires() {
let mut cache = DnsCache::new(Duration::from_millis(100));
let records = vec![make_record("1")];
cache.set(records);
assert!(cache.is_valid());
thread::sleep(Duration::from_millis(150));
assert!(!cache.is_valid());
assert!(cache.get().is_none());
}
#[test]
fn test_cache_invalidate() {
let mut cache = DnsCache::with_default_ttl();
cache.set(vec![make_record("1")]);
assert!(cache.is_valid());
cache.invalidate();
assert!(!cache.is_valid());
assert!(cache.get().is_none());
}
#[test]
fn test_cache_age() {
let mut cache = DnsCache::with_default_ttl();
assert!(cache.age().is_none());
cache.set(vec![make_record("1")]);
assert!(cache.age().is_some());
assert!(cache.age().unwrap() < Duration::from_secs(1));
}
#[test]
fn test_cache_remaining_ttl() {
let mut cache = DnsCache::new(Duration::from_secs(10));
assert!(cache.remaining_ttl().is_none());
cache.set(vec![make_record("1")]);
let remaining = cache.remaining_ttl().unwrap();
assert!(remaining <= Duration::from_secs(10));
assert!(remaining > Duration::from_secs(9));
}
#[test]
fn test_cache_remaining_ttl_zero_when_expired() {
let mut cache = DnsCache::new(Duration::from_millis(50));
cache.set(vec![make_record("1")]);
thread::sleep(Duration::from_millis(100));
assert_eq!(cache.remaining_ttl().unwrap(), Duration::ZERO);
}
}