1#![allow(dead_code)]
4
5#[derive(Clone, Debug)]
9pub struct CachedResponse {
10 pub status: u16,
11 pub body: String,
12 pub content_type: String,
13 pub max_age_secs: u64,
14 pub cached_at: u64,
15}
16
17#[derive(Clone, Debug)]
19pub struct ResponseCacheConfig {
20 pub max_entries: usize,
21 pub default_max_age_secs: u64,
22}
23
24impl Default for ResponseCacheConfig {
25 fn default() -> Self {
26 Self {
27 max_entries: 512,
28 default_max_age_secs: 300,
29 }
30 }
31}
32
33pub struct ResponseCache {
35 pub config: ResponseCacheConfig,
36 entries: Vec<(String, CachedResponse)>,
37}
38
39pub fn new_response_cache(config: ResponseCacheConfig) -> ResponseCache {
41 ResponseCache {
42 config,
43 entries: Vec::new(),
44 }
45}
46
47pub fn cache_store(cache: &mut ResponseCache, key: &str, response: CachedResponse) {
49 cache.entries.retain(|(k, _)| k != key);
50 if cache.entries.len() >= cache.config.max_entries {
51 cache.entries.remove(0); }
53 cache.entries.push((key.into(), response));
54}
55
56pub fn cache_get<'a>(cache: &'a ResponseCache, key: &str, now: u64) -> Option<&'a CachedResponse> {
58 cache
59 .entries
60 .iter()
61 .find(|(k, r)| k == key && now.saturating_sub(r.cached_at) < r.max_age_secs)
62 .map(|(_, r)| r)
63}
64
65pub fn cache_invalidate(cache: &mut ResponseCache, key: &str) -> bool {
67 let before = cache.entries.len();
68 cache.entries.retain(|(k, _)| k != key);
69 cache.entries.len() < before
70}
71
72pub fn purge_expired_responses(cache: &mut ResponseCache, now: u64) -> usize {
74 let before = cache.entries.len();
75 cache
76 .entries
77 .retain(|(_, r)| now.saturating_sub(r.cached_at) < r.max_age_secs);
78 before.saturating_sub(cache.entries.len())
79}
80
81pub fn cache_size(cache: &ResponseCache) -> usize {
83 cache.entries.len()
84}
85
86impl ResponseCache {
87 pub fn new(config: ResponseCacheConfig) -> Self {
89 new_response_cache(config)
90 }
91}
92
93fn make_response(status: u16, body: &str, max_age: u64, cached_at: u64) -> CachedResponse {
94 CachedResponse {
95 status,
96 body: body.into(),
97 content_type: "text/plain".into(),
98 max_age_secs: max_age,
99 cached_at,
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 fn make_cache() -> ResponseCache {
108 new_response_cache(ResponseCacheConfig::default())
109 }
110
111 #[test]
112 fn test_store_and_retrieve() {
113 let mut c = make_cache();
114 cache_store(&mut c, "/api", make_response(200, "ok", 300, 0));
115 let r = cache_get(&c, "/api", 100);
116 assert!(r.is_some());
117 }
118
119 #[test]
120 fn test_expired_entry_returns_none() {
121 let mut c = make_cache();
122 cache_store(&mut c, "/api", make_response(200, "ok", 60, 0));
123 assert!(cache_get(&c, "/api", 100).is_none());
125 }
126
127 #[test]
128 fn test_invalidate_removes_entry() {
129 let mut c = make_cache();
130 cache_store(&mut c, "/api", make_response(200, "ok", 300, 0));
131 assert!(cache_invalidate(&mut c, "/api"));
132 assert_eq!(cache_size(&c), 0);
133 }
134
135 #[test]
136 fn test_invalidate_nonexistent_returns_false() {
137 let mut c = make_cache();
138 assert!(!cache_invalidate(&mut c, "/missing"));
139 }
140
141 #[test]
142 fn test_purge_expired_removes_old() {
143 let mut c = make_cache();
144 cache_store(&mut c, "/old", make_response(200, "old", 10, 0));
145 cache_store(&mut c, "/new", make_response(200, "new", 1000, 0));
146 let removed = purge_expired_responses(&mut c, 100);
147 assert_eq!(removed, 1);
148 assert_eq!(cache_size(&c), 1);
149 }
150
151 #[test]
152 fn test_overwrite_existing_key() {
153 let mut c = make_cache();
154 cache_store(&mut c, "/k", make_response(200, "v1", 300, 0));
155 cache_store(&mut c, "/k", make_response(200, "v2", 300, 0));
156 assert_eq!(cache_size(&c), 1);
157 assert_eq!(cache_get(&c, "/k", 0).expect("should succeed").body, "v2");
158 }
159
160 #[test]
161 fn test_eviction_when_at_capacity() {
162 let mut c = new_response_cache(ResponseCacheConfig {
163 max_entries: 2,
164 default_max_age_secs: 300,
165 });
166 cache_store(&mut c, "/a", make_response(200, "a", 300, 0));
167 cache_store(&mut c, "/b", make_response(200, "b", 300, 0));
168 cache_store(&mut c, "/c", make_response(200, "c", 300, 0)); assert!(cache_get(&c, "/a", 0).is_none());
170 assert_eq!(cache_size(&c), 2);
171 }
172
173 #[test]
174 fn test_cache_size_tracks_correctly() {
175 let mut c = make_cache();
176 assert_eq!(cache_size(&c), 0);
177 cache_store(&mut c, "/x", make_response(200, "x", 60, 0));
178 assert_eq!(cache_size(&c), 1);
179 }
180
181 #[test]
182 fn test_response_status_preserved() {
183 let mut c = make_cache();
184 cache_store(&mut c, "/e", make_response(404, "not found", 300, 0));
185 assert_eq!(cache_get(&c, "/e", 0).expect("should succeed").status, 404);
186 }
187}