rust_logic_graph/cache/
entry.rs1use serde::{Deserialize, Serialize};
4use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6use std::time::{Duration, SystemTime};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct CacheKey {
11 pub node_id: String,
12 pub input_hash: u64,
13}
14
15impl CacheKey {
16 pub fn new(node_id: impl Into<String>, inputs: &serde_json::Value) -> Self {
18 let node_id = node_id.into();
19 let input_hash = Self::hash_inputs(inputs);
20 Self {
21 node_id,
22 input_hash,
23 }
24 }
25
26 fn hash_inputs(inputs: &serde_json::Value) -> u64 {
28 let mut hasher = DefaultHasher::new();
29 if let Ok(json_str) = serde_json::to_string(inputs) {
31 json_str.hash(&mut hasher);
32 }
33 hasher.finish()
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct CacheEntry {
40 pub key: CacheKey,
41 pub value: serde_json::Value,
42 pub created_at: SystemTime,
43 pub last_accessed: SystemTime,
44 pub access_count: u64,
45 pub ttl: Option<Duration>,
46 pub size_bytes: usize,
47}
48
49impl CacheEntry {
50 pub fn new(
52 key: CacheKey,
53 value: serde_json::Value,
54 ttl: Option<Duration>,
55 ) -> Self {
56 let size_bytes = Self::estimate_size(&value);
57 let now = SystemTime::now();
58
59 Self {
60 key,
61 value,
62 created_at: now,
63 last_accessed: now,
64 access_count: 0,
65 ttl,
66 size_bytes,
67 }
68 }
69
70 pub fn is_expired(&self) -> bool {
72 if let Some(ttl) = self.ttl {
73 if let Ok(elapsed) = self.created_at.elapsed() {
74 return elapsed > ttl;
75 }
76 }
77 false
78 }
79
80 pub fn mark_accessed(&mut self) {
82 self.last_accessed = SystemTime::now();
83 self.access_count += 1;
84 }
85
86 fn estimate_size(value: &serde_json::Value) -> usize {
88 match serde_json::to_string(value) {
90 Ok(s) => s.len(),
91 Err(_) => 0,
92 }
93 }
94
95 pub fn age(&self) -> Duration {
97 self.created_at.elapsed().unwrap_or(Duration::ZERO)
98 }
99
100 pub fn idle_time(&self) -> Duration {
102 self.last_accessed.elapsed().unwrap_or(Duration::ZERO)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use serde_json::json;
110
111 #[test]
112 fn test_cache_key_creation() {
113 let inputs = json!({"x": 10, "y": 20});
114 let key1 = CacheKey::new("node1", &inputs);
115 let key2 = CacheKey::new("node1", &inputs);
116
117 assert_eq!(key1, key2);
118 assert_eq!(key1.node_id, "node1");
119 }
120
121 #[test]
122 fn test_cache_key_different_inputs() {
123 let inputs1 = json!({"x": 10});
124 let inputs2 = json!({"x": 20});
125
126 let key1 = CacheKey::new("node1", &inputs1);
127 let key2 = CacheKey::new("node1", &inputs2);
128
129 assert_ne!(key1.input_hash, key2.input_hash);
130 }
131
132 #[test]
133 fn test_cache_entry_expiration() {
134 let key = CacheKey::new("node1", &json!({}));
135 let mut entry = CacheEntry::new(
136 key,
137 json!({"result": 42}),
138 Some(Duration::from_millis(1)),
139 );
140
141 assert!(!entry.is_expired());
142
143 std::thread::sleep(Duration::from_millis(10));
144 assert!(entry.is_expired());
145 }
146
147 #[test]
148 fn test_cache_entry_no_expiration() {
149 let key = CacheKey::new("node1", &json!({}));
150 let entry = CacheEntry::new(key, json!({"result": 42}), None);
151
152 assert!(!entry.is_expired());
153 }
154
155 #[test]
156 fn test_cache_entry_access_tracking() {
157 let key = CacheKey::new("node1", &json!({}));
158 let mut entry = CacheEntry::new(key, json!({"result": 42}), None);
159
160 assert_eq!(entry.access_count, 0);
161
162 entry.mark_accessed();
163 assert_eq!(entry.access_count, 1);
164
165 entry.mark_accessed();
166 assert_eq!(entry.access_count, 2);
167 }
168}