Skip to main content

crates_docs/tools/docs/
cache.rs

1//! Document cache module
2
3use crate::cache::Cache;
4use std::sync::Arc;
5use std::time::Duration;
6
7/// TTL configuration for document cache
8#[derive(Debug, Clone, Copy)]
9pub struct DocCacheTtl {
10    /// TTL for crate documentation (seconds)
11    pub crate_docs_secs: u64,
12    /// TTL for search results (seconds)
13    pub search_results_secs: u64,
14    /// TTL for item documentation (seconds)
15    pub item_docs_secs: u64,
16}
17
18impl Default for DocCacheTtl {
19    fn default() -> Self {
20        Self {
21            crate_docs_secs: 3600,    // 1 hour
22            search_results_secs: 300, // 5 minutes
23            item_docs_secs: 1800,     // 30 minutes
24        }
25    }
26}
27
28impl DocCacheTtl {
29    /// Create TTL config from `CacheConfig`
30    #[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/// Document cache service
41#[derive(Clone)]
42pub struct DocCache {
43    cache: Arc<dyn Cache>,
44    ttl: DocCacheTtl,
45}
46
47impl DocCache {
48    /// Create a new document cache with default TTL
49    pub fn new(cache: Arc<dyn Cache>) -> Self {
50        Self {
51            cache,
52            ttl: DocCacheTtl::default(),
53        }
54    }
55
56    /// Create a new document cache with custom TTL configuration
57    #[must_use]
58    pub fn with_ttl(cache: Arc<dyn Cache>, ttl: DocCacheTtl) -> Self {
59        Self { cache, ttl }
60    }
61
62    /// Get cached document
63    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    /// Set cached document
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if the cache operation fails
73    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    /// Get cached search results
90    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    /// Set cached search results
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if the cache operation fails
100    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    /// Get cached item documentation
117    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    /// Set cached item documentation
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if the cache operation fails
132    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    /// Clear cache
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the cache operation fails
154    pub async fn clear(&self) -> crate::error::Result<()> {
155        self.cache.clear().await
156    }
157
158    /// Build crate cache key
159    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    /// Build search cache key
168    fn search_cache_key(query: &str, limit: u32) -> String {
169        format!("search:{query}:{limit}")
170    }
171
172    /// Build item cache key
173    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        // 测试 crate 文档缓存
201        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        // 测试搜索结果缓存
209        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        // 测试项目文档缓存
217        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        // 测试清理
232        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}