Skip to main content

offline_protocol_data/
value.rs

1//! Values that can be stored in a document collection.
2
3use serde::{Deserialize, Serialize};
4
5/// A value stored in a map or list.
6///
7/// Deliberately flat. Structured values go in as JSON strings and merge
8/// whole (last write wins per key), which is the honest description of what
9/// v1 replicates: whole collections within a space, no nested container
10/// addressing and no query language. [`DataValue::Attachment`] is the one
11/// composite, and it is composite because it is a reference rather than a
12/// value: see its own documentation.
13///
14/// The engine's own value type never appears here. That is what lets the
15/// engine be replaced without a breaking change to any caller, the FFI, or
16/// any binding.
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(tag = "kind", rename_all = "snake_case")]
19pub enum DataValue {
20    /// An explicit null.
21    Null,
22    /// A boolean.
23    Bool {
24        /// The value.
25        value: bool,
26    },
27    /// A signed 64-bit integer.
28    Int {
29        /// The value.
30        value: i64,
31    },
32    /// A double.
33    Float {
34        /// The value.
35        value: f64,
36    },
37    /// A UTF-8 string.
38    Text {
39        /// The value.
40        value: String,
41    },
42    /// Opaque bytes.
43    Bytes {
44        /// The value.
45        value: Vec<u8>,
46    },
47    /// A reference to a blob that is not in the document.
48    ///
49    /// The only value whose payload never enters the CRDT. The document
50    /// holds the hash, the size, and enough to show the thing; the bytes
51    /// travel on the media path that already carries files.
52    ///
53    /// The failure this prevents is a document that has to hold a photo. A
54    /// document is bounded by one sealed record, so a layer that inlined
55    /// blobs would be a layer that could not carry the blobs people
56    /// actually send, and would discover that at commit time with the
57    /// picture already on screen.
58    ///
59    /// A reference is replaced, never edited. Two members who attach
60    /// different blobs to the same key resolve the way every other map
61    /// value resolves, and neither replica is left holding half of each.
62    /// That is what the "no multi-writer attachment mutation" non-goal
63    /// means in practice, and it is a property of this shape rather than a
64    /// rule anyone has to remember.
65    Attachment {
66        /// Lowercase hex SHA-256 of the whole blob, exactly 64 characters.
67        ///
68        /// This is the address, not a checksum carried alongside one. The
69        /// same bytes attached twice are the same attachment, and a blob
70        /// arriving from a peer is accepted only if it hashes to the
71        /// reference that asked for it, which is what makes fetching from
72        /// an authenticated peer safe without trusting that peer.
73        hash: String,
74        /// Length of the blob in bytes.
75        ///
76        /// Carried so an application can decide whether it wants the thing
77        /// before asking for it over a radio that may be Bluetooth.
78        size: u64,
79        /// Display name, if the writer had one.
80        #[serde(default, skip_serializing_if = "Option::is_none")]
81        name: Option<String>,
82        /// Media type, if the writer knew it.
83        #[serde(default, skip_serializing_if = "Option::is_none")]
84        mime: Option<String>,
85    },
86}
87
88impl DataValue {
89    /// Convenience constructor for a string value.
90    pub fn text(value: impl Into<String>) -> Self {
91        Self::Text {
92            value: value.into(),
93        }
94    }
95
96    /// Convenience constructor for an integer value.
97    pub fn int(value: i64) -> Self {
98        Self::Int { value }
99    }
100
101    /// Convenience constructor for a boolean value.
102    pub fn bool(value: bool) -> Self {
103        Self::Bool { value }
104    }
105
106    /// Convenience constructor for a float value.
107    pub fn float(value: f64) -> Self {
108        Self::Float { value }
109    }
110
111    /// Convenience constructor for a byte value.
112    pub fn bytes(value: impl Into<Vec<u8>>) -> Self {
113        Self::Bytes {
114            value: value.into(),
115        }
116    }
117
118    /// Convenience constructor for an attachment reference with no
119    /// display name and no media type.
120    ///
121    /// The hash is not checked here. It is checked where every other value
122    /// is checked, at the operation that writes it, so a bad one is refused
123    /// with the same error as a bad key rather than panicking at a
124    /// constructor.
125    pub fn attachment(hash: impl Into<String>, size: u64) -> Self {
126        Self::Attachment {
127            hash: hash.into(),
128            size,
129            name: None,
130            mime: None,
131        }
132    }
133}