Skip to main content

threatflux_cache/
entry.rs

1//! Cache entry types and metadata traits
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::fmt::Debug;
6use std::hash::Hash;
7
8/// A cache entry containing a key-value pair with metadata
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CacheEntry<K, V, M = ()>
11where
12    K: Clone + Hash + Eq,
13    V: Clone,
14    M: Clone,
15{
16    /// The cache key
17    pub key: K,
18    /// The cached value
19    pub value: V,
20    /// Optional metadata associated with the entry
21    pub metadata: M,
22    /// Timestamp when the entry was created
23    pub timestamp: DateTime<Utc>,
24    /// Optional expiry time for TTL-based eviction
25    pub expiry: Option<DateTime<Utc>>,
26    /// Number of times this entry has been accessed
27    pub access_count: u64,
28    /// Last access timestamp
29    pub last_accessed: DateTime<Utc>,
30}
31
32impl<K, V, M> CacheEntry<K, V, M>
33where
34    K: Clone + Hash + Eq,
35    V: Clone,
36    M: Clone + Default,
37{
38    /// Create a new cache entry with default metadata
39    pub fn new(key: K, value: V) -> Self {
40        let now = Utc::now();
41        Self {
42            key,
43            value,
44            metadata: M::default(),
45            timestamp: now,
46            expiry: None,
47            access_count: 0,
48            last_accessed: now,
49        }
50    }
51}
52
53impl<K, V, M> CacheEntry<K, V, M>
54where
55    K: Clone + Hash + Eq,
56    V: Clone,
57    M: Clone,
58{
59    /// Create a new cache entry with metadata
60    pub fn with_metadata(key: K, value: V, metadata: M) -> Self {
61        let now = Utc::now();
62        Self {
63            key,
64            value,
65            metadata,
66            timestamp: now,
67            expiry: None,
68            access_count: 0,
69            last_accessed: now,
70        }
71    }
72
73    /// Set expiry time for the entry
74    pub fn with_ttl(mut self, ttl: chrono::Duration) -> Self {
75        self.expiry = Some(self.timestamp + ttl);
76        self
77    }
78
79    /// Check if the entry has expired
80    pub fn is_expired(&self) -> bool {
81        if let Some(expiry) = self.expiry {
82            Utc::now() > expiry
83        } else {
84            false
85        }
86    }
87
88    /// Update access statistics
89    pub fn record_access(&mut self) {
90        self.access_count += 1;
91        self.last_accessed = Utc::now();
92    }
93
94    /// Get the age of the entry
95    pub fn age(&self) -> chrono::Duration {
96        Utc::now() - self.timestamp
97    }
98}
99
100/// Trait for cache entry metadata
101pub trait EntryMetadata:
102    Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static
103{
104    /// Get execution time in milliseconds if applicable
105    fn execution_time_ms(&self) -> Option<u64> {
106        None
107    }
108
109    /// Get the size of the cached data if applicable
110    fn size_bytes(&self) -> Option<u64> {
111        None
112    }
113
114    /// Get a category or type identifier
115    fn category(&self) -> Option<&str> {
116        None
117    }
118}
119
120/// Empty metadata implementation
121impl EntryMetadata for () {}
122
123/// Simple metadata implementation with common fields
124#[derive(Debug, Clone, Serialize, Deserialize, Default)]
125pub struct BasicMetadata {
126    /// Execution time in milliseconds
127    pub execution_time_ms: Option<u64>,
128    /// Size in bytes
129    pub size_bytes: Option<u64>,
130    /// Category or type
131    pub category: Option<String>,
132    /// Additional tags
133    pub tags: Vec<String>,
134}
135
136impl EntryMetadata for BasicMetadata {
137    fn execution_time_ms(&self) -> Option<u64> {
138        self.execution_time_ms
139    }
140
141    fn size_bytes(&self) -> Option<u64> {
142        self.size_bytes
143    }
144
145    fn category(&self) -> Option<&str> {
146        self.category.as_deref()
147    }
148}
149
150/// Statistics for a group of cache entries
151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
152pub struct EntryStatistics {
153    /// Total number of entries
154    pub total_count: usize,
155    /// Total size in bytes
156    pub total_size_bytes: u64,
157    /// Average execution time
158    pub avg_execution_time_ms: f64,
159    /// Average age of entries
160    pub avg_age_seconds: f64,
161    /// Number of expired entries
162    pub expired_count: usize,
163    /// Total access count
164    pub total_access_count: u64,
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_cache_entry_creation() {
173        #[allow(clippy::type_complexity)]
174        let entry: CacheEntry<String, String, ()> =
175            CacheEntry::new("key1".to_string(), "value1".to_string());
176        assert_eq!(entry.key, "key1");
177        assert_eq!(entry.value, "value1");
178        assert_eq!(entry.access_count, 0);
179        assert!(!entry.is_expired());
180    }
181
182    #[test]
183    fn test_cache_entry_ttl() {
184        #[allow(clippy::type_complexity)]
185        let entry: CacheEntry<String, String, ()> =
186            CacheEntry::new("key1".to_string(), "value1".to_string())
187                .with_ttl(chrono::Duration::seconds(60));
188
189        assert!(entry.expiry.is_some());
190        assert!(!entry.is_expired());
191    }
192
193    #[test]
194    fn test_cache_entry_metadata() {
195        let metadata = BasicMetadata {
196            execution_time_ms: Some(100),
197            size_bytes: Some(1024),
198            category: Some("test".to_string()),
199            tags: vec!["tag1".to_string()],
200        };
201
202        let entry = CacheEntry::with_metadata("key1".to_string(), "value1".to_string(), metadata);
203        assert_eq!(entry.metadata.execution_time_ms(), Some(100));
204        assert_eq!(entry.metadata.size_bytes(), Some(1024));
205        assert_eq!(entry.metadata.category(), Some("test"));
206    }
207
208    #[test]
209    #[allow(clippy::type_complexity)]
210    fn test_entry_access_tracking() {
211        let mut entry: CacheEntry<String, String, ()> =
212            CacheEntry::new("key1".to_string(), "value1".to_string());
213        let initial_time = entry.last_accessed;
214
215        // Sleep a tiny bit to ensure time difference
216        std::thread::sleep(std::time::Duration::from_millis(10));
217
218        entry.record_access();
219        assert_eq!(entry.access_count, 1);
220        assert!(entry.last_accessed > initial_time);
221
222        entry.record_access();
223        assert_eq!(entry.access_count, 2);
224    }
225}