arete_server/
shared_entity.rs1use std::borrow::Cow;
13use std::ops::Index;
14use std::sync::Arc;
15
16use serde_json::Value;
17
18pub const VERSION_FIELD: &str = "_version";
20
21#[derive(Clone, Debug)]
28pub struct SharedEntity {
29 fields: Arc<Value>,
31 version: Option<Value>,
33}
34
35impl SharedEntity {
36 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 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 pub fn version(&self) -> Option<&Value> {
81 self.version.as_ref()
82 }
83
84 pub fn shares_fields_with(&self, other: &SharedEntity) -> bool {
87 Arc::ptr_eq(&self.fields, &other.fields)
88 }
89
90 pub fn to_value(&self) -> Value {
92 with_version(Value::clone(&self.fields), self.version.clone())
93 }
94
95 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
115impl 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
123impl 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
133pub trait EntityFields {
135 fn field(&self, name: &str) -> Option<&Value>;
137
138 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
169pub 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
188pub(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
197pub(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}