r_cache/
cache.rs

1use dashmap::DashMap;
2
3use crate::item::Item;
4use std::hash::Hash;
5use std::time::Duration;
6
7pub struct Cache<T, V> {
8    items: DashMap<T, Item<V>>,
9    item_duration: Option<Duration>,
10}
11
12impl<T, V> Cache<T, V>
13where
14    T: Eq + Hash,
15    V: Clone,
16{
17    /// Construct a new `Cache` with a default item expiration time.
18    /// An item duration of `None` means items do not expire by default.
19    ///
20    /// # Example
21    ///
22    /// ```rust
23    /// use async_std::sync::Arc;
24    /// use async_std::task;
25    /// use r_cache::cache::Cache;
26    /// use std::time::Duration;
27    ///
28    /// const KEY: i8 = 0;
29    /// const VALUE: &str = "VALUE";
30    ///
31    /// #[async_std::main]
32    /// async fn main() {
33    ///     let cache = Arc::new(Cache::new(Some(Duration::from_secs(5 * 60))));
34    ///     task::spawn({
35    ///         let cache = Arc::clone(&cache);
36    ///         async move {
37    ///             loop {
38    ///                 task::sleep(Duration::from_secs(10 * 60)).await;
39    ///                 cache.remove_expired();
40    ///             }
41    ///         }
42    ///     });
43    ///
44    ///     cache.set(KEY, VALUE, None);
45    ///
46    ///     assert_eq!(VALUE, cache.get(&KEY).unwrap())
47    /// }
48    /// ```
49    pub fn new(item_duration: Option<Duration>) -> Self {
50        Cache {
51            items: DashMap::new(),
52            item_duration,
53        }
54    }
55
56    /// Get a cache item associated with a given key.
57    pub fn get(&self, key: &T) -> Option<V>
58    where
59        T: Eq + Hash,
60        V: Clone,
61    {
62        self.items
63            .get(key)
64            .filter(|item| !item.expired())
65            .map(|item| item.object.clone())
66    }
67
68    /// Set an item in the cache with an associated key.
69    /// The item will have the default cache expiration time if custom duration of `None` is given.
70    pub fn set(&self, key: T, value: V, custom_duration: Option<Duration>) -> Option<V>
71    where
72        T: Eq + Hash,
73    {
74        self.items
75            .insert(
76                key,
77                Item::new(value, custom_duration.or(self.item_duration)),
78            )
79            .map(|item| item.object)
80    }
81
82    /// Remove all expired items from the cache.
83    pub fn remove_expired(&self)
84    where
85        T: Eq + Hash + Clone,
86    {
87        self.items.retain(|_, item| !item.expired());
88        self.shrink();
89    }
90
91    /// Remove an item from the cache associated with a given key.
92    pub fn remove(&self, key: &T) -> Option<V>
93    where
94        T: Eq + Hash,
95    {
96        let item = self.items.remove(key).map(|(_, item)| item.object);
97        self.shrink();
98
99        item
100    }
101
102    /// Clear the entire cache of all items regardless of expiry times.
103    pub fn clear(&self) {
104        self.items.clear();
105        self.shrink();
106    }
107
108    /// Reclaim memory from removed / cleared items
109    pub fn shrink(&self)
110    where
111        T: Eq + Hash,
112    {
113        self.items.shrink_to_fit()
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use crate::cache::Cache;
120    use std::time::Duration;
121
122    const KEY: i8 = 0;
123    const VALUE: &str = "VALUE";
124
125    #[test]
126    fn set_and_get_value_with_default_duration() {
127        let cache = Cache::new(Some(Duration::from_secs(2)));
128        cache.set(KEY, VALUE, None);
129        let value = cache.get(&KEY);
130        match value {
131            Some(value) => assert_eq!(value, VALUE),
132            None => panic!("value was not found in cache"),
133        };
134    }
135
136    #[test]
137    fn set_and_get_value_without_duration() {
138        let cache = Cache::new(None);
139        cache.set(KEY, VALUE, None);
140        let value = cache.get(&KEY);
141        match value {
142            Some(value) => assert_eq!(value, VALUE),
143            None => panic!("value was not found in cache"),
144        };
145    }
146
147    #[test]
148    fn set_and_get_value_with_custom_duration() {
149        let cache = Cache::new(Some(Duration::from_secs(0)));
150        cache.set(KEY, VALUE, Some(Duration::from_secs(2)));
151        let value = cache.get(&KEY);
152        match value {
153            Some(value) => assert_eq!(value, VALUE),
154            None => panic!("value was not found in cache"),
155        };
156    }
157
158    #[test]
159    fn set_do_not_get_expired_value() {
160        let cache = Cache::new(Some(Duration::from_secs(0)));
161        cache.set(KEY, VALUE, None);
162        let value = cache.get(&KEY);
163        if value.is_some() {
164            panic!("found expired value in cache")
165        };
166    }
167
168    #[test]
169    fn set_replace_existing_value() {
170        const NEW_VALUE: &str = "NEW_VALUE";
171        let cache = Cache::new(Some(Duration::from_secs(2)));
172        cache.set(KEY, VALUE, None);
173        cache.set(KEY, NEW_VALUE, None);
174        let value = cache.get(&KEY);
175        match value {
176            Some(value) => assert_eq!(value, NEW_VALUE),
177            None => panic!("value was not found in cache"),
178        };
179    }
180
181    #[test]
182    fn remove_expired_item() {
183        let cache = Cache::new(Some(Duration::from_secs(0)));
184        cache.set(KEY, VALUE, None);
185        cache.remove_expired();
186        if cache.items.get(&KEY).is_some() {
187            panic!("found expired item in cache")
188        };
189    }
190
191    #[test]
192    fn remove_expired_do_not_remove_not_expired_item() {
193        let cache = Cache::new(Some(Duration::from_secs(2)));
194        cache.set(KEY, VALUE, None);
195        cache.remove_expired();
196        if cache.items.get(&KEY).is_none() {
197            panic!("could not find not expired item in cache")
198        };
199    }
200
201    #[test]
202    fn clear_not_expired_item() {
203        let cache = Cache::new(Some(Duration::from_secs(2)));
204        cache.set(KEY, VALUE, None);
205        cache.clear();
206        if cache.items.get(&KEY).is_some() {
207            panic!("found item in cache")
208        };
209    }
210
211    #[test]
212    fn remove_remove_expired_item() {
213        let cache = Cache::new(Some(Duration::from_secs(2)));
214        cache.set(KEY, VALUE, None);
215        if let None = cache.remove(&KEY) {
216            panic!("none returned from removing existing value")
217        };
218        if cache.items.get(&KEY).is_some() {
219            panic!("found not expired item in cache")
220        };
221    }
222
223    #[test]
224    fn remove_return_none_if_not_found() {
225        let cache: Cache<i8, &str> = Cache::new(Some(Duration::from_secs(2)));
226        if let Some(_) = cache.remove(&KEY) {
227            panic!("some value was returned from remove")
228        };
229    }
230}