1use crate::cache::Cache;
4use std::sync::Arc;
5use std::time::Duration;
6
7#[derive(Debug, Clone, Copy)]
9pub struct DocCacheTtl {
10 pub crate_docs_secs: u64,
12 pub search_results_secs: u64,
14 pub item_docs_secs: u64,
16}
17
18impl Default for DocCacheTtl {
19 fn default() -> Self {
20 Self {
21 crate_docs_secs: 3600, search_results_secs: 300, item_docs_secs: 1800, }
25 }
26}
27
28impl DocCacheTtl {
29 #[must_use]
31 pub fn from_cache_config(config: &crate::cache::CacheConfig) -> Self {
32 Self {
33 crate_docs_secs: config.crate_docs_ttl_secs.unwrap_or(3600),
34 search_results_secs: config.search_results_ttl_secs.unwrap_or(300),
35 item_docs_secs: config.item_docs_ttl_secs.unwrap_or(1800),
36 }
37 }
38}
39
40#[derive(Clone)]
42pub struct DocCache {
43 cache: Arc<dyn Cache>,
44 ttl: DocCacheTtl,
45}
46
47impl DocCache {
48 pub fn new(cache: Arc<dyn Cache>) -> Self {
50 Self {
51 cache,
52 ttl: DocCacheTtl::default(),
53 }
54 }
55
56 #[must_use]
58 pub fn with_ttl(cache: Arc<dyn Cache>, ttl: DocCacheTtl) -> Self {
59 Self { cache, ttl }
60 }
61
62 pub async fn get_crate_docs(&self, crate_name: &str, version: Option<&str>) -> Option<String> {
64 let key = Self::crate_cache_key(crate_name, version);
65 self.cache.get(&key).await
66 }
67
68 pub async fn set_crate_docs(
74 &self,
75 crate_name: &str,
76 version: Option<&str>,
77 content: String,
78 ) -> crate::error::Result<()> {
79 let key = Self::crate_cache_key(crate_name, version);
80 self.cache
81 .set(
82 key,
83 content,
84 Some(Duration::from_secs(self.ttl.crate_docs_secs)),
85 )
86 .await
87 }
88
89 pub async fn get_search_results(&self, query: &str, limit: u32) -> Option<String> {
91 let key = Self::search_cache_key(query, limit);
92 self.cache.get(&key).await
93 }
94
95 pub async fn set_search_results(
101 &self,
102 query: &str,
103 limit: u32,
104 content: String,
105 ) -> crate::error::Result<()> {
106 let key = Self::search_cache_key(query, limit);
107 self.cache
108 .set(
109 key,
110 content,
111 Some(Duration::from_secs(self.ttl.search_results_secs)),
112 )
113 .await
114 }
115
116 pub async fn get_item_docs(
118 &self,
119 crate_name: &str,
120 item_path: &str,
121 version: Option<&str>,
122 ) -> Option<String> {
123 let key = Self::item_cache_key(crate_name, item_path, version);
124 self.cache.get(&key).await
125 }
126
127 pub async fn set_item_docs(
133 &self,
134 crate_name: &str,
135 item_path: &str,
136 version: Option<&str>,
137 content: String,
138 ) -> crate::error::Result<()> {
139 let key = Self::item_cache_key(crate_name, item_path, version);
140 self.cache
141 .set(
142 key,
143 content,
144 Some(Duration::from_secs(self.ttl.item_docs_secs)),
145 )
146 .await
147 }
148
149 pub async fn clear(&self) -> crate::error::Result<()> {
155 self.cache.clear().await
156 }
157
158 fn crate_cache_key(crate_name: &str, version: Option<&str>) -> String {
160 if let Some(ver) = version {
161 format!("crate:{crate_name}:{ver}")
162 } else {
163 format!("crate:{crate_name}")
164 }
165 }
166
167 fn search_cache_key(query: &str, limit: u32) -> String {
169 format!("search:{query}:{limit}")
170 }
171
172 fn item_cache_key(crate_name: &str, item_path: &str, version: Option<&str>) -> String {
174 if let Some(ver) = version {
175 format!("item:{crate_name}:{ver}:{item_path}")
176 } else {
177 format!("item:{crate_name}:{item_path}")
178 }
179 }
180}
181
182impl Default for DocCache {
183 fn default() -> Self {
184 let cache = Arc::new(crate::cache::memory::MemoryCache::new(1000));
185 Self::new(cache)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::cache::memory::MemoryCache;
193
194 #[tokio::test]
195 async fn test_doc_cache() {
196 let memory_cache = MemoryCache::new(100);
197 let cache = Arc::new(memory_cache);
198 let doc_cache = DocCache::new(cache);
199
200 doc_cache
202 .set_crate_docs("serde", Some("1.0"), "Test docs".to_string())
203 .await
204 .expect("set_crate_docs should succeed");
205 let cached = doc_cache.get_crate_docs("serde", Some("1.0")).await;
206 assert_eq!(cached, Some("Test docs".to_string()));
207
208 doc_cache
210 .set_search_results("web framework", 10, "Search results".to_string())
211 .await
212 .expect("set_search_results should succeed");
213 let search_cached = doc_cache.get_search_results("web framework", 10).await;
214 assert_eq!(search_cached, Some("Search results".to_string()));
215
216 doc_cache
218 .set_item_docs(
219 "serde",
220 "serde::Serialize",
221 Some("1.0"),
222 "Item docs".to_string(),
223 )
224 .await
225 .expect("set_item_docs should succeed");
226 let item_cached = doc_cache
227 .get_item_docs("serde", "serde::Serialize", Some("1.0"))
228 .await;
229 assert_eq!(item_cached, Some("Item docs".to_string()));
230
231 doc_cache.clear().await.expect("clear should succeed");
233 let cleared = doc_cache.get_crate_docs("serde", Some("1.0")).await;
234 assert_eq!(cleared, None);
235 }
236
237 #[test]
238 fn test_cache_key_generation() {
239 assert_eq!(DocCache::crate_cache_key("serde", None), "crate:serde");
240 assert_eq!(
241 DocCache::crate_cache_key("serde", Some("1.0")),
242 "crate:serde:1.0"
243 );
244
245 assert_eq!(
246 DocCache::search_cache_key("web framework", 10),
247 "search:web framework:10"
248 );
249
250 assert_eq!(
251 DocCache::item_cache_key("serde", "Serialize", None),
252 "item:serde:Serialize"
253 );
254 assert_eq!(
255 DocCache::item_cache_key("serde", "Serialize", Some("1.0")),
256 "item:serde:1.0:Serialize"
257 );
258 }
259
260 #[test]
261 fn test_doc_cache_ttl_default() {
262 let ttl = DocCacheTtl::default();
263 assert_eq!(ttl.crate_docs_secs, 3600);
264 assert_eq!(ttl.search_results_secs, 300);
265 assert_eq!(ttl.item_docs_secs, 1800);
266 }
267
268 #[test]
269 fn test_doc_cache_ttl_from_config() {
270 let config = crate::cache::CacheConfig {
271 cache_type: "memory".to_string(),
272 memory_size: Some(1000),
273 redis_url: None,
274 key_prefix: String::new(),
275 default_ttl: Some(3600),
276 crate_docs_ttl_secs: Some(7200),
277 item_docs_ttl_secs: Some(3600),
278 search_results_ttl_secs: Some(600),
279 };
280 let ttl = DocCacheTtl::from_cache_config(&config);
281 assert_eq!(ttl.crate_docs_secs, 7200);
282 assert_eq!(ttl.item_docs_secs, 3600);
283 assert_eq!(ttl.search_results_secs, 600);
284 }
285}