oxihuman_core/
diff_tracker.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct DiffEntry {
9 pub property: String,
10 pub old_value: String,
11 pub new_value: String,
12 pub timestamp: u64,
13}
14
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct DiffTracker {
18 entries: Vec<DiffEntry>,
19 clock: u64,
20 max_entries: usize,
21}
22
23#[allow(dead_code)]
24impl DiffTracker {
25 pub fn new(max_entries: usize) -> Self {
26 Self {
27 entries: Vec::new(),
28 clock: 0,
29 max_entries: max_entries.max(1),
30 }
31 }
32
33 pub fn advance_clock(&mut self, dt: u64) {
34 self.clock += dt;
35 }
36
37 pub fn record(&mut self, property: &str, old_value: &str, new_value: &str) {
38 if self.entries.len() >= self.max_entries {
39 self.entries.remove(0);
40 }
41 self.entries.push(DiffEntry {
42 property: property.to_string(),
43 old_value: old_value.to_string(),
44 new_value: new_value.to_string(),
45 timestamp: self.clock,
46 });
47 }
48
49 pub fn count(&self) -> usize {
50 self.entries.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
54 self.entries.is_empty()
55 }
56
57 pub fn latest(&self) -> Option<&DiffEntry> {
58 self.entries.last()
59 }
60
61 pub fn by_property(&self, property: &str) -> Vec<&DiffEntry> {
62 self.entries
63 .iter()
64 .filter(|e| e.property == property)
65 .collect()
66 }
67
68 pub fn since(&self, timestamp: u64) -> Vec<&DiffEntry> {
69 self.entries
70 .iter()
71 .filter(|e| e.timestamp >= timestamp)
72 .collect()
73 }
74
75 pub fn changed_properties(&self) -> Vec<String> {
76 let mut props: Vec<String> = self.entries.iter().map(|e| e.property.clone()).collect();
77 props.sort();
78 props.dedup();
79 props
80 }
81
82 pub fn has_changes_for(&self, property: &str) -> bool {
83 self.entries.iter().any(|e| e.property == property)
84 }
85
86 pub fn clear(&mut self) {
87 self.entries.clear();
88 }
89
90 pub fn revert_latest(&mut self) -> Option<DiffEntry> {
91 self.entries.pop()
92 }
93
94 pub fn all_entries(&self) -> &[DiffEntry] {
95 &self.entries
96 }
97
98 pub fn max_entries(&self) -> usize {
99 self.max_entries
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_new() {
109 let dt = DiffTracker::new(100);
110 assert!(dt.is_empty());
111 }
112
113 #[test]
114 fn test_record() {
115 let mut dt = DiffTracker::new(100);
116 dt.record("color", "red", "blue");
117 assert_eq!(dt.count(), 1);
118 }
119
120 #[test]
121 fn test_latest() {
122 let mut dt = DiffTracker::new(100);
123 dt.record("a", "1", "2");
124 dt.record("b", "3", "4");
125 assert_eq!(dt.latest().expect("should succeed").property, "b");
126 }
127
128 #[test]
129 fn test_by_property() {
130 let mut dt = DiffTracker::new(100);
131 dt.record("x", "1", "2");
132 dt.record("y", "3", "4");
133 dt.record("x", "2", "3");
134 assert_eq!(dt.by_property("x").len(), 2);
135 }
136
137 #[test]
138 fn test_since() {
139 let mut dt = DiffTracker::new(100);
140 dt.record("a", "1", "2");
141 dt.advance_clock(10);
142 dt.record("b", "3", "4");
143 assert_eq!(dt.since(5).len(), 1);
144 }
145
146 #[test]
147 fn test_changed_properties() {
148 let mut dt = DiffTracker::new(100);
149 dt.record("b", "1", "2");
150 dt.record("a", "3", "4");
151 dt.record("b", "2", "3");
152 let props = dt.changed_properties();
153 assert_eq!(props, vec!["a", "b"]);
154 }
155
156 #[test]
157 fn test_max_entries_eviction() {
158 let mut dt = DiffTracker::new(2);
159 dt.record("a", "1", "2");
160 dt.record("b", "3", "4");
161 dt.record("c", "5", "6");
162 assert_eq!(dt.count(), 2);
163 assert!(!dt.has_changes_for("a"));
164 }
165
166 #[test]
167 fn test_revert_latest() {
168 let mut dt = DiffTracker::new(100);
169 dt.record("x", "old", "new");
170 let reverted = dt.revert_latest().expect("should succeed");
171 assert_eq!(reverted.property, "x");
172 assert!(dt.is_empty());
173 }
174
175 #[test]
176 fn test_clear() {
177 let mut dt = DiffTracker::new(100);
178 dt.record("a", "1", "2");
179 dt.clear();
180 assert!(dt.is_empty());
181 }
182
183 #[test]
184 fn test_has_changes_for() {
185 let mut dt = DiffTracker::new(100);
186 assert!(!dt.has_changes_for("x"));
187 dt.record("x", "1", "2");
188 assert!(dt.has_changes_for("x"));
189 }
190}