1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::fmt::Debug;
6use std::hash::Hash;
7
8#[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 pub key: K,
18 pub value: V,
20 pub metadata: M,
22 pub timestamp: DateTime<Utc>,
24 pub expiry: Option<DateTime<Utc>>,
26 pub access_count: u64,
28 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 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 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 pub fn with_ttl(mut self, ttl: chrono::Duration) -> Self {
75 self.expiry = Some(self.timestamp + ttl);
76 self
77 }
78
79 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 pub fn record_access(&mut self) {
90 self.access_count += 1;
91 self.last_accessed = Utc::now();
92 }
93
94 pub fn age(&self) -> chrono::Duration {
96 Utc::now() - self.timestamp
97 }
98}
99
100pub trait EntryMetadata:
102 Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static
103{
104 fn execution_time_ms(&self) -> Option<u64> {
106 None
107 }
108
109 fn size_bytes(&self) -> Option<u64> {
111 None
112 }
113
114 fn category(&self) -> Option<&str> {
116 None
117 }
118}
119
120impl EntryMetadata for () {}
122
123#[derive(Debug, Clone, Serialize, Deserialize, Default)]
125pub struct BasicMetadata {
126 pub execution_time_ms: Option<u64>,
128 pub size_bytes: Option<u64>,
130 pub category: Option<String>,
132 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
152pub struct EntryStatistics {
153 pub total_count: usize,
155 pub total_size_bytes: u64,
157 pub avg_execution_time_ms: f64,
159 pub avg_age_seconds: f64,
161 pub expired_count: usize,
163 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 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}