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