use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList, VecDeque};
use std::hash::Hash;
use std::str::FromStr;
use automerge::{
ChangeHash, ObjId, ObjType, Prop, ReadDoc, ScalarValue, Value, transaction::Transactable,
};
use crate::config;
use crate::{
Automorph, ChangeReport, Error, MapChanges, MapCursor, PrimitiveChanged, Result, ScalarCursor,
VecChanges, VecCursor,
};
fn check_collection_size(len: usize, collection_type: &str) -> Result<()> {
let max_size = config::max_collection_size();
if len > max_size {
return Err(Error::invalid_value(format!(
"{} size {} exceeds maximum allowed size of {} (configurable via automorph::config::set_max_collection_size)",
collection_type, len, max_size
)));
}
Ok(())
}
fn save_list_element<D: Transactable + ReadDoc, S: Automorph>(
doc: &mut D,
list_id: &ObjId,
idx: usize,
value: &S,
) -> Result<()> {
doc.insert(list_id, idx, ScalarValue::Null)?;
value.save(doc, list_id, idx)
}
impl<S: Automorph> Automorph for Vec<S> {
type Changes = VecChanges<S::Changes>;
type Cursor = VecCursor<S::Cursor>;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let (list_id, existing_len) = match cur {
Some((Value::Object(ObjType::List), id)) => {
let list_len = doc.length(&id);
for idx in (self.len()..list_len).rev() {
doc.delete(&id, idx)?;
}
(id, list_len.min(self.len()))
}
_ => (doc.put_object(obj, prop, ObjType::List)?, 0),
};
for (idx, v) in self.iter().enumerate() {
if idx < existing_len {
v.save(doc, &list_id, idx)?;
} else {
save_list_element(doc, &list_id, idx, v)?;
}
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length(&id);
check_collection_size(len, "Vec")?;
let mut result = Vec::with_capacity(len);
for idx in 0..len {
let item = S::load(doc, &id, idx).map_err(|e| e.with_index(idx))?;
result.push(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch("Vec (List)", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length_at(&id, heads);
check_collection_size(len, "Vec")?;
let mut result = Vec::with_capacity(len);
for idx in 0..len {
let item = S::load_at(doc, &id, idx, heads).map_err(|e| e.with_index(idx))?;
result.push(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch("Vec (List)", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let mut changes = VecChanges::default();
let doc_len = doc.length(&id);
let self_len = self.len();
if doc_len > self_len {
changes.added = (self_len..doc_len).collect();
}
if self_len > doc_len {
changes.removed = (doc_len..self_len).collect();
}
let common_len = std::cmp::min(self_len, doc_len);
for (idx, item) in self.iter().enumerate().take(common_len) {
let item_changes = item.diff(doc, &id, idx)?;
if item_changes.any() {
changes.modified.insert(idx, item_changes);
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch("Vec (List)", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let mut changes = VecChanges::default();
let doc_len = doc.length_at(&id, heads);
let self_len = self.len();
if doc_len > self_len {
changes.added = (self_len..doc_len).collect();
}
if self_len > doc_len {
changes.removed = (doc_len..self_len).collect();
}
let common_len = std::cmp::min(self_len, doc_len);
for (idx, item) in self.iter().enumerate().take(common_len) {
let item_changes = item.diff_at(doc, &id, idx, heads)?;
if item_changes.any() {
changes.modified.insert(idx, item_changes);
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch("Vec (List)", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let mut changes = VecChanges::default();
let doc_len = doc.length(&id);
check_collection_size(doc_len, "Vec")?;
let self_len = self.len();
let common_len = std::cmp::min(self_len, doc_len);
for (idx, item) in self.iter_mut().enumerate().take(common_len) {
let item_changes = item.update(doc, &id, idx)?;
if item_changes.any() {
changes.modified.insert(idx, item_changes);
}
}
if doc_len > self_len {
changes.added = (self_len..doc_len).collect();
for idx in self_len..doc_len {
let item = S::load(doc, &id, idx)?;
self.push(item);
}
}
if self_len > doc_len {
changes.removed = (doc_len..self_len).collect();
self.truncate(doc_len);
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch("Vec (List)", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let mut changes = VecChanges::default();
let doc_len = doc.length_at(&id, heads);
check_collection_size(doc_len, "Vec")?;
let self_len = self.len();
let common_len = std::cmp::min(self_len, doc_len);
for (idx, item) in self.iter_mut().enumerate().take(common_len) {
let item_changes = item.update_at(doc, &id, idx, heads)?;
if item_changes.any() {
changes.modified.insert(idx, item_changes);
}
}
if doc_len > self_len {
changes.added = (self_len..doc_len).collect();
for idx in self_len..doc_len {
let new_item = S::load_at(doc, &id, idx, heads)?;
self.push(new_item);
}
}
if self_len > doc_len {
changes.removed = (doc_len..self_len).collect();
self.truncate(doc_len);
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch("Vec (List)", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
}
impl<S: Automorph> Automorph for VecDeque<S> {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let vec: Vec<_> = self.iter().collect();
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let list_id = match cur {
Some((Value::Object(ObjType::List), id)) => {
let list_len = doc.length(&id);
for idx in (vec.len()..list_len).rev() {
doc.delete(&id, idx)?;
}
id
}
_ => doc.put_object(obj, prop, ObjType::List)?,
};
for (idx, v) in vec.iter().enumerate() {
v.save(doc, &list_id, idx)?;
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length(&id);
check_collection_size(len, "VecDeque")?;
let mut result = VecDeque::with_capacity(len);
for idx in 0..len {
let item = S::load(doc, &id, idx).map_err(|e| e.with_index(idx))?;
result.push_back(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"VecDeque (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length_at(&id, heads);
check_collection_size(len, "VecDeque")?;
let mut result = VecDeque::with_capacity(len);
for idx in 0..len {
let item = S::load_at(doc, &id, idx, heads).map_err(|e| e.with_index(idx))?;
result.push_back(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"VecDeque (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), list_id)) => {
let doc_len = doc.length(&list_id);
if self.len() != doc_len {
return Ok(PrimitiveChanged::new(true));
}
for (idx, item) in self.iter().enumerate() {
let element_changes = item.diff(doc, &list_id, idx)?;
if element_changes.any() {
return Ok(PrimitiveChanged::new(true));
}
}
Ok(PrimitiveChanged::new(false))
}
Some((v, _)) => Err(Error::type_mismatch(
"VecDeque (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), list_id)) => {
let doc_len = doc.length_at(&list_id, heads);
if self.len() != doc_len {
return Ok(PrimitiveChanged::new(true));
}
for (idx, item) in self.iter().enumerate() {
let element_changes = item.diff_at(doc, &list_id, idx, heads)?;
if element_changes.any() {
return Ok(PrimitiveChanged::new(true));
}
}
Ok(PrimitiveChanged::new(false))
}
Some((v, _)) => Err(Error::type_mismatch(
"VecDeque (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let changed = self.diff(doc, obj, prop.clone())?.any();
let new_value = Self::load(doc, obj, prop)?;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let changed = self.diff_at(doc, obj, prop.clone(), heads)?.any();
let new_value = Self::load_at(doc, obj, prop, heads)?;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
}
impl<S: Automorph> Automorph for LinkedList<S> {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let vec: Vec<_> = self.iter().collect();
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let list_id = match cur {
Some((Value::Object(ObjType::List), id)) => {
let list_len = doc.length(&id);
for idx in (vec.len()..list_len).rev() {
doc.delete(&id, idx)?;
}
id
}
_ => doc.put_object(obj, prop, ObjType::List)?,
};
for (idx, v) in vec.iter().enumerate() {
v.save(doc, &list_id, idx)?;
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length(&id);
check_collection_size(len, "LinkedList")?;
let mut result = LinkedList::new();
for idx in 0..len {
let item = S::load(doc, &id, idx).map_err(|e| e.with_index(idx))?;
result.push_back(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"LinkedList (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length_at(&id, heads);
check_collection_size(len, "LinkedList")?;
let mut result = LinkedList::new();
for idx in 0..len {
let item = S::load_at(doc, &id, idx, heads).map_err(|e| e.with_index(idx))?;
result.push_back(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"LinkedList (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), list_id)) => {
let doc_len = doc.length(&list_id);
if self.len() != doc_len {
return Ok(PrimitiveChanged::new(true));
}
for (idx, item) in self.iter().enumerate() {
let element_changes = item.diff(doc, &list_id, idx)?;
if element_changes.any() {
return Ok(PrimitiveChanged::new(true));
}
}
Ok(PrimitiveChanged::new(false))
}
Some((v, _)) => Err(Error::type_mismatch(
"LinkedList (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), list_id)) => {
let doc_len = doc.length_at(&list_id, heads);
if self.len() != doc_len {
return Ok(PrimitiveChanged::new(true));
}
for (idx, item) in self.iter().enumerate() {
let element_changes = item.diff_at(doc, &list_id, idx, heads)?;
if element_changes.any() {
return Ok(PrimitiveChanged::new(true));
}
}
Ok(PrimitiveChanged::new(false))
}
Some((v, _)) => Err(Error::type_mismatch(
"LinkedList (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let changed = self.diff(doc, obj, prop.clone())?.any();
let new_value = Self::load(doc, obj, prop)?;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let changed = self.diff_at(doc, obj, prop.clone(), heads)?.any();
let new_value = Self::load_at(doc, obj, prop, heads)?;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
}
impl<K, V> Automorph for HashMap<K, V>
where
K: ToString + FromStr + Eq + Hash + Clone,
V: Automorph,
{
type Changes = MapChanges<String, V::Changes>;
type Cursor = MapCursor<V::Cursor>;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let map_id = match cur {
Some((Value::Object(ObjType::Map), id)) => {
let existing_keys: Vec<String> = doc.keys(&id).collect();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
for k in existing_keys {
if !our_keys.contains(&k) {
doc.delete(&id, &k)?;
}
}
id
}
_ => doc.put_object(obj, prop, ObjType::Map)?,
};
for (k, v) in self {
v.save(doc, &map_id, k.to_string())?;
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), id)) => {
let keys: Vec<String> = doc.keys(&id).collect();
check_collection_size(keys.len(), "HashMap")?;
let mut result = HashMap::with_capacity(keys.len());
for key in keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse HashMap key '{}' as type {}",
key,
std::any::type_name::<K>()
))
})?;
let v = V::load(doc, &id, key.clone()).map_err(|e| e.with_field(key))?;
result.insert(k, v);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), id)) => {
let keys: Vec<String> = doc.keys_at(&id, heads).collect();
check_collection_size(keys.len(), "HashMap")?;
let mut result = HashMap::with_capacity(keys.len());
for key in keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse HashMap key '{}' as type {}",
key,
std::any::type_name::<K>()
))
})?;
let v =
V::load_at(doc, &id, key.clone(), heads).map_err(|e| e.with_field(key))?;
result.insert(k, v);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let doc_keys: HashSet<String> = doc.keys(&id).collect();
for key in &doc_keys {
if !our_keys.contains(key) {
changes.added.insert(key.clone());
}
}
for key in &our_keys {
if !doc_keys.contains(key) {
changes.removed.insert(key.clone());
}
}
for (k, v) in self {
let key = k.to_string();
if doc_keys.contains(&key) {
let value_changes = v.diff(doc, &id, key.clone())?;
if value_changes.any() {
changes.modified.insert(key, value_changes);
}
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let doc_keys: HashSet<String> = doc.keys_at(&id, heads).collect();
for key in &doc_keys {
if !our_keys.contains(key) {
changes.added.insert(key.clone());
}
}
for key in &our_keys {
if !doc_keys.contains(key) {
changes.removed.insert(key.clone());
}
}
for (k, v) in self {
let key = k.to_string();
if doc_keys.contains(&key) {
let value_changes = v.diff_at(doc, &id, key.clone(), heads)?;
if value_changes.any() {
changes.modified.insert(key, value_changes);
}
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let doc_keys: HashSet<String> = doc.keys(&id).collect();
check_collection_size(doc_keys.len(), "HashMap")?;
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let to_remove: Vec<K> = self
.keys()
.filter(|k| !doc_keys.contains(&k.to_string()))
.cloned()
.collect();
for k in &to_remove {
self.remove(k);
changes.removed.insert(k.to_string());
}
for key in doc_keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse map key '{}' as target type",
key
))
})?;
if our_keys.contains(&key) {
if let Some(v) = self.get_mut(&k) {
let item_changes = v
.update(doc, &id, key.clone())
.map_err(|e| e.with_field(key.clone()))?;
if item_changes.any() {
changes.modified.insert(key, item_changes);
}
}
} else {
let v = V::load(doc, &id, key.clone())
.map_err(|e| e.with_field(key.clone()))?;
self.insert(k, v);
changes.added.insert(key);
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), id)) => {
let doc_keys: HashSet<String> = doc.keys_at(&id, heads).collect();
check_collection_size(doc_keys.len(), "HashMap")?;
let mut changes = MapChanges::default();
let doc_keys: HashSet<String> = doc.keys_at(&id, heads).collect();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let to_remove: Vec<K> = self
.keys()
.filter(|k| !doc_keys.contains(&k.to_string()))
.cloned()
.collect();
for k in &to_remove {
self.remove(k);
changes.removed.insert(k.to_string());
}
for key in doc_keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse map key '{}' as target type",
key
))
})?;
if our_keys.contains(&key) {
if let Some(v) = self.get_mut(&k) {
let item_changes = v
.update_at(doc, &id, key.clone(), heads)
.map_err(|e| e.with_field(key.clone()))?;
if item_changes.any() {
changes.modified.insert(key, item_changes);
}
}
} else {
let v = V::load_at(doc, &id, key.clone(), heads)
.map_err(|e| e.with_field(key.clone()))?;
self.insert(k, v);
changes.added.insert(key);
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
}
impl<K, V> Automorph for BTreeMap<K, V>
where
K: ToString + FromStr + Ord + Clone,
V: Automorph,
{
type Changes = MapChanges<String, V::Changes>;
type Cursor = MapCursor<V::Cursor>;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let map_id = match cur {
Some((Value::Object(ObjType::Map), id)) => {
let existing_keys: Vec<String> = doc.keys(&id).collect();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
for k in existing_keys {
if !our_keys.contains(&k) {
doc.delete(&id, &k)?;
}
}
id
}
_ => doc.put_object(obj, prop, ObjType::Map)?,
};
for (k, v) in self {
v.save(doc, &map_id, k.to_string())?;
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), id)) => {
let keys: Vec<String> = doc.keys(&id).collect();
check_collection_size(keys.len(), "BTreeMap")?;
let mut result = BTreeMap::new();
for key in keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse map key '{}' as target type",
key
))
})?;
let v = V::load(doc, &id, key.clone()).map_err(|e| e.with_field(key))?;
result.insert(k, v);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), id)) => {
let keys: Vec<String> = doc.keys_at(&id, heads).collect();
check_collection_size(keys.len(), "BTreeMap")?;
let mut result = BTreeMap::new();
for key in keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse map key '{}' as target type",
key
))
})?;
let v =
V::load_at(doc, &id, key.clone(), heads).map_err(|e| e.with_field(key))?;
result.insert(k, v);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let doc_keys: HashSet<String> = doc.keys(&id).collect();
for key in &doc_keys {
if !our_keys.contains(key) {
changes.added.insert(key.clone());
}
}
for key in &our_keys {
if !doc_keys.contains(key) {
changes.removed.insert(key.clone());
}
}
for (k, v) in self {
let key = k.to_string();
if doc_keys.contains(&key) {
let value_changes = v.diff(doc, &id, key.clone())?;
if value_changes.any() {
changes.modified.insert(key, value_changes);
}
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let doc_keys: HashSet<String> = doc.keys_at(&id, heads).collect();
for key in &doc_keys {
if !our_keys.contains(key) {
changes.added.insert(key.clone());
}
}
for key in &our_keys {
if !doc_keys.contains(key) {
changes.removed.insert(key.clone());
}
}
for (k, v) in self {
let key = k.to_string();
if doc_keys.contains(&key) {
let value_changes = v.diff_at(doc, &id, key.clone(), heads)?;
if value_changes.any() {
changes.modified.insert(key, value_changes);
}
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let doc_keys: HashSet<String> = doc.keys(&id).collect();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let to_remove: Vec<K> = self
.keys()
.filter(|k| !doc_keys.contains(&k.to_string()))
.cloned()
.collect();
for k in &to_remove {
self.remove(k);
changes.removed.insert(k.to_string());
}
for key in doc_keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse map key '{}' as target type",
key
))
})?;
if our_keys.contains(&key) {
if let Some(v) = self.get_mut(&k) {
let item_changes = v
.update(doc, &id, key.clone())
.map_err(|e| e.with_field(key.clone()))?;
if item_changes.any() {
changes.modified.insert(key, item_changes);
}
}
} else {
let v = V::load(doc, &id, key.clone())
.map_err(|e| e.with_field(key.clone()))?;
self.insert(k, v);
changes.added.insert(key);
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), id)) => {
let mut changes = MapChanges::default();
let doc_keys: HashSet<String> = doc.keys_at(&id, heads).collect();
let our_keys: HashSet<String> = self.keys().map(|k| k.to_string()).collect();
let to_remove: Vec<K> = self
.keys()
.filter(|k| !doc_keys.contains(&k.to_string()))
.cloned()
.collect();
for k in &to_remove {
self.remove(k);
changes.removed.insert(k.to_string());
}
for key in doc_keys {
let k = K::from_str(&key).map_err(|_| {
Error::invalid_value(format!(
"cannot parse map key '{}' as target type",
key
))
})?;
if our_keys.contains(&key) {
if let Some(v) = self.get_mut(&k) {
let item_changes = v
.update_at(doc, &id, key.clone(), heads)
.map_err(|e| e.with_field(key.clone()))?;
if item_changes.any() {
changes.modified.insert(key, item_changes);
}
}
} else {
let v = V::load_at(doc, &id, key.clone(), heads)
.map_err(|e| e.with_field(key.clone()))?;
self.insert(k, v);
changes.added.insert(key);
}
}
Ok(changes)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeMap (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
}
impl<S: Automorph + Eq + Hash> Automorph for HashSet<S> {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let vec: Vec<_> = self.iter().collect();
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let list_id = match cur {
Some((Value::Object(ObjType::List), id)) => {
let list_len = doc.length(&id);
for idx in (0..list_len).rev() {
doc.delete(&id, idx)?;
}
id
}
_ => doc.put_object(obj, prop, ObjType::List)?,
};
for (idx, v) in vec.iter().enumerate() {
v.save(doc, &list_id, idx)?;
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length(&id);
check_collection_size(len, "HashSet")?;
let mut result = HashSet::with_capacity(len);
for idx in 0..len {
let item = S::load(doc, &id, idx).map_err(|e| e.with_index(idx))?;
result.insert(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashSet (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length_at(&id, heads);
check_collection_size(len, "HashSet")?;
let mut result = HashSet::with_capacity(len);
for idx in 0..len {
let item = S::load_at(doc, &id, idx, heads).map_err(|e| e.with_index(idx))?;
result.insert(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"HashSet (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let doc_value = Self::load(doc, obj, prop)?;
Ok(PrimitiveChanged::new(
self.len() != doc_value.len() || *self != doc_value,
))
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let doc_value = Self::load_at(doc, obj, prop, heads)?;
Ok(PrimitiveChanged::new(
self.len() != doc_value.len() || *self != doc_value,
))
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let new_value = Self::load(doc, obj, prop)?;
let changed = *self != new_value;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let new_value = Self::load_at(doc, obj, prop, heads)?;
let changed = *self != new_value;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
}
impl<S: Automorph + Ord> Automorph for BTreeSet<S> {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let vec: Vec<_> = self.iter().collect();
let prop: Prop = prop.into();
let obj = obj.as_ref();
let cur = doc.get(obj, prop.clone())?;
let list_id = match cur {
Some((Value::Object(ObjType::List), id)) => {
let list_len = doc.length(&id);
for idx in (0..list_len).rev() {
doc.delete(&id, idx)?;
}
id
}
_ => doc.put_object(obj, prop, ObjType::List)?,
};
for (idx, v) in vec.iter().enumerate() {
v.save(doc, &list_id, idx)?;
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length(&id);
check_collection_size(len, "BTreeSet")?;
let mut result = BTreeSet::new();
for idx in 0..len {
let item = S::load(doc, &id, idx).map_err(|e| e.with_index(idx))?;
result.insert(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeSet (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::List), id)) => {
let len = doc.length_at(&id, heads);
check_collection_size(len, "BTreeSet")?;
let mut result = BTreeSet::new();
for idx in 0..len {
let item = S::load_at(doc, &id, idx, heads).map_err(|e| e.with_index(idx))?;
result.insert(item);
}
Ok(result)
}
Some((v, _)) => Err(Error::type_mismatch(
"BTreeSet (List)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let doc_value = Self::load(doc, obj, prop)?;
Ok(PrimitiveChanged::new(
self.len() != doc_value.len() || *self != doc_value,
))
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let doc_value = Self::load_at(doc, obj, prop, heads)?;
Ok(PrimitiveChanged::new(
self.len() != doc_value.len() || *self != doc_value,
))
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let new_value = Self::load(doc, obj, prop)?;
let changed = *self != new_value;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let new_value = Self::load_at(doc, obj, prop, heads)?;
let changed = *self != new_value;
*self = new_value;
Ok(PrimitiveChanged::new(changed))
}
}