eidetica/crdt/doc/list.rs
1//! List positioning system and List type for CRDT documents.
2//!
3//! This module provides both the Position type for stable list ordering
4//! and the List type itself for ordered collections in CRDT documents.
5
6use std::{cmp::Ordering, collections::BTreeMap};
7
8use uuid::Uuid;
9
10// Import Value from the value module
11use super::value::Value;
12use crate::crdt::{CRDTError, traits::Data};
13
14/// Represents a position in a CRDT list using rational numbers.
15///
16/// This type provides a stable ordering mechanism for list elements that allows
17/// insertion between any two existing elements without requiring renumbering.
18/// Each position consists of:
19/// - A rational number (numerator/denominator) for ordering
20/// - A unique UUID for deterministic tie-breaking
21///
22/// # Examples
23///
24/// ```
25/// use eidetica::crdt::doc::list::Position;
26///
27/// let pos1 = Position::new(10, 1);
28/// let pos2 = Position::new(20, 1);
29/// let between = Position::between(&pos1, &pos2);
30///
31/// assert!(pos1 < between);
32/// assert!(between < pos2);
33/// ```
34#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
35pub struct Position {
36 /// Numerator of the rational number
37 pub numerator: i64,
38 /// Denominator of the rational number (always positive)
39 pub denominator: u64,
40 /// Unique identifier for deterministic ordering
41 pub unique_id: Uuid,
42}
43
44impl Position {
45 /// Creates a new position with the specified rational number.
46 ///
47 /// # Arguments
48 /// * `numerator` - The numerator of the rational number
49 /// * `denominator` - The denominator of the rational number (must be > 0)
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use eidetica::crdt::doc::list::Position;
55 ///
56 /// let pos = Position::new(3, 2); // Represents 3/2 = 1.5
57 /// ```
58 pub fn new(numerator: i64, denominator: u64) -> Self {
59 assert!(denominator > 0, "Denominator must be positive");
60 let mut pos = Self {
61 numerator,
62 denominator,
63 unique_id: Uuid::new_v4(),
64 };
65 pos.reduce();
66 pos
67 }
68
69 /// Creates a position at the beginning of the sequence.
70 ///
71 /// # Examples
72 ///
73 /// ```
74 /// use eidetica::crdt::doc::list::Position;
75 ///
76 /// let beginning = Position::beginning();
77 /// let after = Position::new(1, 1);
78 /// assert!(beginning < after);
79 /// ```
80 pub fn beginning() -> Self {
81 Self::new(0, 1)
82 }
83
84 /// Creates a position at the end of the sequence.
85 ///
86 /// # Examples
87 ///
88 /// ```
89 /// use eidetica::crdt::doc::list::Position;
90 ///
91 /// let end = Position::end();
92 /// let before = Position::new(1000, 1);
93 /// assert!(before < end);
94 /// ```
95 pub fn end() -> Self {
96 Self::new(i64::MAX, 1)
97 }
98
99 /// Creates a position between two existing positions.
100 ///
101 /// This method finds the rational number that falls between the two given positions
102 /// and creates a new position with that value.
103 ///
104 /// # Arguments
105 /// * `left` - The left (smaller) position
106 /// * `right` - The right (larger) position
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use eidetica::crdt::doc::list::Position;
112 ///
113 /// let pos1 = Position::new(1, 1);
114 /// let pos2 = Position::new(3, 1);
115 /// let between = Position::between(&pos1, &pos2);
116 ///
117 /// assert!(pos1 < between);
118 /// assert!(between < pos2);
119 /// ```
120 pub fn between(left: &Position, right: &Position) -> Self {
121 // Convert to common denominator for easier calculation
122 let left_num = left.numerator as i128 * right.denominator as i128;
123 let right_num = right.numerator as i128 * left.denominator as i128;
124 let common_denom = left.denominator as i128 * right.denominator as i128;
125
126 // Find the midpoint
127 let mid_num = (left_num + right_num) / 2;
128
129 // If the midpoint is the same as one of the endpoints, we need to increase precision
130 if mid_num == left_num || mid_num == right_num {
131 // Double the denominator to increase precision
132 let new_denom = common_denom * 2;
133 let new_mid_num = (left_num * 2 + right_num * 2) / 2;
134
135 Self::new(new_mid_num as i64, new_denom as u64)
136 } else {
137 Self::new(mid_num as i64, common_denom as u64)
138 }
139 }
140
141 /// Reduces the fraction to its simplest form.
142 fn reduce(&mut self) {
143 let gcd = gcd(self.numerator.unsigned_abs(), self.denominator);
144 self.numerator /= gcd as i64;
145 self.denominator /= gcd;
146 }
147
148 /// Returns the rational value as a floating point number.
149 ///
150 /// Note: This is primarily for debugging and should not be used for ordering.
151 pub fn as_f64(&self) -> f64 {
152 self.numerator as f64 / self.denominator as f64
153 }
154}
155
156impl PartialOrd for Position {
157 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
158 Some(self.cmp(other))
159 }
160}
161
162impl Ord for Position {
163 fn cmp(&self, other: &Self) -> Ordering {
164 // Compare rational numbers: a/b vs c/d -> a*d vs c*b
165 let left = self.numerator as i128 * other.denominator as i128;
166 let right = other.numerator as i128 * self.denominator as i128;
167
168 match left.cmp(&right) {
169 Ordering::Equal => {
170 // If rational numbers are equal, use UUID for deterministic ordering
171 self.unique_id.cmp(&other.unique_id)
172 }
173 ordering => ordering,
174 }
175 }
176}
177
178/// Calculates the greatest common divisor of two numbers.
179fn gcd(a: u64, b: u64) -> u64 {
180 if b == 0 { a } else { gcd(b, a % b) }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_position_creation() {
189 let pos = Position::new(3, 2);
190 assert_eq!(pos.numerator, 3);
191 assert_eq!(pos.denominator, 2);
192 }
193
194 #[test]
195 fn test_position_reduction() {
196 let pos = Position::new(6, 4);
197 // Should be reduced to 3/2
198 assert_eq!(pos.numerator, 3);
199 assert_eq!(pos.denominator, 2);
200 }
201
202 #[test]
203 fn test_position_ordering() {
204 let pos1 = Position::new(1, 2); // 0.5
205 let pos2 = Position::new(3, 4); // 0.75
206 let pos3 = Position::new(1, 1); // 1.0
207
208 assert!(pos1 < pos2);
209 assert!(pos2 < pos3);
210 assert!(pos1 < pos3);
211 }
212
213 #[test]
214 fn test_position_between() {
215 let pos1 = Position::new(1, 1);
216 let pos2 = Position::new(3, 1);
217 let between = Position::between(&pos1, &pos2);
218
219 assert!(pos1 < between);
220 assert!(between < pos2);
221 }
222
223 #[test]
224 fn test_position_beginning_end() {
225 let beginning = Position::beginning();
226 let end = Position::end();
227 let middle = Position::new(100, 1);
228
229 assert!(beginning < middle);
230 assert!(middle < end);
231 }
232
233 #[test]
234 fn test_position_uuid_ordering() {
235 let pos1 = Position::new(1, 1);
236 let pos2 = Position::new(1, 1);
237
238 // Same rational number, but different UUIDs should provide deterministic ordering
239 assert_ne!(pos1.cmp(&pos2), Ordering::Equal);
240 }
241}
242
243/// Ordered list with stable positioning for CRDT operations.
244///
245/// `List` maintains a stable ordering of elements using [`Position`] keys
246/// in a `BTreeMap`. This design enables concurrent insertions without requiring
247/// coordination between distributed replicas.
248///
249/// # Key Features
250///
251/// - **Stable ordering**: Elements maintain their relative positions even with concurrent modifications
252/// - **Insertion anywhere**: Can insert between any two existing elements
253/// - **CRDT semantics**: Proper merge behavior for distributed systems
254/// - **Efficient access**: O(log n) access by position, O(1) by index for small lists
255///
256/// # Usage Patterns
257///
258/// ```
259/// # use eidetica::crdt::doc::List;
260/// # use eidetica::crdt::doc::list::Position;
261/// let mut list = List::new();
262///
263/// // Simple append operations
264/// list.push("first"); // Returns index 0
265/// list.push("second"); // Returns index 1
266///
267/// // Insert between existing elements using index
268/// list.insert(1, "between").unwrap();
269///
270/// // Access by traditional index
271/// assert_eq!(list.get(0).unwrap().as_text(), Some("first"));
272///
273/// // Advanced users can use Position for precise control
274/// let pos1 = Position::new(1, 1); // 1.0
275/// let pos2 = Position::new(2, 1); // 2.0
276/// let middle = Position::between(&pos1, &pos2);
277/// list.insert_at_position(middle, "advanced");
278/// ```
279///
280/// # Concurrent Operations
281///
282/// When two replicas insert at the same logical position, the rational number
283/// system ensures a consistent final order:
284///
285/// ```text
286/// Replica A: ["item1", "item3"] -> inserts "item2" between them
287/// Replica B: ["item1", "item3"] -> inserts "item4" between them
288///
289/// After merge: ["item1", "item2", "item4", "item3"]
290/// (order determined by Position comparison)
291/// ```
292#[derive(Debug, Clone, PartialEq, Eq)]
293pub struct List {
294 /// Internal storage using BTreeMap for ordered access
295 items: BTreeMap<Position, Value>,
296}
297
298impl List {
299 /// Creates a new empty list
300 pub fn new() -> Self {
301 Self {
302 items: BTreeMap::new(),
303 }
304 }
305
306 /// Returns the number of items in the list (excluding tombstones)
307 pub fn len(&self) -> usize {
308 self.items
309 .values()
310 .filter(|v| !matches!(v, Value::Deleted))
311 .count()
312 }
313
314 /// Returns the total number of items including tombstones
315 pub fn total_len(&self) -> usize {
316 self.items.len()
317 }
318
319 /// Returns true if the list is empty
320 pub fn is_empty(&self) -> bool {
321 self.items.is_empty()
322 }
323
324 /// Pushes a value to the end of the list
325 /// Returns the index of the newly added element
326 pub fn push(&mut self, value: impl Into<Value>) -> usize {
327 let value = value.into();
328 let position = if let Some((last_pos, _)) = self.items.last_key_value() {
329 // Create a position after the last element
330 Position::new(last_pos.numerator.saturating_add(1), 1)
331 } else {
332 // First element
333 Position::beginning()
334 };
335
336 self.items.insert(position, value);
337 // Return the index (count of non-tombstone elements - 1)
338 self.len() - 1
339 }
340
341 /// Inserts a value at a specific index
342 pub fn insert(&mut self, index: usize, value: impl Into<Value>) -> Result<(), CRDTError> {
343 let len = self.len();
344 if index > len {
345 return Err(CRDTError::ListIndexOutOfBounds { index, len });
346 }
347
348 let position = if index == 0 {
349 // Insert at beginning
350 if let Some((first_pos, _)) = self.items.first_key_value() {
351 Position::new(first_pos.numerator - 1, first_pos.denominator)
352 } else {
353 Position::beginning()
354 }
355 } else if index == len {
356 // Insert at end (same as push)
357 if let Some((last_pos, _)) = self.items.last_key_value() {
358 Position::new(last_pos.numerator + 1, last_pos.denominator)
359 } else {
360 Position::beginning()
361 }
362 } else {
363 // Insert between two existing positions
364 let positions: Vec<_> = self.items.keys().collect();
365 let left_pos = positions[index - 1];
366 let right_pos = positions[index];
367 Position::between(left_pos, right_pos)
368 };
369
370 self.items.insert(position, value.into());
371 Ok(())
372 }
373
374 /// Gets a value by index (0-based), filtering out tombstones
375 pub fn get(&self, index: usize) -> Option<&Value> {
376 self.items
377 .values()
378 .filter(|v| !matches!(v, Value::Deleted))
379 .nth(index)
380 }
381
382 /// Gets a mutable reference to a value by index (0-based), filtering out tombstones
383 pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
384 // Find the position of the nth non-tombstone element
385 let mut current_index = 0;
386 let mut target_position = None;
387
388 for (pos, value) in &self.items {
389 if !matches!(value, Value::Deleted) {
390 if current_index == index {
391 target_position = Some(pos.clone());
392 break;
393 }
394 current_index += 1;
395 }
396 }
397
398 if let Some(pos) = target_position {
399 self.items.get_mut(&pos)
400 } else {
401 None
402 }
403 }
404
405 /// Inserts a value at a specific position (advanced API)
406 pub fn insert_at_position(&mut self, position: Position, value: impl Into<Value>) {
407 self.items.insert(position, value.into());
408 }
409
410 /// Gets a value by position
411 pub fn get_by_position(&self, position: &Position) -> Option<&Value> {
412 self.items.get(position)
413 }
414
415 /// Gets a mutable reference to a value by position
416 pub fn get_by_position_mut(&mut self, position: &Position) -> Option<&mut Value> {
417 self.items.get_mut(position)
418 }
419
420 /// Sets a value at a specific index, returns the old value if present
421 /// Only considers non-tombstone elements for indexing
422 pub fn set(&mut self, index: usize, value: impl Into<Value>) -> Option<Value> {
423 let value = value.into();
424 // Find the position of the nth non-tombstone element
425 let mut current_index = 0;
426 let mut target_position = None;
427
428 for (pos, val) in &self.items {
429 if !matches!(val, Value::Deleted) {
430 if current_index == index {
431 target_position = Some(pos.clone());
432 break;
433 }
434 current_index += 1;
435 }
436 }
437
438 if let Some(pos) = target_position {
439 self.items.insert(pos, value)
440 } else {
441 None
442 }
443 }
444
445 /// Removes a value by index (tombstones it for CRDT semantics)
446 /// Only considers non-tombstone elements for indexing
447 pub fn remove(&mut self, index: usize) -> Option<Value> {
448 // Find the position of the nth non-tombstone element
449 let mut current_index = 0;
450 let mut target_position = None;
451
452 for (pos, val) in &self.items {
453 if !matches!(val, Value::Deleted) {
454 if current_index == index {
455 target_position = Some(pos.clone());
456 break;
457 }
458 current_index += 1;
459 }
460 }
461
462 if let Some(pos) = target_position {
463 let old_value = self.items.get(&pos).cloned();
464 self.items.insert(pos, Value::Deleted);
465 old_value
466 } else {
467 None
468 }
469 }
470
471 /// Removes a value by position
472 pub fn remove_by_position(&mut self, position: &Position) -> Option<Value> {
473 self.items.remove(position)
474 }
475
476 /// Returns an iterator over the values in order (excluding tombstones)
477 pub fn iter(&self) -> impl Iterator<Item = &Value> {
478 self.items.values().filter(|v| !matches!(v, Value::Deleted))
479 }
480
481 /// Returns an iterator over all values including tombstones
482 pub fn iter_all(&self) -> impl Iterator<Item = &Value> {
483 self.items.values()
484 }
485
486 /// Returns an iterator over position-value pairs in order
487 pub fn iter_with_positions(&self) -> impl Iterator<Item = (&Position, &Value)> {
488 self.items.iter()
489 }
490
491 /// Returns a mutable iterator over the values in order
492 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Value> {
493 self.items.values_mut()
494 }
495
496 /// Merges another List into this one (CRDT merge operation)
497 pub fn merge(&mut self, other: &List) {
498 for (position, value) in &other.items {
499 match self.items.get_mut(position) {
500 Some(existing_value) => {
501 // If both lists have the same position, merge the values
502 existing_value.merge(value);
503 }
504 None => {
505 // Position doesn't exist, add it
506 self.items.insert(position.clone(), value.clone());
507 }
508 }
509 }
510 }
511
512 /// Clears all items from the list
513 pub fn clear(&mut self) {
514 self.items.clear();
515 }
516
517 /// Converts to a Vec of values (loses position information)
518 pub fn to_vec(&self) -> Vec<Value> {
519 self.items.values().cloned().collect()
520 }
521}
522
523impl Default for List {
524 fn default() -> Self {
525 Self::new()
526 }
527}
528
529// Custom serde implementation to handle Position keys in JSON
530impl serde::Serialize for List {
531 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
532 where
533 S: serde::Serializer,
534 {
535 use serde::ser::SerializeSeq;
536
537 // Serialize as an array of [position, value] pairs
538 let mut seq = serializer.serialize_seq(Some(self.items.len()))?;
539 for (position, value) in &self.items {
540 seq.serialize_element(&(position, value))?;
541 }
542 seq.end()
543 }
544}
545
546impl<'de> serde::Deserialize<'de> for List {
547 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
548 where
549 D: serde::Deserializer<'de>,
550 {
551 use std::fmt;
552
553 use serde::de::{SeqAccess, Visitor};
554
555 struct ListVisitor;
556
557 impl<'de> Visitor<'de> for ListVisitor {
558 type Value = List;
559
560 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
561 formatter.write_str("a sequence of [position, value] pairs")
562 }
563
564 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
565 where
566 A: SeqAccess<'de>,
567 {
568 let mut items = BTreeMap::new();
569
570 while let Some((position, value)) = seq.next_element::<(Position, Value)>()? {
571 items.insert(position, value);
572 }
573
574 Ok(List { items })
575 }
576 }
577
578 deserializer.deserialize_seq(ListVisitor)
579 }
580}
581
582impl FromIterator<Value> for List {
583 fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
584 let mut list = List::new();
585 for value in iter {
586 list.push(value);
587 }
588 list
589 }
590}
591
592// Data trait implementations
593impl Data for List {}