Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/crdt/doc/
value.rs

1//! Value types for CRDT documents.
2//!
3//! This module provides the Value enum that represents all possible values
4//! that can be stored within a CRDT document. Values can be either leaf values
5//! (primitives like integers, strings, booleans) or branch values (nested
6//! structures like nodes and lists).
7
8use std::fmt;
9
10// Forward declarations for types defined in other modules
11use super::list::List;
12use crate::crdt::{
13    CRDTError, Doc,
14    traits::{CRDT, Data},
15};
16
17/// Values that can be stored in CRDT documents.
18///
19/// `Value` represents all possible data types that can be stored within
20/// a CRDT document structure. Values can be either leaf values (terminal data)
21/// or branch values (containing other structures).
22///
23/// # Value Types
24///
25/// ## Leaf Values (Terminal Nodes)
26/// - [`Value::Null`] - Represents null/empty values
27/// - [`Value::Bool`] - Boolean values (true/false)
28/// - [`Value::Int`] - 64-bit signed integers
29/// - [`Value::Text`] - UTF-8 text strings
30///
31/// ## Branch Values (Container Nodes)
32/// - [`Value::Doc`] - Nested document structures
33/// - [`Value::List`] - Ordered collections with stable positioning
34///
35/// ## CRDT Semantics
36/// - [`Value::Deleted`] - Tombstone marker for deleted values
37///
38/// # Direct Comparisons
39///
40/// `Value` implements `PartialEq` with primitive types for ergonomic comparisons:
41///
42/// ```
43/// # use eidetica::crdt::doc::Value;
44/// let text = Value::Text("hello".to_string());
45/// let number = Value::Int(42);
46/// let flag = Value::Bool(true);
47///
48/// // Direct comparison with primitives
49/// assert!(text == "hello");
50/// assert!(number == 42);
51/// assert!(flag == true);
52///
53/// // Reverse comparisons also work
54/// assert!("hello" == text);
55/// assert!(42 == number);
56/// assert!(true == flag);
57///
58/// // Type mismatches return false
59/// assert!(!(text == 42));
60/// assert!(!(number == "hello"));
61/// ```
62///
63/// # CRDT Merge Behavior
64///
65/// - **Leaf values**: Last-write-wins semantics
66/// - **Branch values**: Structural merging (recursive for Doc, positional for List)
67/// - **Tombstones**: Deletion markers that win over any non-deleted value
68/// - **Resurrection**: Non-deleted values can overwrite tombstones
69///
70/// ```
71/// # use eidetica::crdt::doc::Value;
72/// let mut val1 = Value::Int(42);
73/// let val2 = Value::Int(100);
74/// val1.merge(&val2);  // val1 becomes 100 (last-write-wins)
75///
76/// let mut val3 = Value::Text("hello".to_string());
77/// let deleted = Value::Deleted;
78/// val3.merge(&deleted);  // val3 becomes Deleted (tombstone wins)
79/// ```
80#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
81pub enum Value {
82    // Leaf values (terminal nodes)
83    /// Null/empty value
84    Null,
85    /// Boolean value
86    Bool(bool),
87    /// Integer value
88    Int(i64),
89    /// Text string value
90    Text(String),
91
92    // Branch values (can contain other nodes)
93    /// Sub-tree containing other nodes
94    Doc(Doc),
95    /// Ordered collection of values
96    List(List),
97
98    // CRDT semantics
99    /// Tombstone marker for deleted values
100    Deleted,
101}
102
103impl Value {
104    /// Returns true if this is a leaf value (terminal node)
105    pub fn is_leaf(&self) -> bool {
106        matches!(
107            self,
108            Value::Null | Value::Bool(_) | Value::Int(_) | Value::Text(_) | Value::Deleted
109        )
110    }
111
112    /// Returns true if this is a branch value (can contain other nodes)
113    pub fn is_branch(&self) -> bool {
114        matches!(self, Value::Doc(_) | Value::List(_))
115    }
116
117    /// Returns true if this value represents a deletion
118    pub fn is_deleted(&self) -> bool {
119        matches!(self, Value::Deleted)
120    }
121
122    /// Returns true if this is a null value
123    pub fn is_null(&self) -> bool {
124        matches!(self, Value::Null)
125    }
126
127    /// Returns the type name as a string
128    pub fn type_name(&self) -> &'static str {
129        match self {
130            Value::Null => "null",
131            Value::Bool(_) => "bool",
132            Value::Int(_) => "int",
133            Value::Text(_) => "text",
134            Value::Doc(_) => "doc",
135            Value::List(_) => "list",
136            Value::Deleted => "deleted",
137        }
138    }
139
140    /// Attempts to convert to a boolean
141    pub fn as_bool(&self) -> Option<bool> {
142        match self {
143            Value::Bool(b) => Some(*b),
144            _ => None,
145        }
146    }
147
148    /// Attempts to convert to a boolean, returning default if not a bool
149    pub fn as_bool_or(&self, default: bool) -> bool {
150        self.as_bool().unwrap_or(default)
151    }
152
153    /// Attempts to convert to a boolean, returning false if not a bool
154    pub fn as_bool_or_false(&self) -> bool {
155        self.as_bool().unwrap_or(false)
156    }
157
158    /// Attempts to convert to an integer
159    pub fn as_int(&self) -> Option<i64> {
160        match self {
161            Value::Int(n) => Some(*n),
162            _ => None,
163        }
164    }
165
166    /// Attempts to convert to an integer, returning default if not an int
167    pub fn as_int_or(&self, default: i64) -> i64 {
168        self.as_int().unwrap_or(default)
169    }
170
171    /// Attempts to convert to an integer, returning 0 if not an int
172    pub fn as_int_or_zero(&self) -> i64 {
173        self.as_int().unwrap_or(0)
174    }
175
176    /// Attempts to convert to a string
177    pub fn as_text(&self) -> Option<&str> {
178        match self {
179            Value::Text(s) => Some(s),
180            _ => None,
181        }
182    }
183
184    /// Attempts to convert to a string, returning empty string if not text
185    pub fn as_text_or_empty(&self) -> &str {
186        self.as_text().unwrap_or("")
187    }
188
189    /// Attempts to convert to a Doc (returns immutable reference)
190    pub fn as_doc(&self) -> Option<&Doc> {
191        match self {
192            Value::Doc(node) => Some(node),
193            _ => None,
194        }
195    }
196
197    /// Attempts to convert to a mutable Doc reference
198    pub fn as_doc_mut(&mut self) -> Option<&mut Doc> {
199        match self {
200            Value::Doc(node) => Some(node),
201            _ => None,
202        }
203    }
204
205    /// Attempts to convert to a list (returns immutable reference)
206    pub fn as_list(&self) -> Option<&List> {
207        match self {
208            Value::List(list) => Some(list),
209            _ => None,
210        }
211    }
212
213    /// Attempts to convert to a mutable list reference
214    pub fn as_list_mut(&mut self) -> Option<&mut List> {
215        match self {
216            Value::List(list) => Some(list),
217            _ => None,
218        }
219    }
220
221    /// Merges another Value into this one (CRDT merge operation)
222    pub fn merge(&mut self, other: &Value) {
223        if matches!(self, Value::Deleted) {
224            // If self is deleted, other value wins (resurrection)
225            *self = other.clone();
226            return;
227        }
228
229        if matches!(other, Value::Deleted) {
230            // If other is deleted, the tombstone wins (deletion)
231            *self = Value::Deleted;
232            return;
233        }
234
235        // Handle specific cases without moving self
236        match other {
237            Value::Doc(other_node) => {
238                if let Value::Doc(self_node) = self {
239                    match self_node.merge(other_node) {
240                        Ok(merged) => *self_node = merged,
241                        Err(_) => *self = other.clone(),
242                    }
243                } else {
244                    *self = other.clone();
245                }
246            }
247            Value::List(other_list) => {
248                if let Value::List(self_list) = self {
249                    self_list.merge(other_list);
250                } else {
251                    // Different types, replace with other
252                    *self = other.clone();
253                }
254            }
255            _ => {
256                // For leaf values, implement last-write-wins
257                *self = other.clone();
258            }
259        }
260    }
261
262    /// Converts to a JSON-like string representation for human-readable output.
263    ///
264    /// This method produces clean JSON output intended for display, debugging, and export.
265    /// It differs from serde serialization in important ways:
266    ///
267    /// - **Tombstones**: Deleted values appear as `null` instead of being preserved as tombstones
268    /// - **Purpose**: Human-readable output, not CRDT state preservation
269    /// - **Use cases**: Display, debugging, export to external systems
270    ///
271    /// For complete CRDT state preservation including tombstones, use serde serialization instead.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// # use eidetica::crdt::doc::Value;
277    /// let value = Value::Text("hello".to_string());
278    /// assert_eq!(value.to_json_string(), "\"hello\"");
279    ///
280    /// let deleted = Value::Deleted;
281    /// assert_eq!(deleted.to_json_string(), "null"); // Tombstones become null
282    /// ```
283    pub fn to_json_string(&self) -> String {
284        match self {
285            Value::Null => "null".to_string(),
286            Value::Bool(b) => b.to_string(),
287            Value::Int(n) => n.to_string(),
288            Value::Text(s) => format!("\"{}\"", s.replace('\"', "\\\"")),
289            Value::Doc(doc) => doc.to_json_string(),
290            Value::List(list) => {
291                let mut result = String::with_capacity(list.len() * 8); // Reasonable initial capacity
292                result.push('[');
293                for (i, item) in list.iter().enumerate() {
294                    if i > 0 {
295                        result.push(',');
296                    }
297                    result.push_str(&item.to_json_string());
298                }
299                result.push(']');
300                result
301            }
302            Value::Deleted => "null".to_string(), // Deleted values appear as null
303        }
304    }
305}
306
307impl fmt::Display for Value {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match self {
310            Value::Null => write!(f, "null"),
311            Value::Bool(b) => write!(f, "{b}"),
312            Value::Int(n) => write!(f, "{n}"),
313            Value::Text(s) => write!(f, "{s}"),
314            Value::Doc(doc) => write!(f, "{doc}"),
315            Value::List(list) => {
316                write!(f, "[")?;
317                for (i, item) in list.iter().enumerate() {
318                    if i > 0 {
319                        write!(f, ", ")?;
320                    }
321                    write!(f, "{item}")?;
322                }
323                write!(f, "]")
324            }
325            Value::Deleted => write!(f, "<deleted>"),
326        }
327    }
328}
329
330// Convenient From implementations for common types
331impl From<bool> for Value {
332    fn from(value: bool) -> Self {
333        Value::Bool(value)
334    }
335}
336
337impl From<i64> for Value {
338    fn from(value: i64) -> Self {
339        Value::Int(value)
340    }
341}
342
343impl From<u64> for Value {
344    fn from(value: u64) -> Self {
345        // Convert to i64, clamping if necessary
346        Value::Int(value as i64)
347    }
348}
349
350impl From<f64> for Value {
351    fn from(value: f64) -> Self {
352        // Convert to i64, truncating the fractional part
353        Value::Int(value as i64)
354    }
355}
356
357impl From<i32> for Value {
358    fn from(value: i32) -> Self {
359        Value::Int(value as i64)
360    }
361}
362
363impl From<u32> for Value {
364    fn from(value: u32) -> Self {
365        Value::Int(value as i64)
366    }
367}
368
369impl From<f32> for Value {
370    fn from(value: f32) -> Self {
371        Value::Int(value as i64)
372    }
373}
374
375impl From<String> for Value {
376    fn from(value: String) -> Self {
377        Value::Text(value)
378    }
379}
380
381impl From<&str> for Value {
382    fn from(value: &str) -> Self {
383        Value::Text(value.to_string())
384    }
385}
386
387impl From<Doc> for Value {
388    fn from(value: Doc) -> Self {
389        Value::Doc(value)
390    }
391}
392
393impl From<List> for Value {
394    fn from(value: List) -> Self {
395        Value::List(value)
396    }
397}
398
399// Convenience conversion from Doc to Value for ergonomic API usage
400// This allows Doc instances to be used directly in contexts expecting Value,
401// particularly useful in testing and when building nested document structures.
402
403// TryFrom implementations for better type coercion
404impl TryFrom<&Value> for String {
405    type Error = CRDTError;
406
407    fn try_from(value: &Value) -> Result<Self, Self::Error> {
408        match value {
409            Value::Text(s) => Ok(s.clone()),
410            _ => Err(CRDTError::TypeMismatch {
411                expected: "String".to_string(),
412                actual: format!("{value:?}"),
413            }),
414        }
415    }
416}
417
418impl<'a> TryFrom<&'a Value> for &'a str {
419    type Error = CRDTError;
420
421    fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
422        match value {
423            Value::Text(s) => Ok(s),
424            _ => Err(CRDTError::TypeMismatch {
425                expected: "&str".to_string(),
426                actual: format!("{value:?}"),
427            }),
428        }
429    }
430}
431
432impl TryFrom<&Value> for i64 {
433    type Error = CRDTError;
434
435    fn try_from(value: &Value) -> Result<Self, Self::Error> {
436        match value {
437            Value::Int(n) => Ok(*n),
438            _ => Err(CRDTError::TypeMismatch {
439                expected: "i64".to_string(),
440                actual: format!("{value:?}"),
441            }),
442        }
443    }
444}
445
446impl TryFrom<&Value> for bool {
447    type Error = CRDTError;
448
449    fn try_from(value: &Value) -> Result<Self, Self::Error> {
450        match value {
451            Value::Bool(b) => Ok(*b),
452            _ => Err(CRDTError::TypeMismatch {
453                expected: "bool".to_string(),
454                actual: format!("{value:?}"),
455            }),
456        }
457    }
458}
459
460// Note: Reference types (&Node, &List) have lifetime issues with TryFrom
461// Users should use the existing as_node() and as_list() methods for references
462// Or clone into owned types when needed
463
464impl TryFrom<&Value> for Doc {
465    type Error = CRDTError;
466
467    fn try_from(value: &Value) -> Result<Self, Self::Error> {
468        match value {
469            Value::Doc(doc) => Ok(doc.clone()),
470            _ => Err(CRDTError::TypeMismatch {
471                expected: "Doc".to_string(),
472                actual: format!("{value:?}"),
473            }),
474        }
475    }
476}
477
478impl TryFrom<&Value> for List {
479    type Error = CRDTError;
480
481    fn try_from(value: &Value) -> Result<Self, Self::Error> {
482        match value {
483            Value::List(list) => Ok(list.clone()),
484            _ => Err(CRDTError::TypeMismatch {
485                expected: "List".to_string(),
486                actual: format!("{value:?}"),
487            }),
488        }
489    }
490}
491
492// PartialEq implementations for comparing Value with other types
493impl PartialEq<str> for Value {
494    fn eq(&self, other: &str) -> bool {
495        match self {
496            Value::Text(s) => s == other,
497            _ => false,
498        }
499    }
500}
501
502impl PartialEq<&str> for Value {
503    fn eq(&self, other: &&str) -> bool {
504        self == *other
505    }
506}
507
508impl PartialEq<String> for Value {
509    fn eq(&self, other: &String) -> bool {
510        match self {
511            Value::Text(s) => s == other,
512            _ => false,
513        }
514    }
515}
516
517impl PartialEq<i64> for Value {
518    fn eq(&self, other: &i64) -> bool {
519        match self {
520            Value::Int(n) => n == other,
521            _ => false,
522        }
523    }
524}
525
526impl PartialEq<i32> for Value {
527    fn eq(&self, other: &i32) -> bool {
528        match self {
529            Value::Int(n) => *n == *other as i64,
530            _ => false,
531        }
532    }
533}
534
535impl PartialEq<u32> for Value {
536    fn eq(&self, other: &u32) -> bool {
537        match self {
538            Value::Int(n) => *n == *other as i64,
539            _ => false,
540        }
541    }
542}
543
544impl PartialEq<bool> for Value {
545    fn eq(&self, other: &bool) -> bool {
546        match self {
547            Value::Bool(b) => b == other,
548            _ => false,
549        }
550    }
551}
552
553// Reverse implementations for symmetry
554impl PartialEq<Value> for str {
555    fn eq(&self, other: &Value) -> bool {
556        other == self
557    }
558}
559
560impl PartialEq<Value> for &str {
561    fn eq(&self, other: &Value) -> bool {
562        other == *self
563    }
564}
565
566impl PartialEq<Value> for String {
567    fn eq(&self, other: &Value) -> bool {
568        other == self
569    }
570}
571
572impl PartialEq<Value> for i64 {
573    fn eq(&self, other: &Value) -> bool {
574        other == self
575    }
576}
577
578impl PartialEq<Value> for i32 {
579    fn eq(&self, other: &Value) -> bool {
580        other == self
581    }
582}
583
584impl PartialEq<Value> for u32 {
585    fn eq(&self, other: &Value) -> bool {
586        other == self
587    }
588}
589
590impl PartialEq<Value> for bool {
591    fn eq(&self, other: &Value) -> bool {
592        other == self
593    }
594}
595
596// Data trait implementation
597impl Data for Value {}