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;
15
16use serde_json::Value;
17
18/// The field each view's copy of an entity stamps with its own frame version.
19pub const VERSION_FIELD: &str = "_version";
20
21/// One copy of a cached entity: fields shared with every other copy that has
22/// the same ones, plus this copy's `_version`.
23///
24/// It reads and compares as the whole entity it stands for, `_version`
25/// included: [`Self::to_value`] rebuilds that entity, and equality, field
26/// access ([`EntityFields`]) and indexing see it too.
27#[derive(Clone, Debug)]
28pub struct SharedEntity {
29    /// Every field but `_version`. Never has a top-level `_version`.
30    fields: Arc<Value>,
31    /// `_version`, if the entity is an object that has one.
32    version: Option<Value>,
33}
34
35impl SharedEntity {
36    /// Hold `entity` unshared.
37    pub fn new(entity: Value) -> Self {
38        let (fields, version) = split_version(entity);
39        Self {
40            fields: Arc::new(fields),
41            version,
42        }
43    }
44
45    /// Hold `fields`, shared with whoever else holds them, under `version`.
46    ///
47    /// `fields` must not have a top-level `_version`, and only an object
48    /// carries one.
49    pub(crate) fn from_parts(fields: Arc<Value>, version: Option<Value>) -> Self {
50        debug_assert!(
51            fields
52                .as_object()
53                .is_none_or(|map| !map.contains_key(VERSION_FIELD)),
54            "shared fields must not carry `_version`"
55        );
56        debug_assert!(
57            fields.is_object() || version.is_none(),
58            "only an object carries `_version`"
59        );
60        Self { fields, version }
61    }
62
63    pub(crate) fn into_parts(self) -> (Arc<Value>, Option<Value>) {
64        (self.fields, self.version)
65    }
66
67    pub(crate) fn fields(&self) -> &Arc<Value> {
68        &self.fields
69    }
70
71    pub(crate) fn fields_mut(&mut self) -> &mut Arc<Value> {
72        &mut self.fields
73    }
74
75    pub(crate) fn version_mut(&mut self) -> &mut Option<Value> {
76        &mut self.version
77    }
78
79    /// The entity's `_version`, if it has one.
80    pub fn version(&self) -> Option<&Value> {
81        self.version.as_ref()
82    }
83
84    /// Whether `other` shares this entity's fields rather than holding a copy
85    /// of its own. Sharing says nothing about `_version`.
86    pub fn shares_fields_with(&self, other: &SharedEntity) -> bool {
87        Arc::ptr_eq(&self.fields, &other.fields)
88    }
89
90    /// The whole entity, as an owned value.
91    pub fn to_value(&self) -> Value {
92        with_version(Value::clone(&self.fields), self.version.clone())
93    }
94
95    /// The whole entity, copying the fields only if another holder shares
96    /// them.
97    pub fn into_value(self) -> Value {
98        let fields = Arc::try_unwrap(self.fields).unwrap_or_else(|shared| Value::clone(&shared));
99        with_version(fields, self.version)
100    }
101}
102
103impl From<Value> for SharedEntity {
104    fn from(entity: Value) -> Self {
105        Self::new(entity)
106    }
107}
108
109impl From<SharedEntity> for Value {
110    fn from(entity: SharedEntity) -> Self {
111        entity.into_value()
112    }
113}
114
115/// Equal when the whole entities are: `_version` and every other field.
116impl PartialEq for SharedEntity {
117    fn eq(&self, other: &Self) -> bool {
118        self.version == other.version
119            && (Arc::ptr_eq(&self.fields, &other.fields) || self.fields == other.fields)
120    }
121}
122
123/// Reads a field the way indexing a [`Value`] does: `Null` when it is absent.
124impl Index<&str> for SharedEntity {
125    type Output = Value;
126
127    fn index(&self, field: &str) -> &Value {
128        static NULL: Value = Value::Null;
129        self.field(field).unwrap_or(&NULL)
130    }
131}
132
133/// Field access shared by plain values and [`SharedEntity`] copies.
134pub trait EntityFields {
135    /// The top-level field `name`, if the entity is an object that has it.
136    fn field(&self, name: &str) -> Option<&Value>;
137
138    /// The whole entity. Borrowed whenever it can be.
139    fn whole(&self) -> Cow<'_, Value>;
140}
141
142impl EntityFields for Value {
143    fn field(&self, name: &str) -> Option<&Value> {
144        self.get(name)
145    }
146
147    fn whole(&self) -> Cow<'_, Value> {
148        Cow::Borrowed(self)
149    }
150}
151
152impl EntityFields for SharedEntity {
153    fn field(&self, name: &str) -> Option<&Value> {
154        if name == VERSION_FIELD {
155            self.version.as_ref()
156        } else {
157            self.fields.get(name)
158        }
159    }
160
161    fn whole(&self) -> Cow<'_, Value> {
162        match self.version {
163            None => Cow::Borrowed(&self.fields),
164            Some(_) => Cow::Owned(self.to_value()),
165        }
166    }
167}
168
169/// The value at `path`, field names from the top of `entity`; the whole entity
170/// for an empty path. `None` as soon as a step is missing.
171pub fn lookup<'a, E, I, S>(entity: &'a E, path: I) -> Option<Cow<'a, Value>>
172where
173    E: EntityFields + ?Sized,
174    I: IntoIterator<Item = S>,
175    S: AsRef<str>,
176{
177    let mut path = path.into_iter();
178    let Some(first) = path.next() else {
179        return Some(entity.whole());
180    };
181    let mut current = entity.field(first.as_ref())?;
182    for segment in path {
183        current = current.get(segment.as_ref())?;
184    }
185    Some(Cow::Borrowed(current))
186}
187
188/// Take `_version` out of `value`, if it is an object that has one.
189pub(crate) fn split_version(mut value: Value) -> (Value, Option<Value>) {
190    let version = match &mut value {
191        Value::Object(map) => map.remove(VERSION_FIELD),
192        _ => None,
193    };
194    (value, version)
195}
196
197/// Put `version` back into `fields` as `_version`. Only an object takes one.
198pub(crate) fn with_version(mut fields: Value, version: Option<Value>) -> Value {
199    if let (Value::Object(map), Some(version)) = (&mut fields, version) {
200        map.insert(VERSION_FIELD.to_string(), version);
201    }
202    fields
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use serde_json::json;
209
210    #[test]
211    fn it_reads_compares_and_rebuilds_as_the_whole_entity() {
212        let whole = json!({"a": 1, "_seq": "1:0", "_version": "e:7", "nested": {"b": [1, 2]}});
213        let entity = SharedEntity::new(whole.clone());
214        assert_eq!(entity.version(), Some(&json!("e:7")));
215        assert!(entity.fields().get(VERSION_FIELD).is_none());
216        assert_eq!(entity.field("_version"), Some(&json!("e:7")));
217        assert_eq!(entity["a"], 1);
218        assert_eq!(entity["missing"], Value::Null);
219        assert_eq!(
220            lookup(&entity, ["nested", "b"]).as_deref(),
221            Some(&json!([1, 2]))
222        );
223        assert_eq!(
224            lookup(&entity, ["_version"]).as_deref(),
225            Some(&json!("e:7"))
226        );
227        assert_eq!(lookup(&entity, Vec::<&str>::new()).as_deref(), Some(&whole));
228        assert_eq!(entity.to_value(), whole);
229        assert_eq!(
230            serde_json::to_string(&entity.clone().into_value()).unwrap(),
231            serde_json::to_string(&whole).unwrap(),
232            "the rebuilt entity serializes byte for byte as the original"
233        );
234        assert_eq!(entity, SharedEntity::new(whole));
235    }
236
237    #[test]
238    fn copies_that_differ_only_in_version_are_not_equal() {
239        let list = SharedEntity::new(json!({"a": 1, "_version": "e:1"}));
240        let (fields, _) = list.clone().into_parts();
241        let state = SharedEntity::from_parts(fields, Some(json!("e:2")));
242        assert!(state.shares_fields_with(&list));
243        assert_ne!(state, list);
244        assert_eq!(state.to_value(), json!({"a": 1, "_version": "e:2"}));
245    }
246
247    #[test]
248    fn a_value_that_is_not_an_object_has_no_version() {
249        let entity = SharedEntity::new(json!([1, 2, 3]));
250        assert_eq!(entity.version(), None);
251        assert_eq!(entity.field("_version"), None);
252        assert_eq!(entity.into_value(), json!([1, 2, 3]));
253    }
254}