use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::Deref;
use automerge::{ChangeHash, ObjId, Prop, ReadDoc, transaction::Transactable};
use crate::Result;
pub trait Automorph: Sized {
type Changes: ChangeReport;
type Cursor: FieldCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()>;
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self>;
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes>;
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self>;
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes>;
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes>;
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes>;
fn diff_versions<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
from_heads: &[ChangeHash],
to_heads: &[ChangeHash],
) -> Result<Self::Changes> {
let obj = obj.as_ref();
let prop: Prop = prop.into();
let from_value = Self::load_at(doc, obj, prop.clone(), from_heads)?;
from_value.diff_at(doc, obj, prop, to_heads)
}
fn tracked<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Tracked<Self>> {
Tracked::load(doc, obj, prop)
}
fn tracked_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Tracked<Self>> {
Tracked::load_at(doc, obj, prop, heads)
}
fn save_flat<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
) -> Result<()>
where
Self: Automorph,
{
self.save(doc, obj, "")
}
fn load_flat<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>) -> Result<Self>
where
Self: Automorph,
{
Self::load(doc, obj, "")
}
fn load_flat_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
heads: &[ChangeHash],
) -> Result<Self>
where
Self: Automorph,
{
Self::load_at(doc, obj, "", heads)
}
fn diff_flat<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
) -> Result<Self::Changes>
where
Self: Automorph,
{
self.diff(doc, obj, "")
}
fn diff_flat_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
heads: &[ChangeHash],
) -> Result<Self::Changes>
where
Self: Automorph,
{
self.diff_at(doc, obj, "", heads)
}
fn update_flat<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
) -> Result<Self::Changes>
where
Self: Automorph,
{
self.update(doc, obj, "")
}
fn update_flat_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
heads: &[ChangeHash],
) -> Result<Self::Changes>
where
Self: Automorph,
{
self.update_at(doc, obj, "", heads)
}
}
pub trait ChangeReport: Default + Clone + Debug {
fn any(&self) -> bool;
fn none(&self) -> bool {
!self.any()
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>>;
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>>;
}
#[derive(Clone, Debug, Default)]
pub struct PrimitiveChanged {
pub changed: bool,
}
impl PrimitiveChanged {
pub fn new(changed: bool) -> Self {
Self { changed }
}
}
impl ChangeReport for PrimitiveChanged {
fn any(&self) -> bool {
self.changed
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>> {
if self.changed { vec![vec![]] } else { vec![] }
}
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>> {
self.paths()
}
}
pub trait FieldCursor: Default + Clone + Debug {
type Changes: ChangeReport;
fn diff<D: ReadDoc>(&self, doc: &D, obj: &ObjId) -> Result<Self::Changes>;
fn refresh<D: ReadDoc>(&mut self, doc: &D, obj: &ObjId) -> Result<()>;
fn has_changes<D: ReadDoc>(&self, doc: &D, obj: &ObjId) -> Result<bool> {
Ok(self.diff(doc, obj)?.any())
}
}
#[derive(Clone, Debug, Default)]
pub struct ScalarCursor {
pub obj_id: Option<ObjId>,
}
impl FieldCursor for ScalarCursor {
type Changes = PrimitiveChanged;
fn diff<D: ReadDoc>(&self, _doc: &D, obj: &ObjId) -> Result<Self::Changes> {
let changed = match &self.obj_id {
Some(cached) => cached != obj,
None => true, };
Ok(PrimitiveChanged::new(changed))
}
fn refresh<D: ReadDoc>(&mut self, _doc: &D, obj: &ObjId) -> Result<()> {
self.obj_id = Some(obj.clone());
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub struct VecChanges<C: ChangeReport> {
pub added: Vec<usize>,
pub removed: Vec<usize>,
pub modified: std::collections::HashMap<usize, C>,
}
impl<C: ChangeReport> ChangeReport for VecChanges<C> {
fn any(&self) -> bool {
!self.added.is_empty() || !self.removed.is_empty() || !self.modified.is_empty()
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>> {
let mut paths = Vec::new();
if self.any() {
paths.push(vec![]);
}
for idx in &self.added {
paths.push(vec![Cow::Owned(idx.to_string())]);
}
for idx in &self.removed {
paths.push(vec![Cow::Owned(idx.to_string())]);
}
for (idx, changes) in &self.modified {
let idx_str: Cow<'static, str> = Cow::Owned(idx.to_string());
paths.push(vec![idx_str.clone()]);
for nested_path in changes.paths() {
let mut full_path = vec![idx_str.clone()];
full_path.extend(nested_path);
paths.push(full_path);
}
}
paths
}
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>> {
let mut paths = Vec::new();
for idx in &self.added {
paths.push(vec![Cow::Owned(idx.to_string())]);
}
for idx in &self.removed {
paths.push(vec![Cow::Owned(idx.to_string())]);
}
for (idx, changes) in &self.modified {
let idx_str: Cow<'static, str> = Cow::Owned(idx.to_string());
let nested_leaves = changes.leaf_paths();
if nested_leaves.is_empty() {
paths.push(vec![idx_str]);
} else {
for nested_path in nested_leaves {
let mut full_path = vec![idx_str.clone()];
full_path.extend(nested_path);
paths.push(full_path);
}
}
}
if paths.is_empty() && self.any() {
paths.push(vec![]);
}
paths
}
}
#[derive(Clone, Debug, Default)]
pub struct MapChanges<K: Clone + Eq + Hash + Debug, C: ChangeReport> {
pub added: HashSet<K>,
pub removed: HashSet<K>,
pub modified: std::collections::HashMap<K, C>,
}
impl<K: Clone + Eq + Hash + Debug + Default + ToString, C: ChangeReport> ChangeReport
for MapChanges<K, C>
{
fn any(&self) -> bool {
!self.added.is_empty() || !self.removed.is_empty() || !self.modified.is_empty()
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>> {
let mut paths = Vec::new();
if self.any() {
paths.push(vec![]);
}
for key in &self.added {
paths.push(vec![Cow::Owned(key.to_string())]);
}
for key in &self.removed {
paths.push(vec![Cow::Owned(key.to_string())]);
}
for (key, changes) in &self.modified {
let key_str: Cow<'static, str> = Cow::Owned(key.to_string());
paths.push(vec![key_str.clone()]);
for nested_path in changes.paths() {
let mut full_path = vec![key_str.clone()];
full_path.extend(nested_path);
paths.push(full_path);
}
}
paths
}
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>> {
let mut paths = Vec::new();
for key in &self.added {
paths.push(vec![Cow::<'static, str>::Owned(key.to_string())]);
}
for key in &self.removed {
paths.push(vec![Cow::<'static, str>::Owned(key.to_string())]);
}
for (key, changes) in &self.modified {
let key_str: Cow<'static, str> = Cow::Owned(key.to_string());
let nested_leaves = changes.leaf_paths();
if nested_leaves.is_empty() {
paths.push(vec![key_str]);
} else {
for nested_path in nested_leaves {
let mut full_path = vec![key_str.clone()];
full_path.extend(nested_path);
paths.push(full_path);
}
}
}
if paths.is_empty() && self.any() {
paths.push(vec![]);
}
paths
}
}
#[derive(Clone, Debug, Default)]
pub enum OptionChanges<C: ChangeReport> {
#[default]
Unchanged,
BecameSome,
BecameNone,
Inner(C),
}
impl<C: ChangeReport> ChangeReport for OptionChanges<C> {
fn any(&self) -> bool {
match self {
OptionChanges::Unchanged => false,
OptionChanges::BecameSome => true,
OptionChanges::BecameNone => true,
OptionChanges::Inner(c) => c.any(),
}
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>> {
match self {
OptionChanges::Unchanged => vec![],
OptionChanges::BecameSome | OptionChanges::BecameNone => vec![vec![]],
OptionChanges::Inner(c) => c.paths(),
}
}
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>> {
match self {
OptionChanges::Unchanged => vec![],
OptionChanges::BecameSome | OptionChanges::BecameNone => vec![vec![]],
OptionChanges::Inner(c) => c.leaf_paths(),
}
}
}
impl<C: ChangeReport> OptionChanges<C> {
pub fn became_some(&self) -> bool {
matches!(self, OptionChanges::BecameSome)
}
pub fn became_none(&self) -> bool {
matches!(self, OptionChanges::BecameNone)
}
pub fn inner(&self) -> Option<&C> {
match self {
OptionChanges::Inner(c) => Some(c),
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]
pub enum ResultChanges<OkC: ChangeReport, ErrC: ChangeReport> {
#[default]
Unchanged,
BecameOk,
BecameErr,
OkInner(OkC),
ErrInner(ErrC),
}
impl<OkC: ChangeReport, ErrC: ChangeReport> ChangeReport for ResultChanges<OkC, ErrC> {
fn any(&self) -> bool {
match self {
ResultChanges::Unchanged => false,
ResultChanges::BecameOk => true,
ResultChanges::BecameErr => true,
ResultChanges::OkInner(c) => c.any(),
ResultChanges::ErrInner(c) => c.any(),
}
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>> {
match self {
ResultChanges::Unchanged => vec![],
ResultChanges::BecameOk | ResultChanges::BecameErr => vec![vec![]],
ResultChanges::OkInner(c) => c.paths(),
ResultChanges::ErrInner(c) => c.paths(),
}
}
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>> {
match self {
ResultChanges::Unchanged => vec![],
ResultChanges::BecameOk | ResultChanges::BecameErr => vec![vec![]],
ResultChanges::OkInner(c) => c.leaf_paths(),
ResultChanges::ErrInner(c) => c.leaf_paths(),
}
}
}
impl<OkC: ChangeReport, ErrC: ChangeReport> ResultChanges<OkC, ErrC> {
pub fn became_ok(&self) -> bool {
matches!(self, ResultChanges::BecameOk)
}
pub fn became_err(&self) -> bool {
matches!(self, ResultChanges::BecameErr)
}
pub fn ok_inner(&self) -> Option<&OkC> {
match self {
ResultChanges::OkInner(c) => Some(c),
_ => None,
}
}
pub fn err_inner(&self) -> Option<&ErrC> {
match self {
ResultChanges::ErrInner(c) => Some(c),
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct VecCursor<C: FieldCursor> {
pub list_id: Option<ObjId>,
pub len: usize,
pub elements: Vec<C>,
}
impl<C: FieldCursor> FieldCursor for VecCursor<C> {
type Changes = VecChanges<C::Changes>;
fn diff<D: ReadDoc>(&self, doc: &D, obj: &ObjId) -> Result<Self::Changes> {
let mut changes = VecChanges::default();
let current_len = doc.length(obj);
if current_len > self.len {
changes.added = (self.len..current_len).collect();
}
if current_len < self.len {
changes.removed = (current_len..self.len).collect();
}
let check_len = std::cmp::min(current_len, self.len);
for idx in 0..check_len {
if idx < self.elements.len() {
if let Some((_, element_obj_id)) = doc.get(obj, idx)? {
let element_changes = self.elements[idx].diff(doc, &element_obj_id)?;
if element_changes.any() {
changes.modified.insert(idx, element_changes);
}
}
}
}
Ok(changes)
}
fn refresh<D: ReadDoc>(&mut self, doc: &D, obj: &ObjId) -> Result<()> {
let new_len = doc.length(obj);
self.list_id = Some(obj.clone());
self.elements.resize_with(new_len, C::default);
for idx in 0..new_len {
if let Some((_, element_obj_id)) = doc.get(obj, idx)? {
self.elements[idx].refresh(doc, &element_obj_id)?;
}
}
self.len = new_len;
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub struct MapCursor<C: FieldCursor> {
pub map_id: Option<ObjId>,
pub entries: std::collections::HashMap<String, C>,
}
impl<C: FieldCursor> FieldCursor for MapCursor<C> {
type Changes = MapChanges<String, C::Changes>;
fn diff<D: ReadDoc>(&self, doc: &D, obj: &ObjId) -> Result<Self::Changes> {
let mut changes = MapChanges::default();
let current_keys: HashSet<String> = doc.keys(obj).collect();
for key in ¤t_keys {
if !self.entries.contains_key(key) {
changes.added.insert(key.clone());
}
}
for key in self.entries.keys() {
if !current_keys.contains(key) {
changes.removed.insert(key.clone());
}
}
for (key, cursor) in &self.entries {
if current_keys.contains(key) {
if let Some((_, entry_obj_id)) = doc.get(obj, key.as_str())? {
let entry_changes = cursor.diff(doc, &entry_obj_id)?;
if entry_changes.any() {
changes.modified.insert(key.clone(), entry_changes);
}
}
}
}
Ok(changes)
}
fn refresh<D: ReadDoc>(&mut self, doc: &D, obj: &ObjId) -> Result<()> {
self.map_id = Some(obj.clone());
let current_keys: HashSet<String> = doc.keys(obj).collect();
self.entries.retain(|k, _| current_keys.contains(k));
for key in current_keys {
if let Some((_, entry_obj_id)) = doc.get(obj, key.as_str())? {
let cursor = self.entries.entry(key).or_default();
cursor.refresh(doc, &entry_obj_id)?;
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Tracked<T: Automorph> {
value: T,
cursor: T::Cursor,
obj: ObjId,
prop: Prop,
}
impl<T: Automorph> Tracked<T> {
pub fn load<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self> {
let obj = obj.as_ref().clone();
let prop: Prop = prop.into();
let value = T::load(doc, &obj, prop.clone())?;
let mut cursor = T::Cursor::default();
if let Some((_, value_obj_id)) = doc.get(&obj, prop.clone())? {
cursor.refresh(doc, &value_obj_id)?;
}
Ok(Self {
value,
cursor,
obj,
prop,
})
}
pub fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let obj = obj.as_ref().clone();
let prop: Prop = prop.into();
let value = T::load_at(doc, &obj, prop.clone(), heads)?;
let mut cursor = T::Cursor::default();
if let Some((_, value_obj_id)) = doc.get_at(&obj, prop.clone(), heads)? {
cursor.refresh(doc, &value_obj_id)?;
}
Ok(Self {
value,
cursor,
obj,
prop,
})
}
pub fn has_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool> {
let changes = self.value.diff(doc, &self.obj, self.prop.clone())?;
Ok(changes.any())
}
pub fn has_object_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool> {
if let Some((_, value_obj_id)) = doc.get(&self.obj, self.prop.clone())? {
self.cursor.has_changes(doc, &value_obj_id)
} else {
Ok(true)
}
}
#[deprecated(
since = "0.2.0",
note = "Renamed to has_object_changes() for clarity. For full change detection, use has_changes()."
)]
pub fn has_structural_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool> {
self.has_object_changes(doc)
}
#[deprecated(
since = "0.2.0",
note = "Renamed to has_object_changes() to clarify that it only detects structural changes (nested objects), not scalar field changes."
)]
pub fn might_have_changes<D: ReadDoc>(&self, doc: &D) -> Result<bool> {
self.has_object_changes(doc)
}
pub fn cursor_diff<D: ReadDoc>(&self, doc: &D) -> Result<<T::Cursor as FieldCursor>::Changes> {
if let Some((_, value_obj_id)) = doc.get(&self.obj, self.prop.clone())? {
self.cursor.diff(doc, &value_obj_id)
} else {
Ok(<T::Cursor as FieldCursor>::Changes::default())
}
}
pub fn update<D: ReadDoc>(&mut self, doc: &D) -> Result<T::Changes> {
let changes = self.value.update(doc, &self.obj, self.prop.clone())?;
if let Some((_, value_obj_id)) = doc.get(&self.obj, self.prop.clone())? {
self.cursor.refresh(doc, &value_obj_id)?;
}
Ok(changes)
}
pub fn save<D: Transactable + ReadDoc>(&self, doc: &mut D) -> Result<()> {
self.value.save(doc, &self.obj, self.prop.clone())
}
pub fn get(&self) -> &T {
&self.value
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.value
}
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: Automorph> Deref for Tracked<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T: Automorph + Default> Default for Tracked<T> {
fn default() -> Self {
Self {
value: T::default(),
cursor: T::Cursor::default(),
obj: automerge::ROOT,
prop: Prop::Map(String::new()),
}
}
}