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}