use std::borrow::Cow;
use std::ops::Index;
use std::sync::{Arc, OnceLock};
use serde::ser::SerializeMap as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
pub const VERSION_FIELD: &str = "_version";
#[derive(Clone, Debug)]
pub struct SharedEntity {
fields: Arc<Value>,
version: Option<Value>,
}
impl SharedEntity {
pub fn new(entity: Value) -> Self {
let (fields, version) = split_version(entity);
Self {
fields: Arc::new(fields),
version,
}
}
pub(crate) fn from_parts(fields: Arc<Value>, version: Option<Value>) -> Self {
debug_assert!(
fields
.as_object()
.is_none_or(|map| !map.contains_key(VERSION_FIELD)),
"shared fields must not carry `_version`"
);
debug_assert!(
fields.is_object() || version.is_none(),
"only an object carries `_version`"
);
Self { fields, version }
}
pub(crate) fn into_parts(self) -> (Arc<Value>, Option<Value>) {
(self.fields, self.version)
}
pub(crate) fn fields(&self) -> &Arc<Value> {
&self.fields
}
pub(crate) fn fields_mut(&mut self) -> &mut Arc<Value> {
&mut self.fields
}
pub(crate) fn version_mut(&mut self) -> &mut Option<Value> {
&mut self.version
}
pub fn version(&self) -> Option<&Value> {
self.version.as_ref()
}
pub fn shares_fields_with(&self, other: &SharedEntity) -> bool {
Arc::ptr_eq(&self.fields, &other.fields)
}
pub fn to_value(&self) -> Value {
with_version(Value::clone(&self.fields), self.version.clone())
}
pub fn into_value(self) -> Value {
let fields = Arc::try_unwrap(self.fields).unwrap_or_else(|shared| Value::clone(&shared));
with_version(fields, self.version)
}
}
impl From<Value> for SharedEntity {
fn from(entity: Value) -> Self {
Self::new(entity)
}
}
impl From<SharedEntity> for Value {
fn from(entity: SharedEntity) -> Self {
entity.into_value()
}
}
impl PartialEq for SharedEntity {
fn eq(&self, other: &Self) -> bool {
self.version == other.version
&& (Arc::ptr_eq(&self.fields, &other.fields) || self.fields == other.fields)
}
}
impl Serialize for SharedEntity {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.serialize_with(serializer, |_, value| value, |value| value)
}
}
impl SharedEntity {
pub(crate) fn serialize_with<'a, S, F, O>(
&'a self,
serializer: S,
field: impl Fn(&'a str, &'a Value) -> F,
other: impl FnOnce(&'a Value) -> O,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
F: Serialize,
O: Serialize,
{
let Value::Object(fields) = &*self.fields else {
return other(&self.fields).serialize(serializer);
};
let mut map =
serializer.serialize_map(Some(fields.len() + usize::from(self.version.is_some())))?;
let mut pending = self.version.as_ref();
for (key, value) in fields {
if maps_sort_keys() && key.as_str() > VERSION_FIELD {
if let Some(version) = pending.take() {
map.serialize_entry(VERSION_FIELD, &field(VERSION_FIELD, version))?;
}
}
map.serialize_entry(key, &field(key, value))?;
}
if let Some(version) = pending {
map.serialize_entry(VERSION_FIELD, &field(VERSION_FIELD, version))?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for SharedEntity {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Value::deserialize(deserializer).map(SharedEntity::new)
}
}
pub(crate) fn maps_sort_keys() -> bool {
static SORTED: OnceLock<bool> = OnceLock::new();
*SORTED.get_or_init(|| {
let mut map = serde_json::Map::new();
map.insert("b".to_string(), Value::Null);
map.insert("a".to_string(), Value::Null);
map.keys().next().map(String::as_str) == Some("a")
})
}
impl Index<&str> for SharedEntity {
type Output = Value;
fn index(&self, field: &str) -> &Value {
static NULL: Value = Value::Null;
self.field(field).unwrap_or(&NULL)
}
}
pub trait EntityFields {
fn field(&self, name: &str) -> Option<&Value>;
fn whole(&self) -> Cow<'_, Value>;
}
impl EntityFields for Value {
fn field(&self, name: &str) -> Option<&Value> {
self.get(name)
}
fn whole(&self) -> Cow<'_, Value> {
Cow::Borrowed(self)
}
}
impl EntityFields for SharedEntity {
fn field(&self, name: &str) -> Option<&Value> {
if name == VERSION_FIELD {
self.version.as_ref()
} else {
self.fields.get(name)
}
}
fn whole(&self) -> Cow<'_, Value> {
match self.version {
None => Cow::Borrowed(&self.fields),
Some(_) => Cow::Owned(self.to_value()),
}
}
}
pub fn lookup<'a, E, I, S>(entity: &'a E, path: I) -> Option<Cow<'a, Value>>
where
E: EntityFields + ?Sized,
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut path = path.into_iter();
let Some(first) = path.next() else {
return Some(entity.whole());
};
let mut current = entity.field(first.as_ref())?;
for segment in path {
current = current.get(segment.as_ref())?;
}
Some(Cow::Borrowed(current))
}
pub(crate) fn split_version(mut value: Value) -> (Value, Option<Value>) {
let version = match &mut value {
Value::Object(map) => map.remove(VERSION_FIELD),
_ => None,
};
(value, version)
}
pub(crate) fn with_version(mut fields: Value, version: Option<Value>) -> Value {
if let (Value::Object(map), Some(version)) = (&mut fields, version) {
map.insert(VERSION_FIELD.to_string(), version);
}
fields
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn it_reads_compares_and_rebuilds_as_the_whole_entity() {
let whole = json!({"a": 1, "_seq": "1:0", "_version": "e:7", "nested": {"b": [1, 2]}});
let entity = SharedEntity::new(whole.clone());
assert_eq!(entity.version(), Some(&json!("e:7")));
assert!(entity.fields().get(VERSION_FIELD).is_none());
assert_eq!(entity.field("_version"), Some(&json!("e:7")));
assert_eq!(entity["a"], 1);
assert_eq!(entity["missing"], Value::Null);
assert_eq!(
lookup(&entity, ["nested", "b"]).as_deref(),
Some(&json!([1, 2]))
);
assert_eq!(
lookup(&entity, ["_version"]).as_deref(),
Some(&json!("e:7"))
);
assert_eq!(lookup(&entity, Vec::<&str>::new()).as_deref(), Some(&whole));
assert_eq!(entity.to_value(), whole);
assert_eq!(
serde_json::to_string(&entity.clone().into_value()).unwrap(),
serde_json::to_string(&whole).unwrap(),
"the rebuilt entity serializes byte for byte as the original"
);
assert_eq!(entity, SharedEntity::new(whole));
}
#[test]
fn copies_that_differ_only_in_version_are_not_equal() {
let list = SharedEntity::new(json!({"a": 1, "_version": "e:1"}));
let (fields, _) = list.clone().into_parts();
let state = SharedEntity::from_parts(fields, Some(json!("e:2")));
assert!(state.shares_fields_with(&list));
assert_ne!(state, list);
assert_eq!(state.to_value(), json!({"a": 1, "_version": "e:2"}));
}
#[test]
fn it_serializes_as_the_whole_entity() {
let entities = [
json!({"a": 1, "_version": "e:1"}),
json!({"_a": 1, "_seq": "1:0", "_version": "e:1", "_z": 2, "Z": 3, "z": [1, {"_version": 2}]}),
json!({"_version": "e:1"}),
json!({"a": 1}),
json!({"_version": {"nested": [1, 2]}, "b": null}),
json!([1, 2, {"_version": "kept in place"}]),
json!("text"),
json!(null),
];
for whole in entities {
let entity = SharedEntity::new(whole.clone());
assert_eq!(
serde_json::to_string(&entity).unwrap(),
serde_json::to_string(&whole).unwrap(),
"{whole}"
);
let read: SharedEntity = serde_json::from_value(whole.clone()).unwrap();
assert_eq!(read, entity);
}
}
#[test]
fn a_value_that_is_not_an_object_has_no_version() {
let entity = SharedEntity::new(json!([1, 2, 3]));
assert_eq!(entity.version(), None);
assert_eq!(entity.field("_version"), None);
assert_eq!(entity.into_value(), json!([1, 2, 3]));
}
}