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}