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(key: CacheKey, value: serde_json::Value, ttl: Option<Duration>) -> Self {
52 let size_bytes = Self::estimate_size(&value);
53 let now = SystemTime::now();
54
55 Self {
56 key,
57 value,
58 created_at: now,
59 last_accessed: now,
60 access_count: 0,
61 ttl,
62 size_bytes,
63 }
64 }
65
66 pub fn is_expired(&self) -> bool {
68 if let Some(ttl) = self.ttl {
69 if let Ok(elapsed) = self.created_at.elapsed() {
70 return elapsed > ttl;
71 }
72 }
73 false
74 }
75
76 pub fn mark_accessed(&mut self) {
78 self.last_accessed = SystemTime::now();
79 self.access_count += 1;
80 }
81
82 fn estimate_size(value: &serde_json::Value) -> usize {
84 match serde_json::to_string(value) {
86 Ok(s) => s.len(),
87 Err(_) => 0,
88 }
89 }
90
91 pub fn age(&self) -> Duration {
93 self.created_at.elapsed().unwrap_or(Duration::ZERO)
94 }
95
96 pub fn idle_time(&self) -> Duration {
98 self.last_accessed.elapsed().unwrap_or(Duration::ZERO)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use serde_json::json;
106
107 #[test]
108 fn test_cache_key_creation() {
109 let inputs = json!({"x": 10, "y": 20});
110 let key1 = CacheKey::new("node1", &inputs);
111 let key2 = CacheKey::new("node1", &inputs);
112
113 assert_eq!(key1, key2);
114 assert_eq!(key1.node_id, "node1");
115 }
116
117 #[test]
118 fn test_cache_key_different_inputs() {
119 let inputs1 = json!({"x": 10});
120 let inputs2 = json!({"x": 20});
121
122 let key1 = CacheKey::new("node1", &inputs1);
123 let key2 = CacheKey::new("node1", &inputs2);
124
125 assert_ne!(key1.input_hash, key2.input_hash);
126 }
127
128 #[test]
129 fn test_cache_entry_expiration() {
130 let key = CacheKey::new("node1", &json!({}));
131 let mut entry = CacheEntry::new(key, json!({"result": 42}), Some(Duration::from_millis(1)));
132
133 assert!(!entry.is_expired());
134
135 std::thread::sleep(Duration::from_millis(10));
136 assert!(entry.is_expired());
137 }
138
139 #[test]
140 fn test_cache_entry_no_expiration() {
141 let key = CacheKey::new("node1", &json!({}));
142 let entry = CacheEntry::new(key, json!({"result": 42}), None);
143
144 assert!(!entry.is_expired());
145 }
146
147 #[test]
148 fn test_cache_entry_access_tracking() {
149 let key = CacheKey::new("node1", &json!({}));
150 let mut entry = CacheEntry::new(key, json!({"result": 42}), None);
151
152 assert_eq!(entry.access_count, 0);
153
154 entry.mark_accessed();
155 assert_eq!(entry.access_count, 1);
156
157 entry.mark_accessed();
158 assert_eq!(entry.access_count, 2);
159 }
160}