cloudflare_dns/api/
cache.rs1#![allow(dead_code)]
2
3use std::time::{Duration, Instant};
9
10use crate::api::DnsRecord;
11
12pub struct DnsCache {
17 records: Option<Vec<DnsRecord>>,
19 last_updated: Option<Instant>,
21 ttl: Duration,
23}
24
25impl DnsCache {
26 pub fn new(ttl: Duration) -> Self {
31 Self {
32 records: None,
33 last_updated: None,
34 ttl,
35 }
36 }
37
38 pub fn with_default_ttl() -> Self {
40 Self::new(Duration::from_secs(60))
41 }
42
43 pub fn is_valid(&self) -> bool {
45 match self.last_updated {
46 Some(updated) => updated.elapsed() < self.ttl,
47 None => false,
48 }
49 }
50
51 pub fn get(&self) -> Option<&Vec<DnsRecord>> {
53 if self.is_valid() {
54 self.records.as_ref()
55 } else {
56 None
57 }
58 }
59
60 pub fn set(&mut self, records: Vec<DnsRecord>) {
62 self.records = Some(records);
63 self.last_updated = Some(Instant::now());
64 }
65
66 pub fn invalidate(&mut self) {
68 self.records = None;
69 self.last_updated = None;
70 }
71
72 pub fn age(&self) -> Option<Duration> {
74 self.last_updated.map(|t| t.elapsed())
75 }
76
77 pub fn remaining_ttl(&self) -> Option<Duration> {
79 self.last_updated.map(|updated| {
80 let elapsed = updated.elapsed();
81 if elapsed < self.ttl {
82 self.ttl - elapsed
83 } else {
84 Duration::ZERO
85 }
86 })
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use std::thread;
94
95 fn make_record(id: &str) -> DnsRecord {
96 DnsRecord {
97 id: Some(id.to_string()),
98 record_type: "A".to_string(),
99 name: "example.com".to_string(),
100 content: "127.0.0.1".to_string(),
101 ttl: Some(300),
102 proxied: Some(false),
103 comment: None,
104 }
105 }
106
107 #[test]
108 fn test_cache_initially_invalid() {
109 let cache = DnsCache::with_default_ttl();
110 assert!(!cache.is_valid());
111 assert!(cache.get().is_none());
112 }
113
114 #[test]
115 fn test_cache_set_and_get() {
116 let mut cache = DnsCache::with_default_ttl();
117 let records = vec![make_record("1"), make_record("2")];
118 cache.set(records);
119
120 assert!(cache.is_valid());
121 let cached = cache.get().unwrap();
122 assert_eq!(cached.len(), 2);
123 assert_eq!(cached[0].id, Some("1".to_string()));
124 }
125
126 #[test]
127 fn test_cache_expires() {
128 let mut cache = DnsCache::new(Duration::from_millis(100));
129 let records = vec![make_record("1")];
130 cache.set(records);
131
132 assert!(cache.is_valid());
133
134 thread::sleep(Duration::from_millis(150));
136
137 assert!(!cache.is_valid());
138 assert!(cache.get().is_none());
139 }
140
141 #[test]
142 fn test_cache_invalidate() {
143 let mut cache = DnsCache::with_default_ttl();
144 cache.set(vec![make_record("1")]);
145 assert!(cache.is_valid());
146
147 cache.invalidate();
148 assert!(!cache.is_valid());
149 assert!(cache.get().is_none());
150 }
151
152 #[test]
153 fn test_cache_age() {
154 let mut cache = DnsCache::with_default_ttl();
155 assert!(cache.age().is_none());
156
157 cache.set(vec![make_record("1")]);
158 assert!(cache.age().is_some());
159 assert!(cache.age().unwrap() < Duration::from_secs(1));
160 }
161
162 #[test]
163 fn test_cache_remaining_ttl() {
164 let mut cache = DnsCache::new(Duration::from_secs(10));
165 assert!(cache.remaining_ttl().is_none());
166
167 cache.set(vec![make_record("1")]);
168 let remaining = cache.remaining_ttl().unwrap();
169 assert!(remaining <= Duration::from_secs(10));
170 assert!(remaining > Duration::from_secs(9));
171 }
172
173 #[test]
174 fn test_cache_remaining_ttl_zero_when_expired() {
175 let mut cache = DnsCache::new(Duration::from_millis(50));
176 cache.set(vec![make_record("1")]);
177
178 thread::sleep(Duration::from_millis(100));
179 assert_eq!(cache.remaining_ttl().unwrap(), Duration::ZERO);
180 }
181}