use std::ops::RangeFull;
use automerge::ScalarValue;
use crate::{Doc, Prop, ReadDoc};
mod impls;
mod map;
mod seq;
pub trait Reconciler {
type Error: std::error::Error + From<StaleHeads>;
type Map<'a>: MapReconciler<Error = Self::Error>
where
Self: 'a;
type Seq<'a>: SeqReconciler<Error = Self::Error>
where
Self: 'a;
type Text<'a>: TextReconciler<Error = Self::Error>
where
Self: 'a;
type Counter<'a>: CounterReconciler<Error = Self::Error>
where
Self: 'a;
fn none(&mut self) -> Result<(), Self::Error>;
fn bytes<B: AsRef<[u8]>>(&mut self, value: B) -> Result<(), Self::Error>;
fn timestamp(&mut self, value: i64) -> Result<(), Self::Error>;
fn boolean(&mut self, value: bool) -> Result<(), Self::Error>;
fn str<S: AsRef<str>>(&mut self, value: S) -> Result<(), Self::Error>;
fn u64(&mut self, value: u64) -> Result<(), Self::Error>;
fn i64(&mut self, value: i64) -> Result<(), Self::Error>;
fn f64(&mut self, value: f64) -> Result<(), Self::Error>;
fn map(&mut self) -> Result<Self::Map<'_>, Self::Error>;
fn seq(&mut self) -> Result<Self::Seq<'_>, Self::Error>;
fn text(&mut self) -> Result<Self::Text<'_>, Self::Error>;
fn counter(&mut self) -> Result<Self::Counter<'_>, Self::Error>;
fn heads(&self) -> &[automerge::ChangeHash];
}
pub trait MapReconciler {
type Error: std::error::Error + From<StaleHeads>;
type EntriesIter<'a>: Iterator<Item = (&'a str, automerge::Value<'a>)>
where
Self: 'a;
fn entries(&self) -> Self::EntriesIter<'_>;
fn entry<P: AsRef<str>>(&self, prop: P) -> Option<automerge::Value<'_>>;
fn put<R: Reconcile, P: AsRef<str>>(&mut self, prop: P, value: R) -> Result<(), Self::Error>;
fn delete<P: AsRef<str>>(&mut self, prop: P) -> Result<(), Self::Error>;
fn hydrate_entry_key<'a, R: Reconcile, P: AsRef<str>>(
&self,
prop: P,
) -> Result<LoadKey<R::Key<'a>>, Self::Error>;
fn replace<R: Reconcile, P: AsRef<str>>(
&mut self,
prop: P,
value: R,
) -> Result<(), Self::Error> {
self.delete(&prop)?;
self.put(prop, value)?;
Ok(())
}
}
pub trait SeqReconciler {
type Error: std::error::Error + From<StaleHeads>;
type ItemIter<'a>: Iterator<Item = automerge::Value<'a>>
where
Self: 'a;
fn items(&self) -> Self::ItemIter<'_>;
fn get(&self, index: usize) -> Result<Option<automerge::Value<'_>>, Self::Error>;
fn hydrate_item_key<'a, R: Reconcile>(
&self,
index: usize,
) -> Result<LoadKey<R::Key<'a>>, Self::Error>;
fn insert<R: Reconcile>(&mut self, index: usize, value: R) -> Result<(), Self::Error>;
fn set<R: Reconcile>(&mut self, index: usize, value: R) -> Result<(), Self::Error>;
fn delete(&mut self, index: usize) -> Result<(), Self::Error>;
fn len(&self) -> Result<usize, Self::Error>;
fn is_empty(&self) -> Result<bool, Self::Error> {
Ok(self.len()? == 0)
}
}
pub trait CounterReconciler {
type Error: std::error::Error + From<StaleHeads>;
fn increment(&mut self, by: i64) -> Result<(), Self::Error>;
fn set(&mut self, value: i64) -> Result<(), Self::Error>;
}
pub trait TextReconciler {
type Error: std::error::Error + From<StaleHeads>;
fn splice<S: AsRef<str>>(
&mut self,
pos: usize,
delete: usize,
insert: S,
) -> Result<(), Self::Error>;
fn heads(&self) -> &[automerge::ChangeHash];
}
#[derive(Clone, PartialEq, Eq)]
pub struct NoKey;
#[derive(Debug)]
pub enum LoadKey<K> {
NoKey,
KeyNotFound,
Found(K),
}
impl<K> LoadKey<K> {
pub fn map<L, F: Fn(K) -> L>(self, f: F) -> LoadKey<L> {
match self {
Self::NoKey => LoadKey::NoKey,
Self::KeyNotFound => LoadKey::KeyNotFound,
Self::Found(k) => LoadKey::Found(f(k)),
}
}
}
pub trait Reconcile {
type Key<'a>: PartialEq;
fn reconcile<R: Reconciler>(&self, reconciler: R) -> Result<(), R::Error>;
fn hydrate_key<'a, D: ReadDoc>(
#[allow(unused_variables)] doc: &D,
#[allow(unused_variables)] obj: &automerge::ObjId,
#[allow(unused_variables)] prop: Prop<'_>,
) -> Result<LoadKey<Self::Key<'a>>, ReconcileError> {
Ok(LoadKey::NoKey)
}
fn key(&self) -> LoadKey<Self::Key<'_>> {
LoadKey::NoKey
}
}
#[derive(Debug, thiserror::Error)]
pub enum ReconcileError {
#[error(transparent)]
Automerge(#[from] automerge::AutomergeError),
#[error("the top level object must reconile to a map")]
TopLevelNotMap,
#[error(transparent)]
StaleHeads(#[from] StaleHeads),
}
#[derive(Debug, thiserror::Error)]
#[error("the data to be reconciled is stale, expected heads were {expected:?} but found {found:?}")]
pub struct StaleHeads {
pub expected: Vec<automerge::ChangeHash>,
pub found: Vec<automerge::ChangeHash>,
}
struct RootReconciler<'a, D> {
heads: Vec<automerge::ChangeHash>,
doc: &'a mut D,
}
impl<'a, D: Doc> Reconciler for RootReconciler<'a, D> {
type Error = ReconcileError;
type Map<'b> = InMap<'b, D>
where Self: 'b;
type Seq<'b> = InSeq<'b, D>
where Self: 'b;
type Text<'b> = InText<'b, D>
where Self: 'b;
type Counter<'b> = AtCounter<'b, D>
where Self: 'b;
fn none(&mut self) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn bytes<B: AsRef<[u8]>>(&mut self, _value: B) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn boolean(&mut self, _value: bool) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn timestamp(&mut self, _value: i64) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn str<S: AsRef<str>>(&mut self, _value: S) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn u64(&mut self, _value: u64) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn i64(&mut self, _value: i64) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn f64(&mut self, _value: f64) -> Result<(), Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn map(&mut self) -> Result<InMap<'_, D>, Self::Error> {
Ok(InMap {
heads: &self.heads,
current_obj: automerge::ROOT,
doc: self.doc,
})
}
fn seq(&mut self) -> Result<InSeq<'_, D>, Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn text(&mut self) -> Result<Self::Text<'_>, Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn counter(&mut self) -> Result<Self::Counter<'a>, Self::Error> {
Err(ReconcileError::TopLevelNotMap)
}
fn heads(&self) -> &[automerge::ChangeHash] {
&self.heads
}
}
enum PropAction<'a> {
Put(Prop<'a>),
Insert(u32),
}
impl<'a> PropAction<'a> {
fn get_target<'b, D: Doc>(
&self,
doc: &'b D,
obj: &automerge::ObjId,
) -> Result<Option<(automerge::Value<'b>, automerge::ObjId)>, automerge::AutomergeError> {
match self {
Self::Put(prop) => doc.get(obj, prop),
Self::Insert(_idx) => Ok(None),
}
}
fn create_target_obj<D: Doc>(
&self,
doc: &mut D,
obj: &automerge::ObjId,
objtype: automerge::ObjType,
) -> Result<automerge::ObjId, automerge::AutomergeError> {
match self {
Self::Put(prop) => doc.put_object(obj, prop, objtype),
Self::Insert(idx) => doc.insert_object(obj, (*idx) as usize, objtype),
}
}
fn create_primitive<V: Into<automerge::ScalarValue>, D: Doc>(
&self,
doc: &mut D,
obj: &automerge::ObjId,
value: V,
) -> Result<(), automerge::AutomergeError> {
match self {
Self::Put(p) => doc.put(obj, p, value),
Self::Insert(idx) => doc.insert(obj, (*idx) as usize, value),
}
}
}
struct PropReconciler<'a, D> {
heads: &'a [automerge::ChangeHash],
doc: &'a mut D,
current_obj: automerge::ObjId,
action: PropAction<'a>,
}
impl<'a, D: Doc> Reconciler for PropReconciler<'a, D> {
type Error = ReconcileError;
type Map<'b> = InMap<'b, D>
where Self: 'b;
type Seq<'b> = InSeq<'b, D>
where Self: 'b;
type Text<'b> = InText<'b, D>
where Self: 'b;
type Counter<'b> = AtCounter<'b, D>
where Self: 'b;
fn none(&mut self) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, ScalarValue::Null)
.map_err(ReconcileError::from)
}
fn bytes<B: AsRef<[u8]>>(&mut self, value: B) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, value.as_ref().to_vec())
.map_err(ReconcileError::from)
}
fn boolean(&mut self, value: bool) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, value)
.map_err(ReconcileError::from)
}
fn timestamp(&mut self, value: i64) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, ScalarValue::Timestamp(value))
.map_err(ReconcileError::from)
}
fn str<S: AsRef<str>>(&mut self, value: S) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, value.as_ref())
.map_err(ReconcileError::from)
}
fn u64(&mut self, value: u64) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, value)
.map_err(ReconcileError::from)
}
fn i64(&mut self, value: i64) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, value)
.map_err(ReconcileError::from)
}
fn f64(&mut self, value: f64) -> Result<(), Self::Error> {
self.action
.create_primitive(self.doc, &self.current_obj, value)
.map_err(ReconcileError::from)
}
fn map(&mut self) -> Result<InMap<'_, D>, Self::Error> {
use automerge::{ObjType, Value};
let map_id = if let Some((Value::Object(ObjType::Map), id)) =
self.action.get_target(self.doc, &self.current_obj)?
{
id
} else {
self.action
.create_target_obj(self.doc, &self.current_obj, ObjType::Map)?
};
Ok(InMap {
heads: self.heads,
current_obj: map_id,
doc: self.doc,
})
}
fn seq(&mut self) -> Result<InSeq<'_, D>, Self::Error> {
use automerge::{ObjType, Value};
let seq_id = if let Some((Value::Object(ObjType::List), id)) =
self.action.get_target(self.doc, &self.current_obj)?
{
id
} else {
self.action
.create_target_obj(self.doc, &self.current_obj, ObjType::List)?
};
Ok(InSeq {
heads: self.heads,
obj: seq_id,
doc: self.doc,
})
}
fn text(&mut self) -> Result<Self::Text<'_>, Self::Error> {
use automerge::{ObjType, Value};
let text_id = if let Some((Value::Object(ObjType::Text), id)) =
self.action.get_target(self.doc, &self.current_obj)?
{
id
} else {
self.action
.create_target_obj(self.doc, &self.current_obj, ObjType::Text)?
};
Ok(InText {
heads: self.heads,
obj: text_id,
doc: self.doc,
})
}
fn counter(&mut self) -> Result<Self::Counter<'_>, Self::Error> {
Ok(AtCounter {
doc: self.doc,
current_obj: &self.current_obj,
action: &self.action,
})
}
fn heads(&self) -> &[automerge::ChangeHash] {
self.heads
}
}
struct AtCounter<'a, D> {
doc: &'a mut D,
current_obj: &'a automerge::ObjId,
action: &'a PropAction<'a>,
}
impl<'a, D: Doc> CounterReconciler for AtCounter<'a, D> {
type Error = ReconcileError;
fn increment(&mut self, by: i64) -> Result<(), Self::Error> {
use automerge::Value;
match &self.action {
PropAction::Put(prop) => {
if let Some((Value::Scalar(s), _)) = self.doc.get(self.current_obj, prop)? {
if let ScalarValue::Counter(_) = s.as_ref() {
self.doc.increment(self.current_obj, prop, by)?;
return Ok(());
}
}
self.doc
.put(self.current_obj, prop, ScalarValue::Counter(by.into()))?;
Ok(())
}
PropAction::Insert(idx) => {
self.doc.insert(
self.current_obj,
(*idx) as usize,
ScalarValue::Counter(by.into()),
)?;
Ok(())
}
}
}
fn set(&mut self, value: i64) -> Result<(), Self::Error> {
self.action.create_primitive(
self.doc,
self.current_obj,
automerge::ScalarValue::Counter(value.into()),
)?;
Ok(())
}
}
struct InMap<'a, D> {
heads: &'a [automerge::ChangeHash],
doc: &'a mut D,
current_obj: automerge::ObjId,
}
impl<'a, D: Doc> MapReconciler for InMap<'a, D> {
type Error = ReconcileError;
type EntriesIter<'b> = InMapEntries<'b>
where Self: 'b;
fn entries(&self) -> Self::EntriesIter<'_> {
InMapEntries {
map_range: self.doc.map_range(&self.current_obj, ..),
}
}
fn entry<P: AsRef<str>>(&self, prop: P) -> Option<automerge::Value<'_>> {
self.doc
.get(&self.current_obj, prop.as_ref())
.ok()
.flatten()
.map(|v| v.0)
}
fn put<R: Reconcile, P: AsRef<str>>(&mut self, prop: P, value: R) -> Result<(), Self::Error> {
let reconciler = PropReconciler {
heads: self.heads,
current_obj: self.current_obj.clone(),
doc: self.doc,
action: PropAction::Put(prop.as_ref().into()),
};
value.reconcile(reconciler)?;
Ok(())
}
fn delete<P: AsRef<str>>(&mut self, prop: P) -> Result<(), Self::Error> {
self.doc
.delete(&self.current_obj, prop.as_ref())
.map_err(ReconcileError::from)
}
fn hydrate_entry_key<'b, R: Reconcile, P: AsRef<str>>(
&self,
prop: P,
) -> Result<LoadKey<R::Key<'b>>, Self::Error> {
R::hydrate_key(self.doc, &self.current_obj, prop.as_ref().into())
}
}
struct InMapEntries<'a> {
map_range: automerge::MapRange<'a, RangeFull>,
}
impl<'a> Iterator for InMapEntries<'a> {
type Item = (&'a str, automerge::Value<'a>);
fn next(&mut self) -> Option<Self::Item> {
self.map_range.next().map(|(key, val, _)| (key, val))
}
}
struct InSeq<'a, D> {
heads: &'a [automerge::ChangeHash],
doc: &'a mut D,
obj: automerge::ObjId,
}
struct ItemsInSeq<'a> {
list_range: automerge::ListRange<'a, RangeFull>,
}
impl<'a> Iterator for ItemsInSeq<'a> {
type Item = automerge::Value<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.list_range.next().map(|i| i.1)
}
}
impl<'a, D: Doc> SeqReconciler for InSeq<'a, D> {
type Error = ReconcileError;
type ItemIter<'b> = ItemsInSeq<'b>
where Self: 'b;
fn items<'b>(&'_ self) -> Self::ItemIter<'_> {
ItemsInSeq {
list_range: self.doc.list_range(&self.obj, ..),
}
}
fn get(&'_ self, index: usize) -> Result<Option<automerge::Value<'_>>, Self::Error> {
Ok(self.doc.get(&self.obj, index)?.map(|(v, _)| v))
}
fn insert<R: Reconcile>(&mut self, index: usize, value: R) -> Result<(), Self::Error> {
let reconciler = PropReconciler {
heads: self.heads,
doc: self.doc,
current_obj: self.obj.clone(),
action: PropAction::Insert(index as u32),
};
value.reconcile(reconciler)?;
Ok(())
}
fn set<R: Reconcile>(&mut self, index: usize, value: R) -> Result<(), Self::Error> {
let reconciler = PropReconciler {
heads: self.heads,
doc: self.doc,
current_obj: self.obj.clone(),
action: PropAction::Put(index.into()),
};
value.reconcile(reconciler)?;
Ok(())
}
fn delete<'b>(&mut self, index: usize) -> Result<(), Self::Error> {
self.doc
.delete(&self.obj, index)
.map_err(ReconcileError::from)
}
fn len(&self) -> Result<usize, Self::Error> {
Ok(self.doc.length(&self.obj))
}
fn hydrate_item_key<'b, R: Reconcile>(
&self,
index: usize,
) -> Result<LoadKey<R::Key<'b>>, Self::Error> {
if self.doc.get(&self.obj, index)?.is_some() {
R::hydrate_key(self.doc, &self.obj, index.into())
} else {
Ok(LoadKey::KeyNotFound)
}
}
}
struct InText<'a, D> {
heads: &'a [automerge::ChangeHash],
doc: &'a mut D,
obj: automerge::ObjId,
}
impl<'a, D: Doc> TextReconciler for InText<'a, D> {
type Error = ReconcileError;
fn splice<S: AsRef<str>>(
&mut self,
pos: usize,
delete: usize,
text: S,
) -> Result<(), Self::Error> {
self.doc
.splice_text(&self.obj, pos, delete, text.as_ref())?;
Ok(())
}
fn heads(&self) -> &[automerge::ChangeHash] {
self.heads
}
}
pub fn reconcile<R: Reconcile, D: Doc>(doc: &mut D, value: R) -> Result<(), ReconcileError> {
let reconciler = RootReconciler {
heads: doc.get_heads(),
doc,
};
value.reconcile(reconciler)?;
Ok(())
}
pub fn reconcile_prop<'a, R: Reconcile, O: AsRef<automerge::ObjId>, P: Into<Prop<'a>>>(
doc: &mut automerge::AutoCommit,
obj: O,
prop: P,
value: R,
) -> Result<(), ReconcileError> {
let heads = doc.get_heads();
let reconciler = PropReconciler {
heads: &heads,
doc,
action: PropAction::Put(prop.into()),
current_obj: obj.as_ref().clone(),
};
value.reconcile(reconciler)?;
Ok(())
}
pub fn reconcile_insert<R: Reconcile>(
doc: &mut automerge::AutoCommit,
obj: automerge::ObjId,
idx: usize,
value: R,
) -> Result<(), ReconcileError> {
let heads = doc.get_heads();
let reconciler = PropReconciler {
heads: &heads,
doc,
action: PropAction::Insert(idx as u32),
current_obj: obj,
};
value.reconcile(reconciler)?;
Ok(())
}
pub fn hydrate_key<'a, D: ReadDoc, H: crate::Hydrate + Clone>(
doc: &D,
obj: &automerge::ObjId,
outer: Prop<'a>,
inner: Prop<'a>,
) -> Result<LoadKey<H>, ReconcileError> {
use crate::hydrate::HydrateResultExt;
Ok(
crate::hydrate::hydrate_path(doc, obj, vec![outer, inner].into_iter())
.strip_unexpected()?
.map(LoadKey::Found)
.unwrap_or(LoadKey::KeyNotFound),
)
}
#[cfg(test)]
mod tests {
use super::*;
use automerge_test::{assert_doc, list, map};
struct Contact {
name: String,
addresses: Vec<Address>,
id: u64,
}
struct Address {
line_one: String,
line_two: String,
}
impl Reconcile for Address {
type Key<'a> = NoKey;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut map = reconciler.map()?;
map.put("line_one", &self.line_one)?;
map.put("line_two", &self.line_two)?;
Ok(())
}
}
impl Reconcile for Contact {
type Key<'a> = NoKey;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut map = reconciler.map()?;
map.put("addresses", &self.addresses)?;
map.put("name", &self.name)?;
map.put("id", self.id)?;
Ok(())
}
}
#[test]
fn basic_reconciliation() {
let mut bob = Contact {
name: "bob".to_string(),
id: 1,
addresses: vec![Address {
line_one: "line one".to_string(),
line_two: "line two".to_string(),
}],
};
let mut doc = automerge::AutoCommit::new();
reconcile(&mut doc, &bob).unwrap();
assert_doc!(
doc.document(),
map! {
"name" => { "bob" },
"id" => { 1_u64 },
"addresses" => { list!{
{ map! {
"line_one" => { "line one" },
"line_two" => { "line two" },
}}
}
}}
);
let mut doc2 = doc.clone().with_actor("actor2".as_bytes().into());
bob.name = "Bobsson".to_string();
reconcile(&mut doc2, &bob).unwrap();
let mut doc3 = doc.clone().with_actor("actor3".as_bytes().into());
bob.addresses[0].line_one = "Line one the premier".to_string();
reconcile(&mut doc3, &bob).unwrap();
doc.merge(&mut doc2).unwrap();
doc.merge(&mut doc3).unwrap();
assert_doc!(
doc.document(),
map! {
"name" => { "Bobsson" },
"id" => { 1_u64 },
"addresses" => { list!{
{ map! {
"line_one" => { "Line one the premier" },
"line_two" => { "line two" },
}}
}
}}
);
}
#[test]
fn test_with_transaction() {
let mut doc = automerge::Automerge::new();
let mut tx = doc.transaction();
let bob = Contact {
name: "bob".to_string(),
id: 1,
addresses: vec![Address {
line_one: "line one".to_string(),
line_two: "line two".to_string(),
}],
};
reconcile(&mut tx, &bob).unwrap();
tx.commit();
assert_doc!(
&doc,
map! {
"name" => { "bob" },
"id" => { 1_u64 },
"addresses" => { list!{
{ map! {
"line_one" => { "line one" },
"line_two" => { "line two" },
}}
}
}}
);
}
}