1use crate::{JavaError, JavaInfo};
4use std::time::{Duration, Instant};
5
6const DEFAULT_TTL: Duration = Duration::from_secs(300);
7
8#[derive(Debug)]
16pub struct JavaCache {
17 results: Vec<JavaInfo>,
18 cached_at: Option<Instant>,
19 ttl: Duration,
20}
21
22impl Default for JavaCache {
23 fn default() -> Self {
24 Self::new(DEFAULT_TTL)
25 }
26}
27
28impl JavaCache {
29 pub fn new(ttl: Duration) -> Self {
36 Self {
37 results: Vec::new(),
38 cached_at: None,
39 ttl,
40 }
41 }
42
43 pub fn ttl(mut self, ttl: Duration) -> Self {
54 self.ttl = ttl;
55 self
56 }
57
58 pub fn get_or_refresh<F>(&mut self, fetcher: F) -> Result<&[JavaInfo], JavaError>
77 where
78 F: Fn() -> Result<Vec<JavaInfo>, JavaError>,
79 {
80 if self.is_fresh() {
81 log::debug!(
82 "JavaCache: returning {} cached result(s)",
83 self.results.len()
84 );
85 return Ok(&self.results);
86 }
87
88 log::debug!("JavaCache: cache expired, fetching...");
89 self.results = fetcher()?;
90 self.cached_at = Some(Instant::now());
91 Ok(&self.results)
92 }
93
94 pub fn force_refresh<F>(&mut self, fetcher: F) -> Result<&[JavaInfo], JavaError>
112 where
113 F: Fn() -> Result<Vec<JavaInfo>, JavaError>,
114 {
115 log::debug!("JavaCache: force refresh");
116 self.results = fetcher()?;
117 self.cached_at = Some(Instant::now());
118 Ok(&self.results)
119 }
120
121 pub fn clear(&mut self) {
126 self.results.clear();
127 self.cached_at = None;
128 }
129
130 fn is_fresh(&self) -> bool {
131 self.cached_at.is_some_and(|t| t.elapsed() < self.ttl)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 fn dummy_fetcher(count: usize) -> impl Fn() -> Result<Vec<JavaInfo>, JavaError> {
140 move || {
141 let mut results = Vec::new();
142 for i in 0..count {
143 results.push(JavaInfo {
144 name: format!("Java {}", i),
145 version: format!("{}.0.0", 8 + i),
146 ..Default::default()
147 });
148 }
149 Ok(results)
150 }
151 }
152
153 #[test]
154 fn test_cache_initial_miss() {
155 let mut cache = JavaCache::new(Duration::from_secs(60));
156 let results = cache.get_or_refresh(dummy_fetcher(2)).unwrap();
157 assert_eq!(results.len(), 2);
158 }
159
160 #[test]
161 fn test_cache_hit_within_ttl() {
162 let mut cache = JavaCache::new(Duration::from_secs(60));
163 let _ = cache.get_or_refresh(dummy_fetcher(1)).unwrap();
164 let results = cache.get_or_refresh(dummy_fetcher(999)).unwrap();
165 assert_eq!(results.len(), 1);
166 }
167
168 #[test]
169 fn test_force_refresh() {
170 let mut cache = JavaCache::new(Duration::from_secs(60));
171 let _ = cache.get_or_refresh(dummy_fetcher(1)).unwrap();
172 let results = cache.force_refresh(dummy_fetcher(3)).unwrap();
173 assert_eq!(results.len(), 3);
174 }
175
176 #[test]
177 fn test_clear() {
178 let mut cache = JavaCache::new(Duration::from_secs(60));
179 let _ = cache.get_or_refresh(dummy_fetcher(1)).unwrap();
180 cache.clear();
181 assert!(cache.cached_at.is_none());
182 }
183
184 #[test]
185 fn test_default_ttl() {
186 let cache = JavaCache::default();
187 assert_eq!(cache.ttl, DEFAULT_TTL);
188 }
189
190 #[test]
191 fn test_custom_ttl_builder() {
192 let cache = JavaCache::new(Duration::from_secs(10)).ttl(Duration::from_secs(30));
193 assert_eq!(cache.ttl, Duration::from_secs(30));
194 }
195
196 #[test]
197 fn test_cache_fetcher_error() {
198 let mut cache = JavaCache::new(Duration::from_secs(60));
199 let result: Result<&[JavaInfo], JavaError> =
200 cache.get_or_refresh(|| Err(JavaError::Other("fetch failed".into())));
201 assert!(result.is_err());
202 assert!(result.unwrap_err().to_string().contains("fetch failed"));
203 }
204
205 #[test]
206 fn test_cache_ttl_zero() {
207 let mut cache = JavaCache::new(Duration::ZERO);
208 let r1 = cache.get_or_refresh(dummy_fetcher(1)).unwrap();
209 assert_eq!(r1.len(), 1);
210 let r2 = cache.get_or_refresh(dummy_fetcher(2)).unwrap();
212 assert_eq!(r2.len(), 2);
213 }
214}