simple_cacher/lib.rs
1//! # Simple Cacher
2//!
3//! A high-performance, flexible caching library for Rust with custom matching capabilities
4//! and automatic expiration.
5//!
6//! ## Key Features
7//!
8//! - **Fast O(1) exact key lookups** using IndexMap
9//! - **Custom pattern matching** via the `Matcher<T>` trait
10//! - **Automatic expiration** with configurable TTL per entry
11//! - **Size-limited caches** with FIFO eviction (oldest entries removed first)
12//! - **Lazy cleanup** - expired entries removed on access
13//! - **Zero-copy value access** through references
14//!
15//! ## Quick Start
16//!
17//! ```rust
18//! use simple_cacher::*;
19//! use std::time::Duration;
20//!
21//! // Create a cache with 5-minute TTL
22//! let mut cache = SimpleCacher::new(Duration::from_secs(300));
23//!
24//! // Insert data
25//! cache.insert("user:123".to_string(), "Alice".to_string());
26//!
27//! // Retrieve data
28//! match cache.get(&"user:123".to_string()) {
29//! Ok(entry) => println!("Found: {}", entry.value()),
30//! Err(SimpleCacheError::NotFound) => println!("Not found"),
31//! Err(SimpleCacheError::Expired) => println!("Expired"),
32//! }
33//! ```
34//!
35//! ## Custom Matching
36//!
37//! ```rust
38//! use simple_cacher::*;
39//! use std::time::Duration;
40//!
41//! let mut cache = SimpleCacher::new(Duration::from_secs(300));
42//! cache.insert("user:alice".to_string(), "Alice Johnson".to_string());
43//! cache.insert("admin:bob".to_string(), "Bob Admin".to_string());
44//!
45//! // Find by prefix
46//! let user_matcher = PrefixMatcher::new("user:");
47//! if let Ok(user) = cache.get_by_matcher(&user_matcher) {
48//! println!("Found user: {}", user.value());
49//! }
50//! ```
51
52use indexmap::IndexMap;
53use std::time::{Duration, Instant};
54
55/// Error types returned by cache operations.
56#[derive(Debug, Clone, PartialEq)]
57pub enum SimpleCacheError {
58 /// The requested key was not found in the cache.
59 NotFound,
60 /// The entry was found but has expired and was automatically removed.
61 Expired,
62}
63
64impl std::fmt::Display for SimpleCacheError {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 match self {
67 SimpleCacheError::NotFound => write!(f, "Cache entry not found"),
68 SimpleCacheError::Expired => write!(f, "Cache entry has expired"),
69 }
70 }
71}
72
73impl std::error::Error for SimpleCacheError {}
74
75/// A cached value with metadata about its creation time and expiration.
76///
77/// This struct wraps the actual cached value along with timing information
78/// to support automatic expiration and cache management.
79///
80/// # Examples
81///
82/// ```rust
83/// use simple_cacher::*;
84/// use std::time::Duration;
85///
86/// let mut cache = SimpleCacher::new(Duration::from_secs(60));
87/// cache.insert("key".to_string(), "value".to_string());
88///
89/// if let Ok(entry) = cache.get(&"key".to_string()) {
90/// println!("Value: {}", entry.value());
91/// println!("Age: {:?}", entry.age());
92/// println!("Expired: {}", entry.is_expired());
93/// }
94/// ```
95pub struct SimpleCacheObject<U> {
96 created_at: Instant,
97 value: U,
98 max_age: Duration,
99}
100
101impl<U> SimpleCacheObject<U> {
102 /// Creates a new cache object with the given value and maximum age.
103 fn new(value: U, max_age: Duration) -> Self {
104 Self {
105 created_at: Instant::now(),
106 value,
107 max_age,
108 }
109 }
110
111 /// Returns `true` if this cache entry has expired based on its max age.
112 ///
113 /// # Examples
114 ///
115 /// ```rust
116 /// use simple_cacher::*;
117 /// use std::time::Duration;
118 ///
119 /// let mut cache = SimpleCacher::new(Duration::from_millis(100));
120 /// cache.insert("key".to_string(), "value".to_string());
121 ///
122 /// if let Ok(entry) = cache.get(&"key".to_string()) {
123 /// // Should not be expired immediately
124 /// assert!(!entry.is_expired());
125 /// }
126 /// ```
127 pub fn is_expired(&self) -> bool {
128 self.created_at.elapsed() > self.max_age
129 }
130
131 /// Returns a reference to the cached value.
132 ///
133 /// # Examples
134 ///
135 /// ```rust
136 /// use simple_cacher::*;
137 /// use std::time::Duration;
138 ///
139 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
140 /// cache.insert("greeting".to_string(), "Hello, World!".to_string());
141 ///
142 /// if let Ok(entry) = cache.get(&"greeting".to_string()) {
143 /// assert_eq!(entry.value(), "Hello, World!");
144 /// }
145 /// ```
146 pub fn value(&self) -> &U {
147 &self.value
148 }
149
150 /// Returns a mutable reference to the cached value.
151 ///
152 /// # Examples
153 ///
154 /// ```rust
155 /// use simple_cacher::*;
156 /// use std::time::Duration;
157 ///
158 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
159 /// cache.insert("counter".to_string(), 0u32);
160 ///
161 /// if let Ok(entry) = cache.get_mut(&"counter".to_string()) {
162 /// *entry.value_mut() += 1;
163 /// assert_eq!(*entry.value(), 1);
164 /// }
165 /// ```
166 pub fn value_mut(&mut self) -> &mut U {
167 &mut self.value
168 }
169
170 /// Consumes the cache object and returns the cached value.
171 ///
172 /// # Examples
173 ///
174 /// ```rust
175 /// use simple_cacher::*;
176 /// use std::time::Duration;
177 ///
178 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
179 /// cache.insert("data".to_string(), vec![1, 2, 3]);
180 ///
181 /// if let Some(entry) = cache.remove(&"data".to_string()) {
182 /// let data = entry.into_value();
183 /// assert_eq!(data, vec![1, 2, 3]);
184 /// }
185 /// ```
186 pub fn into_value(self) -> U {
187 self.value
188 }
189
190 /// Returns the age of this cache entry (time since creation).
191 ///
192 /// # Examples
193 ///
194 /// ```rust
195 /// use simple_cacher::*;
196 /// use std::time::Duration;
197 ///
198 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
199 /// cache.insert("key".to_string(), "value".to_string());
200 ///
201 /// if let Ok(entry) = cache.get(&"key".to_string()) {
202 /// let age = entry.age();
203 /// assert!(age.as_millis() >= 0);
204 /// }
205 /// ```
206 pub fn age(&self) -> Duration {
207 self.created_at.elapsed()
208 }
209
210 /// Returns the instant when this entry was created.
211 ///
212 /// # Examples
213 ///
214 /// ```rust
215 /// use simple_cacher::*;
216 /// use std::time::{Duration, Instant};
217 ///
218 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
219 /// let before = Instant::now();
220 /// cache.insert("key".to_string(), "value".to_string());
221 /// let after = Instant::now();
222 ///
223 /// if let Ok(entry) = cache.get(&"key".to_string()) {
224 /// let created = entry.created_at();
225 /// assert!(created >= before && created <= after);
226 /// }
227 /// ```
228 pub fn created_at(&self) -> Instant {
229 self.created_at
230 }
231}
232
233/// Trait for implementing custom matching logic against cache keys.
234///
235/// This trait allows you to define complex search patterns for finding cached entries
236/// beyond simple exact key matching. Implementations can match based on patterns,
237/// ranges, regular expressions, or any custom logic.
238///
239/// # Examples
240///
241/// ```rust
242/// use simple_cacher::*;
243/// use std::time::Duration;
244///
245/// // Custom matcher for email domains
246/// struct DomainMatcher {
247/// domain: String,
248/// }
249///
250/// impl Matcher<String> for DomainMatcher {
251/// fn matches(&self, email: &String) -> bool {
252/// email.ends_with(&format!("@{}", self.domain))
253/// }
254/// }
255///
256/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
257/// cache.insert("alice@company.com".to_string(), "Alice".to_string());
258/// cache.insert("bob@company.com".to_string(), "Bob".to_string());
259/// cache.insert("charlie@gmail.com".to_string(), "Charlie".to_string());
260///
261/// let company_matcher = DomainMatcher { domain: "company.com".to_string() };
262/// let company_users = cache.get_all_by_matcher(&company_matcher);
263/// assert_eq!(company_users.len(), 2);
264/// ```
265pub trait Matcher<T> {
266 /// Returns `true` if the given key matches this matcher's criteria.
267 ///
268 /// # Arguments
269 ///
270 /// * `key` - The cache key to test against this matcher
271 ///
272 /// # Returns
273 ///
274 /// `true` if the key matches, `false` otherwise
275 fn matches(&self, key: &T) -> bool;
276}
277
278/// A high-performance cache with automatic expiration and custom matching capabilities.
279///
280/// `SimpleCacher` provides fast O(1) exact key lookups using an IndexMap, along with
281/// flexible O(n) pattern matching via the `Matcher` trait. Entries automatically expire
282/// based on configurable TTL values, and the cache can be size-limited with FIFO eviction.
283///
284/// # Type Parameters
285///
286/// * `T` - The type of keys (must implement `Clone + Eq + Hash`)
287/// * `U` - The type of cached values
288///
289/// # Examples
290///
291/// ## Basic Usage
292///
293/// ```rust
294/// use simple_cacher::*;
295/// use std::time::Duration;
296///
297/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
298///
299/// // Insert a value
300/// cache.insert("user:123".to_string(), "Alice Johnson".to_string());
301///
302/// // Retrieve it
303/// match cache.get(&"user:123".to_string()) {
304/// Ok(entry) => println!("Found: {}", entry.value()),
305/// Err(SimpleCacheError::NotFound) => println!("Not found"),
306/// Err(SimpleCacheError::Expired) => println!("Expired and removed"),
307/// }
308/// ```
309///
310/// ## Size-Limited Cache
311///
312/// ```rust
313/// use simple_cacher::*;
314/// use std::time::Duration;
315///
316/// // Cache with max 1000 entries
317/// let mut cache = SimpleCacher::with_max_size(Duration::from_secs(300), 1000);
318///
319/// // When full, oldest entries are automatically removed
320/// for i in 0..1500 {
321/// cache.insert(format!("key_{}", i), format!("value_{}", i));
322/// }
323///
324/// assert_eq!(cache.len(), 1000); // Only newest 1000 entries remain
325/// ```
326pub struct SimpleCacher<T, U> {
327 cache: IndexMap<T, SimpleCacheObject<U>>,
328 max_age: Duration,
329 max_size: Option<usize>,
330}
331
332impl<T, U> SimpleCacher<T, U>
333where
334 T: Clone + Eq + std::hash::Hash,
335{
336 /// Creates a new cache with the specified maximum age for entries.
337 ///
338 /// All entries inserted into this cache will expire after the given duration,
339 /// unless overridden with `insert_with_ttl`.
340 ///
341 /// # Arguments
342 ///
343 /// * `max_age` - Default time-to-live for cache entries
344 ///
345 /// # Examples
346 ///
347 /// ```rust
348 /// use simple_cacher::*;
349 /// use std::time::Duration;
350 ///
351 /// let mut cache = SimpleCacher::new(Duration::from_secs(300)); // 5 minutes
352 /// cache.insert("key".to_string(), "value".to_string());
353 /// ```
354 pub fn new(max_age: Duration) -> Self {
355 Self {
356 cache: IndexMap::new(),
357 max_age,
358 max_size: None,
359 }
360 }
361
362 /// Creates a new cache with both maximum age and maximum size constraints.
363 ///
364 /// When the cache exceeds `max_size` entries, the oldest entries are automatically
365 /// removed to make room for new ones (FIFO eviction policy).
366 ///
367 /// # Arguments
368 ///
369 /// * `max_age` - Default time-to-live for cache entries
370 /// * `max_size` - Maximum number of entries to keep in the cache
371 ///
372 /// # Examples
373 ///
374 /// ```rust
375 /// use simple_cacher::*;
376 /// use std::time::Duration;
377 ///
378 /// let mut cache: SimpleCacher<String,String> = SimpleCacher::with_max_size(
379 /// Duration::from_secs(300), // 5 minutes TTL
380 /// 1000 // max 1000 entries
381 /// );
382 /// ```
383 pub fn with_max_size(max_age: Duration, max_size: usize) -> Self {
384 Self {
385 cache: IndexMap::new(),
386 max_age,
387 max_size: Some(max_size),
388 }
389 }
390
391 /// Retrieves an entry by exact key match in O(1) time.
392 ///
393 /// If the entry exists but has expired, it will be automatically removed
394 /// from the cache and `SimpleCacheError::Expired` will be returned.
395 ///
396 /// # Arguments
397 ///
398 /// * `key` - The key to look up
399 ///
400 /// # Returns
401 ///
402 /// * `Ok(&SimpleCacheObject<U>)` - The cached entry if found and not expired
403 /// * `Err(SimpleCacheError::NotFound)` - The key doesn't exist
404 /// * `Err(SimpleCacheError::Expired)` - The entry existed but expired (now removed)
405 ///
406 /// # Examples
407 ///
408 /// ```rust
409 /// use simple_cacher::*;
410 /// use std::time::Duration;
411 ///
412 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
413 /// cache.insert("user:123".to_string(), "Alice".to_string());
414 ///
415 /// match cache.get(&"user:123".to_string()) {
416 /// Ok(entry) => {
417 /// println!("Found: {}", entry.value());
418 /// println!("Age: {:?}", entry.age());
419 /// }
420 /// Err(SimpleCacheError::NotFound) => println!("User not found"),
421 /// Err(SimpleCacheError::Expired) => println!("User data expired"),
422 /// }
423 /// ```
424 pub fn get(&mut self, key: &T) -> Result<&SimpleCacheObject<U>, SimpleCacheError> {
425 // Check if entry exists and if it's expired
426 let should_remove = match self.cache.get(key) {
427 Some(obj) => obj.is_expired(),
428 None => return Err(SimpleCacheError::NotFound),
429 };
430
431 if should_remove {
432 self.cache.shift_remove(key);
433 return Err(SimpleCacheError::Expired);
434 }
435
436 // Safe to get immutable reference now
437 Ok(self.cache.get(key).unwrap())
438 }
439
440 /// Retrieves a mutable reference to an entry by exact key match.
441 ///
442 /// Similar to `get()`, but returns a mutable reference that allows you to modify
443 /// the cached value in place. Expired entries are automatically removed.
444 ///
445 /// # Arguments
446 ///
447 /// * `key` - The key to look up
448 ///
449 /// # Returns
450 ///
451 /// * `Ok(&mut SimpleCacheObject<U>)` - Mutable reference to the cached entry
452 /// * `Err(SimpleCacheError::NotFound)` - The key doesn't exist
453 /// * `Err(SimpleCacheError::Expired)` - The entry existed but expired (now removed)
454 ///
455 /// # Examples
456 ///
457 /// ```rust
458 /// use simple_cacher::*;
459 /// use std::time::Duration;
460 ///
461 /// let mut cache = SimpleCacher::new(Duration::from_secs(60));
462 /// cache.insert("counter".to_string(), 0u32);
463 ///
464 /// if let Ok(entry) = cache.get_mut(&"counter".to_string()) {
465 /// *entry.value_mut() += 1;
466 /// assert_eq!(*entry.value(), 1);
467 /// }
468 /// ```
469 pub fn get_mut(&mut self, key: &T) -> Result<&mut SimpleCacheObject<U>, SimpleCacheError> {
470 // Check if exists and if it's expired first
471 let should_remove = match self.cache.get(key) {
472 Some(obj) => obj.is_expired(),
473 None => return Err(SimpleCacheError::NotFound),
474 };
475
476 if should_remove {
477 self.cache.shift_remove(key);
478 return Err(SimpleCacheError::Expired);
479 }
480
481 // Safe to get mutable reference now
482 Ok(self.cache.get_mut(key).unwrap())
483 }
484
485 /// Finds the first entry matching the given matcher in O(n) time.
486 ///
487 /// This method iterates through all cache entries and returns the first one
488 /// that matches the provided matcher's criteria. Expired entries encountered
489 /// during the search are automatically cleaned up.
490 ///
491 /// # Arguments
492 ///
493 /// * `matcher` - An implementation of `Matcher<T>` that defines the search criteria
494 ///
495 /// # Returns
496 ///
497 /// * `Ok(&SimpleCacheObject<U>)` - The first matching entry found
498 /// * `Err(SimpleCacheError::NotFound)` - No matching entries found
499 ///
500 /// # Examples
501 ///
502 /// ```rust
503 /// use simple_cacher::*;
504 /// use std::time::Duration;
505 ///
506 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
507 /// cache.insert("user:alice".to_string(), "Alice".to_string());
508 /// cache.insert("user:bob".to_string(), "Bob".to_string());
509 /// cache.insert("admin:charlie".to_string(), "Charlie".to_string());
510 ///
511 /// let user_matcher = PrefixMatcher::new("user:");
512 /// if let Ok(user) = cache.get_by_matcher(&user_matcher) {
513 /// println!("Found user: {}", user.value());
514 /// }
515 /// ```
516 pub fn get_by_matcher<M>(
517 &mut self,
518 matcher: &M,
519 ) -> Result<&SimpleCacheObject<U>, SimpleCacheError>
520 where
521 M: Matcher<T>,
522 {
523 // First pass: collect expired keys and find match
524 let mut expired_keys = Vec::new();
525 let mut found_key = None;
526
527 for (key, obj) in &self.cache {
528 if obj.is_expired() {
529 expired_keys.push(key.clone());
530 } else if found_key.is_none() && matcher.matches(key) {
531 found_key = Some(key.clone());
532 }
533 }
534
535 // Clean up expired entries
536 for key in expired_keys {
537 self.cache.shift_remove(&key);
538 }
539
540 // Return the found entry (get fresh reference after cleanup)
541 if let Some(key) = found_key {
542 self.cache.get(&key).ok_or(SimpleCacheError::NotFound)
543 } else {
544 Err(SimpleCacheError::NotFound)
545 }
546 }
547
548 /// Finds all entries matching the given matcher.
549 ///
550 /// This method returns all cache entries that match the provided matcher's criteria.
551 /// The cache is automatically cleaned of expired entries before searching.
552 ///
553 /// # Arguments
554 ///
555 /// * `matcher` - An implementation of `Matcher<T>` that defines the search criteria
556 ///
557 /// # Returns
558 ///
559 /// A vector of tuples containing references to matching keys and their cached values.
560 /// The vector may be empty if no matches are found.
561 ///
562 /// # Examples
563 ///
564 /// ```rust
565 /// use simple_cacher::*;
566 /// use std::time::Duration;
567 ///
568 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
569 /// cache.insert("user:alice".to_string(), "Alice".to_string());
570 /// cache.insert("user:bob".to_string(), "Bob".to_string());
571 /// cache.insert("admin:charlie".to_string(), "Charlie".to_string());
572 ///
573 /// let user_matcher = PrefixMatcher::new("user:");
574 /// let users = cache.get_all_by_matcher(&user_matcher);
575 /// println!("Found {} users", users.len());
576 /// ```
577 pub fn get_all_by_matcher<M>(&mut self, matcher: &M) -> Vec<(&T, &SimpleCacheObject<U>)>
578 where
579 M: Matcher<T>,
580 {
581 // Clean up expired entries first
582 self.cleanup_expired();
583
584 self.cache
585 .iter()
586 .filter(|(key, obj)| !obj.is_expired() && matcher.matches(key))
587 .collect()
588 }
589
590 /// Inserts a new entry into the cache with the default TTL.
591 ///
592 /// If the cache has a size limit and is at capacity, the oldest entry
593 /// will be automatically removed to make room for the new entry (FIFO eviction).
594 /// If an entry with the same key already exists, it will be replaced.
595 ///
596 /// # Arguments
597 ///
598 /// * `key` - The key to associate with the value
599 /// * `value` - The value to cache
600 ///
601 /// # Examples
602 ///
603 /// ```rust
604 /// use simple_cacher::*;
605 /// use std::time::Duration;
606 ///
607 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
608 /// cache.insert("user:123".to_string(), "Alice Johnson".to_string());
609 /// ```
610 pub fn insert(&mut self, key: T, value: U) {
611 // Enforce max size by removing oldest entries (FIFO)
612 if let Some(max_size) = self.max_size {
613 while self.cache.len() >= max_size {
614 self.cache.shift_remove_index(0);
615 }
616 }
617
618 let cache_obj = SimpleCacheObject::new(value, self.max_age);
619 self.cache.insert(key, cache_obj);
620 }
621
622 /// Inserts a new entry into the cache with a custom TTL.
623 ///
624 /// This allows you to override the default TTL for specific entries,
625 /// useful for caching data with different freshness requirements.
626 ///
627 /// # Arguments
628 ///
629 /// * `key` - The key to associate with the value
630 /// * `value` - The value to cache
631 /// * `ttl` - Custom time-to-live for this specific entry
632 ///
633 /// # Examples
634 ///
635 /// ```rust
636 /// use simple_cacher::*;
637 /// use std::time::Duration;
638 ///
639 /// let mut cache = SimpleCacher::new(Duration::from_secs(300)); // Default 5 min
640 ///
641 /// // Cache with custom 1-hour TTL
642 /// cache.insert_with_ttl(
643 /// "important_data".to_string(),
644 /// "critical information".to_string(),
645 /// Duration::from_secs(3600)
646 /// );
647 /// ```
648 pub fn insert_with_ttl(&mut self, key: T, value: U, ttl: Duration) {
649 if let Some(max_size) = self.max_size {
650 while self.cache.len() >= max_size {
651 self.cache.shift_remove_index(0);
652 }
653 }
654
655 let cache_obj = SimpleCacheObject::new(value, ttl);
656 self.cache.insert(key, cache_obj);
657 }
658
659 /// Removes an entry by key and returns it if it existed.
660 ///
661 /// This method removes the entry regardless of whether it has expired.
662 /// Returns `None` if the key doesn't exist.
663 ///
664 /// # Arguments
665 ///
666 /// * `key` - The key of the entry to remove
667 ///
668 /// # Returns
669 ///
670 /// `Some(SimpleCacheObject<U>)` if the key existed, `None` otherwise
671 ///
672 /// # Examples
673 ///
674 /// ```rust
675 /// use simple_cacher::*;
676 /// use std::time::Duration;
677 ///
678 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
679 /// cache.insert("temp_data".to_string(), "temporary".to_string());
680 ///
681 /// if let Some(removed) = cache.remove(&"temp_data".to_string()) {
682 /// println!("Removed: {}", removed.into_value());
683 /// }
684 /// ```
685 pub fn remove(&mut self, key: &T) -> Option<SimpleCacheObject<U>> {
686 self.cache.shift_remove(key)
687 }
688
689 /// Checks if a key exists in the cache and is not expired.
690 ///
691 /// This is a lightweight check that doesn't trigger cleanup of expired entries.
692 ///
693 /// # Arguments
694 ///
695 /// * `key` - The key to check for existence
696 ///
697 /// # Returns
698 ///
699 /// `true` if the key exists and is not expired, `false` otherwise
700 ///
701 /// # Examples
702 ///
703 /// ```rust
704 /// use simple_cacher::*;
705 /// use std::time::Duration;
706 ///
707 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
708 /// cache.insert("key".to_string(), "value".to_string());
709 ///
710 /// assert!(cache.contains_key(&"key".to_string()));
711 /// assert!(!cache.contains_key(&"nonexistent".to_string()));
712 /// ```
713 pub fn contains_key(&self, key: &T) -> bool {
714 self.cache
715 .get(key)
716 .map(|obj| !obj.is_expired())
717 .unwrap_or(false)
718 }
719
720 /// Manually removes all expired entries from the cache.
721 ///
722 /// This method performs a full scan of the cache and removes all entries
723 /// that have exceeded their TTL. This can be useful for periodic cleanup
724 /// to free memory and maintain cache performance.
725 ///
726 /// # Returns
727 ///
728 /// The number of expired entries that were removed
729 ///
730 /// # Examples
731 ///
732 /// ```rust
733 /// use simple_cacher::*;
734 /// use std::time::Duration;
735 ///
736 /// let mut cache = SimpleCacher::new(Duration::from_millis(100));
737 /// cache.insert("key1".to_string(), "value1".to_string());
738 /// cache.insert("key2".to_string(), "value2".to_string());
739 ///
740 /// // Wait for expiration
741 /// std::thread::sleep(Duration::from_millis(150));
742 ///
743 /// let removed = cache.cleanup_expired();
744 /// println!("Cleaned up {} expired entries", removed);
745 /// ```
746 pub fn cleanup_expired(&mut self) -> usize {
747 let expired_keys: Vec<T> = self
748 .cache
749 .iter()
750 .filter_map(|(k, v)| {
751 if v.is_expired() {
752 Some(k.clone())
753 } else {
754 None
755 }
756 })
757 .collect();
758
759 let count = expired_keys.len();
760 for key in expired_keys {
761 self.cache.shift_remove(&key);
762 }
763 count
764 }
765
766 /// Returns the total number of entries in the cache (including expired ones).
767 ///
768 /// Note that this includes expired entries that haven't been cleaned up yet.
769 /// Use `active_len()` to get only non-expired entries.
770 ///
771 /// # Examples
772 ///
773 /// ```rust
774 /// use simple_cacher::*;
775 /// use std::time::Duration;
776 ///
777 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
778 /// cache.insert("key1".to_string(), "value1".to_string());
779 /// cache.insert("key2".to_string(), "value2".to_string());
780 ///
781 /// assert_eq!(cache.len(), 2);
782 /// ```
783 pub fn len(&self) -> usize {
784 self.cache.len()
785 }
786
787 /// Returns the number of non-expired entries in the cache.
788 ///
789 /// This method counts only entries that are still valid (not expired).
790 /// It does not modify the cache or remove expired entries.
791 ///
792 /// # Examples
793 ///
794 /// ```rust
795 /// use simple_cacher::*;
796 /// use std::time::Duration;
797 ///
798 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
799 /// cache.insert("key1".to_string(), "value1".to_string());
800 /// cache.insert("key2".to_string(), "value2".to_string());
801 ///
802 /// assert_eq!(cache.active_len(), 2);
803 /// ```
804 pub fn active_len(&self) -> usize {
805 self.cache
806 .iter()
807 .filter(|(_, obj)| !obj.is_expired())
808 .count()
809 }
810
811 /// Returns `true` if the cache contains no entries.
812 ///
813 /// # Examples
814 ///
815 /// ```rust
816 /// use simple_cacher::*;
817 /// use std::time::Duration;
818 ///
819 /// let cache = SimpleCacher::<String, String>::new(Duration::from_secs(300));
820 /// assert!(cache.is_empty());
821 /// ```
822 pub fn is_empty(&self) -> bool {
823 self.cache.is_empty()
824 }
825
826 /// Removes all entries from the cache.
827 ///
828 /// After calling this method, the cache will be empty and `len()` will return 0.
829 ///
830 /// # Examples
831 ///
832 /// ```rust
833 /// use simple_cacher::*;
834 /// use std::time::Duration;
835 ///
836 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
837 /// cache.insert("key".to_string(), "value".to_string());
838 /// assert_eq!(cache.len(), 1);
839 ///
840 /// cache.clear();
841 /// assert_eq!(cache.len(), 0);
842 /// ```
843 pub fn clear(&mut self) {
844 self.cache.clear();
845 }
846
847 /// Returns comprehensive statistics about the cache state.
848 ///
849 /// This provides detailed information about cache usage, including total entries,
850 /// active (non-expired) entries, expired entries, and configuration settings.
851 ///
852 /// # Returns
853 ///
854 /// A `CacheStats` struct containing cache metrics
855 ///
856 /// # Examples
857 ///
858 /// ```rust
859 /// use simple_cacher::*;
860 /// use std::time::Duration;
861 ///
862 /// let mut cache = SimpleCacher::with_max_size(Duration::from_secs(300), 1000);
863 /// cache.insert("key1".to_string(), "value1".to_string());
864 /// cache.insert("key2".to_string(), "value2".to_string());
865 ///
866 /// let stats = cache.stats();
867 /// println!("Active entries: {}", stats.active_entries);
868 /// println!("Max size: {:?}", stats.max_size);
869 /// ```
870 pub fn stats(&self) -> CacheStats {
871 let total = self.cache.len();
872 let expired = self
873 .cache
874 .iter()
875 .filter(|(_, obj)| obj.is_expired())
876 .count();
877
878 CacheStats {
879 total_entries: total,
880 active_entries: total - expired,
881 expired_entries: expired,
882 max_size: self.max_size,
883 max_age: self.max_age,
884 }
885 }
886
887 /// Returns an iterator over all non-expired entries in the cache.
888 ///
889 /// This iterator yields tuples of `(&T, &SimpleCacheObject<U>)` for each
890 /// active (non-expired) entry. Expired entries are skipped.
891 ///
892 /// # Returns
893 ///
894 /// An iterator over active cache entries
895 ///
896 /// # Examples
897 ///
898 /// ```rust
899 /// use simple_cacher::*;
900 /// use std::time::Duration;
901 ///
902 /// let mut cache = SimpleCacher::new(Duration::from_secs(300));
903 /// cache.insert("user:1".to_string(), "Alice".to_string());
904 /// cache.insert("user:2".to_string(), "Bob".to_string());
905 ///
906 /// for (key, entry) in cache.iter_active() {
907 /// println!("{}: {} (age: {:?})", key, entry.value(), entry.age());
908 /// }
909 /// ```
910 pub fn iter_active(&self) -> impl Iterator<Item = (&T, &SimpleCacheObject<U>)> {
911 self.cache.iter().filter(|(_, obj)| !obj.is_expired())
912 }
913}
914
915/// Statistics about cache state and performance.
916///
917/// This struct provides detailed metrics about cache usage, including
918/// the number of active and expired entries, size limits, and TTL settings.
919#[derive(Debug, Clone)]
920pub struct CacheStats {
921 /// Total number of entries in the cache (including expired)
922 pub total_entries: usize,
923 /// Number of non-expired entries
924 pub active_entries: usize,
925 /// Number of expired entries (not yet cleaned up)
926 pub expired_entries: usize,
927 /// Maximum number of entries allowed (None if unlimited)
928 pub max_size: Option<usize>,
929 /// Default time-to-live for new entries
930 pub max_age: Duration,
931}
932
933// ========== Built-in Matchers ==========
934
935/// Exact equality matcher for cache keys.
936///
937/// This matcher performs exact equality comparison, similar to using `get()` directly,
938/// but is useful in generic code where you need a `Matcher` implementation.
939///
940/// # Examples
941///
942/// ```rust
943/// use simple_cacher::*;
944/// use std::time::Duration;
945///
946/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
947/// cache.insert("exact_key".to_string(), "value".to_string());
948///
949/// let matcher = ExactMatcher::new("exact_key".to_string());
950/// if let Ok(entry) = cache.get_by_matcher(&matcher) {
951/// println!("Found: {}", entry.value());
952/// }
953/// ```
954pub struct ExactMatcher<T> {
955 target: T,
956}
957
958impl<T> ExactMatcher<T> {
959 /// Creates a new exact matcher for the given target value.
960 ///
961 /// # Arguments
962 ///
963 /// * `target` - The exact value to match against
964 pub fn new(target: T) -> Self {
965 Self { target }
966 }
967}
968
969impl<T> Matcher<T> for ExactMatcher<T>
970where
971 T: PartialEq,
972{
973 fn matches(&self, key: &T) -> bool {
974 key == &self.target
975 }
976}
977
978/// String prefix matcher for finding keys that start with a specific string.
979///
980/// This matcher is useful for finding groups of related cache entries that
981/// follow a naming convention with common prefixes.
982///
983/// # Examples
984///
985/// ```rust
986/// use simple_cacher::*;
987/// use std::time::Duration;
988///
989/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
990/// cache.insert("user:alice".to_string(), "Alice Johnson".to_string());
991/// cache.insert("user:bob".to_string(), "Bob Smith".to_string());
992/// cache.insert("admin:charlie".to_string(), "Charlie Admin".to_string());
993///
994/// let user_matcher = PrefixMatcher::new("user:");
995/// let users = cache.get_all_by_matcher(&user_matcher);
996/// assert_eq!(users.len(), 2); // Found alice and bob
997/// ```
998pub struct PrefixMatcher {
999 prefix: String,
1000}
1001
1002impl PrefixMatcher {
1003 /// Creates a new prefix matcher.
1004 ///
1005 /// # Arguments
1006 ///
1007 /// * `prefix` - The prefix string to match against
1008 pub fn new(prefix: impl Into<String>) -> Self {
1009 Self {
1010 prefix: prefix.into(),
1011 }
1012 }
1013}
1014
1015impl Matcher<String> for PrefixMatcher {
1016 fn matches(&self, key: &String) -> bool {
1017 key.starts_with(&self.prefix)
1018 }
1019}
1020
1021impl Matcher<&str> for PrefixMatcher {
1022 fn matches(&self, key: &&str) -> bool {
1023 key.starts_with(&self.prefix)
1024 }
1025}
1026
1027/// String suffix matcher for finding keys that end with a specific string.
1028///
1029/// This matcher is useful for finding cache entries based on file extensions,
1030/// domain names, or other suffix-based patterns.
1031///
1032/// # Examples
1033///
1034/// ```rust
1035/// use simple_cacher::*;
1036/// use std::time::Duration;
1037///
1038/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
1039/// cache.insert("document.pdf".to_string(), "PDF content".to_string());
1040/// cache.insert("image.jpg".to_string(), "JPEG data".to_string());
1041/// cache.insert("script.js".to_string(), "JavaScript code".to_string());
1042///
1043/// let pdf_matcher = SuffixMatcher::new(".pdf");
1044/// let pdfs = cache.get_all_by_matcher(&pdf_matcher);
1045/// assert_eq!(pdfs.len(), 1);
1046/// ```
1047pub struct SuffixMatcher {
1048 suffix: String,
1049}
1050
1051impl SuffixMatcher {
1052 /// Creates a new suffix matcher.
1053 ///
1054 /// # Arguments
1055 ///
1056 /// * `suffix` - The suffix string to match against
1057 pub fn new(suffix: impl Into<String>) -> Self {
1058 Self {
1059 suffix: suffix.into(),
1060 }
1061 }
1062}
1063
1064impl Matcher<String> for SuffixMatcher {
1065 fn matches(&self, key: &String) -> bool {
1066 key.ends_with(&self.suffix)
1067 }
1068}
1069
1070impl Matcher<&str> for SuffixMatcher {
1071 fn matches(&self, key: &&str) -> bool {
1072 key.ends_with(&self.suffix)
1073 }
1074}
1075
1076/// String substring matcher for finding keys that contain a specific string.
1077///
1078/// This matcher searches for cache entries where the key contains the specified
1079/// substring anywhere within it.
1080///
1081/// # Examples
1082///
1083/// ```rust
1084/// use simple_cacher::*;
1085/// use std::time::Duration;
1086///
1087/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
1088/// cache.insert("user_profile_123".to_string(), "Profile data".to_string());
1089/// cache.insert("user_settings_456".to_string(), "Settings data".to_string());
1090/// cache.insert("admin_config".to_string(), "Config data".to_string());
1091///
1092/// let profile_matcher = ContainsMatcher::new("profile");
1093/// let profiles = cache.get_all_by_matcher(&profile_matcher);
1094/// assert_eq!(profiles.len(), 1);
1095/// ```
1096pub struct ContainsMatcher {
1097 substring: String,
1098}
1099
1100impl ContainsMatcher {
1101 /// Creates a new substring matcher.
1102 ///
1103 /// # Arguments
1104 ///
1105 /// * `substring` - The substring to search for within keys
1106 pub fn new(substring: impl Into<String>) -> Self {
1107 Self {
1108 substring: substring.into(),
1109 }
1110 }
1111}
1112
1113impl Matcher<String> for ContainsMatcher {
1114 fn matches(&self, key: &String) -> bool {
1115 key.contains(&self.substring)
1116 }
1117}
1118
1119impl Matcher<&str> for ContainsMatcher {
1120 fn matches(&self, key: &&str) -> bool {
1121 key.contains(&self.substring)
1122 }
1123}
1124
1125/// Numeric range matcher for finding keys within a specified range.
1126///
1127/// This matcher is useful for numeric keys like IDs, scores, timestamps,
1128/// or any other ordered numeric data.
1129///
1130/// # Examples
1131///
1132/// ```rust
1133/// use simple_cacher::*;
1134/// use std::time::Duration;
1135///
1136/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
1137/// cache.insert(85, "Good score".to_string());
1138/// cache.insert(92, "Excellent score".to_string());
1139/// cache.insert(67, "Average score".to_string());
1140/// cache.insert(45, "Poor score".to_string());
1141///
1142/// let high_score_matcher = RangeMatcher::new(80, 100);
1143/// let high_scores = cache.get_all_by_matcher(&high_score_matcher);
1144/// assert_eq!(high_scores.len(), 2); // 85 and 92
1145/// ```
1146pub struct RangeMatcher<T> {
1147 min: T,
1148 max: T,
1149 inclusive: bool,
1150}
1151
1152impl<T> RangeMatcher<T> {
1153 /// Creates a new inclusive range matcher.
1154 ///
1155 /// # Arguments
1156 ///
1157 /// * `min` - Minimum value (inclusive)
1158 /// * `max` - Maximum value (inclusive)
1159 pub fn new(min: T, max: T) -> Self {
1160 Self {
1161 min,
1162 max,
1163 inclusive: true,
1164 }
1165 }
1166
1167 /// Creates a new exclusive range matcher.
1168 ///
1169 /// # Arguments
1170 ///
1171 /// * `min` - Minimum value (exclusive)
1172 /// * `max` - Maximum value (exclusive)
1173 pub fn exclusive(min: T, max: T) -> Self {
1174 Self {
1175 min,
1176 max,
1177 inclusive: false,
1178 }
1179 }
1180}
1181
1182impl<T> Matcher<T> for RangeMatcher<T>
1183where
1184 T: PartialOrd,
1185{
1186 fn matches(&self, key: &T) -> bool {
1187 if self.inclusive {
1188 key >= &self.min && key <= &self.max
1189 } else {
1190 key > &self.min && key < &self.max
1191 }
1192 }
1193}
1194
1195/// Function-based matcher for maximum flexibility in matching logic.
1196///
1197/// This matcher allows you to provide a custom function that determines
1198/// whether a key matches. This is the most flexible matcher and can implement
1199/// any matching logic you need.
1200///
1201/// # Examples
1202///
1203/// ```rust
1204/// use simple_cacher::*;
1205/// use std::time::Duration;
1206///
1207/// let mut cache = SimpleCacher::new(Duration::from_secs(300));
1208/// cache.insert(2, "Even number".to_string());
1209/// cache.insert(3, "Odd number".to_string());
1210/// cache.insert(4, "Even number".to_string());
1211/// cache.insert(5, "Odd number".to_string());
1212///
1213/// // Find even numbers
1214/// let even_matcher = FnMatcher::new(|&key: &i32| key % 2 == 0);
1215/// let even_numbers = cache.get_all_by_matcher(&even_matcher);
1216/// assert_eq!(even_numbers.len(), 2); // 2 and 4
1217/// ```
1218pub struct FnMatcher<T, F>
1219where
1220 F: Fn(&T) -> bool,
1221{
1222 matcher_fn: F,
1223 _phantom: std::marker::PhantomData<T>,
1224}
1225
1226impl<T, F> FnMatcher<T, F>
1227where
1228 F: Fn(&T) -> bool,
1229{
1230 /// Creates a new function-based matcher.
1231 ///
1232 /// # Arguments
1233 ///
1234 /// * `matcher_fn` - A function that takes a key reference and returns `true` if it matches
1235 ///
1236 /// # Examples
1237 ///
1238 /// ```rust
1239 /// use simple_cacher::*;
1240 ///
1241 /// // Match strings longer than 5 characters
1242 /// let long_string_matcher = FnMatcher::new(|s: &String| s.len() > 5);
1243 /// ```
1244 pub fn new(matcher_fn: F) -> Self {
1245 Self {
1246 matcher_fn,
1247 _phantom: std::marker::PhantomData,
1248 }
1249 }
1250}
1251
1252impl<T, F> Matcher<T> for FnMatcher<T, F>
1253where
1254 F: Fn(&T) -> bool,
1255{
1256 fn matches(&self, key: &T) -> bool {
1257 (self.matcher_fn)(key)
1258 }
1259}
1260
1261// ========== Tests ==========
1262
1263#[cfg(test)]
1264mod tests {
1265 use super::*;
1266 use std::thread;
1267
1268 #[test]
1269 fn test_basic_cache_operations() {
1270 let mut cache = SimpleCacher::new(Duration::from_secs(1));
1271
1272 // Test insert and get
1273 cache.insert("key1".to_string(), "value1".to_string());
1274 assert_eq!(cache.get(&"key1".to_string()).unwrap().value(), "value1");
1275
1276 // Test not found
1277 assert!(matches!(
1278 cache.get(&"nonexistent".to_string()),
1279 Err(SimpleCacheError::NotFound)
1280 ));
1281 }
1282
1283 #[test]
1284 fn test_expiration() {
1285 let mut cache = SimpleCacher::new(Duration::from_millis(100));
1286
1287 cache.insert("key1".to_string(), "value1".to_string());
1288 assert!(cache.get(&"key1".to_string()).is_ok());
1289
1290 thread::sleep(Duration::from_millis(150));
1291 assert!(matches!(
1292 cache.get(&"key1".to_string()),
1293 Err(SimpleCacheError::Expired)
1294 ));
1295 }
1296
1297 #[test]
1298 fn test_max_size() {
1299 let mut cache = SimpleCacher::with_max_size(Duration::from_secs(10), 2);
1300
1301 cache.insert(1, "value1");
1302 cache.insert(2, "value2");
1303 cache.insert(3, "value3"); // Should evict key 1
1304
1305 assert!(matches!(cache.get(&1), Err(SimpleCacheError::NotFound)));
1306 assert!(cache.get(&2).is_ok());
1307 assert!(cache.get(&3).is_ok());
1308 }
1309
1310 #[test]
1311 fn test_prefix_matcher() {
1312 let mut cache = SimpleCacher::new(Duration::from_secs(10));
1313
1314 cache.insert("prefix_key1".to_string(), "value1");
1315 cache.insert("prefix_key2".to_string(), "value2");
1316 cache.insert("other_key".to_string(), "value3");
1317
1318 let matcher = PrefixMatcher::new("prefix_");
1319 let result = cache.get_by_matcher(&matcher);
1320 assert!(result.is_ok());
1321 assert!(result.unwrap().value().starts_with("value"));
1322 }
1323
1324 #[test]
1325 fn test_range_matcher() {
1326 let mut cache = SimpleCacher::new(Duration::from_secs(10));
1327
1328 cache.insert(1, "value1");
1329 cache.insert(5, "value5");
1330 cache.insert(10, "value10");
1331 cache.insert(15, "value15");
1332
1333 let matcher = RangeMatcher::new(3, 12);
1334 let result = cache.get_by_matcher(&matcher);
1335 assert!(result.is_ok());
1336
1337 // Should find either key 5 or 10
1338 let found_value = result.unwrap().value();
1339 assert!(found_value.to_string() == "value5" || found_value.to_string() == "value10");
1340 }
1341
1342 #[test]
1343 fn test_function_matcher() {
1344 let mut cache = SimpleCacher::new(Duration::from_secs(10));
1345
1346 cache.insert(2, "even");
1347 cache.insert(3, "odd");
1348 cache.insert(4, "even");
1349 cache.insert(5, "odd");
1350
1351 let even_matcher = FnMatcher::new(|&key: &i32| key % 2 == 0);
1352 let result = cache.get_by_matcher(&even_matcher);
1353 assert!(result.is_ok());
1354 assert_eq!(result.unwrap().value().to_string(), "even");
1355 }
1356}