1use 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
20pub const VERSION_FIELD: &str = "_version";
22
23#[derive(Clone, Debug)]
31pub struct SharedEntity {
32 fields: Arc<Value>,
34 version: Option<Value>,
36}
37
38impl SharedEntity {
39 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 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 pub fn version(&self) -> Option<&Value> {
84 self.version.as_ref()
85 }
86
87 pub fn shares_fields_with(&self, other: &SharedEntity) -> bool {
90 Arc::ptr_eq(&self.fields, &other.fields)
91 }
92
93 pub fn to_value(&self) -> Value {
95 with_version(Value::clone(&self.fields), self.version.clone())
96 }
97
98 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
118impl 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
126impl 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 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
177pub(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
191impl 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
201pub trait EntityFields {
203 fn field(&self, name: &str) -> Option<&Value>;
205
206 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
237pub 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
256pub(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
265pub(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 #[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}