1use crate::sync::{Timestamp, VectorClock};
13use crate::{ClientID, DocumentID, FieldPath};
14use serde::{Deserialize, Serialize};
17use serde_json::Value as JsonValue;
18use std::collections::HashMap;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Document {
23 pub id: DocumentID,
25
26 pub fields: HashMap<FieldPath, Field>,
28
29 pub version: VectorClock,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct Field {
36 pub value: JsonValue,
38
39 pub timestamp: Timestamp,
41}
42
43impl Document {
44 pub fn new(id: DocumentID) -> Self {
46 Self {
47 id,
48 fields: HashMap::new(),
49 version: VectorClock::new(),
50 }
51 }
52
53 pub fn set_field(
58 &mut self,
59 field_path: FieldPath,
60 value: JsonValue,
61 clock: u64,
62 client_id: ClientID,
63 ) {
64 let timestamp = Timestamp::new(clock, client_id);
65 let new_field = Field { value, timestamp };
66
67 self.merge_field(field_path, new_field);
69 }
70
71 pub fn get_field(&self, field_path: &FieldPath) -> Option<&JsonValue> {
73 self.fields.get(field_path).map(|f| &f.value)
74 }
75
76 pub fn merge_field(&mut self, field_path: FieldPath, remote_field: Field) -> bool {
86 match self.fields.get(&field_path) {
87 Some(local_field) => {
88 match remote_field.timestamp.compare_lww(&local_field.timestamp) {
90 std::cmp::Ordering::Greater => {
91 self.fields.insert(field_path, remote_field);
93 true
94 }
95 std::cmp::Ordering::Less => {
96 false
98 }
99 std::cmp::Ordering::Equal => {
100 let local_json = serde_json::to_string(&local_field.value).unwrap();
105 let remote_json = serde_json::to_string(&remote_field.value).unwrap();
106
107 if remote_json > local_json {
108 self.fields.insert(field_path, remote_field);
109 true
110 } else {
111 false
113 }
114 }
115 }
116 }
117 None => {
118 self.fields.insert(field_path, remote_field);
120 true
121 }
122 }
123 }
124
125 pub fn merge(&mut self, remote: &Document) -> usize {
130 let mut updated_count = 0;
131
132 for (field_path, remote_field) in &remote.fields {
134 if self.merge_field(field_path.clone(), remote_field.clone()) {
135 updated_count += 1;
136 }
137 }
138
139 self.version.merge(&remote.version);
141
142 updated_count
143 }
144
145 pub fn to_json(&self) -> JsonValue {
147 let mut obj = serde_json::Map::new();
148
149 for (field_path, field) in &self.fields {
150 obj.insert(field_path.clone(), field.value.clone());
151 }
152
153 JsonValue::Object(obj)
154 }
155
156 pub fn field_paths(&self) -> Vec<&FieldPath> {
158 self.fields.keys().collect()
159 }
160
161 pub fn is_empty(&self) -> bool {
163 self.fields.is_empty()
164 }
165
166 pub fn field_count(&self) -> usize {
168 self.fields.len()
169 }
170
171 pub fn id(&self) -> &DocumentID {
173 &self.id
174 }
175
176 pub fn version(&self) -> &VectorClock {
178 &self.version
179 }
180
181 pub fn fields(&self) -> &HashMap<FieldPath, Field> {
183 &self.fields
184 }
185
186 pub fn delete_field(&mut self, field_path: &FieldPath) {
188 self.fields.remove(field_path);
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use serde_json::json;
196
197 #[test]
198 fn test_document_creation() {
199 let doc = Document::new("doc-123".to_string());
200 assert_eq!(doc.id, "doc-123");
201 assert!(doc.is_empty());
202 }
203
204 #[test]
205 fn test_set_and_get_field() {
206 let mut doc = Document::new("doc-123".to_string());
207
208 doc.set_field(
209 "title".to_string(),
210 json!("Hello World"),
211 1,
212 "client1".to_string(),
213 );
214
215 assert_eq!(
216 doc.get_field(&"title".to_string()),
217 Some(&json!("Hello World"))
218 );
219 assert_eq!(doc.field_count(), 1);
220 }
221
222 #[test]
223 fn test_lww_merge_remote_wins() {
224 let mut doc = Document::new("doc-123".to_string());
225
226 doc.set_field(
228 "title".to_string(),
229 json!("Local Title"),
230 1,
231 "client1".to_string(),
232 );
233
234 let remote_field = Field {
236 value: json!("Remote Title"),
237 timestamp: Timestamp::new(2, "client2".to_string()),
238 };
239
240 let updated = doc.merge_field("title".to_string(), remote_field);
241
242 assert!(updated);
243 assert_eq!(
244 doc.get_field(&"title".to_string()),
245 Some(&json!("Remote Title"))
246 );
247 }
248
249 #[test]
250 fn test_lww_merge_local_wins() {
251 let mut doc = Document::new("doc-123".to_string());
252
253 doc.set_field(
255 "title".to_string(),
256 json!("Local Title"),
257 2,
258 "client1".to_string(),
259 );
260
261 let remote_field = Field {
263 value: json!("Remote Title"),
264 timestamp: Timestamp::new(1, "client2".to_string()),
265 };
266
267 let updated = doc.merge_field("title".to_string(), remote_field);
268
269 assert!(!updated);
270 assert_eq!(
271 doc.get_field(&"title".to_string()),
272 Some(&json!("Local Title"))
273 );
274 }
275
276 #[test]
277 fn test_lww_merge_tie_breaking() {
278 let mut doc = Document::new("doc-123".to_string());
279
280 doc.set_field(
282 "title".to_string(),
283 json!("Local Title"),
284 1,
285 "client1".to_string(),
286 );
287
288 let remote_field = Field {
290 value: json!("Remote Title"),
291 timestamp: Timestamp::new(1, "client2".to_string()),
292 };
293
294 let updated = doc.merge_field("title".to_string(), remote_field);
295
296 assert!(updated);
298 assert_eq!(
299 doc.get_field(&"title".to_string()),
300 Some(&json!("Remote Title"))
301 );
302 }
303
304 #[test]
305 fn test_merge_entire_document() {
306 let mut doc1 = Document::new("doc-123".to_string());
307 doc1.set_field(
308 "field1".to_string(),
309 json!("value1"),
310 1,
311 "client1".to_string(),
312 );
313 doc1.set_field(
314 "field2".to_string(),
315 json!("value2"),
316 1,
317 "client1".to_string(),
318 );
319
320 let mut doc2 = Document::new("doc-123".to_string());
321 doc2.set_field(
322 "field1".to_string(),
323 json!("new_value1"),
324 2,
325 "client2".to_string(),
326 );
327 doc2.set_field(
328 "field3".to_string(),
329 json!("value3"),
330 1,
331 "client2".to_string(),
332 );
333
334 let updated_count = doc1.merge(&doc2);
336
337 assert_eq!(updated_count, 2);
341 assert_eq!(
342 doc1.get_field(&"field1".to_string()),
343 Some(&json!("new_value1"))
344 );
345 assert_eq!(
346 doc1.get_field(&"field2".to_string()),
347 Some(&json!("value2"))
348 );
349 assert_eq!(
350 doc1.get_field(&"field3".to_string()),
351 Some(&json!("value3"))
352 );
353 }
354
355 #[test]
356 fn test_document_to_json() {
357 let mut doc = Document::new("doc-123".to_string());
358 doc.set_field(
359 "title".to_string(),
360 json!("Hello"),
361 1,
362 "client1".to_string(),
363 );
364 doc.set_field("count".to_string(), json!(42), 1, "client1".to_string());
365
366 let json = doc.to_json();
367 assert_eq!(json["title"], json!("Hello"));
368 assert_eq!(json["count"], json!(42));
369 }
370
371 #[test]
372 fn test_convergence_property() {
373 let mut replica1 = Document::new("doc-123".to_string());
376 let mut replica2 = Document::new("doc-123".to_string());
377
378 let client1_update = Document {
380 id: "doc-123".to_string(),
381 fields: {
382 let mut map = HashMap::new();
383 map.insert(
384 "field1".to_string(),
385 Field {
386 value: json!("A"),
387 timestamp: Timestamp::new(1, "client1".to_string()),
388 },
389 );
390 map
391 },
392 version: VectorClock::new(),
393 };
394
395 let client2_update = Document {
397 id: "doc-123".to_string(),
398 fields: {
399 let mut map = HashMap::new();
400 map.insert(
401 "field1".to_string(),
402 Field {
403 value: json!("B"),
404 timestamp: Timestamp::new(2, "client2".to_string()),
405 },
406 );
407 map
408 },
409 version: VectorClock::new(),
410 };
411
412 replica1.merge(&client1_update);
414 replica1.merge(&client2_update);
415
416 replica2.merge(&client2_update);
418 replica2.merge(&client1_update);
419
420 assert_eq!(
422 replica1.get_field(&"field1".to_string()),
423 replica2.get_field(&"field1".to_string())
424 );
425
426 assert_eq!(replica1.get_field(&"field1".to_string()), Some(&json!("B")));
428 assert_eq!(replica2.get_field(&"field1".to_string()), Some(&json!("B")));
429 }
430}