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}