cachelito_core/
cache_entry.rs

1use std::time::Instant;
2
3/// Internal wrapper that tracks when a value was inserted into the cache.
4/// Used for TTL expiration support.
5///
6/// This structure is used internally to support TTL (Time To Live) expiration.
7/// Each cached value is wrapped in a `CacheEntry` which records the insertion
8/// timestamp using `Instant::now()`.
9///
10/// # Type Parameters
11///
12/// * `R` - The type of the cached value
13///
14/// # Fields
15///
16/// * `value` - The actual cached value
17/// * `inserted_at` - The `Instant` when this entry was created
18/// * `frequency` - The number of times this entry has been accessed (for LFU policy)
19///
20/// # Examples
21///
22/// ```
23/// use cachelito_core::CacheEntry;
24///
25/// let entry = CacheEntry::new(42);
26/// assert_eq!(entry.value, 42);
27/// assert_eq!(entry.frequency, 0);
28///
29/// // Check if expired (TTL of 60 seconds)
30/// assert!(!entry.is_expired(Some(60)));
31/// ```
32#[derive(Clone)]
33pub struct CacheEntry<R> {
34    pub value: R,
35    pub inserted_at: Instant,
36    pub frequency: u64,
37}
38
39impl<R> CacheEntry<R> {
40    /// Creates a new cache entry with the current timestamp.
41    ///
42    /// # Arguments
43    ///
44    /// * `value` - The value to cache
45    ///
46    /// # Returns
47    ///
48    /// A new `CacheEntry` with `inserted_at` set to `Instant::now()` and `frequency` set to 0
49    pub fn new(value: R) -> Self {
50        Self {
51            value,
52            inserted_at: Instant::now(),
53            frequency: 0,
54        }
55    }
56
57    /// Returns true if the entry has expired based on the provided TTL.
58    ///
59    /// # Arguments
60    ///
61    /// * `ttl` - Optional time-to-live in seconds. `None` means no expiration.
62    ///
63    /// # Returns
64    ///
65    /// * `true` if the entry age exceeds the TTL
66    /// * `false` if TTL is `None` or the entry is still valid
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use cachelito_core::CacheEntry;
72    /// use std::thread;
73    /// use std::time::Duration;
74    ///
75    /// let entry = CacheEntry::new("data");
76    ///
77    /// // Fresh entry is not expired
78    /// assert!(!entry.is_expired(Some(1)));
79    ///
80    /// // Wait 2 seconds
81    /// thread::sleep(Duration::from_secs(2));
82    ///
83    /// // Now it's expired (TTL was 1 second)
84    /// assert!(entry.is_expired(Some(1)));
85    ///
86    /// // No TTL means never expires
87    /// assert!(!entry.is_expired(None));
88    /// ```
89    pub fn is_expired(&self, ttl: Option<u64>) -> bool {
90        if let Some(ttl_secs) = ttl {
91            self.inserted_at.elapsed().as_secs() >= ttl_secs
92        } else {
93            false
94        }
95    }
96
97    /// Increments the access frequency counter.
98    ///
99    /// This method is used by the LFU (Least Frequently Used) eviction policy
100    /// to track how many times an entry has been accessed.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use cachelito_core::CacheEntry;
106    ///
107    /// let mut entry = CacheEntry::new(42);
108    /// assert_eq!(entry.frequency, 0);
109    ///
110    /// entry.increment_frequency();
111    /// assert_eq!(entry.frequency, 1);
112    ///
113    /// entry.increment_frequency();
114    /// assert_eq!(entry.frequency, 2);
115    /// ```
116    pub fn increment_frequency(&mut self) {
117        self.frequency = self.frequency.saturating_add(1);
118    }
119}
120
121// Implement MemoryEstimator for CacheEntry
122use crate::MemoryEstimator;
123
124impl<R: MemoryEstimator> MemoryEstimator for CacheEntry<R> {
125    fn estimate_memory(&self) -> usize {
126        // Base struct size (stack-allocated data: Instant + u64)
127        let base = std::mem::size_of::<Self>();
128
129        // Size of the value (including heap allocations)
130        let value_size = self.value.estimate_memory();
131
132        // Note: We subtract size_of_val(&self.value) to avoid double-counting
133        // the stack-allocated part which is already included in `base`
134        base + value_size.saturating_sub(std::mem::size_of_val(&self.value))
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::thread;
142    use std::time::Duration;
143
144    #[test]
145    fn test_new_entry_not_expired() {
146        let entry = CacheEntry::new(42);
147        assert_eq!(entry.value, 42);
148        assert!(!entry.is_expired(Some(10)));
149    }
150
151    #[test]
152    fn test_entry_expiration() {
153        let entry = CacheEntry::new("data");
154        thread::sleep(Duration::from_secs(2));
155        assert!(entry.is_expired(Some(1)));
156        assert!(!entry.is_expired(Some(3)));
157    }
158
159    #[test]
160    fn test_no_ttl_never_expires() {
161        let entry = CacheEntry::new(100);
162        thread::sleep(Duration::from_millis(100));
163        assert!(!entry.is_expired(None));
164    }
165
166    #[test]
167    fn test_memory_estimation_primitive() {
168        let entry = CacheEntry::new(42i32);
169        let estimated = entry.estimate_memory();
170
171        // Should be approximately: size_of::<CacheEntry<i32>>()
172        // (Instant + u64 + i32)
173        assert!(estimated >= std::mem::size_of::<CacheEntry<i32>>());
174    }
175
176    #[test]
177    fn test_memory_estimation_string() {
178        let s = String::from("Hello, World!");
179        let entry = CacheEntry::new(s.clone());
180        let estimated = entry.estimate_memory();
181
182        // Should include struct overhead + string capacity
183        let expected_min = std::mem::size_of::<CacheEntry<String>>() + s.capacity();
184        assert!(estimated >= expected_min);
185    }
186
187    #[test]
188    fn test_memory_estimation_vec() {
189        let v = vec![1, 2, 3, 4, 5];
190        let entry = CacheEntry::new(v.clone());
191        let estimated = entry.estimate_memory();
192
193        // Should include struct overhead + vec capacity
194        let expected_min =
195            std::mem::size_of::<CacheEntry<Vec<i32>>>() + v.capacity() * std::mem::size_of::<i32>();
196        assert!(estimated >= expected_min);
197    }
198}