Skip to main content

diamond_types_extended/
value.rs

1//! Unified value types for the Diamond Types Extended API.
2//!
3//! This module provides the public value types:
4//! - [`Value`] — read values (primitives + CRDT references)
5//! - [`PrimitiveValue`] — write values (primitives only, used in set/add/remove)
6//! - [`MaterializedValue`] — fully resolved document tree from checkout()
7//! - [`CrdtId`] — opaque handle to nested CRDTs
8//! - [`Conflicted`] — wrapper for values with concurrent conflicts
9
10use std::collections::BTreeMap;
11use std::fmt;
12
13use ordered_float::NotNan;
14use crate::{CRDTKind, CreateValue, DTValue, LV, Primitive, RegisterValue};
15use smartstring::alias::String as SmartString;
16
17/// Opaque handle to a nested CRDT within a Document.
18///
19/// This handle can be used to navigate to nested CRDTs (maps, text, sets, registers)
20/// without exposing internal implementation details.
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
22pub struct CrdtId(pub(crate) LV);
23
24impl CrdtId {
25    /// Get the internal LV (for debugging/testing).
26    #[doc(hidden)]
27    pub fn as_lv(&self) -> LV {
28        self.0
29    }
30}
31
32/// Unified value type for reading from CRDTs.
33///
34/// This enum represents all values that can be read from CRDTs.
35/// Primitive variants contain data directly; CRDT variants contain
36/// opaque [`CrdtId`] handles for navigation.
37///
38/// For writing primitive values, use [`PrimitiveValue`] instead —
39/// it prevents accidentally passing CRDT references to `set()`.
40#[derive(Debug, Clone, Eq, PartialEq)]
41pub enum Value {
42    /// Nil/null value.
43    Nil,
44    /// Boolean value.
45    Bool(bool),
46    /// 64-bit signed integer.
47    Int(i64),
48    /// 64-bit floating point (never NaN).
49    Float(NotNan<f64>),
50    /// String value (UTF-8).
51    Str(String),
52    /// Reference to a nested Map CRDT.
53    Map(CrdtId),
54    /// Reference to a nested Text CRDT.
55    Text(CrdtId),
56    /// Reference to a nested Set CRDT.
57    Set(CrdtId),
58    /// Reference to a nested Register CRDT.
59    Register(CrdtId),
60}
61
62impl Value {
63    /// Check if this value is nil.
64    pub fn is_nil(&self) -> bool {
65        matches!(self, Value::Nil)
66    }
67
68    /// Check if this value is a primitive (not a CRDT reference).
69    pub fn is_primitive(&self) -> bool {
70        matches!(self, Value::Nil | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::Str(_))
71    }
72
73    /// Check if this value is a CRDT reference.
74    pub fn is_crdt(&self) -> bool {
75        matches!(self, Value::Map(_) | Value::Text(_) | Value::Set(_) | Value::Register(_))
76    }
77
78    /// Get as bool if this is a boolean.
79    pub fn as_bool(&self) -> Option<bool> {
80        match self {
81            Value::Bool(b) => Some(*b),
82            _ => None,
83        }
84    }
85
86    /// Get as i64 if this is an integer.
87    pub fn as_int(&self) -> Option<i64> {
88        match self {
89            Value::Int(n) => Some(*n),
90            _ => None,
91        }
92    }
93
94    /// Get as f64 if this is a float.
95    /// Get as f64 if this is a float.
96    pub fn as_f64(&self) -> Option<f64> {
97        match self {
98            Value::Float(n) => Some(n.into_inner()),
99            _ => None,
100        }
101    }
102
103    /// Get as str if this is a string.
104    pub fn as_str(&self) -> Option<&str> {
105        match self {
106            Value::Str(s) => Some(s),
107            _ => None,
108        }
109    }
110
111    /// Get the CRDT ID if this is a CRDT reference.
112    pub fn crdt_id(&self) -> Option<CrdtId> {
113        match self {
114            Value::Map(id) | Value::Text(id) | Value::Set(id) | Value::Register(id) => Some(*id),
115            _ => None,
116        }
117    }
118}
119
120impl fmt::Display for Value {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Value::Nil => write!(f, "nil"),
124            Value::Bool(b) => write!(f, "{}", b),
125            Value::Int(n) => write!(f, "{}", n),
126            Value::Float(n) => write!(f, "{}", n),
127            Value::Str(s) => write!(f, "{:?}", s),
128            Value::Map(id) => write!(f, "Map({:?})", id),
129            Value::Text(id) => write!(f, "Text({:?})", id),
130            Value::Set(id) => write!(f, "Set({:?})", id),
131            Value::Register(id) => write!(f, "Register({:?})", id),
132        }
133    }
134}
135
136// ============ PrimitiveValue — type-safe write input ============
137
138/// Primitive value type for write operations.
139///
140/// This is the input type for `MapMut::set()`, `SetMut::add()`, and
141/// `SetMut::remove()`. Unlike [`Value`], it cannot contain CRDT references,
142/// so the type system prevents passing a Map/Text/Set/Register handle
143/// where a primitive is expected.
144///
145/// Use the `create_map()`, `create_text()`, etc. methods to create
146/// nested CRDTs instead.
147#[derive(Debug, Clone, Eq, PartialEq)]
148#[derive(serde::Serialize, serde::Deserialize)]
149pub enum PrimitiveValue {
150    /// Nil/null value.
151    Nil,
152    /// Boolean value.
153    Bool(bool),
154    /// 64-bit signed integer.
155    Int(i64),
156    /// 64-bit floating point (never NaN).
157    Float(NotNan<f64>),
158    /// String value (UTF-8).
159    Str(String),
160}
161
162impl From<bool> for PrimitiveValue {
163    fn from(b: bool) -> Self { PrimitiveValue::Bool(b) }
164}
165
166impl From<i64> for PrimitiveValue {
167    fn from(n: i64) -> Self { PrimitiveValue::Int(n) }
168}
169
170impl From<i32> for PrimitiveValue {
171    fn from(n: i32) -> Self { PrimitiveValue::Int(n as i64) }
172}
173
174/// Panics if the value is NaN.
175impl From<f64> for PrimitiveValue {
176    fn from(n: f64) -> Self { PrimitiveValue::Float(NotNan::new(n).expect("NaN is not a valid CRDT value")) }
177}
178
179impl From<String> for PrimitiveValue {
180    fn from(s: String) -> Self { PrimitiveValue::Str(s) }
181}
182
183impl From<&str> for PrimitiveValue {
184    fn from(s: &str) -> Self { PrimitiveValue::Str(s.to_string()) }
185}
186
187impl From<()> for PrimitiveValue {
188    fn from(_: ()) -> Self { PrimitiveValue::Nil }
189}
190
191impl From<PrimitiveValue> for Primitive {
192    fn from(v: PrimitiveValue) -> Self {
193        match v {
194            PrimitiveValue::Nil => Primitive::Nil,
195            PrimitiveValue::Bool(b) => Primitive::Bool(b),
196            PrimitiveValue::Int(n) => Primitive::I64(n),
197            PrimitiveValue::Float(n) => Primitive::F64(n),
198            PrimitiveValue::Str(s) => Primitive::Str(s.into()),
199        }
200    }
201}
202
203impl From<PrimitiveValue> for CreateValue {
204    fn from(v: PrimitiveValue) -> Self {
205        CreateValue::Primitive(v.into())
206    }
207}
208
209impl From<PrimitiveValue> for Value {
210    fn from(v: PrimitiveValue) -> Self {
211        match v {
212            PrimitiveValue::Nil => Value::Nil,
213            PrimitiveValue::Bool(b) => Value::Bool(b),
214            PrimitiveValue::Int(n) => Value::Int(n),
215            PrimitiveValue::Float(n) => Value::Float(n),
216
217            PrimitiveValue::Str(s) => Value::Str(s),
218        }
219    }
220}
221
222// ============ MaterializedValue — checkout output ============
223
224/// Fully resolved document tree value.
225///
226/// Returned by `Document::checkout()` to represent the entire document state
227/// without exposing internal types. Unlike [`Value`], this enum contains
228/// the actual nested data rather than CRDT handles.
229#[derive(Debug, Clone, Eq, PartialEq)]
230#[derive(serde::Serialize, serde::Deserialize)]
231pub enum MaterializedValue {
232    /// Nil/null value.
233    Nil,
234    /// Boolean value.
235    Bool(bool),
236    /// 64-bit signed integer.
237    Int(i64),
238    /// 64-bit floating point (never NaN).
239    Float(NotNan<f64>),
240    /// String value (UTF-8).
241    Str(String),
242    /// Text CRDT content.
243    Text(String),
244    /// Nested map with string keys.
245    Map(BTreeMap<String, MaterializedValue>),
246    /// OR-Set containing primitive values.
247    Set(Vec<PrimitiveValue>),
248}
249
250impl From<DTValue> for MaterializedValue {
251    fn from(dt: DTValue) -> Self {
252        match dt {
253            DTValue::Primitive(p) => match p {
254                Primitive::Nil | Primitive::InvalidUninitialized => MaterializedValue::Nil,
255                Primitive::Bool(b) => MaterializedValue::Bool(b),
256                Primitive::I64(n) => MaterializedValue::Int(n),
257                Primitive::F64(n) => MaterializedValue::Float(n),
258                Primitive::Str(s) => MaterializedValue::Str(s.to_string()),
259            },
260            DTValue::Register(inner) => (*inner).into(),
261            DTValue::Text(s) => MaterializedValue::Text(s),
262            DTValue::Map(entries) => MaterializedValue::Map(
263                entries.into_iter()
264                    .map(|(k, v)| (k.to_string(), (*v).into()))
265                    .collect()
266            ),
267            DTValue::Set(members) => MaterializedValue::Set(
268                members.into_iter()
269                    .map(|p| match p {
270                        Primitive::Nil | Primitive::InvalidUninitialized => PrimitiveValue::Nil,
271                        Primitive::Bool(b) => PrimitiveValue::Bool(b),
272                        Primitive::I64(n) => PrimitiveValue::Int(n),
273                        Primitive::F64(n) => PrimitiveValue::Float(n),
274                        Primitive::Str(s) => PrimitiveValue::Str(s.to_string()),
275                    })
276                    .collect()
277            ),
278        }
279    }
280}
281
282pub(crate) fn checkout_to_materialized(
283    raw: BTreeMap<SmartString, Box<DTValue>>
284) -> BTreeMap<String, MaterializedValue> {
285    raw.into_iter()
286        .map(|(k, v)| (k.to_string(), (*v).into()))
287        .collect()
288}
289
290// ============ Conversions from Rust types to Value (read type) ============
291
292impl From<bool> for Value {
293    fn from(b: bool) -> Self { Value::Bool(b) }
294}
295
296impl From<i64> for Value {
297    fn from(n: i64) -> Self { Value::Int(n) }
298}
299
300impl From<i32> for Value {
301    fn from(n: i32) -> Self { Value::Int(n as i64) }
302}
303
304/// Panics if the value is NaN.
305impl From<f64> for Value {
306    fn from(n: f64) -> Self { Value::Float(NotNan::new(n).expect("NaN is not a valid CRDT value")) }
307}
308
309impl From<String> for Value {
310    fn from(s: String) -> Self { Value::Str(s) }
311}
312
313impl From<&str> for Value {
314    fn from(s: &str) -> Self { Value::Str(s.to_string()) }
315}
316
317impl From<()> for Value {
318    fn from(_: ()) -> Self { Value::Nil }
319}
320
321// ============ Internal conversions ============
322
323impl From<Primitive> for Value {
324    fn from(p: Primitive) -> Self {
325        match p {
326            Primitive::Nil => Value::Nil,
327            Primitive::Bool(b) => Value::Bool(b),
328            Primitive::I64(n) => Value::Int(n),
329            Primitive::F64(n) => Value::Float(n),
330            Primitive::Str(s) => Value::Str(s.to_string()),
331            Primitive::InvalidUninitialized => Value::Nil,
332        }
333    }
334}
335
336impl From<RegisterValue> for Value {
337    fn from(rv: RegisterValue) -> Self {
338        match rv {
339            RegisterValue::Primitive(p) => p.into(),
340            RegisterValue::OwnedCRDT(kind, lv) => {
341                let id = CrdtId(lv);
342                match kind {
343                    CRDTKind::Map => Value::Map(id),
344                    CRDTKind::Text => Value::Text(id),
345                    CRDTKind::Set => Value::Set(id),
346                    CRDTKind::Register => Value::Register(id),
347                    CRDTKind::Collection => Value::Map(id),
348                }
349            }
350        }
351    }
352}
353
354impl From<Value> for Primitive {
355    fn from(v: Value) -> Self {
356        match v {
357            Value::Nil => Primitive::Nil,
358            Value::Bool(b) => Primitive::Bool(b),
359            Value::Int(n) => Primitive::I64(n),
360            Value::Float(n) => Primitive::F64(n),
361            Value::Str(s) => Primitive::Str(s.into()),
362            _ => Primitive::Nil,
363        }
364    }
365}
366
367impl From<Value> for CreateValue {
368    fn from(v: Value) -> Self {
369        match v {
370            Value::Nil => CreateValue::Primitive(Primitive::Nil),
371            Value::Bool(b) => CreateValue::Primitive(Primitive::Bool(b)),
372            Value::Int(n) => CreateValue::Primitive(Primitive::I64(n)),
373            Value::Float(n) => CreateValue::Primitive(Primitive::F64(n)),
374            Value::Str(s) => CreateValue::Primitive(Primitive::Str(s.into())),
375            Value::Map(_) => CreateValue::NewCRDT(CRDTKind::Map),
376            Value::Text(_) => CreateValue::NewCRDT(CRDTKind::Text),
377            Value::Set(_) => CreateValue::NewCRDT(CRDTKind::Set),
378            Value::Register(_) => CreateValue::NewCRDT(CRDTKind::Register),
379        }
380    }
381}
382
383// ============ Conflicted<T> ============
384
385/// Wrapper for values that may have concurrent conflicts.
386///
387/// In LWW (Last-Writer-Wins) semantics, concurrent writes result in one
388/// value "winning" deterministically. The losing values are preserved
389/// in `conflicts` for applications that want custom merge strategies.
390#[derive(Debug, Clone, Eq, PartialEq)]
391pub struct Conflicted<T> {
392    /// The "winning" value according to LWW semantics.
393    pub value: T,
394    /// Concurrent values that lost the tie-break.
395    pub conflicts: Vec<T>,
396}
397
398impl<T> Conflicted<T> {
399    /// Create a new Conflicted with no conflicts.
400    pub fn new(value: T) -> Self {
401        Self {
402            value,
403            conflicts: Vec::new(),
404        }
405    }
406
407    /// Create a new Conflicted with conflicts.
408    pub fn with_conflicts(value: T, conflicts: Vec<T>) -> Self {
409        Self { value, conflicts }
410    }
411
412    /// Check if there are any conflicts.
413    pub fn has_conflicts(&self) -> bool {
414        !self.conflicts.is_empty()
415    }
416
417    /// Get all values (winning + conflicts) as an iterator.
418    pub fn all_values(&self) -> impl Iterator<Item = &T> {
419        std::iter::once(&self.value).chain(self.conflicts.iter())
420    }
421
422    /// Map the value and conflicts through a function.
423    pub fn map<U, F: Fn(&T) -> U>(&self, f: F) -> Conflicted<U> {
424        Conflicted {
425            value: f(&self.value),
426            conflicts: self.conflicts.iter().map(f).collect(),
427        }
428    }
429
430    /// Extract the winning value, discarding conflicts.
431    pub fn into_value(self) -> T {
432        self.value
433    }
434
435    /// Extract all values (winning + conflicts).
436    pub fn into_all_values(self) -> Vec<T> {
437        let mut all = vec![self.value];
438        all.extend(self.conflicts);
439        all
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn test_value_primitives() {
449        assert!(Value::Nil.is_nil());
450        assert!(Value::Nil.is_primitive());
451        assert!(!Value::Nil.is_crdt());
452
453        assert_eq!(Value::Bool(true).as_bool(), Some(true));
454        assert_eq!(Value::Int(42).as_int(), Some(42));
455        assert_eq!(Value::Str("hello".into()).as_str(), Some("hello"));
456    }
457
458    #[test]
459    fn test_value_conversions() {
460        let v: Value = true.into();
461        assert_eq!(v, Value::Bool(true));
462
463        let v: Value = 42i64.into();
464        assert_eq!(v, Value::Int(42));
465
466        let v: Value = "hello".into();
467        assert_eq!(v, Value::Str("hello".into()));
468    }
469
470    #[test]
471    fn test_primitive_value_conversions() {
472        let v: PrimitiveValue = true.into();
473        assert_eq!(v, PrimitiveValue::Bool(true));
474
475        let v: PrimitiveValue = 42i64.into();
476        assert_eq!(v, PrimitiveValue::Int(42));
477
478        let v: PrimitiveValue = "hello".into();
479        assert_eq!(v, PrimitiveValue::Str("hello".into()));
480
481        let v: PrimitiveValue = ().into();
482        assert_eq!(v, PrimitiveValue::Nil);
483    }
484
485    #[test]
486    fn test_conflicted() {
487        let c = Conflicted::new(42);
488        assert!(!c.has_conflicts());
489        assert_eq!(c.all_values().count(), 1);
490
491        let c = Conflicted::with_conflicts(42, vec![43, 44]);
492        assert!(c.has_conflicts());
493        assert_eq!(c.all_values().collect::<Vec<_>>(), vec![&42, &43, &44]);
494    }
495
496    #[test]
497    fn test_conflicted_into_value_no_clone() {
498        // Verify into_value works without Clone bound
499        let c = Conflicted::new(String::from("hello"));
500        let val = c.into_value();
501        assert_eq!(val, "hello");
502    }
503}