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}