spec_ai_knowledge_graph/
vector_clock.rs1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::{HashMap, HashSet};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct VectorClock {
7 versions: HashMap<String, i64>,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ClockOrder {
12 Before,
13 After,
14 Concurrent,
15 Equal,
16}
17
18impl VectorClock {
19 pub fn new() -> Self {
20 Self {
21 versions: HashMap::new(),
22 }
23 }
24
25 pub fn from_json(json: &str) -> Result<Self> {
26 if json.is_empty() || json == "{}" {
27 return Ok(Self::new());
28 }
29 serde_json::from_str(json).context("parsing vector clock JSON")
30 }
31
32 pub fn to_json(&self) -> Result<String> {
33 serde_json::to_string(&self).context("serializing vector clock")
34 }
35
36 pub fn get(&self, instance_id: &str) -> i64 {
37 self.versions.get(instance_id).copied().unwrap_or(0)
38 }
39
40 pub fn set(&mut self, instance_id: String, version: i64) {
41 self.versions.insert(instance_id, version);
42 }
43
44 pub fn increment(&mut self, instance_id: &str) -> i64 {
45 let version = self.get(instance_id) + 1;
46 self.versions.insert(instance_id.to_string(), version);
47 version
48 }
49
50 pub fn merge(&mut self, other: &VectorClock) {
51 for (instance_id, &other_version) in &other.versions {
52 let current_version = self.get(instance_id);
53 if other_version > current_version {
54 self.versions.insert(instance_id.clone(), other_version);
55 }
56 }
57 }
58
59 pub fn compare(&self, other: &VectorClock) -> ClockOrder {
60 let mut self_less_or_equal = true;
61 let mut other_less_or_equal = true;
62
63 let all_instances: HashSet<_> = self.versions.keys().chain(other.versions.keys()).collect();
64
65 for instance_id in all_instances {
66 let self_version = self.get(instance_id);
67 let other_version = other.get(instance_id);
68
69 if self_version > other_version {
70 self_less_or_equal = false;
71 }
72 if other_version > self_version {
73 other_less_or_equal = false;
74 }
75 }
76
77 match (self_less_or_equal, other_less_or_equal) {
78 (true, true) => ClockOrder::Equal,
79 (true, false) => ClockOrder::Before,
80 (false, true) => ClockOrder::After,
81 (false, false) => ClockOrder::Concurrent,
82 }
83 }
84
85 pub fn happens_before(&self, other: &VectorClock) -> bool {
86 matches!(self.compare(other), ClockOrder::Before)
87 }
88
89 pub fn is_concurrent(&self, other: &VectorClock) -> bool {
90 matches!(self.compare(other), ClockOrder::Concurrent)
91 }
92
93 pub fn is_equal(&self, other: &VectorClock) -> bool {
94 matches!(self.compare(other), ClockOrder::Equal)
95 }
96
97 pub fn instances(&self) -> Vec<String> {
98 self.versions.keys().cloned().collect()
99 }
100
101 pub fn instance_count(&self) -> usize {
102 self.versions.len()
103 }
104
105 pub fn is_empty(&self) -> bool {
106 self.versions.is_empty()
107 }
108
109 pub fn clear(&mut self) {
110 self.versions.clear();
111 }
112}
113
114impl Default for VectorClock {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl std::fmt::Display for VectorClock {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{{")?;
123 let mut first = true;
124 for (instance_id, version) in &self.versions {
125 if !first {
126 write!(f, ", ")?;
127 }
128 write!(f, "{}: {}", instance_id, version)?;
129 first = false;
130 }
131 write!(f, "}}")
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_new_clock_is_empty() {
141 let clock = VectorClock::new();
142 assert!(clock.is_empty());
143 assert_eq!(clock.get("instance1"), 0);
144 }
145
146 #[test]
147 fn test_increment() {
148 let mut clock = VectorClock::new();
149 assert_eq!(clock.increment("instance1"), 1);
150 assert_eq!(clock.increment("instance1"), 2);
151 assert_eq!(clock.get("instance1"), 2);
152 }
153
154 #[test]
155 fn test_merge() {
156 let mut clock1 = VectorClock::new();
157 clock1.set("a".to_string(), 1);
158 clock1.set("b".to_string(), 2);
159
160 let mut clock2 = VectorClock::new();
161 clock2.set("b".to_string(), 3);
162 clock2.set("c".to_string(), 1);
163
164 clock1.merge(&clock2);
165
166 assert_eq!(clock1.get("a"), 1);
167 assert_eq!(clock1.get("b"), 3);
168 assert_eq!(clock1.get("c"), 1);
169 }
170}