Skip to main content

arete_server/
shared_entity.rs

1//! Cached entities that share their fields between the views holding them.
2//!
3//! Every view of an export receives the same patches, and a derived view holds
4//! the entity its source view holds, so most cached copies of one entity have
5//! the same fields. Each view's frames carry their own `_version` (see the
6//! projector), which a view's copy keeps, so the copies differ in that one
7//! field. A [`SharedEntity`] keeps every other field behind an [`Arc`] that the
8//! copies share, and `_version` beside it, per copy. A change replaces or
9//! copies the shared fields before it writes (copy-on-write), so it never
10//! reaches another holder's copy.
11
12use std::borrow::Cow;
13use std::ops::Index;
14use std::sync::{Arc, OnceLock};
15
16use serde::ser::SerializeMap as _;
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18use serde_json::Value;
19
20/// The field each view's copy of an entity stamps with its own frame version.
21pub const VERSION_FIELD: &str = "_version";
22
23/// One copy of a cached entity: fields shared with every other copy that has
24/// the same ones, plus this copy's `_version`.
25///
26/// It reads and compares as the whole entity it stands for, `_version`
27/// included: [`Self::to_value`] rebuilds that entity, and equality, field
28/// access ([`EntityFields`]) and indexing see it too. It serializes exactly as
29/// that entity does, without rebuilding it.
30#[derive(Clone, Debug)]
31pub struct SharedEntity {
32    /// Every field but `_version`. Never has a top-level `_version`.
33    fields: Arc<Value>,
34    /// `_version`, if the entity is an object that has one.
35    version: Option<Value>,
36}
37
38impl SharedEntity {
39    /// Hold `entity` unshared.
40    pub fn new(entity: Value) -> Self {
41        let (fields, version) = split_version(entity);
42        Self {
43            fields: Arc::new(fields),
44            version,
45        }
46    }
47
48    /// Hold `fields`, shared with whoever else holds them, under `version`.
49    ///
50    /// `fields` must not have a top-level `_version`, and only an object
51    /// carries one.
52    pub(crate) fn from_parts(fields: Arc<Value>, version: Option<Value>) -> Self {
53        debug_assert!(
54            fields
55                .as_object()
56                .is_none_or(|map| !map.contains_key(VERSION_FIELD)),
57            "shared fields must not carry `_version`"
58        );
59        debug_assert!(
60            fields.is_object() || version.is_none(),
61            "only an object carries `_version`"
62        );
63        Self { fields, version }
64    }
65
66    pub(crate) fn into_parts(self) -> (Arc<Value>, Option<Value>) {
67        (self.fields, self.version)
68    }
69
70    pub(crate) fn fields(&self) -> &Arc<Value> {
71        &self.fields
72    }
73
74    pub(crate) fn fields_mut(&mut self) -> &mut Arc<Value> {
75        &mut self.fields
76    }
77
78    pub(crate) fn version_mut(&mut self) -> &mut Option<Value> {
79        &mut self.version
80    }
81
82    /// The entity's `_version`, if it has one.
83    pub fn version(&self) -> Option<&Value> {
84        self.version.as_ref()
85    }
86
87    /// Whether `other` shares this entity's fields rather than holding a copy
88    /// of its own. Sharing says nothing about `_version`.
89    pub fn shares_fields_with(&self, other: &SharedEntity) -> bool {
90        Arc::ptr_eq(&self.fields, &other.fields)
91    }
92
93    /// The whole entity, as an owned value.
94    pub fn to_value(&self) -> Value {
95        with_version(Value::clone(&self.fields), self.version.clone())
96    }
97
98    /// The whole entity, copying the fields only if another holder shares
99    /// them.
100    pub fn into_value(self) -> Value {
101        let fields = Arc::try_unwrap(self.fields).unwrap_or_else(|shared| Value::clone(&shared));
102        with_version(fields, self.version)
103    }
104}
105
106impl From<Value> for SharedEntity {
107    fn from(entity: Value) -> Self {
108        Self::new(entity)
109    }
110}
111
112impl From<SharedEntity> for Value {
113    fn from(entity: SharedEntity) -> Self {
114        entity.into_value()
115    }
116}
117
118/// Equal when the whole entities are: `_version` and every other field.
119impl PartialEq for SharedEntity {
120    fn eq(&self, other: &Self) -> bool {
121        self.version == other.version
122            && (Arc::ptr_eq(&self.fields, &other.fields) || self.fields == other.fields)
123    }
124}
125
126/// Serializes exactly as [`SharedEntity::to_value`] would, in any format,
127/// without building that value: `_version` is written where rebuilding the
128/// entity would have put it.
129impl Serialize for SharedEntity {
130    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
131        self.serialize_with(serializer, |_, value| value, |value| value)
132    }
133}
134
135impl SharedEntity {
136    /// Serialize the entity as [`Serialize`] does, passing each top-level
137    /// field's value (`_version` included) through `field`, or the whole
138    /// entity through `other` if it is not an object.
139    pub(crate) fn serialize_with<'a, S, F, O>(
140        &'a self,
141        serializer: S,
142        field: impl Fn(&'a str, &'a Value) -> F,
143        other: impl FnOnce(&'a Value) -> O,
144    ) -> Result<S::Ok, S::Error>
145    where
146        S: Serializer,
147        F: Serialize,
148        O: Serialize,
149    {
150        let Value::Object(fields) = &*self.fields else {
151            return other(&self.fields).serialize(serializer);
152        };
153        let mut map =
154            serializer.serialize_map(Some(fields.len() + usize::from(self.version.is_some())))?;
155        let mut pending = self.version.as_ref();
156        for (key, value) in fields {
157            if maps_sort_keys() && key.as_str() > VERSION_FIELD {
158                if let Some(version) = pending.take() {
159                    map.serialize_entry(VERSION_FIELD, &field(VERSION_FIELD, version))?;
160                }
161            }
162            map.serialize_entry(key, &field(key, value))?;
163        }
164        if let Some(version) = pending {
165            map.serialize_entry(VERSION_FIELD, &field(VERSION_FIELD, version))?;
166        }
167        map.end()
168    }
169}
170
171impl<'de> Deserialize<'de> for SharedEntity {
172    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
173        Value::deserialize(deserializer).map(SharedEntity::new)
174    }
175}
176
177/// Whether `serde_json` keeps an object's keys sorted, its default, rather
178/// than in insertion order (its `preserve_order` feature). Rebuilding an
179/// entity inserts `_version` last, which lands it in key order in the first
180/// case and at the end in the second.
181pub(crate) fn maps_sort_keys() -> bool {
182    static SORTED: OnceLock<bool> = OnceLock::new();
183    *SORTED.get_or_init(|| {
184        let mut map = serde_json::Map::new();
185        map.insert("b".to_string(), Value::Null);
186        map.insert("a".to_string(), Value::Null);
187        map.keys().next().map(String::as_str) == Some("a")
188    })
189}
190
191/// Reads a field the way indexing a [`Value`] does: `Null` when it is absent.
192impl Index<&str> for SharedEntity {
193    type Output = Value;
194
195    fn index(&self, field: &str) -> &Value {
196        static NULL: Value = Value::Null;
197        self.field(field).unwrap_or(&NULL)
198    }
199}
200
201/// Field access shared by plain values and [`SharedEntity`] copies.
202pub trait EntityFields {
203    /// The top-level field `name`, if the entity is an object that has it.
204    fn field(&self, name: &str) -> Option<&Value>;
205
206    /// The whole entity. Borrowed whenever it can be.
207    fn whole(&self) -> Cow<'_, Value>;
208}
209
210impl EntityFields for Value {
211    fn field(&self, name: &str) -> Option<&Value> {
212        self.get(name)
213    }
214
215    fn whole(&self) -> Cow<'_, Value> {
216        Cow::Borrowed(self)
217    }
218}
219
220impl EntityFields for SharedEntity {
221    fn field(&self, name: &str) -> Option<&Value> {
222        if name == VERSION_FIELD {
223            self.version.as_ref()
224        } else {
225            self.fields.get(name)
226        }
227    }
228
229    fn whole(&self) -> Cow<'_, Value> {
230        match self.version {
231            None => Cow::Borrowed(&self.fields),
232            Some(_) => Cow::Owned(self.to_value()),
233        }
234    }
235}
236
237/// The value at `path`, field names from the top of `entity`; the whole entity
238/// for an empty path. `None` as soon as a step is missing.
239pub fn lookup<'a, E, I, S>(entity: &'a E, path: I) -> Option<Cow<'a, Value>>
240where
241    E: EntityFields + ?Sized,
242    I: IntoIterator<Item = S>,
243    S: AsRef<str>,
244{
245    let mut path = path.into_iter();
246    let Some(first) = path.next() else {
247        return Some(entity.whole());
248    };
249    let mut current = entity.field(first.as_ref())?;
250    for segment in path {
251        current = current.get(segment.as_ref())?;
252    }
253    Some(Cow::Borrowed(current))
254}
255
256/// Take `_version` out of `value`, if it is an object that has one.
257pub(crate) fn split_version(mut value: Value) -> (Value, Option<Value>) {
258    let version = match &mut value {
259        Value::Object(map) => map.remove(VERSION_FIELD),
260        _ => None,
261    };
262    (value, version)
263}
264
265/// Put `version` back into `fields` as `_version`. Only an object takes one.
266pub(crate) fn with_version(mut fields: Value, version: Option<Value>) -> Value {
267    if let (Value::Object(map), Some(version)) = (&mut fields, version) {
268        map.insert(VERSION_FIELD.to_string(), version);
269    }
270    fields
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use serde_json::json;
277
278    #[test]
279    fn it_reads_compares_and_rebuilds_as_the_whole_entity() {
280        let whole = json!({"a": 1, "_seq": "1:0", "_version": "e:7", "nested": {"b": [1, 2]}});
281        let entity = SharedEntity::new(whole.clone());
282        assert_eq!(entity.version(), Some(&json!("e:7")));
283        assert!(entity.fields().get(VERSION_FIELD).is_none());
284        assert_eq!(entity.field("_version"), Some(&json!("e:7")));
285        assert_eq!(entity["a"], 1);
286        assert_eq!(entity["missing"], Value::Null);
287        assert_eq!(
288            lookup(&entity, ["nested", "b"]).as_deref(),
289            Some(&json!([1, 2]))
290        );
291        assert_eq!(
292            lookup(&entity, ["_version"]).as_deref(),
293            Some(&json!("e:7"))
294        );
295        assert_eq!(lookup(&entity, Vec::<&str>::new()).as_deref(), Some(&whole));
296        assert_eq!(entity.to_value(), whole);
297        assert_eq!(
298            serde_json::to_string(&entity.clone().into_value()).unwrap(),
299            serde_json::to_string(&whole).unwrap(),
300            "the rebuilt entity serializes byte for byte as the original"
301        );
302        assert_eq!(entity, SharedEntity::new(whole));
303    }
304
305    #[test]
306    fn copies_that_differ_only_in_version_are_not_equal() {
307        let list = SharedEntity::new(json!({"a": 1, "_version": "e:1"}));
308        let (fields, _) = list.clone().into_parts();
309        let state = SharedEntity::from_parts(fields, Some(json!("e:2")));
310        assert!(state.shares_fields_with(&list));
311        assert_ne!(state, list);
312        assert_eq!(state.to_value(), json!({"a": 1, "_version": "e:2"}));
313    }
314
315    /// Serializing a copy writes the bytes the whole entity would, wherever
316    /// `_version` falls among the other fields.
317    #[test]
318    fn it_serializes_as_the_whole_entity() {
319        let entities = [
320            json!({"a": 1, "_version": "e:1"}),
321            json!({"_a": 1, "_seq": "1:0", "_version": "e:1", "_z": 2, "Z": 3, "z": [1, {"_version": 2}]}),
322            json!({"_version": "e:1"}),
323            json!({"a": 1}),
324            json!({"_version": {"nested": [1, 2]}, "b": null}),
325            json!([1, 2, {"_version": "kept in place"}]),
326            json!("text"),
327            json!(null),
328        ];
329        for whole in entities {
330            let entity = SharedEntity::new(whole.clone());
331            assert_eq!(
332                serde_json::to_string(&entity).unwrap(),
333                serde_json::to_string(&whole).unwrap(),
334                "{whole}"
335            );
336            let read: SharedEntity = serde_json::from_value(whole.clone()).unwrap();
337            assert_eq!(read, entity);
338        }
339    }
340
341    #[test]
342    fn a_value_that_is_not_an_object_has_no_version() {
343        let entity = SharedEntity::new(json!([1, 2, 3]));
344        assert_eq!(entity.version(), None);
345        assert_eq!(entity.field("_version"), None);
346        assert_eq!(entity.into_value(), json!([1, 2, 3]));
347    }
348}