in_memory_cache/
cache.rs

1//! The main cache interface.
2//!
3//! This module provides the primary `Cache` type that users interact with.
4//! It wraps the internal storage and provides a clean, thread-safe API.
5
6use bytes::Bytes;
7use std::sync::Arc;
8use std::time::Duration;
9
10use crate::config::CacheConfig;
11use crate::stats::{CacheStats, StatsSnapshot};
12use crate::storage::Db;
13
14/// A thread-safe, in-memory cache with optional TTL and LRU eviction.
15///
16/// # Features
17/// - **Thread-safe**: Can be safely shared across threads using `Arc<Cache>` or cloning.
18/// - **TTL support**: Entries can have optional time-to-live.
19/// - **LRU eviction**: When capacity is reached, least recently used entries are evicted.
20/// - **Statistics**: Track hits, misses, evictions, and more.
21///
22/// # Example
23/// ```
24/// use in_memory_cache::{Cache, CacheConfig};
25/// use std::time::Duration;
26///
27/// // Create a cache with max 1000 entries and 5 minute default TTL
28/// let config = CacheConfig::new()
29///     .max_capacity(1000)
30///     .default_ttl(Duration::from_secs(300))
31///     .build();
32///
33/// let cache = Cache::new(config);
34///
35/// // Basic operations
36/// cache.set("user:123", "Alice");
37/// if let Some(value) = cache.get("user:123") {
38///     println!("Found: {:?}", value);
39/// }
40///
41/// // With explicit TTL
42/// cache.set_with_ttl("session:abc", "data", Duration::from_secs(60));
43///
44/// // Check statistics
45/// let stats = cache.stats();
46/// println!("Hit rate: {:.1}%", stats.hit_rate);
47/// ```
48#[derive(Debug, Clone)]
49pub struct Cache {
50    /// Internal storage.
51    db: Arc<Db>,
52}
53
54impl Cache {
55    /// Create a new cache with the given configuration.
56    ///
57    /// # Arguments
58    /// * `config` - Configuration options for the cache.
59    ///
60    /// # Example
61    /// ```
62    /// use in_memory_cache::{Cache, CacheConfig};
63    ///
64    /// let cache = Cache::new(CacheConfig::default());
65    /// ```
66    pub fn new(config: CacheConfig) -> Self {
67        Self {
68            db: Arc::new(Db::new(config)),
69        }
70    }
71
72    /// Get a value from the cache.
73    ///
74    /// Returns `None` if the key doesn't exist or has expired.
75    /// Accessing a key updates its last-accessed time for LRU tracking.
76    ///
77    /// # Arguments
78    /// * `key` - The key to look up.
79    ///
80    /// # Example
81    /// ```
82    /// use in_memory_cache::{Cache, CacheConfig};
83    ///
84    /// let cache = Cache::new(CacheConfig::default());
85    /// cache.set("key", "value");
86    ///
87    /// match cache.get("key") {
88    ///     Some(value) => println!("Found: {:?}", value),
89    ///     None => println!("Not found"),
90    /// }
91    /// ```
92    pub fn get(&self, key: &str) -> Option<Bytes> {
93        self.db.get(key)
94    }
95
96    /// Set a value in the cache.
97    ///
98    /// If a `default_ttl` is configured, entries will use that TTL.
99    /// Otherwise, entries will not expire.
100    ///
101    /// # Arguments
102    /// * `key` - The key to store the value under.
103    /// * `value` - The value to store (anything that can be converted to `Bytes`).
104    ///
105    /// # Example
106    /// ```
107    /// use in_memory_cache::{Cache, CacheConfig};
108    ///
109    /// let cache = Cache::new(CacheConfig::default());
110    /// cache.set("string_key", "string value");
111    /// cache.set("bytes_key", vec![1, 2, 3, 4]);
112    /// ```
113    pub fn set(&self, key: impl Into<String>, value: impl Into<Bytes>) {
114        self.db.set(key, value);
115    }
116
117    /// Set a value in the cache with a specific TTL.
118    ///
119    /// The entry will be removed after the specified duration.
120    ///
121    /// # Arguments
122    /// * `key` - The key to store the value under.
123    /// * `value` - The value to store.
124    /// * `ttl` - How long the entry should live.
125    ///
126    /// # Example
127    /// ```
128    /// use in_memory_cache::{Cache, CacheConfig};
129    /// use std::time::Duration;
130    ///
131    /// let cache = Cache::new(CacheConfig::default());
132    /// cache.set_with_ttl("session", "data", Duration::from_secs(3600));
133    /// ```
134    pub fn set_with_ttl(&self, key: impl Into<String>, value: impl Into<Bytes>, ttl: Duration) {
135        self.db.set_with_ttl(key, value, ttl);
136    }
137
138    /// Delete a key from the cache.
139    ///
140    /// Returns `true` if the key existed and was removed.
141    ///
142    /// # Arguments
143    /// * `key` - The key to delete.
144    ///
145    /// # Example
146    /// ```
147    /// use in_memory_cache::{Cache, CacheConfig};
148    ///
149    /// let cache = Cache::new(CacheConfig::default());
150    /// cache.set("key", "value");
151    /// assert!(cache.delete("key"));
152    /// assert!(!cache.delete("key")); // Already deleted
153    /// ```
154    pub fn delete(&self, key: &str) -> bool {
155        self.db.delete(key)
156    }
157
158    /// Check if a key exists in the cache.
159    ///
160    /// Returns `false` if the key doesn't exist or has expired.
161    /// Note: This does NOT update the LRU access time.
162    ///
163    /// # Arguments
164    /// * `key` - The key to check.
165    ///
166    /// # Example
167    /// ```
168    /// use in_memory_cache::{Cache, CacheConfig};
169    ///
170    /// let cache = Cache::new(CacheConfig::default());
171    /// assert!(!cache.contains("key"));
172    /// cache.set("key", "value");
173    /// assert!(cache.contains("key"));
174    /// ```
175    pub fn contains(&self, key: &str) -> bool {
176        self.db.contains(key)
177    }
178
179    /// Get the number of entries in the cache.
180    ///
181    /// Note: This may include expired entries that haven't been
182    /// cleaned up yet by lazy expiration or background cleanup.
183    ///
184    /// # Example
185    /// ```
186    /// use in_memory_cache::{Cache, CacheConfig};
187    ///
188    /// let cache = Cache::new(CacheConfig::default());
189    /// assert_eq!(cache.len(), 0);
190    /// cache.set("key", "value");
191    /// assert_eq!(cache.len(), 1);
192    /// ```
193    pub fn len(&self) -> usize {
194        self.db.len()
195    }
196
197    /// Check if the cache is empty.
198    ///
199    /// # Example
200    /// ```
201    /// use in_memory_cache::{Cache, CacheConfig};
202    ///
203    /// let cache = Cache::new(CacheConfig::default());
204    /// assert!(cache.is_empty());
205    /// cache.set("key", "value");
206    /// assert!(!cache.is_empty());
207    /// ```
208    pub fn is_empty(&self) -> bool {
209        self.db.is_empty()
210    }
211
212    /// Remove all entries from the cache.
213    ///
214    /// # Example
215    /// ```
216    /// use in_memory_cache::{Cache, CacheConfig};
217    ///
218    /// let cache = Cache::new(CacheConfig::default());
219    /// cache.set("key1", "value1");
220    /// cache.set("key2", "value2");
221    /// cache.clear();
222    /// assert!(cache.is_empty());
223    /// ```
224    pub fn clear(&self) {
225        self.db.clear();
226    }
227
228    /// Get a snapshot of the cache statistics.
229    ///
230    /// Returns a point-in-time snapshot of hits, misses, evictions, etc.
231    ///
232    /// # Example
233    /// ```
234    /// use in_memory_cache::{Cache, CacheConfig};
235    ///
236    /// let cache = Cache::new(CacheConfig::default());
237    /// cache.set("key", "value");
238    /// let _ = cache.get("key");        // Hit
239    /// let _ = cache.get("missing");    // Miss
240    ///
241    /// let stats = cache.stats();
242    /// println!("Hits: {}, Misses: {}", stats.hits, stats.misses);
243    /// ```
244    pub fn stats(&self) -> StatsSnapshot {
245        self.db.stats().snapshot()
246    }
247
248    /// Manually trigger cleanup of expired entries.
249    ///
250    /// Returns the number of entries that were removed.
251    /// This is useful if you want to control when cleanup happens
252    /// instead of relying on lazy expiration or background cleanup.
253    ///
254    /// # Example
255    /// ```
256    /// use in_memory_cache::{Cache, CacheConfig};
257    /// use std::time::Duration;
258    ///
259    /// let cache = Cache::new(CacheConfig::default());
260    /// cache.set_with_ttl("key", "value", Duration::from_millis(1));
261    /// std::thread::sleep(Duration::from_millis(10));
262    /// let removed = cache.cleanup_expired();
263    /// println!("Removed {} expired entries", removed);
264    /// ```
265    pub fn cleanup_expired(&self) -> usize {
266        self.db.cleanup_expired()
267    }
268
269    /// Get a reference to the internal statistics counter.
270    ///
271    /// This is useful for integrating with external metrics systems.
272    pub fn stats_ref(&self) -> Arc<CacheStats> {
273        self.db.stats()
274    }
275}
276
277impl Default for Cache {
278    fn default() -> Self {
279        Self::new(CacheConfig::default())
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_cache_basic_operations() {
289        let cache = Cache::default();
290
291        cache.set("key", "value");
292        assert_eq!(cache.get("key"), Some(Bytes::from("value")));
293        assert!(cache.contains("key"));
294
295        cache.delete("key");
296        assert!(!cache.contains("key"));
297    }
298
299    #[test]
300    fn test_cache_is_clone() {
301        let cache1 = Cache::default();
302        cache1.set("key", "value");
303
304        let cache2 = cache1.clone();
305
306        // Both point to the same underlying data
307        assert_eq!(cache2.get("key"), Some(Bytes::from("value")));
308
309        cache2.set("key2", "value2");
310        assert_eq!(cache1.get("key2"), Some(Bytes::from("value2")));
311    }
312
313    #[test]
314    fn test_cache_stats() {
315        let cache = Cache::default();
316
317        cache.set("key", "value");
318        let _ = cache.get("key");
319        let _ = cache.get("missing");
320
321        let stats = cache.stats();
322        assert_eq!(stats.hits, 1);
323        assert_eq!(stats.misses, 1);
324    }
325
326    #[test]
327    fn test_cache_thread_safety() {
328        use std::thread;
329
330        let cache = Cache::default();
331        let mut handles = vec![];
332
333        // Spawn multiple threads that read/write concurrently
334        for i in 0..10 {
335            let cache = cache.clone();
336            let handle = thread::spawn(move || {
337                for j in 0..100 {
338                    let key = format!("key_{}", j);
339                    cache.set(key.clone(), format!("value_{}_{}", i, j));
340                    let _ = cache.get(&key);
341                }
342            });
343            handles.push(handle);
344        }
345
346        for handle in handles {
347            handle.join().unwrap();
348        }
349
350        // Should have completed without panics
351        assert!(!cache.is_empty());
352    }
353}