The Rust API has so far grown somewhat organically driven by the needs of the javascript implementation. This has led to an API which is quite awkward and unfamiliar to Rust programmers. Additionally there is no documentation to speak of. This commit is the first movement towards cleaning things up a bit. We touch a lot of files but the changes are all very mechanical. We introduce a few traits to abstract over the common operations between `Automerge` and `AutoCommit`, and add a whole bunch of documentation. * Add a `ReadDoc` trait to describe methods which read value from a document. make `Transactable` extend `ReadDoc` * Add a `SyncDoc` trait to describe methods necessary for synchronizing documents. * Put the `SyncDoc` implementation for `AutoCommit` behind `AutoCommit::sync` to ensure that any open transactions are closed before taking part in the sync protocol * Split `OpObserver` into two traits: `OpObserver` + `BranchableObserver`. `BranchableObserver` captures the methods which are only needed for observing transactions. * Add a whole bunch of documentation. The main changes Rust users will need to make is: * Import the `ReadDoc` trait wherever you are using the methods which have been moved to it. Optionally change concrete paramters on functions to `ReadDoc` constraints. * Likewise import the `SyncDoc` trait wherever you are doing synchronisation work * If you are using the `AutoCommit::*_sync_message` methods you will need to add a call to `AutoCommit::sync()` first. E.g. `doc.generate_sync_message` becomes `doc.sync().generate_sync_message` * If you have an implementation of `OpObserver` which you are using in an `AutoCommit` then split it into an implementation of `OpObserver` and `BranchableObserver`
121 lines
3.7 KiB
Rust
121 lines
3.7 KiB
Rust
use crate::op_set;
|
|
use crate::op_set::OpSet;
|
|
use crate::types::{ListEncoding, ObjId};
|
|
use crate::{exid::ExId, Prop};
|
|
|
|
/// An iterator over the "parents" of an object
|
|
///
|
|
/// The "parent" of an object in this context is the ([`ExId`], [`Prop`]) pair which specifies the
|
|
/// location of this object in the composite object which contains it. Each element in the iterator
|
|
/// is a [`Parent`], yielded in reverse order. This means that once the iterator returns `None` you
|
|
/// have reached the root of the document.
|
|
///
|
|
/// This is returned by [`crate::ReadDoc::parents`]
|
|
#[derive(Debug)]
|
|
pub struct Parents<'a> {
|
|
pub(crate) obj: ObjId,
|
|
pub(crate) ops: &'a OpSet,
|
|
}
|
|
|
|
impl<'a> Parents<'a> {
|
|
/// Return the path this `Parents` represents
|
|
///
|
|
/// This is _not_ in reverse order.
|
|
pub fn path(self) -> Vec<(ExId, Prop)> {
|
|
let mut path = self
|
|
.map(|Parent { obj, prop, .. }| (obj, prop))
|
|
.collect::<Vec<_>>();
|
|
path.reverse();
|
|
path
|
|
}
|
|
|
|
/// Like `path` but returns `None` if the target is not visible
|
|
pub fn visible_path(self) -> Option<Vec<(ExId, Prop)>> {
|
|
let mut path = Vec::new();
|
|
for Parent { obj, prop, visible } in self {
|
|
if !visible {
|
|
return None;
|
|
}
|
|
path.push((obj, prop))
|
|
}
|
|
path.reverse();
|
|
Some(path)
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for Parents<'a> {
|
|
type Item = Parent;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.obj.is_root() {
|
|
None
|
|
} else if let Some(op_set::Parent { obj, key, visible }) = self.ops.parent_object(&self.obj)
|
|
{
|
|
self.obj = obj;
|
|
Some(Parent {
|
|
obj: self.ops.id_to_exid(self.obj.0),
|
|
prop: self
|
|
.ops
|
|
.export_key(self.obj, key, ListEncoding::List)
|
|
.unwrap(),
|
|
visible,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A component of a path to an object
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct Parent {
|
|
/// The object ID this component refers to
|
|
pub obj: ExId,
|
|
/// The property within `obj` this component refers to
|
|
pub prop: Prop,
|
|
/// Whether this component is "visible"
|
|
///
|
|
/// An "invisible" component is one where the property is hidden, either because it has been
|
|
/// deleted or because there is a conflict on this (object, property) pair and this value does
|
|
/// not win the conflict.
|
|
pub visible: bool,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Parent;
|
|
use crate::{transaction::Transactable, Prop, ReadDoc};
|
|
|
|
#[test]
|
|
fn test_invisible_parents() {
|
|
// Create a document with a list of objects, then delete one of the objects, then generate
|
|
// a path to the deleted object.
|
|
|
|
let mut doc = crate::AutoCommit::new();
|
|
let list = doc
|
|
.put_object(crate::ROOT, "list", crate::ObjType::List)
|
|
.unwrap();
|
|
let obj1 = doc.insert_object(&list, 0, crate::ObjType::Map).unwrap();
|
|
let _obj2 = doc.insert_object(&list, 1, crate::ObjType::Map).unwrap();
|
|
doc.put(&obj1, "key", "value").unwrap();
|
|
doc.delete(&list, 0).unwrap();
|
|
|
|
let mut parents = doc.parents(&obj1).unwrap().collect::<Vec<_>>();
|
|
parents.reverse();
|
|
assert_eq!(
|
|
parents,
|
|
vec![
|
|
Parent {
|
|
obj: crate::ROOT,
|
|
prop: Prop::Map("list".to_string()),
|
|
visible: true,
|
|
},
|
|
Parent {
|
|
obj: list,
|
|
prop: Prop::Seq(0),
|
|
visible: false,
|
|
},
|
|
]
|
|
);
|
|
}
|
|
}
|