1use crate::cache::Cache;
23use std::sync::Arc;
24use std::time::Duration;
25
26#[derive(Debug, Clone, Copy)]
36pub struct DocCacheTtl {
37 pub crate_docs_secs: u64,
39 pub search_results_secs: u64,
41 pub item_docs_secs: u64,
43}
44
45impl Default for DocCacheTtl {
46 fn default() -> Self {
47 Self {
48 crate_docs_secs: 3600, search_results_secs: 300, item_docs_secs: 1800, }
52 }
53}
54
55impl DocCacheTtl {
56 #[must_use]
66 pub fn from_cache_config(config: &crate::cache::CacheConfig) -> Self {
67 Self {
68 crate_docs_secs: config.crate_docs_ttl_secs.unwrap_or(3600),
69 search_results_secs: config.search_results_ttl_secs.unwrap_or(300),
70 item_docs_secs: config.item_docs_ttl_secs.unwrap_or(1800),
71 }
72 }
73}
74
75#[derive(Clone)]
84pub struct DocCache {
85 cache: Arc<dyn Cache>,
86 ttl: DocCacheTtl,
87}
88
89impl DocCache {
90 pub fn new(cache: Arc<dyn Cache>) -> Self {
107 Self {
108 cache,
109 ttl: DocCacheTtl::default(),
110 }
111 }
112
113 #[must_use]
136 pub fn with_ttl(cache: Arc<dyn Cache>, ttl: DocCacheTtl) -> Self {
137 Self { cache, ttl }
138 }
139
140 pub async fn get_crate_docs(&self, crate_name: &str, version: Option<&str>) -> Option<String> {
151 let key = Self::crate_cache_key(crate_name, version);
152 self.cache.get(&key).await
153 }
154
155 pub async fn set_crate_docs(
167 &self,
168 crate_name: &str,
169 version: Option<&str>,
170 content: String,
171 ) -> crate::error::Result<()> {
172 let key = Self::crate_cache_key(crate_name, version);
173 self.cache
174 .set(
175 key,
176 content,
177 Some(Duration::from_secs(self.ttl.crate_docs_secs)),
178 )
179 .await
180 }
181
182 pub async fn get_search_results(&self, query: &str, limit: u32) -> Option<String> {
193 let key = Self::search_cache_key(query, limit);
194 self.cache.get(&key).await
195 }
196
197 pub async fn set_search_results(
209 &self,
210 query: &str,
211 limit: u32,
212 content: String,
213 ) -> crate::error::Result<()> {
214 let key = Self::search_cache_key(query, limit);
215 self.cache
216 .set(
217 key,
218 content,
219 Some(Duration::from_secs(self.ttl.search_results_secs)),
220 )
221 .await
222 }
223
224 pub async fn get_item_docs(
236 &self,
237 crate_name: &str,
238 item_path: &str,
239 version: Option<&str>,
240 ) -> Option<String> {
241 let key = Self::item_cache_key(crate_name, item_path, version);
242 self.cache.get(&key).await
243 }
244
245 pub async fn set_item_docs(
258 &self,
259 crate_name: &str,
260 item_path: &str,
261 version: Option<&str>,
262 content: String,
263 ) -> crate::error::Result<()> {
264 let key = Self::item_cache_key(crate_name, item_path, version);
265 self.cache
266 .set(
267 key,
268 content,
269 Some(Duration::from_secs(self.ttl.item_docs_secs)),
270 )
271 .await
272 }
273
274 pub async fn clear(&self) -> crate::error::Result<()> {
280 self.cache.clear().await
281 }
282
283 fn crate_cache_key(crate_name: &str, version: Option<&str>) -> String {
285 if let Some(ver) = version {
286 format!("crate:{crate_name}:{ver}")
287 } else {
288 format!("crate:{crate_name}")
289 }
290 }
291
292 fn search_cache_key(query: &str, limit: u32) -> String {
294 format!("search:{query}:{limit}")
295 }
296
297 fn item_cache_key(crate_name: &str, item_path: &str, version: Option<&str>) -> String {
299 if let Some(ver) = version {
300 format!("item:{crate_name}:{ver}:{item_path}")
301 } else {
302 format!("item:{crate_name}:{item_path}")
303 }
304 }
305}
306
307impl Default for DocCache {
308 fn default() -> Self {
309 let cache = Arc::new(crate::cache::memory::MemoryCache::new(1000));
310 Self::new(cache)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::cache::memory::MemoryCache;
318
319 #[tokio::test]
320 async fn test_doc_cache() {
321 let memory_cache = MemoryCache::new(100);
322 let cache = Arc::new(memory_cache);
323 let doc_cache = DocCache::new(cache);
324
325 doc_cache
327 .set_crate_docs("serde", Some("1.0"), "Test docs".to_string())
328 .await
329 .expect("set_crate_docs should succeed");
330 let cached = doc_cache.get_crate_docs("serde", Some("1.0")).await;
331 assert_eq!(cached, Some("Test docs".to_string()));
332
333 doc_cache
335 .set_search_results("web framework", 10, "Search results".to_string())
336 .await
337 .expect("set_search_results should succeed");
338 let search_cached = doc_cache.get_search_results("web framework", 10).await;
339 assert_eq!(search_cached, Some("Search results".to_string()));
340
341 doc_cache
343 .set_item_docs(
344 "serde",
345 "serde::Serialize",
346 Some("1.0"),
347 "Item docs".to_string(),
348 )
349 .await
350 .expect("set_item_docs should succeed");
351 let item_cached = doc_cache
352 .get_item_docs("serde", "serde::Serialize", Some("1.0"))
353 .await;
354 assert_eq!(item_cached, Some("Item docs".to_string()));
355
356 doc_cache.clear().await.expect("clear should succeed");
358 let cleared = doc_cache.get_crate_docs("serde", Some("1.0")).await;
359 assert_eq!(cleared, None);
360 }
361
362 #[test]
363 fn test_cache_key_generation() {
364 assert_eq!(DocCache::crate_cache_key("serde", None), "crate:serde");
365 assert_eq!(
366 DocCache::crate_cache_key("serde", Some("1.0")),
367 "crate:serde:1.0"
368 );
369
370 assert_eq!(
371 DocCache::search_cache_key("web framework", 10),
372 "search:web framework:10"
373 );
374
375 assert_eq!(
376 DocCache::item_cache_key("serde", "Serialize", None),
377 "item:serde:Serialize"
378 );
379 assert_eq!(
380 DocCache::item_cache_key("serde", "Serialize", Some("1.0")),
381 "item:serde:1.0:Serialize"
382 );
383 }
384
385 #[test]
386 fn test_doc_cache_ttl_default() {
387 let ttl = DocCacheTtl::default();
388 assert_eq!(ttl.crate_docs_secs, 3600);
389 assert_eq!(ttl.search_results_secs, 300);
390 assert_eq!(ttl.item_docs_secs, 1800);
391 }
392
393 #[test]
394 fn test_doc_cache_ttl_from_config() {
395 let config = crate::cache::CacheConfig {
396 cache_type: "memory".to_string(),
397 memory_size: Some(1000),
398 redis_url: None,
399 key_prefix: String::new(),
400 default_ttl: Some(3600),
401 crate_docs_ttl_secs: Some(7200),
402 item_docs_ttl_secs: Some(3600),
403 search_results_ttl_secs: Some(600),
404 };
405 let ttl = DocCacheTtl::from_cache_config(&config);
406 assert_eq!(ttl.crate_docs_secs, 7200);
407 assert_eq!(ttl.item_docs_secs, 3600);
408 assert_eq!(ttl.search_results_secs, 600);
409 }
410}