nntp_proxy/cache/
article.rs1use crate::types::MessageId;
4use moka::future::Cache;
5use std::sync::Arc;
6use std::time::Duration;
7
8#[derive(Clone, Debug)]
10pub struct CachedArticle {
11 pub response: Arc<Vec<u8>>,
14}
15
16#[derive(Clone)]
21pub struct ArticleCache {
22 cache: Arc<Cache<Arc<str>, CachedArticle>>,
23}
24
25impl ArticleCache {
26 pub fn new(max_capacity: u64, ttl: Duration) -> Self {
32 let cache = Cache::builder()
33 .max_capacity(max_capacity)
34 .time_to_live(ttl)
35 .build();
36
37 Self {
38 cache: Arc::new(cache),
39 }
40 }
41
42 pub async fn get<'a>(&self, message_id: &MessageId<'a>) -> Option<CachedArticle> {
50 self.cache.get(message_id.without_brackets()).await
53 }
54
55 pub async fn insert<'a>(&self, message_id: MessageId<'a>, article: CachedArticle) {
59 let key: Arc<str> = message_id.without_brackets().into();
61 self.cache.insert(key, article).await;
62 }
63
64 pub async fn stats(&self) -> CacheStats {
66 CacheStats {
67 entry_count: self.cache.entry_count(),
68 weighted_size: self.cache.weighted_size(),
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct CacheStats {
76 pub entry_count: u64,
77 pub weighted_size: u64,
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::types::MessageId;
84 use std::time::Duration;
85
86 #[tokio::test]
87 async fn test_arc_str_borrow_lookup() {
88 let cache = ArticleCache::new(100, Duration::from_secs(300));
90
91 let msgid = MessageId::from_borrowed("<test123@example.com>").unwrap();
93 let article = CachedArticle {
94 response: Arc::new(b"220 0 0 <test123@example.com>\r\ntest body\r\n.\r\n".to_vec()),
95 };
96
97 cache.insert(msgid.clone(), article.clone()).await;
98
99 let msgid2 = MessageId::from_borrowed("<test123@example.com>").unwrap();
102 let retrieved = cache.get(&msgid2).await;
103
104 assert!(
105 retrieved.is_some(),
106 "Arc<str> cache should support Borrow<str> lookups"
107 );
108 assert_eq!(
109 retrieved.unwrap().response.as_ref(),
110 article.response.as_ref(),
111 "Retrieved article should match inserted article"
112 );
113 }
114
115 #[tokio::test]
116 async fn test_cache_miss() {
117 let cache = ArticleCache::new(100, Duration::from_secs(300));
118
119 let msgid = MessageId::from_borrowed("<nonexistent@example.com>").unwrap();
120 let result = cache.get(&msgid).await;
121
122 assert!(
123 result.is_none(),
124 "Cache lookup for non-existent key should return None"
125 );
126 }
127}