1use alloc::collections::BTreeMap;
2use alloc::string::String;
3use serde_json::Value;
4
5#[derive(Debug, Clone)]
7pub struct LWWEntry {
8 pub value: Value,
9 pub timestamp_ms: i64,
10 pub writer_id: String,
11}
12
13#[derive(Debug, Clone, Default)]
16pub struct LWWMap {
17 map: BTreeMap<String, LWWEntry>,
18}
19
20impl LWWMap {
21 pub fn new() -> Self {
23 Self { map: BTreeMap::new() }
24 }
25
26 pub fn set(&mut self, key: &str, value: Value, timestamp_ms: i64, writer_id: &str) {
29 if let Some(existing) = self.map.get(key) {
30 if existing.timestamp_ms > timestamp_ms {
31 return;
32 }
33 if existing.timestamp_ms == timestamp_ms && existing.writer_id.as_str() >= writer_id {
34 return;
35 }
36 }
37 self.map.insert(key.into(), LWWEntry { value, timestamp_ms, writer_id: writer_id.into() });
38 }
39
40 pub fn get(&self, key: &str) -> Option<&Value> {
42 self.map.get(key).map(|e| &e.value)
43 }
44
45 pub fn merge(&mut self, other: &LWWMap) {
47 for (k, entry) in &other.map {
48 self.set(k, entry.value.clone(), entry.timestamp_ms, &entry.writer_id);
49 }
50 }
51
52 pub fn to_map(&self) -> BTreeMap<String, Value> {
54 self.map.iter().map(|(k, v)| (k.clone(), v.value.clone())).collect()
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn test_set_get() {
64 let mut m = LWWMap::new();
65 m.set("key", Value::String("hello".into()), 100, "agent-a");
66 assert_eq!(m.get("key"), Some(&Value::String("hello".into())));
67 assert_eq!(m.get("missing"), None);
68 }
69
70 #[test]
71 fn test_last_writer_wins() {
72 let mut m = LWWMap::new();
73 m.set("x", Value::Number(1.into()), 100, "agent-a");
74 m.set("x", Value::Number(0.into()), 50, "agent-b");
76 assert_eq!(m.get("x"), Some(&Value::Number(1.into())), "older write must not overwrite newer value");
77
78 m.set("x", Value::Number(99.into()), 100, "agent-a");
80 assert_eq!(m.get("x"), Some(&Value::Number(1.into())), "equal timestamp+writer must not overwrite");
81
82 m.set("x", Value::Number(77.into()), 100, "agent-z");
84 assert_eq!(m.get("x"), Some(&Value::Number(77.into())), "equal timestamp with higher writer_id must overwrite");
85
86 m.set("x", Value::Number(2.into()), 200, "agent-a");
88 assert_eq!(m.get("x"), Some(&Value::Number(2.into())), "newer write must overwrite");
89 }
90
91 #[test]
92 fn test_merge() {
93 let mut a = LWWMap::new();
94 a.set("shared", Value::String("from_a".into()), 100, "agent-a");
95 a.set("only_a", Value::Bool(true), 50, "agent-a");
96
97 let mut b = LWWMap::new();
98 b.set("shared", Value::String("from_b".into()), 200, "agent-b"); b.set("only_b", Value::Number(42.into()), 75, "agent-b");
100
101 a.merge(&b);
102
103 assert_eq!(
105 a.get("shared"),
106 Some(&Value::String("from_b".into())),
107 "merge must pick the newer value for conflicting keys"
108 );
109 assert_eq!(a.get("only_a"), Some(&Value::Bool(true)));
111 assert_eq!(a.get("only_b"), Some(&Value::Number(42.into())));
112 }
113}