Merge pull request #19 from automerge/bin_change
Refactor Change to Be Binary Encoded
This commit is contained in:
commit
13142048b1
33 changed files with 1407 additions and 1421 deletions
automerge-backend-wasm/src
automerge-backend
Cargo.toml
src
backend.rschange.rscolumnar.rsconcurrent_operations.rsencoding.rserror.rslib.rsop.rsop_handle.rsop_set.rsop_type.rsserialize.rsundo_operation.rs
tests
automerge-c/src
automerge-cli
automerge-frontend
automerge-protocol/src
|
@ -1,10 +1,11 @@
|
|||
//#![feature(set_stdio)]
|
||||
|
||||
use automerge_backend::{AutomergeError, Backend};
|
||||
use automerge_protocol::{ActorID, ChangeHash, ChangeRequest};
|
||||
use automerge_backend::{Backend, Change};
|
||||
use automerge_protocol::{ActorID, ChangeHash, Request};
|
||||
use js_sys::{Array, Uint8Array};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
|
@ -39,54 +40,46 @@ pub struct State {
|
|||
impl State {
|
||||
#[wasm_bindgen(js_name = applyChanges)]
|
||||
pub fn apply_changes(&mut self, changes: Array) -> Result<JsValue, JsValue> {
|
||||
let ch: Vec<Vec<u8>> = changes
|
||||
.iter()
|
||||
.map(|c| c.dyn_into::<Uint8Array>().unwrap().to_vec())
|
||||
.collect();
|
||||
let patch = self
|
||||
.backend
|
||||
.apply_changes_binary(ch)
|
||||
.map_err(automerge_error_to_js)?;
|
||||
let mut ch = Vec::with_capacity(changes.length() as usize);
|
||||
for c in changes.iter() {
|
||||
let bytes = c.dyn_into::<Uint8Array>().unwrap().to_vec();
|
||||
ch.push(Change::from_bytes(bytes).map_err(to_js_err)?);
|
||||
}
|
||||
let patch = self.backend.apply_changes(ch).map_err(to_js_err)?;
|
||||
rust_to_js(&patch)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = loadChanges)]
|
||||
pub fn load_changes(&mut self, changes: Array) -> Result<(), JsValue> {
|
||||
let ch: Vec<Vec<u8>> = changes
|
||||
.iter()
|
||||
.map(|c| c.dyn_into::<Uint8Array>().unwrap().to_vec())
|
||||
.collect();
|
||||
self.backend
|
||||
.load_changes_binary(ch)
|
||||
.map_err(automerge_error_to_js)
|
||||
let mut ch = Vec::with_capacity(changes.length() as usize);
|
||||
for c in changes.iter() {
|
||||
let bytes = c.dyn_into::<Uint8Array>().unwrap().to_vec();
|
||||
ch.push(Change::from_bytes(bytes).unwrap())
|
||||
}
|
||||
self.backend.load_changes(ch).map_err(to_js_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = applyLocalChange)]
|
||||
pub fn apply_local_change(&mut self, change: JsValue) -> Result<JsValue, JsValue> {
|
||||
let c: ChangeRequest = js_to_rust(change)?;
|
||||
let patch = self
|
||||
.backend
|
||||
.apply_local_change(c)
|
||||
.map_err(automerge_error_to_js)?;
|
||||
let c: Request = js_to_rust(change)?;
|
||||
let patch = self.backend.apply_local_change(c).map_err(to_js_err)?;
|
||||
rust_to_js(&patch)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = getPatch)]
|
||||
pub fn get_patch(&self) -> Result<JsValue, JsValue> {
|
||||
let patch = self.backend.get_patch().map_err(automerge_error_to_js)?;
|
||||
let patch = self.backend.get_patch().map_err(to_js_err)?;
|
||||
rust_to_js(&patch)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = getChanges)]
|
||||
pub fn get_changes(&self, have_deps: JsValue) -> Result<Array, JsValue> {
|
||||
let deps: Vec<ChangeHash> = js_to_rust(have_deps)?;
|
||||
let changes = self
|
||||
.backend
|
||||
.get_changes(&deps)
|
||||
.map_err(automerge_error_to_js)?;
|
||||
let changes = self.backend.get_changes(&deps);
|
||||
let result = Array::new();
|
||||
for c in changes {
|
||||
let bytes: Uint8Array = c.as_slice().into();
|
||||
let bytes: Uint8Array = c.bytes.as_slice().into();
|
||||
result.push(bytes.as_ref());
|
||||
}
|
||||
Ok(result)
|
||||
|
@ -98,10 +91,10 @@ impl State {
|
|||
let changes = self
|
||||
.backend
|
||||
.get_changes_for_actor_id(&a)
|
||||
.map_err(automerge_error_to_js)?;
|
||||
.map_err(to_js_err)?;
|
||||
let result = Array::new();
|
||||
for c in changes {
|
||||
let bytes: Uint8Array = c.as_slice().into();
|
||||
let bytes: Uint8Array = c.bytes.as_slice().into();
|
||||
result.push(bytes.as_ref());
|
||||
}
|
||||
Ok(result)
|
||||
|
@ -133,7 +126,7 @@ impl State {
|
|||
|
||||
#[wasm_bindgen(js_name = save)]
|
||||
pub fn save(&self) -> Result<JsValue, JsValue> {
|
||||
let data = self.backend.save().map_err(automerge_error_to_js)?;
|
||||
let data = self.backend.save().map_err(to_js_err)?;
|
||||
let js_bytes: Uint8Array = data.as_slice().into();
|
||||
Ok(js_bytes.into())
|
||||
}
|
||||
|
@ -141,7 +134,7 @@ impl State {
|
|||
#[wasm_bindgen(js_name = load)]
|
||||
pub fn load(data: JsValue) -> Result<State, JsValue> {
|
||||
let data = data.dyn_into::<Uint8Array>().unwrap().to_vec();
|
||||
let backend = Backend::load(data).map_err(automerge_error_to_js)?;
|
||||
let backend = Backend::load(data).map_err(to_js_err)?;
|
||||
Ok(State { backend })
|
||||
}
|
||||
|
||||
|
@ -153,7 +146,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
fn automerge_error_to_js(err: AutomergeError) -> JsValue {
|
||||
fn to_js_err<T: Display>(err: T) -> JsValue {
|
||||
js_sys::Error::new(&std::format!("Automerge error: {}", err)).into()
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ uuid = { version = "^0.5.1", features=["v4"] }
|
|||
wasm-bindgen = "^0.2"
|
||||
js-sys = "^0.3"
|
||||
im-rc = "^14.3.0"
|
||||
sha2 = "^0.8.1"
|
||||
hex = "^0.4.2"
|
||||
rand = { version = "^0.7.3", features=["wasm-bindgen"] }
|
||||
leb128 = "^0.2.4"
|
||||
maplit = "^1.0.2"
|
||||
sha2 = "^0.8.1"
|
||||
leb128 = "^0.2.4"
|
||||
automerge-protocol = { path = "../automerge-protocol" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use crate::actor_map::ActorMap;
|
||||
use crate::columnar::{bin_to_changes, change_to_change, changes_to_bin, changes_to_one_bin};
|
||||
use crate::error::AutomergeError;
|
||||
use crate::op::Operation;
|
||||
use crate::op_handle::OpHandle;
|
||||
use crate::op_set::OpSet;
|
||||
use crate::op_type::OpType;
|
||||
use crate::ordered_set::{OrdDelta, OrderedSet};
|
||||
use crate::pending_diff::PendingDiff;
|
||||
use crate::time;
|
||||
use crate::undo_operation::UndoOperation;
|
||||
use automerge_protocol::{
|
||||
ActorID, Change, ChangeHash, ChangeRequest, ChangeRequestType, Diff, Key, ObjType, ObjectID,
|
||||
OpID, OpRequest, OpType, Operation, Patch, ReqOpType, RequestKey, Value,
|
||||
};
|
||||
use crate::{Change, UnencodedChange};
|
||||
use automerge_protocol as amp;
|
||||
use automerge_protocol::{ActorID, Key, ObjType, ObjectID, OpID};
|
||||
use std::borrow::BorrowMut;
|
||||
use std::cmp::max;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -22,14 +22,12 @@ pub struct Backend {
|
|||
versions: Vec<Version>,
|
||||
queue: Vec<Rc<Change>>,
|
||||
op_set: Rc<OpSet>,
|
||||
//states: ActorStates,
|
||||
states: HashMap<ActorID, Vec<Rc<Change>>>,
|
||||
actors: ActorMap,
|
||||
obj_alias: HashMap<String, ObjectID>,
|
||||
undo_pos: usize,
|
||||
hashes: HashMap<ChangeHash, Rc<Change>>,
|
||||
history: Vec<ChangeHash>,
|
||||
//clock: Clock,
|
||||
hashes: HashMap<amp::ChangeHash, Rc<Change>>,
|
||||
history: Vec<amp::ChangeHash>,
|
||||
pub undo_stack: Vec<Vec<UndoOperation>>,
|
||||
pub redo_stack: Vec<Vec<UndoOperation>>,
|
||||
}
|
||||
|
@ -49,8 +47,7 @@ impl Backend {
|
|||
queue: Vec::new(),
|
||||
actors: ActorMap::new(),
|
||||
obj_alias: HashMap::new(),
|
||||
states: HashMap::new(), //ActorStates::new(),
|
||||
//clock: Clock::empty(),
|
||||
states: HashMap::new(),
|
||||
history: Vec::new(),
|
||||
hashes: HashMap::new(),
|
||||
undo_stack: Vec::new(),
|
||||
|
@ -70,7 +67,7 @@ impl Backend {
|
|||
|
||||
fn process_request(
|
||||
&mut self,
|
||||
request: &ChangeRequest,
|
||||
request: &::Request,
|
||||
op_set: Rc<OpSet>,
|
||||
start_op: u64,
|
||||
) -> Result<Rc<Change>, AutomergeError> {
|
||||
|
@ -82,7 +79,7 @@ impl Backend {
|
|||
let mut elemid_cache: HashMap<ObjectID, Box<dyn OrderedSet<OpID>>> = HashMap::new();
|
||||
if let Some(ops) = &request.ops {
|
||||
for rop in ops.iter() {
|
||||
let id = OpID(start_op + (operations.len() as u64), actor_id.0.clone());
|
||||
let id = OpID::new(start_op + (operations.len() as u64), &actor_id);
|
||||
let insert = rop.insert;
|
||||
let object_id = self.str_to_object(&rop.obj)?;
|
||||
|
||||
|
@ -125,19 +122,19 @@ impl Backend {
|
|||
let key = resolve_key(rop, &id, elemids2)?;
|
||||
let pred = op_set.get_pred(&object_id, &key, insert);
|
||||
let action = match rop.action {
|
||||
ReqOpType::MakeMap => OpType::Make(ObjType::Map),
|
||||
ReqOpType::MakeTable => OpType::Make(ObjType::Table),
|
||||
ReqOpType::MakeList => OpType::Make(ObjType::List),
|
||||
ReqOpType::MakeText => OpType::Make(ObjType::Text),
|
||||
ReqOpType::Del => OpType::Del,
|
||||
ReqOpType::Link => OpType::Link(
|
||||
amp::OpType::MakeMap => OpType::Make(ObjType::Map),
|
||||
amp::OpType::MakeTable => OpType::Make(ObjType::Table),
|
||||
amp::OpType::MakeList => OpType::Make(ObjType::List),
|
||||
amp::OpType::MakeText => OpType::Make(ObjType::Text),
|
||||
amp::OpType::Del => OpType::Del,
|
||||
amp::OpType::Link => OpType::Link(
|
||||
child.ok_or_else(|| AutomergeError::LinkMissingChild(id.clone()))?,
|
||||
),
|
||||
ReqOpType::Inc => OpType::Inc(
|
||||
amp::OpType::Inc => OpType::Inc(
|
||||
rop.to_i64()
|
||||
.ok_or_else(|| AutomergeError::MissingNumberValue(rop.clone()))?,
|
||||
),
|
||||
ReqOpType::Set => OpType::Set(rop.primitive_value()),
|
||||
amp::OpType::Set => OpType::Set(rop.primitive_value()),
|
||||
};
|
||||
|
||||
let op = Operation {
|
||||
|
@ -157,26 +154,28 @@ impl Backend {
|
|||
operations.push(op);
|
||||
}
|
||||
}
|
||||
Ok(change_to_change(Change {
|
||||
start_op,
|
||||
message: request.message.clone(),
|
||||
actor_id: request.actor.clone(),
|
||||
hash: ChangeHash::zero(),
|
||||
seq: request.seq,
|
||||
deps: request.deps.clone().unwrap_or_default(),
|
||||
time,
|
||||
operations,
|
||||
})?)
|
||||
Ok(Rc::new(
|
||||
UnencodedChange {
|
||||
start_op,
|
||||
message: request.message.clone(),
|
||||
actor_id: request.actor.clone(),
|
||||
seq: request.seq,
|
||||
deps: request.deps.clone().unwrap_or_default(),
|
||||
time,
|
||||
operations,
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn make_patch(
|
||||
&self,
|
||||
diffs: Option<Diff>,
|
||||
request: Option<&ChangeRequest>,
|
||||
) -> Result<Patch, AutomergeError> {
|
||||
diffs: Option<amp::Diff>,
|
||||
request: Option<&::Request>,
|
||||
) -> Result<amp::Patch, AutomergeError> {
|
||||
let mut deps: Vec<_> = self.op_set.deps.iter().cloned().collect();
|
||||
deps.sort_unstable();
|
||||
Ok(Patch {
|
||||
Ok(amp::Patch {
|
||||
version: self.versions.last().map(|v| v.version).unwrap_or(0),
|
||||
can_undo: self.can_undo(),
|
||||
can_redo: self.can_redo(),
|
||||
|
@ -187,14 +186,14 @@ impl Backend {
|
|||
.iter()
|
||||
.map(|(k, v)| (k.to_hex_string(), v.len() as u64))
|
||||
.collect(),
|
||||
actor: request.map(|r| r.actor.0.clone()),
|
||||
actor: request.map(|r| r.actor.to_hex_string()),
|
||||
seq: request.map(|r| r.seq),
|
||||
})
|
||||
}
|
||||
|
||||
fn undo(
|
||||
&mut self,
|
||||
request: &ChangeRequest,
|
||||
request: &::Request,
|
||||
start_op: u64,
|
||||
) -> Result<Rc<Change>, AutomergeError> {
|
||||
let undo_pos = self.undo_pos;
|
||||
|
@ -222,26 +221,26 @@ impl Backend {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let change = change_to_change(Change {
|
||||
let change = UnencodedChange {
|
||||
actor_id: request.actor.clone(),
|
||||
seq: request.seq,
|
||||
hash: ChangeHash::zero(),
|
||||
start_op,
|
||||
deps: request.deps.clone().unwrap_or_default(),
|
||||
message: request.message.clone(),
|
||||
time: time::unix_timestamp(),
|
||||
operations,
|
||||
})?;
|
||||
}
|
||||
.into();
|
||||
|
||||
self.undo_pos -= 1;
|
||||
self.redo_stack.push(redo_ops);
|
||||
|
||||
Ok(change)
|
||||
Ok(Rc::new(change))
|
||||
}
|
||||
|
||||
fn redo(
|
||||
&mut self,
|
||||
request: &ChangeRequest,
|
||||
request: &::Request,
|
||||
start_op: u64,
|
||||
) -> Result<Rc<Change>, AutomergeError> {
|
||||
let mut redo_ops = self.redo_stack.pop().ok_or(AutomergeError::NoRedo)?;
|
||||
|
@ -257,28 +256,20 @@ impl Backend {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let change = change_to_change(Change {
|
||||
let change = UnencodedChange {
|
||||
actor_id: request.actor.clone(),
|
||||
seq: request.seq,
|
||||
hash: ChangeHash::zero(),
|
||||
start_op,
|
||||
deps: request.deps.clone().unwrap_or_default(),
|
||||
message: request.message.clone(),
|
||||
time: time::unix_timestamp(),
|
||||
operations,
|
||||
})?;
|
||||
}
|
||||
.into();
|
||||
|
||||
self.undo_pos += 1;
|
||||
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn load_changes_binary(&mut self, data: Vec<Vec<u8>>) -> Result<(), AutomergeError> {
|
||||
let mut changes = Vec::new();
|
||||
for d in data {
|
||||
changes.extend(bin_to_changes(&d)?);
|
||||
}
|
||||
self.load_changes(changes)
|
||||
Ok(Rc::new(change))
|
||||
}
|
||||
|
||||
pub fn load_changes(&mut self, mut changes: Vec<Change>) -> Result<(), AutomergeError> {
|
||||
|
@ -287,15 +278,10 @@ impl Backend {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_changes_binary(&mut self, data: Vec<Vec<u8>>) -> Result<Patch, AutomergeError> {
|
||||
let mut changes = Vec::new();
|
||||
for d in data {
|
||||
changes.extend(bin_to_changes(&d)?);
|
||||
}
|
||||
self.apply_changes(changes)
|
||||
}
|
||||
|
||||
pub fn apply_changes(&mut self, mut changes: Vec<Change>) -> Result<Patch, AutomergeError> {
|
||||
pub fn apply_changes(
|
||||
&mut self,
|
||||
mut changes: Vec<Change>,
|
||||
) -> Result<amp::Patch, AutomergeError> {
|
||||
let op_set = Some(self.op_set.clone());
|
||||
|
||||
self.versions.iter_mut().for_each(|v| {
|
||||
|
@ -320,12 +306,7 @@ impl Backend {
|
|||
for change in v.queue.drain(0..) {
|
||||
let mut m = HashMap::new();
|
||||
Rc::make_mut(op_set)
|
||||
.apply_ops(
|
||||
OpHandle::extract(change.clone()),
|
||||
false,
|
||||
&mut m,
|
||||
&self.actors,
|
||||
)
|
||||
.apply_ops(OpHandle::extract(change), false, &mut m, &self.actors)
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(op_set.clone());
|
||||
|
@ -336,10 +317,10 @@ impl Backend {
|
|||
fn apply(
|
||||
&mut self,
|
||||
mut changes: Vec<Rc<Change>>,
|
||||
request: Option<&ChangeRequest>,
|
||||
request: Option<&::Request>,
|
||||
undoable: bool,
|
||||
incremental: bool,
|
||||
) -> Result<Patch, AutomergeError> {
|
||||
) -> Result<amp::Patch, AutomergeError> {
|
||||
let mut pending_diffs = HashMap::new();
|
||||
|
||||
for change in changes.drain(..) {
|
||||
|
@ -371,8 +352,8 @@ impl Backend {
|
|||
|
||||
pub fn apply_local_change(
|
||||
&mut self,
|
||||
mut request: ChangeRequest,
|
||||
) -> Result<Patch, AutomergeError> {
|
||||
mut request: amp::Request,
|
||||
) -> Result<amp::Patch, AutomergeError> {
|
||||
self.check_for_duplicate(&request)?; // Change has already been applied
|
||||
|
||||
let ver = self.get_version(request.version)?;
|
||||
|
@ -383,12 +364,12 @@ impl Backend {
|
|||
|
||||
let start_op = ver.max_op + 1;
|
||||
let change = match request.request_type {
|
||||
ChangeRequestType::Change => self.process_request(&request, ver, start_op)?,
|
||||
ChangeRequestType::Undo => self.undo(&request, start_op)?,
|
||||
ChangeRequestType::Redo => self.redo(&request, start_op)?,
|
||||
amp::RequestType::Change => self.process_request(&request, ver, start_op)?,
|
||||
amp::RequestType::Undo => self.undo(&request, start_op)?,
|
||||
amp::RequestType::Redo => self.redo(&request, start_op)?,
|
||||
};
|
||||
|
||||
let undoable = request.request_type == ChangeRequestType::Change && request.undoable;
|
||||
let undoable = request.request_type == amp::RequestType::Change && request.undoable;
|
||||
|
||||
let patch = self.apply(vec![change.clone()], Some(&request), undoable, true)?;
|
||||
|
||||
|
@ -397,7 +378,7 @@ impl Backend {
|
|||
Ok(patch)
|
||||
}
|
||||
|
||||
fn check_for_duplicate(&self, request: &ChangeRequest) -> Result<(), AutomergeError> {
|
||||
fn check_for_duplicate(&self, request: &::Request) -> Result<(), AutomergeError> {
|
||||
if self
|
||||
.states
|
||||
.get(&request.actor)
|
||||
|
@ -407,7 +388,8 @@ impl Backend {
|
|||
{
|
||||
return Err(AutomergeError::DuplicateChange(format!(
|
||||
"Change request has already been applied {}:{}",
|
||||
request.actor.0, request.seq
|
||||
request.actor.to_hex_string(),
|
||||
request.seq
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
|
@ -449,12 +431,10 @@ impl Backend {
|
|||
}
|
||||
|
||||
self.states
|
||||
.entry(change.actor_id.clone())
|
||||
.entry(change.actor_id())
|
||||
.or_default()
|
||||
.push(change.clone());
|
||||
|
||||
// self.clock.set(&change.actor_id, change.seq);
|
||||
|
||||
self.hashes.insert(change.hash, change.clone());
|
||||
|
||||
self.history.push(change.hash);
|
||||
|
@ -514,7 +494,7 @@ impl Backend {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_patch(&self) -> Result<Patch, AutomergeError> {
|
||||
pub fn get_patch(&self) -> Result<amp::Patch, AutomergeError> {
|
||||
let diffs = self
|
||||
.op_set
|
||||
.construct_object(&ObjectID::Root, &self.actors)?;
|
||||
|
@ -524,21 +504,15 @@ impl Backend {
|
|||
pub fn get_changes_for_actor_id(
|
||||
&self,
|
||||
actor_id: &ActorID,
|
||||
) -> Result<Vec<Vec<u8>>, AutomergeError> {
|
||||
let changes: Vec<&Change> = self
|
||||
) -> Result<Vec<&Change>, AutomergeError> {
|
||||
Ok(self
|
||||
.states
|
||||
.get(actor_id)
|
||||
.map(|vec| vec.iter().map(|c| c.as_ref()).collect())
|
||||
.unwrap_or_default();
|
||||
changes_to_bin(&changes)
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn get_changes(&self, have_deps: &[ChangeHash]) -> Result<Vec<Vec<u8>>, AutomergeError> {
|
||||
let changes = self.get_changes_unserialized(have_deps);
|
||||
changes_to_bin(&changes)
|
||||
}
|
||||
|
||||
pub fn get_changes_unserialized(&self, have_deps: &[ChangeHash]) -> Vec<&Change> {
|
||||
pub fn get_changes(&self, have_deps: &[amp::ChangeHash]) -> Vec<&Change> {
|
||||
let mut stack = have_deps.to_owned();
|
||||
let mut has_seen = HashSet::new();
|
||||
while let Some(hash) = stack.pop() {
|
||||
|
@ -547,9 +521,7 @@ impl Backend {
|
|||
}
|
||||
has_seen.insert(hash);
|
||||
}
|
||||
self
|
||||
// .states
|
||||
.history
|
||||
self.history
|
||||
.iter()
|
||||
.filter(|hash| !has_seen.contains(hash))
|
||||
.filter_map(|hash| self.hashes.get(hash))
|
||||
|
@ -566,24 +538,23 @@ impl Backend {
|
|||
}
|
||||
|
||||
pub fn save(&self) -> Result<Vec<u8>, AutomergeError> {
|
||||
let changes: Vec<_> = self
|
||||
let bytes: Vec<&[u8]> = self
|
||||
.history
|
||||
.iter()
|
||||
.filter_map(|hash| self.hashes.get(&hash))
|
||||
.map(|ch| ch.as_ref())
|
||||
.map(|r| r.bytes.as_slice())
|
||||
.collect();
|
||||
let data = changes_to_one_bin(&changes)?;
|
||||
Ok(data)
|
||||
Ok(bytes.concat())
|
||||
}
|
||||
|
||||
pub fn load(data: Vec<u8>) -> Result<Self, AutomergeError> {
|
||||
let changes = bin_to_changes(&data)?;
|
||||
let changes = Change::parse(&data)?;
|
||||
let mut backend = Self::init();
|
||||
backend.load_changes(changes)?;
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub fn get_missing_deps(&self) -> Vec<ChangeHash> {
|
||||
pub fn get_missing_deps(&self) -> Vec<amp::ChangeHash> {
|
||||
let in_queue: Vec<_> = self.queue.iter().map(|change| &change.hash).collect();
|
||||
self.queue
|
||||
.iter()
|
||||
|
@ -607,16 +578,16 @@ struct Version {
|
|||
}
|
||||
|
||||
fn resolve_key(
|
||||
rop: &OpRequest,
|
||||
rop: &::Op,
|
||||
id: &OpID,
|
||||
ids: &mut dyn OrderedSet<OpID>,
|
||||
) -> Result<Key, AutomergeError> {
|
||||
let key = &rop.key;
|
||||
let insert = rop.insert;
|
||||
let del = rop.action == ReqOpType::Del;
|
||||
let del = rop.action == amp::OpType::Del;
|
||||
match key {
|
||||
RequestKey::Str(s) => Ok(Key::Map(s.clone())),
|
||||
RequestKey::Num(n) => {
|
||||
amp::RequestKey::Str(s) => Ok(Key::Map(s.clone())),
|
||||
amp::RequestKey::Num(n) => {
|
||||
let n: usize = *n as usize;
|
||||
(if insert {
|
||||
if n == 0 {
|
||||
|
@ -672,8 +643,8 @@ impl OpExt for Operation {
|
|||
fn merge(&mut self, other: Operation) {
|
||||
if let OpType::Inc(delta) = other.action {
|
||||
match self.action {
|
||||
OpType::Set(Value::Counter(number)) => {
|
||||
self.action = OpType::Set(Value::Counter(number + delta))
|
||||
OpType::Set(amp::Value::Counter(number)) => {
|
||||
self.action = OpType::Set(amp::Value::Counter(number + delta))
|
||||
}
|
||||
OpType::Inc(number) => self.action = OpType::Inc(number + delta),
|
||||
_ => {}
|
||||
|
|
474
automerge-backend/src/change.rs
Normal file
474
automerge-backend/src/change.rs
Normal file
|
@ -0,0 +1,474 @@
|
|||
use crate::columnar;
|
||||
use crate::columnar::{
|
||||
ColumnEncoder, KeyIterator, ObjIterator, OperationIterator, PredIterator, ValueIterator,
|
||||
};
|
||||
use crate::encoding::{Decodable, Encodable};
|
||||
use crate::error::AutomergeError;
|
||||
use crate::op::Operation;
|
||||
use automerge_protocol::{ActorID, ChangeHash};
|
||||
use core::fmt::Debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::Range;
|
||||
use std::str;
|
||||
|
||||
const HASH_BYTES: usize = 32;
|
||||
const CHUNK_TYPE: u8 = 1;
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
|
||||
pub struct UnencodedChange {
|
||||
#[serde(rename = "ops")]
|
||||
pub operations: Vec<Operation>,
|
||||
#[serde(rename = "actor")]
|
||||
pub actor_id: ActorID,
|
||||
//pub hash: ChangeHash,
|
||||
pub seq: u64,
|
||||
#[serde(rename = "startOp")]
|
||||
pub start_op: u64,
|
||||
pub time: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
pub deps: Vec<ChangeHash>,
|
||||
}
|
||||
|
||||
impl UnencodedChange {
|
||||
pub fn max_op(&self) -> u64 {
|
||||
self.start_op + (self.operations.len() as u64) - 1
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Change {
|
||||
let mut buf = Vec::new();
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
let chunk = self.encode_chunk();
|
||||
|
||||
hasher.input(&chunk);
|
||||
|
||||
buf.extend(&MAGIC_BYTES);
|
||||
buf.extend(&hasher.result()[0..4]);
|
||||
buf.extend(&chunk);
|
||||
|
||||
// possible optimization here - i can assemble the metadata without having to parse
|
||||
// the generated object
|
||||
// ---
|
||||
// unwrap :: we generated this binchange so there's no chance of bad format
|
||||
// ---
|
||||
|
||||
Change::from_bytes(buf).unwrap()
|
||||
}
|
||||
|
||||
fn encode_chunk(&self) -> Vec<u8> {
|
||||
let mut chunk = vec![CHUNK_TYPE]; // chunk type is always 1
|
||||
// unwrap - io errors cant happen when writing to an in memory vec
|
||||
let data = self.encode_chunk_body().unwrap();
|
||||
leb128::write::unsigned(&mut chunk, data.len() as u64).unwrap();
|
||||
chunk.extend(&data);
|
||||
chunk
|
||||
}
|
||||
|
||||
fn encode_chunk_body(&self) -> io::Result<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
let mut actors = Vec::new();
|
||||
|
||||
actors.push(self.actor_id.clone());
|
||||
|
||||
self.actor_id.to_bytes().encode(&mut buf)?;
|
||||
self.seq.encode(&mut buf)?;
|
||||
self.start_op.encode(&mut buf)?;
|
||||
self.time.encode(&mut buf)?;
|
||||
self.message.encode(&mut buf)?;
|
||||
|
||||
let ops_buf = ColumnEncoder::encode_ops(&self.operations, &mut actors);
|
||||
|
||||
actors[1..].encode(&mut buf)?;
|
||||
|
||||
let mut deps = self.deps.clone();
|
||||
deps.sort_unstable();
|
||||
deps.len().encode(&mut buf)?;
|
||||
for hash in deps.iter() {
|
||||
buf.write_all(&hash.0)?;
|
||||
}
|
||||
|
||||
buf.write_all(&ops_buf)?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Change {
|
||||
pub bytes: Vec<u8>,
|
||||
pub hash: ChangeHash,
|
||||
pub seq: u64,
|
||||
pub start_op: u64,
|
||||
pub time: i64,
|
||||
body: Range<usize>,
|
||||
message: Range<usize>,
|
||||
actors: Vec<ActorID>,
|
||||
pub deps: Vec<ChangeHash>,
|
||||
ops: HashMap<u32, Range<usize>>,
|
||||
}
|
||||
|
||||
impl Change {
|
||||
pub fn actor_id(&self) -> ActorID {
|
||||
self.actors[0].clone()
|
||||
}
|
||||
|
||||
pub fn parse(bytes: &[u8]) -> Result<Vec<Change>, AutomergeError> {
|
||||
let mut changes = Vec::new();
|
||||
let mut cursor = &bytes[..];
|
||||
while !cursor.is_empty() {
|
||||
let (val, len) = read_leb128(&mut &cursor[HEADER_BYTES..])?;
|
||||
let (data, rest) = cursor.split_at(HEADER_BYTES + val + len);
|
||||
changes.push(Self::from_bytes(data.to_vec())?);
|
||||
cursor = rest;
|
||||
}
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Result<Change, AutomergeError> {
|
||||
if bytes.len() <= HEADER_BYTES {
|
||||
return Err(AutomergeError::EncodingError);
|
||||
}
|
||||
|
||||
if bytes[0..4] != MAGIC_BYTES {
|
||||
return Err(AutomergeError::EncodingError);
|
||||
}
|
||||
|
||||
let (val, len) = read_leb128(&mut &bytes[HEADER_BYTES..])?;
|
||||
let body = (HEADER_BYTES + len)..(HEADER_BYTES + len + val);
|
||||
if bytes.len() != body.end {
|
||||
return Err(AutomergeError::EncodingError);
|
||||
}
|
||||
|
||||
let chunktype = bytes[PREAMBLE_BYTES];
|
||||
|
||||
if chunktype == 0 {
|
||||
return Err(AutomergeError::EncodingError); // Format not implemented
|
||||
}
|
||||
|
||||
if chunktype > 1 {
|
||||
return Err(AutomergeError::EncodingError);
|
||||
}
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&bytes[PREAMBLE_BYTES..]);
|
||||
let hash = hasher.result()[..].try_into()?;
|
||||
|
||||
let mut cursor = body.clone();
|
||||
let actor = ActorID::from(&bytes[slice_bytes(&bytes, &mut cursor)?]);
|
||||
let seq = read_slice(&bytes, &mut cursor)?;
|
||||
let start_op = read_slice(&bytes, &mut cursor)?;
|
||||
let time = read_slice(&bytes, &mut cursor)?;
|
||||
let message = slice_bytes(&bytes, &mut cursor)?;
|
||||
let num_actors = read_slice(&bytes, &mut cursor)?;
|
||||
let mut actors = vec![actor];
|
||||
for _ in 0..num_actors {
|
||||
actors.push(ActorID::from(&bytes[slice_bytes(&bytes, &mut cursor)?]));
|
||||
}
|
||||
let mut deps = Vec::new();
|
||||
let num_deps = read_slice(&bytes, &mut cursor)?;
|
||||
for _ in 0..num_deps {
|
||||
let hash = cursor.start..(cursor.start + HASH_BYTES);
|
||||
cursor = hash.end..cursor.end;
|
||||
//let hash = slice_n_bytes(bytes, HASH_BYTES)?;
|
||||
deps.push(bytes[hash].try_into()?);
|
||||
}
|
||||
let mut ops = HashMap::new();
|
||||
let mut last_id = 0;
|
||||
while !bytes[cursor.clone()].is_empty() {
|
||||
let id = read_slice(&bytes, &mut cursor)?;
|
||||
if id < last_id {
|
||||
return Err(AutomergeError::EncodingError);
|
||||
}
|
||||
last_id = id;
|
||||
let column = slice_bytes(&bytes, &mut cursor)?;
|
||||
ops.insert(id, column);
|
||||
}
|
||||
|
||||
Ok(Change {
|
||||
bytes,
|
||||
hash,
|
||||
body,
|
||||
seq,
|
||||
start_op,
|
||||
time,
|
||||
actors,
|
||||
message,
|
||||
deps,
|
||||
ops,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn max_op(&self) -> u64 {
|
||||
// TODO - this could be a lot more efficient
|
||||
let len = self.iter_ops().count();
|
||||
self.start_op + (len as u64) - 1
|
||||
}
|
||||
|
||||
fn message(&self) -> Option<String> {
|
||||
let m = &self.bytes[self.message.clone()];
|
||||
if m.is_empty() {
|
||||
None
|
||||
} else {
|
||||
str::from_utf8(&m).map(|s| s.to_string()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(&self) -> UnencodedChange {
|
||||
UnencodedChange {
|
||||
start_op: self.start_op,
|
||||
seq: self.seq,
|
||||
time: self.time,
|
||||
message: self.message(),
|
||||
actor_id: self.actors[0].clone(),
|
||||
deps: self.deps.clone(),
|
||||
operations: self.iter_ops().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn col_iter<'a, T>(&'a self, col_id: u32) -> T
|
||||
where
|
||||
T: From<&'a [u8]>,
|
||||
{
|
||||
let empty = 0..0;
|
||||
let range = self.ops.get(&col_id).unwrap_or(&empty);
|
||||
let buf = &self.bytes[range.clone()];
|
||||
T::from(&buf)
|
||||
}
|
||||
|
||||
pub fn iter_ops(&self) -> OperationIterator {
|
||||
OperationIterator {
|
||||
objs: ObjIterator {
|
||||
actors: &self.actors,
|
||||
actor: self.col_iter(columnar::COL_OBJ_ACTOR),
|
||||
ctr: self.col_iter(columnar::COL_OBJ_CTR),
|
||||
},
|
||||
chld: ObjIterator {
|
||||
actors: &self.actors,
|
||||
actor: self.col_iter(columnar::COL_CHILD_ACTOR),
|
||||
ctr: self.col_iter(columnar::COL_CHILD_CTR),
|
||||
},
|
||||
keys: KeyIterator {
|
||||
actors: &self.actors,
|
||||
actor: self.col_iter(columnar::COL_KEY_ACTOR),
|
||||
ctr: self.col_iter(columnar::COL_KEY_CTR),
|
||||
str: self.col_iter(columnar::COL_KEY_STR),
|
||||
},
|
||||
value: ValueIterator {
|
||||
val_len: self.col_iter(columnar::COL_VAL_LEN),
|
||||
val_raw: self.col_iter(columnar::COL_VAL_RAW),
|
||||
},
|
||||
pred: PredIterator {
|
||||
actors: &self.actors,
|
||||
pred_num: self.col_iter(columnar::COL_PRED_NUM),
|
||||
pred_actor: self.col_iter(columnar::COL_PRED_ACTOR),
|
||||
pred_ctr: self.col_iter(columnar::COL_PRED_CTR),
|
||||
},
|
||||
insert: self.col_iter(columnar::COL_INSERT),
|
||||
action: self.col_iter(columnar::COL_ACTION),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&UnencodedChange> for Change {
|
||||
fn from(change: &UnencodedChange) -> Change {
|
||||
change.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnencodedChange> for Change {
|
||||
fn from(change: UnencodedChange) -> Change {
|
||||
change.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Change> for UnencodedChange {
|
||||
fn from(change: &Change) -> UnencodedChange {
|
||||
change.decode()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Change {
|
||||
type Error = AutomergeError;
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, AutomergeError> {
|
||||
Change::from_bytes(bytes.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_leb128(bytes: &mut &[u8]) -> Result<(usize, usize), AutomergeError> {
|
||||
let mut buf = &bytes[..];
|
||||
let val = leb128::read::unsigned(&mut buf)? as usize;
|
||||
let leb128_bytes = bytes.len() - buf.len();
|
||||
Ok((val, leb128_bytes))
|
||||
}
|
||||
|
||||
fn read_slice<T: Decodable + Debug>(
|
||||
bytes: &[u8],
|
||||
cursor: &mut Range<usize>,
|
||||
) -> Result<T, AutomergeError> {
|
||||
let view = &bytes[cursor.clone()];
|
||||
let mut reader = &view[..];
|
||||
let val = T::decode::<&[u8]>(&mut reader).ok_or(AutomergeError::EncodingError);
|
||||
let len = view.len() - reader.len();
|
||||
*cursor = (cursor.start + len)..cursor.end;
|
||||
val
|
||||
}
|
||||
|
||||
fn slice_bytes(bytes: &[u8], cursor: &mut Range<usize>) -> Result<Range<usize>, AutomergeError> {
|
||||
let (val, len) = read_leb128(&mut &bytes[cursor.clone()])?;
|
||||
let start = cursor.start + len;
|
||||
let end = start + val;
|
||||
*cursor = end..cursor.end;
|
||||
Ok(start..end)
|
||||
}
|
||||
|
||||
const MAGIC_BYTES: [u8; 4] = [0x85, 0x6f, 0x4a, 0x83];
|
||||
const PREAMBLE_BYTES: usize = 8;
|
||||
const HEADER_BYTES: usize = PREAMBLE_BYTES + 1;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::op_type::OpType;
|
||||
use automerge_protocol::{Key, ObjType, ObjectID, OpID, Value};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_empty_change() {
|
||||
let change1 = UnencodedChange {
|
||||
start_op: 1,
|
||||
seq: 2,
|
||||
time: 1234,
|
||||
message: None,
|
||||
actor_id: ActorID::from_str("deadbeefdeadbeef").unwrap(),
|
||||
deps: vec![],
|
||||
operations: vec![],
|
||||
};
|
||||
let bin1 = change1.encode();
|
||||
let change2 = bin1.decode();
|
||||
let bin2 = change2.encode();
|
||||
assert_eq!(bin1, bin2);
|
||||
assert_eq!(change1, change2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_change() -> Result<(), AutomergeError> {
|
||||
let actor1 = ActorID::from_str("deadbeefdeadbeef").unwrap();
|
||||
let actor2 = ActorID::from_str("feeddefaff").unwrap();
|
||||
let actor3 = ActorID::from_str("00101010fafafafa").unwrap();
|
||||
let opid1 = OpID::new(102, &actor1);
|
||||
let opid2 = OpID::new(391, &actor1);
|
||||
let opid3 = OpID::new(299, &actor2);
|
||||
let opid4 = OpID::new(762, &actor3);
|
||||
let opid5 = OpID::new(100_203, &actor2);
|
||||
let obj1 = ObjectID::ID(opid1.clone());
|
||||
let obj2 = ObjectID::Root;
|
||||
let obj3 = ObjectID::ID(opid4.clone());
|
||||
let key1 = Key::Map("field1".into());
|
||||
let key2 = Key::Map("field2".into());
|
||||
let key3 = Key::Map("field3".into());
|
||||
let head = Key::head();
|
||||
let keyseq1 = Key::from(&opid1);
|
||||
let keyseq2 = Key::from(&opid2);
|
||||
let insert = false;
|
||||
let change1 = UnencodedChange {
|
||||
start_op: 123,
|
||||
seq: 29291,
|
||||
time: 12_341_231,
|
||||
message: Some("This is my message".into()),
|
||||
actor_id: actor1,
|
||||
deps: vec![],
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Set(Value::F64(10.0)),
|
||||
key: key1.clone(),
|
||||
obj: obj1.clone(),
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid2.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Counter(-11)),
|
||||
key: key2.clone(),
|
||||
obj: obj1.clone(),
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid2.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Timestamp(20)),
|
||||
key: key3,
|
||||
obj: obj1.clone(),
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid2],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("some value".into())),
|
||||
key: key2.clone(),
|
||||
obj: obj2.clone(),
|
||||
insert,
|
||||
pred: vec![opid3.clone(), opid4.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
key: key2.clone(),
|
||||
obj: obj2.clone(),
|
||||
insert,
|
||||
pred: vec![opid3.clone(), opid4.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("val1".into())),
|
||||
key: head.clone(),
|
||||
obj: obj3.clone(),
|
||||
insert: true,
|
||||
pred: vec![opid3.clone(), opid4.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("val2".into())),
|
||||
key: head,
|
||||
obj: obj3.clone(),
|
||||
insert: true,
|
||||
pred: vec![opid4.clone(), opid5.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Inc(10),
|
||||
key: key2,
|
||||
obj: obj2,
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid5.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Link(obj3.clone()),
|
||||
obj: obj1,
|
||||
key: key1,
|
||||
insert,
|
||||
pred: vec![opid1, opid3],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
obj: obj3.clone(),
|
||||
key: keyseq1,
|
||||
insert: true,
|
||||
pred: vec![opid4.clone(), opid5.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
obj: obj3,
|
||||
key: keyseq2,
|
||||
insert: true,
|
||||
pred: vec![opid4, opid5],
|
||||
},
|
||||
],
|
||||
};
|
||||
let bin1 = change1.encode();
|
||||
let change2 = bin1.decode();
|
||||
let bin2 = change2.encode();
|
||||
assert_eq!(bin1, bin2);
|
||||
assert_eq!(change1, change2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,139 +1,13 @@
|
|||
use crate::encoding::{BooleanDecoder, Decodable, Decoder, DeltaDecoder, RLEDecoder};
|
||||
use crate::encoding::{BooleanEncoder, ColData, DeltaEncoder, Encodable, RLEEncoder};
|
||||
use crate::error::AutomergeError;
|
||||
use automerge_protocol::{
|
||||
ActorID, Change, ChangeHash, ElementID, Key, ObjType, ObjectID, OpID, OpType, Operation, Value,
|
||||
};
|
||||
use crate::op::Operation;
|
||||
use crate::op_type::OpType;
|
||||
use automerge_protocol::{ActorID, ElementID, Key, ObjType, ObjectID, OpID, Value};
|
||||
use core::fmt::Debug;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
||||
const HASH_BYTES: usize = 32;
|
||||
|
||||
pub(crate) fn bin_to_changes(bindata: &[u8]) -> Result<Vec<Change>, AutomergeError> {
|
||||
BinaryContainer::from(&bindata)?
|
||||
.iter()
|
||||
.map(|bin| bin.to_change())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// FIXME - really I need a bin change constructor
|
||||
pub(crate) fn change_to_change(change: Change) -> Result<Rc<Change>, AutomergeError> {
|
||||
let bin_change = changes_to_bin(&[&change])?;
|
||||
let mut changes = bin_to_changes(&bin_change[0])?;
|
||||
Ok(Rc::new(changes.remove(0)))
|
||||
}
|
||||
|
||||
pub(crate) fn changes_to_bin(changes: &[&Change]) -> Result<Vec<Vec<u8>>, AutomergeError> {
|
||||
let mut bins = Vec::new();
|
||||
for c in changes {
|
||||
let bin = change_to_bin(c)?;
|
||||
bins.push(bin)
|
||||
}
|
||||
Ok(bins)
|
||||
}
|
||||
|
||||
pub fn change_hash(change: Change) -> Result<ChangeHash, AutomergeError> {
|
||||
change_to_change(change).map(|c| c.hash)
|
||||
}
|
||||
|
||||
pub(crate) fn changes_to_one_bin(changes: &[&Change]) -> Result<Vec<u8>, AutomergeError> {
|
||||
let mut data = Vec::new();
|
||||
for c in changes {
|
||||
let bin = change_to_bin(c)?;
|
||||
data.extend(bin)
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn change_to_bin(change: &Change) -> Result<Vec<u8>, AutomergeError> {
|
||||
let mut buf = Vec::new();
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
let chunk = encode_chunk(&change)?;
|
||||
|
||||
hasher.input(&chunk);
|
||||
|
||||
buf.extend(&MAGIC_BYTES);
|
||||
buf.extend(&hasher.result()[0..4]);
|
||||
buf.extend(&chunk);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn encode_chunk(change: &Change) -> Result<Vec<u8>, AutomergeError> {
|
||||
let mut chunk = vec![CHUNK_TYPE]; // chunk type is always 1
|
||||
let data = encode(change)?;
|
||||
leb128::write::unsigned(&mut chunk, data.len() as u64)?;
|
||||
chunk.extend(&data);
|
||||
Ok(chunk)
|
||||
}
|
||||
|
||||
fn encode<V: Encodable>(val: &V) -> Result<Vec<u8>, AutomergeError> {
|
||||
let mut actor_ids = Vec::new();
|
||||
Ok(val.encode_with_actors_to_vec(&mut actor_ids)?)
|
||||
}
|
||||
|
||||
impl Encodable for Change {
|
||||
fn encode_with_actors<R: Write>(
|
||||
&self,
|
||||
buf: &mut R,
|
||||
actors: &mut Vec<ActorID>,
|
||||
) -> io::Result<usize> {
|
||||
let mut len = 0;
|
||||
|
||||
actors.push(self.actor_id.clone());
|
||||
|
||||
len += self.actor_id.to_bytes().encode(buf)?;
|
||||
len += self.seq.encode(buf)?;
|
||||
len += self.start_op.encode(buf)?;
|
||||
len += self.time.encode(buf)?;
|
||||
len += self.message.encode(buf)?;
|
||||
|
||||
// let deps_buf = self.deps.encode_with_actors_to_vec(actors)?;
|
||||
|
||||
let ops_buf = ColumnEncoder::encode_ops(&self.operations, actors);
|
||||
|
||||
len += actors[1..].encode(buf)?;
|
||||
|
||||
let mut deps = self.deps.clone();
|
||||
deps.sort_unstable();
|
||||
len += deps.len().encode(buf)?;
|
||||
for hash in deps.iter() {
|
||||
len += buf.write(&hash.0)?;
|
||||
}
|
||||
|
||||
buf.write_all(&ops_buf)?;
|
||||
len += ops_buf.len();
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl Encodable for Clock {
|
||||
fn encode_with_actors<R: Write>(
|
||||
&self,
|
||||
buf: &mut R,
|
||||
actors: &mut Vec<ActorID>,
|
||||
) -> io::Result<usize> {
|
||||
let mut len = 0;
|
||||
self.0.len().encode(buf)?;
|
||||
let mut keys: Vec<&ActorID> = self.0.keys().collect();
|
||||
keys.sort_unstable();
|
||||
for actor in keys.iter() {
|
||||
let val = self.get(actor);
|
||||
len += actor.encode_with_actors(buf, actors)?;
|
||||
len += val.encode(buf)?;
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
*/
|
||||
use std::str::FromStr;
|
||||
|
||||
impl Encodable for Action {
|
||||
fn encode<R: Write>(&self, buf: &mut R) -> io::Result<usize> {
|
||||
|
@ -152,7 +26,8 @@ impl Encodable for [ActorID] {
|
|||
}
|
||||
|
||||
fn map_string(actor: &str, actors: &mut Vec<ActorID>) -> usize {
|
||||
let a = ActorID(actor.to_string());
|
||||
// FIXME - this can be fixed if OpID() contains an actorid instead of a String
|
||||
let a = ActorID::from_str(actor).unwrap(); // this is only called on interal values
|
||||
map_actor(&a, actors)
|
||||
}
|
||||
|
||||
|
@ -189,213 +64,40 @@ impl Encodable for &[u8] {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_slice<T: Decodable + Debug>(buf: &mut &[u8]) -> Result<T, AutomergeError> {
|
||||
T::decode::<&[u8]>(buf).ok_or(AutomergeError::ChangeBadFormat)
|
||||
pub struct OperationIterator<'a> {
|
||||
pub(crate) action: RLEDecoder<'a, Action>,
|
||||
pub(crate) objs: ObjIterator<'a>,
|
||||
pub(crate) chld: ObjIterator<'a>,
|
||||
pub(crate) keys: KeyIterator<'a>,
|
||||
pub(crate) insert: BooleanDecoder<'a>,
|
||||
pub(crate) value: ValueIterator<'a>,
|
||||
pub(crate) pred: PredIterator<'a>,
|
||||
}
|
||||
|
||||
fn slice_bytes<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8], AutomergeError> {
|
||||
let mut buf = &bytes[..];
|
||||
let len = leb128::read::unsigned(&mut buf)? as usize;
|
||||
let result = &buf[0..len];
|
||||
*bytes = &buf[len..];
|
||||
Ok(result)
|
||||
pub struct ObjIterator<'a> {
|
||||
//actors: &'a Vec<&'a [u8]>,
|
||||
pub(crate) actors: &'a Vec<ActorID>,
|
||||
pub(crate) actor: RLEDecoder<'a, usize>,
|
||||
pub(crate) ctr: RLEDecoder<'a, u64>,
|
||||
}
|
||||
|
||||
fn slice_n_bytes<'a>(bytes: &mut &'a [u8], n: usize) -> Result<&'a [u8], AutomergeError> {
|
||||
let result = &bytes[0..n];
|
||||
*bytes = &bytes[n..];
|
||||
Ok(result)
|
||||
pub struct PredIterator<'a> {
|
||||
pub(crate) actors: &'a Vec<ActorID>,
|
||||
pub(crate) pred_num: RLEDecoder<'a, usize>,
|
||||
pub(crate) pred_actor: RLEDecoder<'a, usize>,
|
||||
pub(crate) pred_ctr: DeltaDecoder<'a>,
|
||||
}
|
||||
|
||||
fn slice_bytes_len(bytes: &[u8]) -> Result<(&[u8], usize), AutomergeError> {
|
||||
let mut view = &bytes[..];
|
||||
let len = leb128::read::unsigned(&mut view)? as usize;
|
||||
let len_bytes = bytes.len() - view.len();
|
||||
Ok((&view[0..len], len + len_bytes))
|
||||
pub struct KeyIterator<'a> {
|
||||
pub(crate) actors: &'a Vec<ActorID>,
|
||||
pub(crate) actor: RLEDecoder<'a, usize>,
|
||||
pub(crate) ctr: DeltaDecoder<'a>,
|
||||
pub(crate) str: RLEDecoder<'a, String>,
|
||||
}
|
||||
|
||||
impl From<leb128::read::Error> for AutomergeError {
|
||||
fn from(_err: leb128::read::Error) -> Self {
|
||||
AutomergeError::ChangeBadFormat
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AutomergeError {
|
||||
fn from(_err: std::io::Error) -> Self {
|
||||
AutomergeError::EncodeFailed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct BinaryContainer<'a> {
|
||||
magic: &'a [u8],
|
||||
checksum: &'a [u8],
|
||||
hash: ChangeHash,
|
||||
body: &'a [u8],
|
||||
chunktype: u8,
|
||||
chunk: BinaryChange<'a>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct BinaryChange<'a> {
|
||||
all: &'a [u8],
|
||||
seq: u64,
|
||||
start_op: u64,
|
||||
time: i64,
|
||||
message: &'a [u8],
|
||||
actors: Vec<&'a [u8]>,
|
||||
deps: Vec<&'a [u8]>,
|
||||
ops: HashMap<u32, &'a [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> BinaryChange<'a> {
|
||||
fn from(bytes: &'a [u8]) -> Result<BinaryChange<'a>, AutomergeError> {
|
||||
let bytes = &mut &bytes[..];
|
||||
let all = &bytes[0..];
|
||||
let actor = slice_bytes(bytes)?;
|
||||
let seq = read_slice(bytes)?;
|
||||
let start_op = read_slice(bytes)?;
|
||||
let time = read_slice(bytes)?;
|
||||
let message = slice_bytes(bytes)?;
|
||||
let num_actors = read_slice(bytes)?;
|
||||
let mut actors = vec![&actor[..]];
|
||||
for _ in 0..num_actors {
|
||||
let actor = slice_bytes(bytes)?;
|
||||
actors.push(actor);
|
||||
}
|
||||
let mut deps = Vec::new();
|
||||
let num_deps = read_slice(bytes)?;
|
||||
for _ in 0..num_deps {
|
||||
let hash = slice_n_bytes(bytes, HASH_BYTES)?;
|
||||
deps.push(hash);
|
||||
}
|
||||
let mut ops = HashMap::new();
|
||||
let mut last_id = 0;
|
||||
while !bytes.is_empty() {
|
||||
let id = read_slice(bytes)?;
|
||||
if id < last_id {
|
||||
return Err(AutomergeError::ChangeBadFormat);
|
||||
}
|
||||
last_id = id;
|
||||
let column = slice_bytes(bytes)?;
|
||||
ops.insert(id, column);
|
||||
}
|
||||
Ok(BinaryChange {
|
||||
all,
|
||||
seq,
|
||||
start_op,
|
||||
time,
|
||||
actors,
|
||||
message,
|
||||
deps,
|
||||
ops,
|
||||
})
|
||||
}
|
||||
|
||||
fn gen_deps(&self) -> Vec<ChangeHash> {
|
||||
// TODO Add error propagation
|
||||
self.deps.iter().map(|&v| v.try_into().unwrap()).collect()
|
||||
}
|
||||
|
||||
fn message(&self) -> Option<String> {
|
||||
if self.message.is_empty() {
|
||||
None
|
||||
} else {
|
||||
str::from_utf8(&self.message).map(|s| s.to_string()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn to_change(&self, hash: ChangeHash) -> Result<Change, AutomergeError> {
|
||||
let change = Change {
|
||||
start_op: self.start_op,
|
||||
seq: self.seq,
|
||||
hash,
|
||||
time: self.time,
|
||||
message: self.message(),
|
||||
actor_id: ActorID::from_bytes(self.actors[0]),
|
||||
deps: self.gen_deps(),
|
||||
operations: self.iter_ops().collect(),
|
||||
};
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
fn col_iter<T>(&self, col_id: u32) -> T
|
||||
where
|
||||
T: From<&'a [u8]>,
|
||||
{
|
||||
let empty = &self.all[0..0];
|
||||
let buf = self.ops.get(&col_id).unwrap_or(&empty);
|
||||
T::from(&buf)
|
||||
}
|
||||
|
||||
fn iter_ops(&self) -> OperationIterator {
|
||||
OperationIterator {
|
||||
objs: ObjIterator {
|
||||
actors: &self.actors,
|
||||
actor: self.col_iter(COL_OBJ_ACTOR),
|
||||
ctr: self.col_iter(COL_OBJ_CTR),
|
||||
},
|
||||
chld: ObjIterator {
|
||||
actors: &self.actors,
|
||||
actor: self.col_iter(COL_CHILD_ACTOR),
|
||||
ctr: self.col_iter(COL_CHILD_CTR),
|
||||
},
|
||||
keys: KeyIterator {
|
||||
actors: &self.actors,
|
||||
actor: self.col_iter(COL_KEY_ACTOR),
|
||||
ctr: self.col_iter(COL_KEY_CTR),
|
||||
str: self.col_iter(COL_KEY_STR),
|
||||
},
|
||||
value: ValueIterator {
|
||||
val_len: self.col_iter(COL_VAL_LEN),
|
||||
val_raw: self.col_iter(COL_VAL_RAW),
|
||||
},
|
||||
pred: PredIterator {
|
||||
actors: &self.actors,
|
||||
pred_num: self.col_iter(COL_PRED_NUM),
|
||||
pred_actor: self.col_iter(COL_PRED_ACTOR),
|
||||
pred_ctr: self.col_iter(COL_PRED_CTR),
|
||||
},
|
||||
insert: self.col_iter(COL_INSERT),
|
||||
action: self.col_iter(COL_ACTION),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OperationIterator<'a> {
|
||||
action: RLEDecoder<'a, Action>,
|
||||
objs: ObjIterator<'a>,
|
||||
chld: ObjIterator<'a>,
|
||||
keys: KeyIterator<'a>,
|
||||
insert: BooleanDecoder<'a>,
|
||||
value: ValueIterator<'a>,
|
||||
pred: PredIterator<'a>,
|
||||
}
|
||||
|
||||
struct ObjIterator<'a> {
|
||||
actors: &'a Vec<&'a [u8]>,
|
||||
actor: RLEDecoder<'a, usize>,
|
||||
ctr: RLEDecoder<'a, u64>,
|
||||
}
|
||||
|
||||
struct PredIterator<'a> {
|
||||
actors: &'a Vec<&'a [u8]>,
|
||||
pred_num: RLEDecoder<'a, usize>,
|
||||
pred_actor: RLEDecoder<'a, usize>,
|
||||
pred_ctr: DeltaDecoder<'a>,
|
||||
}
|
||||
|
||||
struct KeyIterator<'a> {
|
||||
actors: &'a Vec<&'a [u8]>,
|
||||
actor: RLEDecoder<'a, usize>,
|
||||
ctr: DeltaDecoder<'a>,
|
||||
str: RLEDecoder<'a, String>,
|
||||
}
|
||||
|
||||
struct ValueIterator<'a> {
|
||||
val_len: RLEDecoder<'a, usize>,
|
||||
val_raw: Decoder<'a>,
|
||||
pub struct ValueIterator<'a> {
|
||||
pub(crate) val_len: RLEDecoder<'a, usize>,
|
||||
pub(crate) val_raw: Decoder<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PredIterator<'a> {
|
||||
|
@ -406,8 +108,7 @@ impl<'a> Iterator for PredIterator<'a> {
|
|||
for _ in 0..num {
|
||||
let actor = self.pred_actor.next()??;
|
||||
let ctr = self.pred_ctr.next()??;
|
||||
let actor_bytes = self.actors.get(actor)?;
|
||||
let actor_id = ActorID::from_bytes(actor_bytes);
|
||||
let actor_id = self.actors.get(actor)?.clone();
|
||||
let op_id = OpID::new(ctr, &actor_id);
|
||||
p.push(op_id)
|
||||
}
|
||||
|
@ -503,9 +204,8 @@ impl<'a> Iterator for KeyIterator<'a> {
|
|||
(None, None, Some(string)) => Some(Key::Map(string)),
|
||||
(Some(0), Some(0), None) => Some(Key::head()),
|
||||
(Some(actor), Some(ctr), None) => {
|
||||
let actor_bytes = self.actors.get(actor)?;
|
||||
let actor_id = ActorID::from_bytes(actor_bytes);
|
||||
Some(OpID(ctr, actor_id.0).into())
|
||||
let actor_id = self.actors.get(actor)?;
|
||||
Some(OpID::new(ctr, actor_id).into())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
@ -516,8 +216,8 @@ impl<'a> Iterator for ObjIterator<'a> {
|
|||
type Item = ObjectID;
|
||||
fn next(&mut self) -> Option<ObjectID> {
|
||||
if let (Some(actor), Some(ctr)) = (self.actor.next()?, self.ctr.next()?) {
|
||||
let actor_id = ActorID::from_bytes(self.actors.get(actor)?);
|
||||
Some(ObjectID::ID(OpID(ctr, actor_id.0)))
|
||||
let actor_id = self.actors.get(actor)?;
|
||||
Some(ObjectID::ID(OpID::new(ctr, &actor_id)))
|
||||
} else {
|
||||
Some(ObjectID::Root)
|
||||
}
|
||||
|
@ -554,60 +254,6 @@ impl<'a> Iterator for OperationIterator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> BinaryContainer<'a> {
|
||||
fn from(mut bytes: &'a [u8]) -> Result<Vec<BinaryContainer<'a>>, AutomergeError> {
|
||||
let mut changes = Vec::new();
|
||||
while !bytes.is_empty() {
|
||||
let change = Self::parse_single(bytes)?;
|
||||
bytes = &bytes[change.len..];
|
||||
changes.push(change);
|
||||
}
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
fn parse_single(bytes: &'a [u8]) -> Result<BinaryContainer<'a>, AutomergeError> {
|
||||
if bytes.len() < 8 {
|
||||
return Err(AutomergeError::ChangeBadFormat);
|
||||
}
|
||||
let (header, rest) = &bytes.split_at(8);
|
||||
let (magic, checksum) = &header.split_at(4);
|
||||
if magic != &MAGIC_BYTES {
|
||||
return Err(AutomergeError::ChangeBadFormat);
|
||||
}
|
||||
let (chunk_data, chunk_len) = slice_bytes_len(&rest[1..])?;
|
||||
let body = &rest[0..(chunk_len + 1)]; // +1 for chunktype
|
||||
let chunktype = body[0];
|
||||
let len = body.len() + header.len();
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&body);
|
||||
let hash = hasher.result()[..]
|
||||
.try_into()
|
||||
.map_err(|_| AutomergeError::DecodeFailed)?;
|
||||
|
||||
Ok(BinaryContainer {
|
||||
magic,
|
||||
checksum,
|
||||
hash,
|
||||
chunktype,
|
||||
body,
|
||||
chunk: BinaryChange::from(chunk_data)?,
|
||||
len,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
&self.hash.0[0..4] == self.checksum
|
||||
}
|
||||
|
||||
fn to_change(&self) -> Result<Change, AutomergeError> {
|
||||
if !self.is_valid() {
|
||||
return Err(AutomergeError::InvalidChange);
|
||||
}
|
||||
self.chunk.to_change(self.hash)
|
||||
}
|
||||
}
|
||||
|
||||
struct ValEncoder {
|
||||
len: RLEEncoder<usize>,
|
||||
raw: Vec<u8>,
|
||||
|
@ -833,7 +479,7 @@ impl ChildEncoder {
|
|||
}
|
||||
}
|
||||
|
||||
struct ColumnEncoder {
|
||||
pub(crate) struct ColumnEncoder {
|
||||
obj: ObjEncoder,
|
||||
key: KeyEncoder,
|
||||
insert: BooleanEncoder,
|
||||
|
@ -844,7 +490,7 @@ struct ColumnEncoder {
|
|||
}
|
||||
|
||||
impl ColumnEncoder {
|
||||
fn encode_ops(ops: &[Operation], actors: &mut Vec<ActorID>) -> Vec<u8> {
|
||||
pub fn encode_ops(ops: &[Operation], actors: &mut Vec<ActorID>) -> Vec<u8> {
|
||||
let mut e = Self::new();
|
||||
e.encode(ops, actors);
|
||||
e.finish()
|
||||
|
@ -927,8 +573,6 @@ impl ColumnEncoder {
|
|||
}
|
||||
}
|
||||
|
||||
const CHUNK_TYPE: u8 = 1;
|
||||
|
||||
const VALUE_TYPE_NULL: usize = 0;
|
||||
const VALUE_TYPE_FALSE: usize = 1;
|
||||
const VALUE_TYPE_TRUE: usize = 2;
|
||||
|
@ -942,18 +586,18 @@ const VALUE_TYPE_TIMESTAMP: usize = 9;
|
|||
const VALUE_TYPE_MIN_UNKNOWN: usize = 10;
|
||||
const VALUE_TYPE_MAX_UNKNOWN: usize = 15;
|
||||
|
||||
const COLUMN_TYPE_GROUP_CARD: u32 = 0;
|
||||
const COLUMN_TYPE_ACTOR_ID: u32 = 1;
|
||||
const COLUMN_TYPE_INT_RLE: u32 = 2;
|
||||
const COLUMN_TYPE_INT_DELTA: u32 = 3;
|
||||
const COLUMN_TYPE_BOOLEAN: u32 = 4;
|
||||
const COLUMN_TYPE_STRING_RLE: u32 = 5;
|
||||
const COLUMN_TYPE_VALUE_LEN: u32 = 6;
|
||||
const COLUMN_TYPE_VALUE_RAW: u32 = 7;
|
||||
pub(crate) const COLUMN_TYPE_GROUP_CARD: u32 = 0;
|
||||
pub(crate) const COLUMN_TYPE_ACTOR_ID: u32 = 1;
|
||||
pub(crate) const COLUMN_TYPE_INT_RLE: u32 = 2;
|
||||
pub(crate) const COLUMN_TYPE_INT_DELTA: u32 = 3;
|
||||
pub(crate) const COLUMN_TYPE_BOOLEAN: u32 = 4;
|
||||
pub(crate) const COLUMN_TYPE_STRING_RLE: u32 = 5;
|
||||
pub(crate) const COLUMN_TYPE_VALUE_LEN: u32 = 6;
|
||||
pub(crate) const COLUMN_TYPE_VALUE_RAW: u32 = 7;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
#[repr(u32)]
|
||||
enum Action {
|
||||
pub(crate) enum Action {
|
||||
Set,
|
||||
Del,
|
||||
Inc,
|
||||
|
@ -984,167 +628,22 @@ impl Decodable for Action {
|
|||
}
|
||||
}
|
||||
|
||||
const COL_OBJ_ACTOR: u32 = COLUMN_TYPE_ACTOR_ID;
|
||||
const COL_OBJ_CTR: u32 = COLUMN_TYPE_INT_RLE;
|
||||
const COL_KEY_ACTOR: u32 = 1 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
const COL_KEY_CTR: u32 = 1 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
const COL_KEY_STR: u32 = 1 << 3 | COLUMN_TYPE_STRING_RLE;
|
||||
//const COL_ID_ACTOR : u32 = 2 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
//const COL_ID_CTR : u32 = 2 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
const COL_INSERT: u32 = 3 << 3 | COLUMN_TYPE_BOOLEAN;
|
||||
const COL_ACTION: u32 = 4 << 3 | COLUMN_TYPE_INT_RLE;
|
||||
const COL_VAL_LEN: u32 = 5 << 3 | COLUMN_TYPE_VALUE_LEN;
|
||||
const COL_VAL_RAW: u32 = 5 << 3 | COLUMN_TYPE_VALUE_RAW;
|
||||
const COL_CHILD_ACTOR: u32 = 6 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
const COL_CHILD_CTR: u32 = 6 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
const COL_PRED_NUM: u32 = 7 << 3 | COLUMN_TYPE_GROUP_CARD;
|
||||
const COL_PRED_ACTOR: u32 = 7 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
const COL_PRED_CTR: u32 = 7 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
//const COL_SUCC_NUM : u32 = 8 << 3 | COLUMN_TYPE_GROUP_CARD;
|
||||
//const COL_SUCC_ACTOR : u32 = 8 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
//const COL_SUCC_CTR : u32 = 8 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
|
||||
const MAGIC_BYTES: [u8; 4] = [0x85, 0x6f, 0x4a, 0x83];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
//use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_empty_change() {
|
||||
let mut change1 = Change {
|
||||
start_op: 1,
|
||||
seq: 2,
|
||||
time: 1234,
|
||||
message: None,
|
||||
actor_id: ActorID("deadbeefdeadbeef".into()),
|
||||
deps: vec![],
|
||||
operations: vec![],
|
||||
hash: ChangeHash::zero(),
|
||||
};
|
||||
let bin1 = change_to_bin(&change1).unwrap();
|
||||
let changes2 = bin_to_changes(&bin1).unwrap();
|
||||
let bin2 = change_to_bin(&changes2[0]).unwrap();
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
assert_eq!(bin1, bin2);
|
||||
assert_eq!(vec![change1], changes2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_change() -> Result<(), AutomergeError> {
|
||||
let actor1 = ActorID("deadbeefdeadbeef".into());
|
||||
let actor2 = ActorID("feeddefaff".into());
|
||||
let actor3 = ActorID("00101010fafafafa".into());
|
||||
let opid1 = OpID(102, actor1.0.clone());
|
||||
let opid2 = OpID(391, actor1.0.clone());
|
||||
let opid3 = OpID(299, actor2.0.clone());
|
||||
let opid4 = OpID(762, actor3.0);
|
||||
let opid5 = OpID(100_203, actor2.0);
|
||||
let obj1 = ObjectID::ID(opid1.clone());
|
||||
let obj2 = ObjectID::Root;
|
||||
let obj3 = ObjectID::ID(opid4.clone());
|
||||
let key1 = Key::Map("field1".into());
|
||||
let key2 = Key::Map("field2".into());
|
||||
let key3 = Key::Map("field3".into());
|
||||
let head = Key::head();
|
||||
let keyseq1 = Key::from(&opid1);
|
||||
let keyseq2 = Key::from(&opid2);
|
||||
let insert = false;
|
||||
let mut change1 = Change {
|
||||
hash: ChangeHash::zero(),
|
||||
start_op: 123,
|
||||
seq: 29291,
|
||||
time: 12_341_231,
|
||||
message: Some("This is my message".into()),
|
||||
actor_id: actor1,
|
||||
deps: vec![],
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Set(Value::F64(10.0)),
|
||||
key: key1.clone(),
|
||||
obj: obj1.clone(),
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid2.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Counter(-11)),
|
||||
key: key2.clone(),
|
||||
obj: obj1.clone(),
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid2.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Timestamp(20)),
|
||||
key: key3,
|
||||
obj: obj1.clone(),
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid2],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("some value".into())),
|
||||
key: key2.clone(),
|
||||
obj: obj2.clone(),
|
||||
insert,
|
||||
pred: vec![opid3.clone(), opid4.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
key: key2.clone(),
|
||||
obj: obj2.clone(),
|
||||
insert,
|
||||
pred: vec![opid3.clone(), opid4.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("val1".into())),
|
||||
key: head.clone(),
|
||||
obj: obj3.clone(),
|
||||
insert: true,
|
||||
pred: vec![opid3.clone(), opid4.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("val2".into())),
|
||||
key: head,
|
||||
obj: obj3.clone(),
|
||||
insert: true,
|
||||
pred: vec![opid4.clone(), opid5.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Inc(10),
|
||||
key: key2,
|
||||
obj: obj2,
|
||||
insert,
|
||||
pred: vec![opid1.clone(), opid5.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Link(obj3.clone()),
|
||||
obj: obj1,
|
||||
key: key1,
|
||||
insert,
|
||||
pred: vec![opid1, opid3],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
obj: obj3.clone(),
|
||||
key: keyseq1,
|
||||
insert: true,
|
||||
pred: vec![opid4.clone(), opid5.clone()],
|
||||
},
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
obj: obj3,
|
||||
key: keyseq2,
|
||||
insert: true,
|
||||
pred: vec![opid4, opid5],
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
let bin1 = change_to_bin(&change1)?;
|
||||
let changes2 = bin_to_changes(&bin1)?;
|
||||
let bin2 = change_to_bin(&changes2[0])?;
|
||||
assert_eq!(bin1, bin2);
|
||||
assert_eq!(vec![change1], changes2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub(crate) const COL_OBJ_ACTOR: u32 = COLUMN_TYPE_ACTOR_ID;
|
||||
pub(crate) const COL_OBJ_CTR: u32 = COLUMN_TYPE_INT_RLE;
|
||||
pub(crate) const COL_KEY_ACTOR: u32 = 1 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
pub(crate) const COL_KEY_CTR: u32 = 1 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
pub(crate) const COL_KEY_STR: u32 = 1 << 3 | COLUMN_TYPE_STRING_RLE;
|
||||
//pub(crate) const COL_ID_ACTOR : u32 = 2 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
//pub(crate) const COL_ID_CTR : u32 = 2 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
pub(crate) const COL_INSERT: u32 = 3 << 3 | COLUMN_TYPE_BOOLEAN;
|
||||
pub(crate) const COL_ACTION: u32 = 4 << 3 | COLUMN_TYPE_INT_RLE;
|
||||
pub(crate) const COL_VAL_LEN: u32 = 5 << 3 | COLUMN_TYPE_VALUE_LEN;
|
||||
pub(crate) const COL_VAL_RAW: u32 = 5 << 3 | COLUMN_TYPE_VALUE_RAW;
|
||||
pub(crate) const COL_CHILD_ACTOR: u32 = 6 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
pub(crate) const COL_CHILD_CTR: u32 = 6 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
pub(crate) const COL_PRED_NUM: u32 = 7 << 3 | COLUMN_TYPE_GROUP_CARD;
|
||||
pub(crate) const COL_PRED_ACTOR: u32 = 7 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
pub(crate) const COL_PRED_CTR: u32 = 7 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
//pub(crate) const COL_SUCC_NUM : u32 = 8 << 3 | COLUMN_TYPE_GROUP_CARD;
|
||||
//pub(crate) const COL_SUCC_ACTOR : u32 = 8 << 3 | COLUMN_TYPE_ACTOR_ID;
|
||||
//pub(crate) const COL_SUCC_CTR : u32 = 8 << 3 | COLUMN_TYPE_INT_DELTA;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::error::AutomergeError;
|
||||
use crate::op_handle::OpHandle;
|
||||
use automerge_protocol::OpType;
|
||||
use crate::op_type::OpType;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Represents a set of operations which are relevant to either an element ID
|
||||
|
|
|
@ -7,8 +7,8 @@ use std::io::{Read, Write};
|
|||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
fn err(s: &str) -> AutomergeError {
|
||||
AutomergeError::ChangeDecompressError(s.to_string())
|
||||
fn err(_s: &str) -> AutomergeError {
|
||||
AutomergeError::EncodingError
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -29,7 +29,7 @@ impl<'a> Decoder<'a> {
|
|||
|
||||
pub fn read<T: Decodable + Debug>(&mut self) -> Result<T, AutomergeError> {
|
||||
let mut new_buf = &self.buf[..];
|
||||
let val = T::decode::<&[u8]>(&mut new_buf).ok_or(AutomergeError::DecodeFailed)?;
|
||||
let val = T::decode::<&[u8]>(&mut new_buf).ok_or(AutomergeError::EncodingError)?;
|
||||
let delta = self.buf.len() - new_buf.len();
|
||||
if delta == 0 {
|
||||
Err(err("buffer size didnt change..."))
|
||||
|
@ -44,7 +44,7 @@ impl<'a> Decoder<'a> {
|
|||
pub fn read_bytes(&mut self, index: usize) -> Result<&'a [u8], AutomergeError> {
|
||||
let buf = &self.buf[..];
|
||||
if buf.len() < index {
|
||||
Err(AutomergeError::DecodeFailed)
|
||||
Err(AutomergeError::EncodingError)
|
||||
} else {
|
||||
let head = &buf[0..index];
|
||||
self.buf = &buf[index..];
|
||||
|
@ -526,7 +526,7 @@ impl Decodable for ActorID {
|
|||
R: Read,
|
||||
{
|
||||
let buffer = Vec::decode(bytes)?;
|
||||
Some(ActorID::from_bytes(&buffer))
|
||||
Some(buffer.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use automerge_protocol::{ObjectID, OpID, OpRequest};
|
||||
use automerge_protocol::{ObjectID, Op, OpID};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -19,18 +19,26 @@ pub enum AutomergeError {
|
|||
NoUndo,
|
||||
MissingValue,
|
||||
GeneralError(String),
|
||||
MissingNumberValue(OpRequest),
|
||||
MissingNumberValue(Op),
|
||||
UnknownVersion(u64),
|
||||
DuplicateChange(String),
|
||||
DivergedState(String),
|
||||
ChangeDecompressError(String),
|
||||
MapKeyInSeq,
|
||||
HeadToOpID,
|
||||
DocFormatUnimplemented,
|
||||
DivergentChange(String),
|
||||
EncodeFailed,
|
||||
DecodeFailed,
|
||||
InvalidChange,
|
||||
ChangeBadFormat,
|
||||
EncodingError,
|
||||
}
|
||||
|
||||
impl From<automerge_protocol::error::InvalidChangeHashSlice> for AutomergeError {
|
||||
fn from(_: automerge_protocol::error::InvalidChangeHashSlice) -> AutomergeError {
|
||||
AutomergeError::ChangeBadFormat
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AutomergeError {
|
||||
|
@ -51,3 +59,15 @@ impl fmt::Display for InvalidElementID {
|
|||
}
|
||||
|
||||
impl Error for InvalidElementID {}
|
||||
|
||||
impl From<leb128::read::Error> for AutomergeError {
|
||||
fn from(_err: leb128::read::Error) -> Self {
|
||||
AutomergeError::EncodingError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AutomergeError {
|
||||
fn from(_err: std::io::Error) -> Self {
|
||||
AutomergeError::EncodingError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
extern crate hex;
|
||||
extern crate im_rc;
|
||||
extern crate leb128;
|
||||
extern crate maplit;
|
||||
extern crate rand;
|
||||
extern crate sha2;
|
||||
extern crate uuid;
|
||||
extern crate web_sys;
|
||||
|
||||
|
@ -16,20 +14,24 @@ macro_rules! log {
|
|||
|
||||
mod actor_map;
|
||||
mod backend;
|
||||
mod change;
|
||||
mod columnar;
|
||||
mod concurrent_operations;
|
||||
mod encoding;
|
||||
mod error;
|
||||
mod object_store;
|
||||
mod op;
|
||||
mod op_handle;
|
||||
mod op_set;
|
||||
mod op_type;
|
||||
mod ordered_set;
|
||||
mod pending_diff;
|
||||
mod serialize;
|
||||
mod time;
|
||||
mod undo_operation;
|
||||
|
||||
pub use automerge_protocol::Change;
|
||||
pub use backend::Backend;
|
||||
pub use columnar::change_hash;
|
||||
pub use change::{Change, UnencodedChange};
|
||||
pub use error::AutomergeError;
|
||||
pub use op::Operation;
|
||||
pub use op_type::OpType;
|
||||
|
|
246
automerge-backend/src/op.rs
Normal file
246
automerge-backend/src/op.rs
Normal file
|
@ -0,0 +1,246 @@
|
|||
// FIXME
|
||||
use crate::op_type::OpType;
|
||||
use automerge_protocol as amp;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{
|
||||
de::{Error, MapAccess, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
fn read_field<'de, T, M>(
|
||||
name: &'static str,
|
||||
data: &mut Option<T>,
|
||||
map: &mut M,
|
||||
) -> Result<(), M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
if data.is_some() {
|
||||
Err(Error::duplicate_field(name))
|
||||
} else {
|
||||
data.replace(map.next_value()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Operation {
|
||||
pub action: OpType,
|
||||
pub obj: amp::ObjectID,
|
||||
pub key: amp::Key,
|
||||
pub pred: Vec<amp::OpID>,
|
||||
pub insert: bool,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn set(
|
||||
obj: amp::ObjectID,
|
||||
key: amp::Key,
|
||||
value: amp::Value,
|
||||
pred: Vec<amp::OpID>,
|
||||
) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Set(value),
|
||||
obj,
|
||||
key,
|
||||
insert: false,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
obj: amp::ObjectID,
|
||||
key: amp::Key,
|
||||
value: amp::Value,
|
||||
pred: Vec<amp::OpID>,
|
||||
) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Set(value),
|
||||
obj,
|
||||
key,
|
||||
insert: true,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc(obj: amp::ObjectID, key: amp::Key, value: i64, pred: Vec<amp::OpID>) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Inc(value),
|
||||
obj,
|
||||
key,
|
||||
insert: false,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn del(obj: amp::ObjectID, key: amp::Key, pred: Vec<amp::OpID>) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
obj,
|
||||
key,
|
||||
insert: false,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_make(&self) -> bool {
|
||||
self.obj_type().is_some()
|
||||
}
|
||||
|
||||
pub fn is_basic_assign(&self) -> bool {
|
||||
!self.insert
|
||||
&& match self.action {
|
||||
OpType::Del | OpType::Set(_) | OpType::Inc(_) | OpType::Link(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_inc(&self) -> bool {
|
||||
match self.action {
|
||||
OpType::Inc(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn obj_type(&self) -> Option<amp::ObjType> {
|
||||
match self.action {
|
||||
OpType::Make(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Operation {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut fields = 4;
|
||||
|
||||
if self.insert {
|
||||
fields += 1
|
||||
}
|
||||
|
||||
match &self.action {
|
||||
OpType::Set(amp::Value::Timestamp(_)) => fields += 2,
|
||||
OpType::Set(amp::Value::Counter(_)) => fields += 2,
|
||||
OpType::Link(_) | OpType::Inc(_) | OpType::Set(_) => fields += 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut op = serializer.serialize_struct("Operation", fields)?;
|
||||
op.serialize_field("action", &self.action)?;
|
||||
op.serialize_field("obj", &self.obj)?;
|
||||
op.serialize_field("key", &self.key)?;
|
||||
if self.insert {
|
||||
op.serialize_field("insert", &self.insert)?;
|
||||
}
|
||||
match &self.action {
|
||||
OpType::Link(child) => op.serialize_field("child", &child)?,
|
||||
OpType::Inc(n) => op.serialize_field("value", &n)?,
|
||||
OpType::Set(amp::Value::Counter(value)) => {
|
||||
op.serialize_field("value", &value)?;
|
||||
op.serialize_field("datatype", &::DataType::Counter)?;
|
||||
}
|
||||
OpType::Set(amp::Value::Timestamp(value)) => {
|
||||
op.serialize_field("value", &value)?;
|
||||
op.serialize_field("datatype", &::DataType::Timestamp)?;
|
||||
}
|
||||
OpType::Set(value) => op.serialize_field("value", &value)?,
|
||||
_ => {}
|
||||
}
|
||||
op.serialize_field("pred", &self.pred)?;
|
||||
op.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Operation {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
const FIELDS: &[&str] = &["ops", "deps", "message", "seq", "actor", "requestType"];
|
||||
struct OperationVisitor;
|
||||
impl<'de> Visitor<'de> for OperationVisitor {
|
||||
type Value = Operation;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("An operation object")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Operation, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut action: Option<amp::OpType> = None;
|
||||
let mut obj: Option<amp::ObjectID> = None;
|
||||
let mut key: Option<amp::Key> = None;
|
||||
let mut pred: Option<Vec<amp::OpID>> = None;
|
||||
let mut insert: Option<bool> = None;
|
||||
let mut datatype: Option<amp::DataType> = None;
|
||||
let mut value: Option<amp::Value> = None;
|
||||
let mut child: Option<amp::ObjectID> = None;
|
||||
while let Some(field) = map.next_key::<String>()? {
|
||||
match field.as_ref() {
|
||||
"action" => read_field("action", &mut action, &mut map)?,
|
||||
"obj" => read_field("obj", &mut obj, &mut map)?,
|
||||
"key" => read_field("key", &mut key, &mut map)?,
|
||||
"pred" => read_field("pred", &mut pred, &mut map)?,
|
||||
"insert" => read_field("insert", &mut insert, &mut map)?,
|
||||
"datatype" => read_field("datatype", &mut datatype, &mut map)?,
|
||||
"value" => read_field("value", &mut value, &mut map)?,
|
||||
"child" => read_field("child", &mut child, &mut map)?,
|
||||
_ => return Err(Error::unknown_field(&field, FIELDS)),
|
||||
}
|
||||
}
|
||||
let action = action.ok_or_else(|| Error::missing_field("action"))?;
|
||||
let obj = obj.ok_or_else(|| Error::missing_field("obj"))?;
|
||||
let key = key.ok_or_else(|| Error::missing_field("key"))?;
|
||||
let pred = pred.ok_or_else(|| Error::missing_field("pred"))?;
|
||||
let insert = insert.unwrap_or(false);
|
||||
let value = amp::Value::from(value, datatype);
|
||||
let action = match action {
|
||||
amp::OpType::MakeMap => OpType::Make(amp::ObjType::Map),
|
||||
amp::OpType::MakeTable => OpType::Make(amp::ObjType::Table),
|
||||
amp::OpType::MakeList => OpType::Make(amp::ObjType::List),
|
||||
amp::OpType::MakeText => OpType::Make(amp::ObjType::Text),
|
||||
amp::OpType::Del => OpType::Del,
|
||||
amp::OpType::Link => {
|
||||
OpType::Link(child.ok_or_else(|| Error::missing_field("pred"))?)
|
||||
}
|
||||
amp::OpType::Set => OpType::Set(
|
||||
amp::Value::from(value, datatype)
|
||||
.ok_or_else(|| Error::missing_field("value"))?,
|
||||
),
|
||||
amp::OpType::Inc => match value {
|
||||
Some(amp::Value::Int(n)) => Ok(OpType::Inc(n)),
|
||||
Some(amp::Value::Uint(n)) => Ok(OpType::Inc(n as i64)),
|
||||
Some(amp::Value::F64(n)) => Ok(OpType::Inc(n as i64)),
|
||||
Some(amp::Value::F32(n)) => Ok(OpType::Inc(n as i64)),
|
||||
Some(amp::Value::Counter(n)) => Ok(OpType::Inc(n)),
|
||||
Some(amp::Value::Timestamp(n)) => Ok(OpType::Inc(n)),
|
||||
Some(amp::Value::Str(s)) => {
|
||||
Err(Error::invalid_value(Unexpected::Str(&s), &"a number"))
|
||||
}
|
||||
Some(amp::Value::Boolean(b)) => {
|
||||
Err(Error::invalid_value(Unexpected::Bool(b), &"a number"))
|
||||
}
|
||||
Some(amp::Value::Null) => {
|
||||
Err(Error::invalid_value(Unexpected::Other("null"), &"a number"))
|
||||
}
|
||||
None => Err(Error::missing_field("value")),
|
||||
}?,
|
||||
};
|
||||
Ok(Operation {
|
||||
action,
|
||||
obj,
|
||||
key,
|
||||
insert,
|
||||
pred,
|
||||
})
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_struct("Operation", &FIELDS, OperationVisitor)
|
||||
}
|
||||
}
|
|
@ -4,29 +4,34 @@ use std::hash::{Hash, Hasher};
|
|||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::op::Operation;
|
||||
use crate::op_type::OpType;
|
||||
use crate::undo_operation::UndoOperation;
|
||||
use automerge_protocol::{Change, Key, ObjectID, OpID, OpType, Operation, Value};
|
||||
use crate::Change;
|
||||
use automerge_protocol::{Key, ObjectID, OpID, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct OpHandle {
|
||||
pub id: OpID,
|
||||
change: Rc<Change>,
|
||||
index: usize,
|
||||
op: Operation,
|
||||
//change: Rc<Change>,
|
||||
//index: usize,
|
||||
delta: i64,
|
||||
}
|
||||
|
||||
impl OpHandle {
|
||||
pub fn extract(change: Rc<Change>) -> Vec<OpHandle> {
|
||||
change
|
||||
.operations
|
||||
.iter()
|
||||
.iter_ops()
|
||||
// .iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| {
|
||||
let id = OpID(change.start_op + (index as u64), change.actor_id.0.clone());
|
||||
.map(|(index, op)| {
|
||||
let id = OpID::new(change.start_op + (index as u64), &change.actor_id());
|
||||
OpHandle {
|
||||
id,
|
||||
change: change.clone(),
|
||||
index,
|
||||
op,
|
||||
//change: change.clone(),
|
||||
//index,
|
||||
delta: 0,
|
||||
}
|
||||
})
|
||||
|
@ -54,7 +59,7 @@ impl OpHandle {
|
|||
}
|
||||
|
||||
pub fn invert(&self, field_key: &Key) -> UndoOperation {
|
||||
let base_op = &self.change.operations[self.index];
|
||||
let base_op = &self.op;
|
||||
let mut action = base_op.action.clone();
|
||||
let mut key = &base_op.key;
|
||||
if self.insert {
|
||||
|
@ -149,6 +154,6 @@ impl Deref for OpHandle {
|
|||
type Target = Operation;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.change.operations[self.index]
|
||||
&self.op
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,12 @@ use crate::concurrent_operations::ConcurrentOperations;
|
|||
use crate::error::AutomergeError;
|
||||
use crate::object_store::ObjState;
|
||||
use crate::op_handle::OpHandle;
|
||||
use crate::op_type::OpType;
|
||||
use crate::ordered_set::OrderedSet;
|
||||
use crate::pending_diff::PendingDiff;
|
||||
use crate::undo_operation::UndoOperation;
|
||||
use automerge_protocol::{
|
||||
ChangeHash, Diff, DiffEdit, Key, MapDiff, ObjDiff, ObjType, ObjectID, OpID, OpType, SeqDiff,
|
||||
ChangeHash, Diff, DiffEdit, Key, MapDiff, ObjDiff, ObjType, ObjectID, OpID, SeqDiff,
|
||||
};
|
||||
use core::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
use crate::{ObjType, OpType};
|
||||
use automerge_protocol::{ObjType, ObjectID, Value};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum OpType {
|
||||
Make(ObjType),
|
||||
Del,
|
||||
Link(ObjectID),
|
||||
Inc(i64),
|
||||
Set(Value),
|
||||
}
|
||||
|
||||
impl Serialize for OpType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
|
@ -1,5 +1,6 @@
|
|||
use crate::op_type::OpType;
|
||||
use crate::undo_operation::UndoOperation;
|
||||
use automerge_protocol::{DataType, OpType, Value};
|
||||
use automerge_protocol::{DataType, Value};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use automerge_protocol::{Key, ObjectID, OpID, OpType, Operation};
|
||||
use crate::op::Operation;
|
||||
use crate::op_type::OpType;
|
||||
use automerge_protocol::{Key, ObjectID, OpID};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct UndoOperation {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
extern crate automerge_backend;
|
||||
use automerge_backend::{change_hash, AutomergeError, Backend};
|
||||
use automerge_backend::{AutomergeError, Backend, UnencodedChange};
|
||||
use automerge_backend::{OpType, Operation};
|
||||
use automerge_protocol::{
|
||||
ActorID, Change, ChangeHash, Diff, DiffEdit, ElementID, Key, MapDiff, ObjDiff, ObjType,
|
||||
ObjectID, OpType, Operation, Patch, SeqDiff, Value,
|
||||
ActorID, Diff, DiffEdit, ElementID, Key, MapDiff, ObjDiff, ObjType, ObjectID, Patch, SeqDiff,
|
||||
Value,
|
||||
};
|
||||
use maplit::hashmap;
|
||||
use std::convert::TryInto;
|
||||
|
@ -10,13 +11,12 @@ use std::str::FromStr;
|
|||
|
||||
#[test]
|
||||
fn test_incremental_diffs_in_a_map() {
|
||||
let mut change = Change {
|
||||
actor_id: "7b7723afd9e6480397a4d467b7693156".into(),
|
||||
let change = UnencodedChange {
|
||||
actor_id: "7b7723afd9e6480397a4d467b7693156".try_into().unwrap(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation::set(
|
||||
ObjectID::Root,
|
||||
|
@ -24,8 +24,9 @@ fn test_incremental_diffs_in_a_map() {
|
|||
"magpie".into(),
|
||||
vec![],
|
||||
)],
|
||||
};
|
||||
change.hash = change_hash(change.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut backend = Backend::init();
|
||||
let patch = backend.apply_changes(vec![change.clone()]).unwrap();
|
||||
let expected_patch = Patch {
|
||||
|
@ -47,39 +48,37 @@ fn test_incremental_diffs_in_a_map() {
|
|||
|
||||
#[test]
|
||||
fn test_increment_key_in_map() -> Result<(), AutomergeError> {
|
||||
let mut change1 = Change {
|
||||
actor_id: "cdee6963c1664645920be8b41a933c2b".into(),
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: "cdee6963c1664645920be8b41a933c2b".try_into().unwrap(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation::set(
|
||||
ObjectID::Root,
|
||||
"counter".into(),
|
||||
Value::Counter(1),
|
||||
vec![],
|
||||
)],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
actor_id: "cdee6963c1664645920be8b41a933c2b".into(),
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: "cdee6963c1664645920be8b41a933c2b".try_into().unwrap(),
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 2,
|
||||
message: None,
|
||||
deps: vec![change1.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation::inc(
|
||||
ObjectID::Root,
|
||||
"counter".into(),
|
||||
2,
|
||||
vec!["1@cdee6963c1664645920be8b41a933c2b".try_into().unwrap()],
|
||||
)],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 2,
|
||||
|
@ -110,29 +109,27 @@ fn test_increment_key_in_map() -> Result<(), AutomergeError> {
|
|||
|
||||
#[test]
|
||||
fn test_conflict_on_assignment_to_same_map_key() {
|
||||
let mut change1 = Change {
|
||||
actor_id: "ac11".into(),
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: ActorID::from_str("ac11").unwrap(),
|
||||
seq: 1,
|
||||
message: None,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation::set(
|
||||
ObjectID::Root,
|
||||
"bird".into(),
|
||||
"magpie".into(),
|
||||
vec![],
|
||||
)],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
actor_id: "ac22".into(),
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: ActorID::from_str("ac22").unwrap(),
|
||||
start_op: 2,
|
||||
seq: 1,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: vec![change1.hash],
|
||||
time: 0,
|
||||
operations: vec![Operation::set(
|
||||
|
@ -141,8 +138,8 @@ fn test_conflict_on_assignment_to_same_map_key() {
|
|||
"blackbird".into(),
|
||||
vec![],
|
||||
)],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 2,
|
||||
|
@ -176,15 +173,14 @@ fn test_conflict_on_assignment_to_same_map_key() {
|
|||
|
||||
#[test]
|
||||
fn delete_key_from_map() {
|
||||
let actor: ActorID = "cd86c07f109348f494af5be30fdc4c71".into();
|
||||
let mut change1 = Change {
|
||||
let actor: ActorID = "cd86c07f109348f494af5be30fdc4c71".try_into().unwrap();
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set(Value::Str("magpie".into())),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -192,17 +188,16 @@ fn delete_key_from_map() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: vec![change1.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Del,
|
||||
obj: ObjectID::Root,
|
||||
|
@ -210,8 +205,8 @@ fn delete_key_from_map() {
|
|||
pred: vec!["1@cd86c07f109348f494af5be30fdc4c71".try_into().unwrap()],
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
actor: None,
|
||||
|
@ -239,14 +234,13 @@ fn delete_key_from_map() {
|
|||
#[test]
|
||||
fn create_nested_maps() {
|
||||
let actor: ActorID = "d6226fcd55204b82b396f2473da3e26f".try_into().unwrap();
|
||||
let mut change = Change {
|
||||
let change = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
deps: Vec::new(),
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::Map),
|
||||
|
@ -263,8 +257,8 @@ fn create_nested_maps() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
change.hash = change_hash(change.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch: Patch = Patch {
|
||||
actor: None,
|
||||
|
@ -301,9 +295,8 @@ fn create_nested_maps() {
|
|||
#[test]
|
||||
fn test_assign_to_nested_keys_in_map() {
|
||||
let actor: ActorID = "3c39c994039042778f4779a01a59a917".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
hash: ChangeHash::zero(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
|
@ -325,17 +318,16 @@ fn test_assign_to_nested_keys_in_map() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 3,
|
||||
time: 0,
|
||||
deps: vec![change1.hash],
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set(Value::F64(15.0)),
|
||||
obj: "1@3c39c994039042778f4779a01a59a917".try_into().unwrap(),
|
||||
|
@ -343,8 +335,8 @@ fn test_assign_to_nested_keys_in_map() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 2,
|
||||
|
@ -383,14 +375,13 @@ fn test_assign_to_nested_keys_in_map() {
|
|||
|
||||
#[test]
|
||||
fn test_create_lists() {
|
||||
let mut change = Change {
|
||||
let change = UnencodedChange {
|
||||
actor_id: "f82cb62dabe64372ab87466b77792010".try_into().unwrap(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -407,8 +398,8 @@ fn test_create_lists() {
|
|||
pred: Vec::new(),
|
||||
},
|
||||
],
|
||||
};
|
||||
change.hash = change_hash(change.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 1,
|
||||
|
@ -448,14 +439,13 @@ fn test_create_lists() {
|
|||
#[test]
|
||||
fn test_apply_updates_inside_lists() {
|
||||
let actor: ActorID = "4ee4a0d033b841c4b26d73d70a879547".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -472,17 +462,16 @@ fn test_apply_updates_inside_lists() {
|
|||
insert: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 3,
|
||||
time: 0,
|
||||
deps: vec![change1.hash],
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set(Value::Str("greenfinch".into())),
|
||||
obj: "1@4ee4a0d033b841c4b26d73d70a879547".try_into().unwrap(),
|
||||
|
@ -490,8 +479,8 @@ fn test_apply_updates_inside_lists() {
|
|||
pred: vec!["2@4ee4a0d033b841c4b26d73d70a879547".try_into().unwrap()],
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
actor: None,
|
||||
|
@ -532,14 +521,13 @@ fn test_apply_updates_inside_lists() {
|
|||
#[test]
|
||||
fn test_delete_list_elements() {
|
||||
let actor: ActorID = "8a3d4716fdca49f4aa5835901f2034c7".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -556,17 +544,16 @@ fn test_delete_list_elements() {
|
|||
pred: Vec::new(),
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 3,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: vec![change1.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Del,
|
||||
obj: "1@8a3d4716fdca49f4aa5835901f2034c7".try_into().unwrap(),
|
||||
|
@ -576,8 +563,8 @@ fn test_delete_list_elements() {
|
|||
pred: vec!["2@8a3d4716fdca49f4aa5835901f2034c7".try_into().unwrap()],
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 2,
|
||||
|
@ -614,14 +601,13 @@ fn test_delete_list_elements() {
|
|||
#[test]
|
||||
fn test_handle_list_element_insertion_and_deletion_in_same_change() {
|
||||
let actor: ActorID = "ca95bc759404486bbe7b9dd2be779fa8".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -629,17 +615,16 @@ fn test_handle_list_element_insertion_and_deletion_in_same_change() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Set(Value::Str("chaffinch".into())),
|
||||
|
@ -658,8 +643,8 @@ fn test_handle_list_element_insertion_and_deletion_in_same_change() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 2,
|
||||
|
@ -700,13 +685,12 @@ fn test_handle_list_element_insertion_and_deletion_in_same_change() {
|
|||
fn test_handle_changes_within_conflicted_objects() {
|
||||
let actor1: ActorID = "9f17517523e54ee888e9cd51dfd7a572".try_into().unwrap();
|
||||
let actor2: ActorID = "83768a19a13842beb6dde8c68a662fad".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor1.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -715,16 +699,15 @@ fn test_handle_changes_within_conflicted_objects() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor2.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Make(ObjType::Map),
|
||||
|
@ -733,17 +716,16 @@ fn test_handle_changes_within_conflicted_objects() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change3 = Change {
|
||||
let change3 = UnencodedChange {
|
||||
actor_id: actor2.clone(),
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: vec![change2.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set(Value::F64(12.0)),
|
||||
obj: "1@83768a19a13842beb6dde8c68a662fad".try_into().unwrap(),
|
||||
|
@ -751,8 +733,8 @@ fn test_handle_changes_within_conflicted_objects() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change3.hash = change_hash(change3.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 3,
|
||||
|
@ -798,12 +780,11 @@ fn test_handle_changes_within_conflicted_objects() {
|
|||
#[test]
|
||||
fn test_support_date_objects_at_root() {
|
||||
let actor: ActorID = "955afa3bbcc140b3b4bac8836479d650".try_into().unwrap();
|
||||
let mut change = Change {
|
||||
let change = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
message: None,
|
||||
operations: vec![Operation {
|
||||
|
@ -813,8 +794,8 @@ fn test_support_date_objects_at_root() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change.hash = change_hash(change.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 1,
|
||||
|
@ -845,13 +826,12 @@ fn test_support_date_objects_at_root() {
|
|||
#[test]
|
||||
fn test_support_date_objects_in_a_list() {
|
||||
let actor: ActorID = "27d467ecb1a640fb9bed448ce7cf6a44".try_into().unwrap();
|
||||
let mut change = Change {
|
||||
let change = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
message: None,
|
||||
operations: vec![
|
||||
Operation {
|
||||
|
@ -869,8 +849,8 @@ fn test_support_date_objects_in_a_list() {
|
|||
pred: Vec::new(),
|
||||
},
|
||||
],
|
||||
};
|
||||
change.hash = change_hash(change.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 1,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
extern crate automerge_backend;
|
||||
use automerge_backend::{change_hash, Backend};
|
||||
use automerge_backend::{Backend, UnencodedChange};
|
||||
use automerge_backend::{OpType, Operation};
|
||||
use automerge_protocol as protocol;
|
||||
use automerge_protocol::{
|
||||
ActorID, Change, ChangeHash, ChangeRequest, ChangeRequestType, DataType, Diff, DiffEdit,
|
||||
ElementID, MapDiff, ObjType, ObjectID, OpRequest, OpType, Operation, Patch, ReqOpType, SeqDiff,
|
||||
ActorID, ChangeHash, DataType, Diff, DiffEdit, ElementID, MapDiff, ObjType, ObjectID, Op,
|
||||
Patch, Request, RequestType, SeqDiff,
|
||||
};
|
||||
use maplit::hashmap;
|
||||
use std::convert::TryInto;
|
||||
|
@ -11,7 +13,7 @@ use std::{collections::HashSet, str::FromStr};
|
|||
#[test]
|
||||
fn test_apply_local_change() {
|
||||
let actor: ActorID = "eb738e04ef8848ce8b77309b6c7f7e39".try_into().unwrap();
|
||||
let change_request = ChangeRequest {
|
||||
let change_request = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -19,8 +21,8 @@ fn test_apply_local_change() {
|
|||
undoable: false,
|
||||
time: None,
|
||||
deps: None,
|
||||
ops: Some(vec![OpRequest {
|
||||
action: ReqOpType::Set,
|
||||
ops: Some(vec![Op {
|
||||
action: protocol::OpType::Set,
|
||||
value: Some("magpie".into()),
|
||||
datatype: Some(DataType::Undefined),
|
||||
key: "bird".into(),
|
||||
|
@ -28,20 +30,20 @@ fn test_apply_local_change() {
|
|||
child: None,
|
||||
insert: false,
|
||||
}]),
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
};
|
||||
|
||||
let mut backend = Backend::init();
|
||||
let patch = backend.apply_local_change(change_request).unwrap();
|
||||
|
||||
let mut expected_change = Change {
|
||||
let changes = backend.get_changes(&[]);
|
||||
let expected_change = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
time: changes[0].time,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("magpie".into()),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -49,12 +51,9 @@ fn test_apply_local_change() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
let changes: Vec<&Change> = backend.get_changes_unserialized(&[]);
|
||||
let change = changes[0];
|
||||
expected_change.time = change.time;
|
||||
expected_change.hash = change_hash(expected_change.clone()).unwrap();
|
||||
assert_eq!(change, &expected_change);
|
||||
}
|
||||
.encode();
|
||||
assert_eq!(changes[0], &expected_change);
|
||||
|
||||
let expected_patch = Patch {
|
||||
actor: Some(actor.to_string()),
|
||||
|
@ -65,7 +64,7 @@ fn test_apply_local_change() {
|
|||
},
|
||||
can_undo: false,
|
||||
can_redo: false,
|
||||
deps: vec![change.hash],
|
||||
deps: vec![changes[0].hash],
|
||||
diffs: Some(Diff::Map(MapDiff {
|
||||
object_id: ObjectID::Root.to_string(),
|
||||
obj_type: ObjType::Map,
|
||||
|
@ -82,7 +81,7 @@ fn test_apply_local_change() {
|
|||
#[test]
|
||||
fn test_error_on_duplicate_requests() {
|
||||
let actor: ActorID = "37704788917a499cb0206fa8519ac4d9".try_into().unwrap();
|
||||
let change_request1 = ChangeRequest {
|
||||
let change_request1 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -90,8 +89,8 @@ fn test_error_on_duplicate_requests() {
|
|||
undoable: false,
|
||||
time: None,
|
||||
deps: None,
|
||||
ops: Some(vec![OpRequest {
|
||||
action: ReqOpType::Set,
|
||||
ops: Some(vec![Op {
|
||||
action: protocol::OpType::Set,
|
||||
obj: ObjectID::Root.to_string(),
|
||||
key: "bird".into(),
|
||||
child: None,
|
||||
|
@ -99,10 +98,10 @@ fn test_error_on_duplicate_requests() {
|
|||
datatype: Some(DataType::Undefined),
|
||||
insert: false,
|
||||
}]),
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
};
|
||||
|
||||
let change_request2 = ChangeRequest {
|
||||
let change_request2 = Request {
|
||||
actor,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -110,8 +109,8 @@ fn test_error_on_duplicate_requests() {
|
|||
undoable: false,
|
||||
time: None,
|
||||
deps: None,
|
||||
ops: Some(vec![OpRequest {
|
||||
action: ReqOpType::Set,
|
||||
ops: Some(vec![Op {
|
||||
action: protocol::OpType::Set,
|
||||
obj: ObjectID::Root.to_string(),
|
||||
key: "bird".into(),
|
||||
value: Some("jay".into()),
|
||||
|
@ -119,7 +118,7 @@ fn test_error_on_duplicate_requests() {
|
|||
insert: false,
|
||||
datatype: Some(DataType::Undefined),
|
||||
}]),
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
};
|
||||
let mut backend = Backend::init();
|
||||
backend.apply_local_change(change_request1.clone()).unwrap();
|
||||
|
@ -131,7 +130,7 @@ fn test_error_on_duplicate_requests() {
|
|||
#[test]
|
||||
fn test_handle_concurrent_frontend_and_backend_changes() {
|
||||
let actor: ActorID = "cb55260e9d7e457886a4fc73fd949202".try_into().unwrap();
|
||||
let local1 = ChangeRequest {
|
||||
let local1 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -139,9 +138,9 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
deps: None,
|
||||
message: None,
|
||||
undoable: false,
|
||||
request_type: ChangeRequestType::Change,
|
||||
ops: Some(vec![OpRequest {
|
||||
action: ReqOpType::Set,
|
||||
request_type: RequestType::Change,
|
||||
ops: Some(vec![Op {
|
||||
action: protocol::OpType::Set,
|
||||
obj: ObjectID::Root.to_string(),
|
||||
key: "bird".into(),
|
||||
value: Some("magpie".into()),
|
||||
|
@ -151,17 +150,17 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
}]),
|
||||
};
|
||||
|
||||
let local2 = ChangeRequest {
|
||||
let local2 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 2,
|
||||
version: 0,
|
||||
time: None,
|
||||
deps: None,
|
||||
message: None,
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
undoable: false,
|
||||
ops: Some(vec![OpRequest {
|
||||
action: ReqOpType::Set,
|
||||
ops: Some(vec![Op {
|
||||
action: protocol::OpType::Set,
|
||||
obj: ObjectID::Root.to_string(),
|
||||
key: "bird".into(),
|
||||
value: Some("jay".into()),
|
||||
|
@ -171,14 +170,13 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
}]),
|
||||
};
|
||||
let remote_actor: ActorID = "6d48a01318644eed90455d2cb68ac657".try_into().unwrap();
|
||||
let mut remote1 = Change {
|
||||
let remote1 = UnencodedChange {
|
||||
actor_id: remote_actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
deps: Vec::new(),
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("goldfish".into()),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -186,16 +184,15 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
remote1.hash = change_hash(remote1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut expected_change1 = Change {
|
||||
let mut expected_change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("magpie".into()),
|
||||
|
@ -206,14 +203,13 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
}],
|
||||
};
|
||||
|
||||
let mut expected_change2 = Change {
|
||||
let mut expected_change2 = UnencodedChange {
|
||||
actor_id: remote_actor,
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("goldfish".into()),
|
||||
key: "fish".into(),
|
||||
|
@ -223,14 +219,13 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
}],
|
||||
};
|
||||
|
||||
let mut expected_change3 = Change {
|
||||
let mut expected_change3 = UnencodedChange {
|
||||
actor_id: actor,
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("jay".into()),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -242,42 +237,39 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
|
|||
let mut backend = Backend::init();
|
||||
backend.apply_local_change(local1).unwrap();
|
||||
let backend_after_first = backend.clone();
|
||||
let changes1 = backend_after_first.get_changes_unserialized(&[]);
|
||||
let change01: &Change = *changes1.get(0).unwrap();
|
||||
let changes1 = backend_after_first.get_changes(&[]);
|
||||
let change01 = changes1.get(0).unwrap();
|
||||
|
||||
backend.apply_changes(vec![remote1]).unwrap();
|
||||
let backend_after_second = backend.clone();
|
||||
let changes2 = backend_after_second.get_changes_unserialized(&[change01.hash]);
|
||||
let changes2 = backend_after_second.get_changes(&[change01.hash]);
|
||||
let change12 = *changes2.get(0).unwrap();
|
||||
|
||||
backend.apply_local_change(local2).unwrap();
|
||||
let changes3 = backend.get_changes_unserialized(&[change01.hash, change12.hash]);
|
||||
let changes3 = backend.get_changes(&[change01.hash, change12.hash]);
|
||||
let change23 = changes3.get(0).unwrap();
|
||||
|
||||
expected_change1.time = change01.time;
|
||||
expected_change1.hash = change_hash(expected_change1.clone()).unwrap();
|
||||
expected_change2.time = change12.time;
|
||||
expected_change2.hash = change_hash(expected_change2.clone()).unwrap();
|
||||
expected_change3.time = change23.time;
|
||||
expected_change3.deps = vec![change01.hash];
|
||||
expected_change3.hash = change_hash(expected_change3.clone()).unwrap();
|
||||
|
||||
assert_eq!(change01, &expected_change1);
|
||||
assert_eq!(change12, &expected_change2);
|
||||
assert_eq!(change23, &&expected_change3);
|
||||
assert_eq!(change01, &&expected_change1.encode());
|
||||
assert_eq!(change12, &expected_change2.encode());
|
||||
assert_eq!(change23, &&expected_change3.encode());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transform_list_indexes_into_element_ids() {
|
||||
let actor: ActorID = "8f389df8fecb4ddc989102321af3578e".try_into().unwrap();
|
||||
let remote_actor: ActorID = "9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap();
|
||||
let mut remote1 = Change {
|
||||
let remote1 = UnencodedChange {
|
||||
actor_id: remote_actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
key: "birds".into(),
|
||||
|
@ -285,17 +277,16 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
remote1.hash = change_hash(remote1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut remote2 = Change {
|
||||
let remote2 = UnencodedChange {
|
||||
actor_id: remote_actor,
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: vec![remote1.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("magpie".into()),
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
|
||||
|
@ -303,10 +294,10 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
insert: true,
|
||||
pred: Vec::new(),
|
||||
}],
|
||||
};
|
||||
remote2.hash = change_hash(remote2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let local1 = ChangeRequest {
|
||||
let local1 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 1,
|
||||
version: 1,
|
||||
|
@ -314,10 +305,10 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
time: None,
|
||||
deps: None,
|
||||
undoable: false,
|
||||
request_type: ChangeRequestType::Change,
|
||||
ops: Some(vec![OpRequest {
|
||||
request_type: RequestType::Change,
|
||||
ops: Some(vec![Op {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
|
||||
action: ReqOpType::Set,
|
||||
action: protocol::OpType::Set,
|
||||
value: Some("goldfinch".into()),
|
||||
key: 0.into(),
|
||||
datatype: Some(DataType::Undefined),
|
||||
|
@ -325,7 +316,7 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
child: None,
|
||||
}]),
|
||||
};
|
||||
let local2 = ChangeRequest {
|
||||
let local2 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 2,
|
||||
version: 1,
|
||||
|
@ -333,10 +324,10 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
deps: None,
|
||||
time: None,
|
||||
undoable: false,
|
||||
request_type: ChangeRequestType::Change,
|
||||
ops: Some(vec![OpRequest {
|
||||
request_type: RequestType::Change,
|
||||
ops: Some(vec![Op {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
|
||||
action: ReqOpType::Set,
|
||||
action: protocol::OpType::Set,
|
||||
value: Some("wagtail".into()),
|
||||
key: 1.into(),
|
||||
insert: true,
|
||||
|
@ -345,7 +336,7 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
}]),
|
||||
};
|
||||
|
||||
let local3 = ChangeRequest {
|
||||
let local3 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 3,
|
||||
version: 4,
|
||||
|
@ -353,20 +344,20 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
deps: None,
|
||||
time: None,
|
||||
undoable: false,
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
ops: Some(vec![
|
||||
OpRequest {
|
||||
Op {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
|
||||
action: ReqOpType::Set,
|
||||
action: protocol::OpType::Set,
|
||||
key: 0.into(),
|
||||
value: Some("Magpie".into()),
|
||||
insert: false,
|
||||
child: None,
|
||||
datatype: Some(DataType::Undefined),
|
||||
},
|
||||
OpRequest {
|
||||
Op {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
|
||||
action: ReqOpType::Set,
|
||||
action: protocol::OpType::Set,
|
||||
key: 1.into(),
|
||||
value: Some("Goldfinch".into()),
|
||||
child: None,
|
||||
|
@ -376,14 +367,13 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
]),
|
||||
};
|
||||
|
||||
let mut expected_change1 = Change {
|
||||
let mut expected_change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: vec![remote1.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
|
||||
action: OpType::Set("goldfinch".into()),
|
||||
|
@ -392,13 +382,12 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
pred: Vec::new(),
|
||||
}],
|
||||
};
|
||||
let mut expected_change2 = Change {
|
||||
let mut expected_change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 3,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
|
||||
|
@ -410,14 +399,13 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
pred: Vec::new(),
|
||||
}],
|
||||
};
|
||||
let mut expected_change3 = Change {
|
||||
let mut expected_change3 = UnencodedChange {
|
||||
actor_id: actor,
|
||||
seq: 3,
|
||||
start_op: 4,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
|
||||
|
@ -444,48 +432,45 @@ fn test_transform_list_indexes_into_element_ids() {
|
|||
backend.apply_changes(vec![remote1.clone()]).unwrap();
|
||||
backend.apply_local_change(local1).unwrap();
|
||||
let backend_after_first = backend.clone();
|
||||
let changes1 = backend_after_first.get_changes_unserialized(&[remote1.hash]);
|
||||
let changes1 = backend_after_first.get_changes(&[remote1.hash]);
|
||||
let change12 = *changes1.get(0).unwrap();
|
||||
|
||||
backend.apply_changes(vec![remote2.clone()]).unwrap();
|
||||
backend.apply_local_change(local2).unwrap();
|
||||
let backend_after_second = backend.clone();
|
||||
let changes2 = backend_after_second.get_changes_unserialized(&[remote2.hash, change12.hash]);
|
||||
let changes2 = backend_after_second.get_changes(&[remote2.hash, change12.hash]);
|
||||
let change23 = *changes2.get(0).unwrap();
|
||||
|
||||
backend.apply_local_change(local3).unwrap();
|
||||
let changes3 = backend.get_changes_unserialized(&[remote2.hash, change23.hash]);
|
||||
let change34 = changes3.get(0).unwrap();
|
||||
let changes3 = backend.get_changes(&[remote2.hash, change23.hash]);
|
||||
let change34 = changes3.get(0).unwrap().decode();
|
||||
|
||||
expected_change1.time = change12.time;
|
||||
expected_change1.hash = change_hash(expected_change1.clone()).unwrap();
|
||||
expected_change2.time = change23.time;
|
||||
expected_change2.deps = vec![change12.hash];
|
||||
expected_change2.hash = change_hash(expected_change2.clone()).unwrap();
|
||||
expected_change3.time = change34.time;
|
||||
expected_change3.deps = vec![remote2.hash, change23.hash];
|
||||
expected_change3.hash = change_hash(expected_change3.clone()).unwrap();
|
||||
|
||||
assert_eq!(change12, &expected_change1);
|
||||
assert_eq!(change23, &expected_change2);
|
||||
assert_changes_equal((*change34).clone(), expected_change3);
|
||||
assert_eq!(change12, &expected_change1.encode());
|
||||
assert_eq!(change23, &expected_change2.encode());
|
||||
assert_changes_equal(change34, expected_change3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_list_insertion_and_deletion_in_same_change() {
|
||||
let actor: ActorID = "0723d2a1940744868ffd6b294ada813f".try_into().unwrap();
|
||||
let local1 = ChangeRequest {
|
||||
let local1 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
message: None,
|
||||
time: None,
|
||||
undoable: false,
|
||||
deps: None,
|
||||
ops: Some(vec![OpRequest {
|
||||
ops: Some(vec![Op {
|
||||
obj: ObjectID::Root.to_string(),
|
||||
action: ReqOpType::MakeList,
|
||||
action: protocol::OpType::MakeList,
|
||||
key: "birds".into(),
|
||||
child: None,
|
||||
datatype: None,
|
||||
|
@ -494,28 +479,28 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
|
|||
}]),
|
||||
};
|
||||
|
||||
let local2 = ChangeRequest {
|
||||
let local2 = Request {
|
||||
actor: actor.clone(),
|
||||
seq: 2,
|
||||
version: 0,
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
message: None,
|
||||
time: None,
|
||||
undoable: false,
|
||||
deps: None,
|
||||
ops: Some(vec![
|
||||
OpRequest {
|
||||
Op {
|
||||
obj: "1@0723d2a1940744868ffd6b294ada813f".into(),
|
||||
action: ReqOpType::Set,
|
||||
action: protocol::OpType::Set,
|
||||
key: 0.into(),
|
||||
insert: true,
|
||||
value: Some("magpie".into()),
|
||||
child: None,
|
||||
datatype: Some(DataType::Undefined),
|
||||
},
|
||||
OpRequest {
|
||||
Op {
|
||||
obj: "1@0723d2a1940744868ffd6b294ada813f".into(),
|
||||
action: ReqOpType::Del,
|
||||
action: protocol::OpType::Del,
|
||||
key: 0.into(),
|
||||
child: None,
|
||||
insert: false,
|
||||
|
@ -554,14 +539,24 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
|
|||
})),
|
||||
};
|
||||
|
||||
let mut expected_change1 = Change {
|
||||
let mut backend = Backend::init();
|
||||
backend.apply_local_change(local1).unwrap();
|
||||
let patch = backend.apply_local_change(local2).unwrap();
|
||||
expected_patch.deps = patch.deps.clone();
|
||||
assert_eq!(patch, expected_patch);
|
||||
|
||||
let changes = backend.get_changes(&[]);
|
||||
assert_eq!(changes.len(), 2);
|
||||
let change1 = changes[0].clone();
|
||||
let change2 = changes[1].clone();
|
||||
|
||||
let expected_change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
time: change1.time,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
obj: ObjectID::Root,
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -569,17 +564,16 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
|
|||
insert: false,
|
||||
pred: Vec::new(),
|
||||
}],
|
||||
};
|
||||
expected_change1.hash = change_hash(expected_change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut expected_change2 = Change {
|
||||
let expected_change2 = UnencodedChange {
|
||||
actor_id: actor,
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
time: change2.time,
|
||||
message: None,
|
||||
deps: vec![expected_change1.hash],
|
||||
hash: ChangeHash::zero(),
|
||||
deps: vec![change1.hash],
|
||||
operations: vec![
|
||||
Operation {
|
||||
obj: "1@0723d2a1940744868ffd6b294ada813f".try_into().unwrap(),
|
||||
|
@ -598,25 +592,8 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let mut backend = Backend::init();
|
||||
backend.apply_local_change(local1).unwrap();
|
||||
let patch = backend.apply_local_change(local2).unwrap();
|
||||
expected_patch.deps = patch.deps.clone();
|
||||
assert_eq!(patch, expected_patch);
|
||||
|
||||
let changes = backend.get_changes_unserialized(&[]);
|
||||
assert_eq!(changes.len(), 2);
|
||||
let change1: Change = changes[0].clone();
|
||||
let change2: Change = changes[1].clone();
|
||||
|
||||
expected_change1.time = change1.time;
|
||||
expected_change1.hash = change_hash(expected_change1.clone()).unwrap();
|
||||
|
||||
expected_change2.time = change2.time;
|
||||
expected_change2.deps = vec![expected_change1.hash];
|
||||
expected_change2.hash = change_hash(expected_change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
assert_eq!(change1, expected_change1);
|
||||
assert_eq!(change2, expected_change2);
|
||||
|
@ -624,7 +601,7 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
|
|||
|
||||
/// Asserts that the changes are equal without respect to order of the hashes
|
||||
/// in the change dependencies
|
||||
fn assert_changes_equal(mut change1: Change, change2: Change) {
|
||||
fn assert_changes_equal(mut change1: UnencodedChange, change2: UnencodedChange) {
|
||||
let change2_clone = change2.clone();
|
||||
let deps1: HashSet<&ChangeHash> = change1.deps.iter().collect();
|
||||
let deps2: HashSet<&ChangeHash> = change2.deps.iter().collect();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
extern crate automerge_backend;
|
||||
use automerge_backend::{change_hash, Backend, Change};
|
||||
use automerge_backend::{Backend, UnencodedChange};
|
||||
use automerge_backend::{OpType, Operation};
|
||||
use automerge_protocol::{
|
||||
ActorID, ChangeHash, Diff, DiffEdit, ElementID, MapDiff, ObjType, ObjectID, OpType, Operation,
|
||||
Patch, SeqDiff, Value,
|
||||
ActorID, Diff, DiffEdit, ElementID, MapDiff, ObjType, ObjectID, Patch, SeqDiff, Value,
|
||||
};
|
||||
use maplit::hashmap;
|
||||
use std::convert::TryInto;
|
||||
|
@ -10,14 +10,13 @@ use std::convert::TryInto;
|
|||
#[test]
|
||||
fn test_include_most_recent_value_for_key() {
|
||||
let actor: ActorID = "ec28cfbcdb9e4f32ad24b3c776e651b0".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
deps: Vec::new(),
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("magpie".into()),
|
||||
key: "bird".into(),
|
||||
|
@ -25,16 +24,15 @@ fn test_include_most_recent_value_for_key() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: vec![change1.hash],
|
||||
operations: vec![Operation {
|
||||
obj: ObjectID::Root,
|
||||
|
@ -43,8 +41,8 @@ fn test_include_most_recent_value_for_key() {
|
|||
pred: vec!["1@ec28cfbcdb9e4f32ad24b3c776e651b0".try_into().unwrap()],
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
actor: None,
|
||||
|
@ -77,14 +75,13 @@ fn test_include_most_recent_value_for_key() {
|
|||
fn test_includes_conflicting_values_for_key() {
|
||||
let actor1: ActorID = "111111".try_into().unwrap();
|
||||
let actor2: ActorID = "222222".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor1.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
deps: Vec::new(),
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("magpie".into()),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -92,17 +89,16 @@ fn test_includes_conflicting_values_for_key() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor2.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set("blackbird".into()),
|
||||
key: "bird".into(),
|
||||
|
@ -110,8 +106,8 @@ fn test_includes_conflicting_values_for_key() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
@ -145,13 +141,12 @@ fn test_includes_conflicting_values_for_key() {
|
|||
#[test]
|
||||
fn test_handles_counter_increment_at_keys_in_a_map() {
|
||||
let actor: ActorID = "46c92088e4484ae5945dc63bf606a4a5".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set(Value::Counter(1)),
|
||||
|
@ -160,17 +155,16 @@ fn test_handles_counter_increment_at_keys_in_a_map() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 2,
|
||||
time: 0,
|
||||
deps: vec![change1.hash],
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Inc(2),
|
||||
obj: ObjectID::Root,
|
||||
|
@ -178,8 +172,8 @@ fn test_handles_counter_increment_at_keys_in_a_map() {
|
|||
pred: vec!["1@46c92088e4484ae5945dc63bf606a4a5".try_into().unwrap()],
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
@ -211,13 +205,12 @@ fn test_handles_counter_increment_at_keys_in_a_map() {
|
|||
#[test]
|
||||
fn test_creates_nested_maps() {
|
||||
let actor: ActorID = "06148f9422cb40579fd02f1975c34a51".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
|
@ -235,17 +228,16 @@ fn test_creates_nested_maps() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut change2 = Change {
|
||||
let change2 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 2,
|
||||
start_op: 3,
|
||||
time: 0,
|
||||
deps: vec![change1.hash],
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
|
@ -262,8 +254,8 @@ fn test_creates_nested_maps() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
change2.hash = change_hash(change2.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
@ -303,13 +295,12 @@ fn test_creates_nested_maps() {
|
|||
#[test]
|
||||
fn test_create_lists() {
|
||||
let actor: ActorID = "90bf7df682f747fa82ac604b35010906".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
|
@ -327,8 +318,8 @@ fn test_create_lists() {
|
|||
pred: Vec::new(),
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
@ -369,14 +360,13 @@ fn test_create_lists() {
|
|||
#[test]
|
||||
fn test_includes_latests_state_of_list() {
|
||||
let actor: ActorID = "6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -407,8 +397,8 @@ fn test_includes_latests_state_of_list() {
|
|||
insert: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
@ -460,13 +450,12 @@ fn test_includes_latests_state_of_list() {
|
|||
#[test]
|
||||
fn test_includes_date_objects_at_root() {
|
||||
let actor: ActorID = "90f5dd5d4f524e95ad5929e08d1194f1".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
hash: ChangeHash::zero(),
|
||||
deps: Vec::new(),
|
||||
operations: vec![Operation {
|
||||
action: OpType::Set(Value::Timestamp(1_586_541_033_457)),
|
||||
|
@ -475,8 +464,8 @@ fn test_includes_date_objects_at_root() {
|
|||
pred: Vec::new(),
|
||||
insert: false,
|
||||
}],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
@ -508,14 +497,13 @@ fn test_includes_date_objects_at_root() {
|
|||
#[test]
|
||||
fn test_includes_date_objects_in_a_list() {
|
||||
let actor: ActorID = "08b050f976a249349021a2e63d99c8e8".try_into().unwrap();
|
||||
let mut change1 = Change {
|
||||
let change1 = UnencodedChange {
|
||||
actor_id: actor.clone(),
|
||||
seq: 1,
|
||||
start_op: 1,
|
||||
time: 0,
|
||||
message: None,
|
||||
deps: Vec::new(),
|
||||
hash: ChangeHash::zero(),
|
||||
operations: vec![
|
||||
Operation {
|
||||
action: OpType::Make(ObjType::List),
|
||||
|
@ -532,8 +520,8 @@ fn test_includes_date_objects_in_a_list() {
|
|||
pred: Vec::new(),
|
||||
},
|
||||
],
|
||||
};
|
||||
change1.hash = change_hash(change1.clone()).unwrap();
|
||||
}
|
||||
.encode();
|
||||
|
||||
let expected_patch = Patch {
|
||||
version: 0,
|
||||
|
|
|
@ -3,8 +3,8 @@ extern crate errno;
|
|||
extern crate libc;
|
||||
extern crate serde;
|
||||
|
||||
use automerge_backend::AutomergeError;
|
||||
use automerge_protocol::{ActorID, ChangeRequest};
|
||||
use automerge_backend::{AutomergeError, Change};
|
||||
use automerge_protocol::Request;
|
||||
use errno::{set_errno, Errno};
|
||||
use serde::ser::Serialize;
|
||||
use std::convert::TryInto;
|
||||
|
@ -60,13 +60,19 @@ impl Backend {
|
|||
}
|
||||
|
||||
fn handle_binary(&mut self, b: Result<Vec<u8>, AutomergeError>) -> isize {
|
||||
self.handle_binaries(b.map(|b| vec![b]))
|
||||
if let Ok(bin) = b {
|
||||
let len = bin.len();
|
||||
self.binary = vec![bin];
|
||||
len as isize
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_binaries(&mut self, b: Result<Vec<Vec<u8>>, AutomergeError>) -> isize {
|
||||
fn handle_binaries(&mut self, b: Result<Vec<&Change>, AutomergeError>) -> isize {
|
||||
if let Ok(bin) = b {
|
||||
let len = bin[0].len();
|
||||
self.binary = bin;
|
||||
let len = bin[0].bytes.len();
|
||||
self.binary = bin.iter().map(|b| b.bytes.clone()).collect();
|
||||
self.binary.reverse();
|
||||
len as isize
|
||||
} else {
|
||||
|
@ -127,9 +133,14 @@ pub unsafe extern "C" fn automerge_apply_local_change(
|
|||
) -> isize {
|
||||
let request: &CStr = CStr::from_ptr(request);
|
||||
let request = request.to_string_lossy();
|
||||
let request: ChangeRequest = serde_json::from_str(&request).unwrap();
|
||||
let patch = (*backend).apply_local_change(request);
|
||||
(*backend).generate_json(patch)
|
||||
let request: Result<Request, _> = serde_json::from_str(&request);
|
||||
if let Ok(request) = request {
|
||||
let patch = (*backend).apply_local_change(request);
|
||||
(*backend).generate_json(patch)
|
||||
} else {
|
||||
// json parse error
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
@ -154,7 +165,12 @@ pub unsafe extern "C" fn automerge_write_change(
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_apply_changes(backend: *mut Backend) -> isize {
|
||||
if let Some(changes) = (*backend).queue.take() {
|
||||
let patch = (*backend).apply_changes_binary(changes);
|
||||
// FIXME
|
||||
let changes = changes
|
||||
.iter()
|
||||
.map(|c| Change::from_bytes(c.to_vec()).unwrap())
|
||||
.collect();
|
||||
let patch = (*backend).apply_changes(changes);
|
||||
(*backend).generate_json(patch)
|
||||
} else {
|
||||
-1
|
||||
|
@ -174,7 +190,12 @@ pub unsafe extern "C" fn automerge_get_patch(backend: *mut Backend) -> isize {
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_load_changes(backend: *mut Backend) -> isize {
|
||||
if let Some(changes) = (*backend).queue.take() {
|
||||
if (*backend).load_changes_binary(changes).is_ok() {
|
||||
// FIXME
|
||||
let changes = changes
|
||||
.iter()
|
||||
.map(|c| Change::from_bytes(c.to_vec()).unwrap())
|
||||
.collect();
|
||||
if (*backend).load_changes(changes).is_ok() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -219,9 +240,13 @@ pub unsafe extern "C" fn automerge_get_changes_for_actor(
|
|||
) -> isize {
|
||||
let actor: &CStr = CStr::from_ptr(actor);
|
||||
let actor = actor.to_string_lossy();
|
||||
let actor: ActorID = actor.as_ref().into();
|
||||
let changes = (*backend).get_changes_for_actor_id(&actor);
|
||||
(*backend).handle_binaries(changes)
|
||||
if let Ok(actor) = actor.as_ref().try_into() {
|
||||
let changes = (*backend).get_changes_for_actor_id(&actor);
|
||||
(*backend).handle_binaries(changes)
|
||||
} else {
|
||||
// bad actor error
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
@ -243,7 +268,7 @@ pub unsafe extern "C" fn automerge_get_changes(
|
|||
)
|
||||
}
|
||||
let changes = (*backend).get_changes(&have_deps);
|
||||
(*backend).handle_binaries(changes)
|
||||
(*backend).handle_binaries(Ok(changes))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
|
@ -10,6 +10,7 @@ path = "src/main.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { git = "https://github.com/clap-rs/clap/" }
|
||||
automerge-protocol = { path = "../automerge-protocol" }
|
||||
automerge-backend = { path = "../automerge-backend" }
|
||||
automerge-frontend = { path = "../automerge-frontend" }
|
||||
serde_json = "^1.0"
|
||||
|
|
|
@ -13,7 +13,8 @@ fn main() -> io::Result<()> {
|
|||
input_file.read_to_end(&mut input_data)?;
|
||||
|
||||
let mut backend = automerge_backend::Backend::init();
|
||||
backend.load_changes_binary(vec![input_data]).unwrap();
|
||||
let changes = automerge_backend::Change::parse(&input_data).unwrap();
|
||||
backend.load_changes(changes).unwrap();
|
||||
let patch = backend.get_patch().unwrap();
|
||||
let mut frontend = automerge_frontend::Frontend::new();
|
||||
frontend.apply_patch(patch).unwrap();
|
||||
|
|
|
@ -11,6 +11,7 @@ pub enum AutomergeFrontendError {
|
|||
PathIsNotCounter,
|
||||
CannotOverwriteCounter,
|
||||
MismatchedSequenceNumber,
|
||||
InvalidActorIDString(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for AutomergeFrontendError {
|
||||
|
@ -19,6 +20,12 @@ impl fmt::Display for AutomergeFrontendError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<automerge_protocol::error::InvalidActorID> for AutomergeFrontendError {
|
||||
fn from(e: automerge_protocol::error::InvalidActorID) -> AutomergeFrontendError {
|
||||
AutomergeFrontendError::InvalidActorIDString(e.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for AutomergeFrontendError {}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use automerge_protocol::{
|
||||
ActorID, ChangeRequest, ChangeRequestType, ObjectID, OpID, OpRequest, Patch,
|
||||
};
|
||||
use automerge_protocol::{ActorID, ObjectID, Op, OpID, Patch, Request, RequestType};
|
||||
|
||||
mod change_context;
|
||||
mod error;
|
||||
|
@ -13,6 +11,7 @@ use mutation::PathElement;
|
|||
pub use mutation::{LocalChange, MutableDocument, Path};
|
||||
use object::Object;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use std::time;
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
pub use value::{Conflicts, MapType, SequenceType, Value};
|
||||
|
@ -74,7 +73,7 @@ impl FrontendState {
|
|||
if let (Some(patch_actor), Some(patch_seq)) = (&patch.actor, patch.seq) {
|
||||
// If this is a local change corresponding to our actor then we
|
||||
// need to match it against in flight requests
|
||||
if self_actor == &ActorID::from(patch_actor.as_str()) {
|
||||
if self_actor == &ActorID::from_str(patch_actor.as_str())? {
|
||||
// Check that if the patch is for our actor ID then it is not
|
||||
// out of order
|
||||
if new_in_flight_requests[0] != patch_seq {
|
||||
|
@ -139,7 +138,7 @@ impl FrontendState {
|
|||
self,
|
||||
change_closure: F,
|
||||
seq: u64,
|
||||
) -> Result<(Option<Vec<OpRequest>>, FrontendState, Value), AutomergeFrontendError>
|
||||
) -> Result<(Option<Vec<Op>>, FrontendState, Value), AutomergeFrontendError>
|
||||
where
|
||||
F: FnOnce(&mut dyn MutableDocument) -> Result<(), AutomergeFrontendError>,
|
||||
{
|
||||
|
@ -235,7 +234,7 @@ impl Frontend {
|
|||
|
||||
pub fn new_with_initial_state(
|
||||
initial_state: Value,
|
||||
) -> Result<(Self, ChangeRequest), InvalidInitialStateError> {
|
||||
) -> Result<(Self, Request), InvalidInitialStateError> {
|
||||
match &initial_state {
|
||||
Value::Map(kvs, MapType::Map) => {
|
||||
let init_ops = kvs
|
||||
|
@ -252,7 +251,7 @@ impl Frontend {
|
|||
.collect();
|
||||
let mut front = Frontend::new();
|
||||
|
||||
let init_change_request = ChangeRequest {
|
||||
let init_change_request = Request {
|
||||
actor: front.actor_id.clone(),
|
||||
time: system_time(),
|
||||
seq: 1,
|
||||
|
@ -261,7 +260,7 @@ impl Frontend {
|
|||
undoable: false,
|
||||
deps: None,
|
||||
ops: Some(init_ops),
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
};
|
||||
// Unwrap here is fine because it should be impossible to
|
||||
// cause an error applying a local change from a `Value`. If
|
||||
|
@ -285,7 +284,7 @@ impl Frontend {
|
|||
&mut self,
|
||||
message: Option<String>,
|
||||
change_closure: F,
|
||||
) -> Result<Option<ChangeRequest>, AutomergeFrontendError>
|
||||
) -> Result<Option<Request>, AutomergeFrontendError>
|
||||
where
|
||||
F: FnOnce(&mut dyn MutableDocument) -> Result<(), AutomergeFrontendError>,
|
||||
{
|
||||
|
@ -301,7 +300,7 @@ impl Frontend {
|
|||
}
|
||||
self.seq += 1;
|
||||
self.cached_value = new_value;
|
||||
let change_request = ChangeRequest {
|
||||
let change_request = Request {
|
||||
actor: self.actor_id.clone(),
|
||||
seq: self.seq,
|
||||
time: system_time(),
|
||||
|
@ -310,7 +309,7 @@ impl Frontend {
|
|||
undoable: true,
|
||||
deps: None,
|
||||
ops,
|
||||
request_type: ChangeRequestType::Change,
|
||||
request_type: RequestType::Change,
|
||||
};
|
||||
Ok(Some(change_request))
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ impl LocalChange {
|
|||
/// be applied using `ChangeContext::commit`
|
||||
pub struct MutationTracker<'a, 'b> {
|
||||
change_context: &'a mut ChangeContext<'b>,
|
||||
ops: Vec<amp::OpRequest>,
|
||||
ops: Vec<amp::Op>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> MutationTracker<'a, 'b> {
|
||||
|
@ -146,7 +146,7 @@ impl<'a, 'b> MutationTracker<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn ops(&self) -> Option<Vec<amp::OpRequest>> {
|
||||
pub fn ops(&self) -> Option<Vec<amp::Op>> {
|
||||
if !self.ops.is_empty() {
|
||||
Some(self.ops.clone())
|
||||
} else {
|
||||
|
@ -395,8 +395,8 @@ impl<'a, 'b> MutableDocument for MutationTracker<'a, 'b> {
|
|||
if self.value_for_path(&change.path).is_some() {
|
||||
// Unwrap is fine as we know the parent object exists from the above
|
||||
let parent_obj = self.value_for_path(&change.path.parent()).unwrap();
|
||||
let op = amp::OpRequest {
|
||||
action: amp::ReqOpType::Del,
|
||||
let op = amp::Op {
|
||||
action: amp::OpType::Del,
|
||||
// This unwrap is fine because we know the parent
|
||||
// is a container
|
||||
obj: parent_obj.id().unwrap().to_string(),
|
||||
|
@ -446,8 +446,8 @@ impl<'a, 'b> MutableDocument for MutationTracker<'a, 'b> {
|
|||
};
|
||||
// Unwrap is fine as we know the parent object exists from the above
|
||||
let parent_obj = self.value_for_path(&change.path.parent()).unwrap();
|
||||
let op = amp::OpRequest {
|
||||
action: amp::ReqOpType::Inc,
|
||||
let op = amp::Op {
|
||||
action: amp::OpType::Inc,
|
||||
// This unwrap is fine because we know the parent
|
||||
// is a container
|
||||
obj: parent_obj.id().unwrap().to_string(),
|
||||
|
|
|
@ -175,15 +175,15 @@ pub(crate) fn value_to_op_requests(
|
|||
key: PathElement,
|
||||
v: &Value,
|
||||
insert: bool,
|
||||
) -> (Vec<amp::OpRequest>, amp::Diff) {
|
||||
) -> (Vec<amp::Op>, amp::Diff) {
|
||||
match v {
|
||||
Value::Sequence(vs, seq_type) => {
|
||||
let make_action = match seq_type {
|
||||
SequenceType::List => amp::ReqOpType::MakeList,
|
||||
SequenceType::Text => amp::ReqOpType::MakeText,
|
||||
SequenceType::List => amp::OpType::MakeList,
|
||||
SequenceType::Text => amp::OpType::MakeText,
|
||||
};
|
||||
let list_id = new_object_id();
|
||||
let make_op = amp::OpRequest {
|
||||
let make_op = amp::Op {
|
||||
action: make_action,
|
||||
obj: parent_object,
|
||||
key: key.to_request_key(),
|
||||
|
@ -192,14 +192,14 @@ pub(crate) fn value_to_op_requests(
|
|||
datatype: None,
|
||||
insert,
|
||||
};
|
||||
let child_requests_and_diffs: Vec<(Vec<amp::OpRequest>, amp::Diff)> = vs
|
||||
let child_requests_and_diffs: Vec<(Vec<amp::Op>, amp::Diff)> = vs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, v)| {
|
||||
value_to_op_requests(list_id.clone(), PathElement::Index(index), v, true)
|
||||
})
|
||||
.collect();
|
||||
let child_requests: Vec<amp::OpRequest> = child_requests_and_diffs
|
||||
let child_requests: Vec<amp::Op> = child_requests_and_diffs
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(|(o, _)| o)
|
||||
|
@ -229,11 +229,11 @@ pub(crate) fn value_to_op_requests(
|
|||
}
|
||||
Value::Map(kvs, map_type) => {
|
||||
let make_action = match map_type {
|
||||
MapType::Map => amp::ReqOpType::MakeMap,
|
||||
MapType::Table => amp::ReqOpType::MakeTable,
|
||||
MapType::Map => amp::OpType::MakeMap,
|
||||
MapType::Table => amp::OpType::MakeTable,
|
||||
};
|
||||
let map_id = new_object_id();
|
||||
let make_op = amp::OpRequest {
|
||||
let make_op = amp::Op {
|
||||
action: make_action,
|
||||
obj: parent_object,
|
||||
key: key.to_request_key(),
|
||||
|
@ -242,7 +242,7 @@ pub(crate) fn value_to_op_requests(
|
|||
datatype: None,
|
||||
insert,
|
||||
};
|
||||
let child_requests_and_diffs: HashMap<String, (Vec<amp::OpRequest>, amp::Diff)> = kvs
|
||||
let child_requests_and_diffs: HashMap<String, (Vec<amp::Op>, amp::Diff)> = kvs
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
|
@ -252,7 +252,7 @@ pub(crate) fn value_to_op_requests(
|
|||
})
|
||||
.collect();
|
||||
let mut result = vec![make_op];
|
||||
let child_requests: Vec<amp::OpRequest> = child_requests_and_diffs
|
||||
let child_requests: Vec<amp::Op> = child_requests_and_diffs
|
||||
.iter()
|
||||
.flat_map(|(_, (o, _))| o)
|
||||
.cloned()
|
||||
|
@ -274,8 +274,8 @@ pub(crate) fn value_to_op_requests(
|
|||
(result, amp::Diff::Map(child_diff))
|
||||
}
|
||||
Value::Primitive(prim_value) => {
|
||||
let ops = vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
let ops = vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: parent_object,
|
||||
key: key.to_request_key(),
|
||||
child: None,
|
||||
|
@ -294,7 +294,7 @@ fn new_object_id() -> String {
|
|||
}
|
||||
|
||||
pub(crate) fn random_op_id() -> amp::OpID {
|
||||
amp::OpID(1, amp::ActorID::random().0)
|
||||
amp::OpID::new(1, &::ActorID::random())
|
||||
}
|
||||
|
||||
fn value_to_datatype(value: &::Value) -> amp::DataType {
|
||||
|
|
|
@ -4,7 +4,7 @@ use automerge_protocol as amp;
|
|||
use maplit::hashmap;
|
||||
|
||||
fn random_op_id() -> String {
|
||||
amp::OpID(1, amp::ActorID::random().0).to_string()
|
||||
amp::OpID::new(1, &::ActorID::random()).to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -54,7 +54,7 @@ fn use_version_and_sequence_number_from_backend() {
|
|||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let expected_change_request = amp::ChangeRequest {
|
||||
let expected_change_request = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 5,
|
||||
time: req.time,
|
||||
|
@ -62,9 +62,9 @@ fn use_version_and_sequence_number_from_backend() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "partridges".into(),
|
||||
insert: false,
|
||||
|
@ -508,7 +508,7 @@ fn allow_interleacing_of_patches_and_changes() {
|
|||
|
||||
assert_eq!(
|
||||
req1,
|
||||
amp::ChangeRequest {
|
||||
amp::Request {
|
||||
actor: doc.actor_id.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -516,9 +516,9 @@ fn allow_interleacing_of_patches_and_changes() {
|
|||
time: req1.time,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "number".into(),
|
||||
value: Some(amp::Value::Int(1)),
|
||||
|
@ -531,7 +531,7 @@ fn allow_interleacing_of_patches_and_changes() {
|
|||
|
||||
assert_eq!(
|
||||
req2,
|
||||
amp::ChangeRequest {
|
||||
amp::Request {
|
||||
actor: doc.actor_id.clone(),
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -539,9 +539,9 @@ fn allow_interleacing_of_patches_and_changes() {
|
|||
time: req2.time,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "number".into(),
|
||||
value: Some(amp::Value::Int(2)),
|
||||
|
@ -569,7 +569,7 @@ fn allow_interleacing_of_patches_and_changes() {
|
|||
|
||||
assert_eq!(
|
||||
req3,
|
||||
amp::ChangeRequest {
|
||||
amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 3,
|
||||
version: 1,
|
||||
|
@ -577,9 +577,9 @@ fn allow_interleacing_of_patches_and_changes() {
|
|||
time: req3.time,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "number".into(),
|
||||
value: Some(amp::Value::Int(3)),
|
||||
|
|
|
@ -60,7 +60,7 @@ fn test_set_root_object_properties() {
|
|||
cr.time = None;
|
||||
cr
|
||||
});
|
||||
let expected_change = amp::ChangeRequest {
|
||||
let expected_change = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -68,8 +68,8 @@ fn test_set_root_object_properties() {
|
|||
message: Some("set root object".into()),
|
||||
undoable: true,
|
||||
deps: None,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: ROOT_ID.to_string(),
|
||||
key: amp::RequestKey::Str("bird".to_string()),
|
||||
child: None,
|
||||
|
@ -77,7 +77,7 @@ fn test_set_root_object_properties() {
|
|||
datatype: Some(amp::DataType::Undefined),
|
||||
insert: false,
|
||||
}]),
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
request_type: amp::RequestType::Change,
|
||||
};
|
||||
assert_eq!(change_request, Some(expected_change));
|
||||
}
|
||||
|
@ -105,18 +105,18 @@ fn it_should_create_nested_maps() {
|
|||
.unwrap()
|
||||
.unwrap();
|
||||
let birds_id = doc.get_object_id(&Path::root().key("birds")).unwrap();
|
||||
let expected_change = amp::ChangeRequest {
|
||||
let expected_change = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 1,
|
||||
time: change_request.time,
|
||||
message: None,
|
||||
version: 0,
|
||||
undoable: true,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
request_type: amp::RequestType::Change,
|
||||
deps: None,
|
||||
ops: Some(vec![
|
||||
amp::OpRequest {
|
||||
action: amp::ReqOpType::MakeMap,
|
||||
amp::Op {
|
||||
action: amp::OpType::MakeMap,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: amp::RequestKey::Str("birds".into()),
|
||||
child: Some(birds_id.to_string()),
|
||||
|
@ -124,8 +124,8 @@ fn it_should_create_nested_maps() {
|
|||
value: None,
|
||||
insert: false,
|
||||
},
|
||||
amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: birds_id.to_string(),
|
||||
key: amp::RequestKey::Str("wrens".into()),
|
||||
child: None,
|
||||
|
@ -183,7 +183,7 @@ fn apply_updates_inside_nested_maps() {
|
|||
);
|
||||
let birds_id = doc.get_object_id(&Path::root().key("birds")).unwrap();
|
||||
|
||||
let expected_change_request = amp::ChangeRequest {
|
||||
let expected_change_request = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -191,9 +191,9 @@ fn apply_updates_inside_nested_maps() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: birds_id.to_string(),
|
||||
key: "sparrows".into(),
|
||||
child: None,
|
||||
|
@ -242,7 +242,7 @@ fn delete_keys_in_a_map() {
|
|||
}))
|
||||
);
|
||||
|
||||
let expected_change_request = amp::ChangeRequest {
|
||||
let expected_change_request = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -250,9 +250,9 @@ fn delete_keys_in_a_map() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Del,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Del,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "magpies".into(),
|
||||
child: None,
|
||||
|
@ -299,7 +299,7 @@ fn create_lists() {
|
|||
|
||||
let birds_id = doc.get_object_id(&Path::root().key("birds")).unwrap();
|
||||
|
||||
let expected_change_request = amp::ChangeRequest {
|
||||
let expected_change_request = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -307,10 +307,10 @@ fn create_lists() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![
|
||||
amp::OpRequest {
|
||||
action: amp::ReqOpType::MakeList,
|
||||
amp::Op {
|
||||
action: amp::OpType::MakeList,
|
||||
key: "birds".into(),
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
child: Some(birds_id.to_string()),
|
||||
|
@ -318,8 +318,8 @@ fn create_lists() {
|
|||
datatype: None,
|
||||
insert: false,
|
||||
},
|
||||
amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: birds_id.to_string(),
|
||||
key: 0.into(),
|
||||
child: None,
|
||||
|
@ -367,7 +367,7 @@ fn apply_updates_inside_lists() {
|
|||
|
||||
let birds_id = doc.get_object_id(&Path::root().key("birds")).unwrap();
|
||||
|
||||
let expected_change_request = amp::ChangeRequest {
|
||||
let expected_change_request = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -375,9 +375,9 @@ fn apply_updates_inside_lists() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: birds_id.to_string(),
|
||||
key: 0.into(),
|
||||
child: None,
|
||||
|
@ -421,7 +421,7 @@ fn delete_list_elements() {
|
|||
|
||||
let birds_id = doc.get_object_id(&Path::root().key("birds")).unwrap();
|
||||
|
||||
let expected_change_request = amp::ChangeRequest {
|
||||
let expected_change_request = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -429,9 +429,9 @@ fn delete_list_elements() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Del,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Del,
|
||||
obj: birds_id.to_string(),
|
||||
key: 0.into(),
|
||||
child: None,
|
||||
|
@ -488,7 +488,7 @@ fn handle_counters_inside_maps() {
|
|||
)
|
||||
);
|
||||
|
||||
let expected_change_request_1 = amp::ChangeRequest {
|
||||
let expected_change_request_1 = amp::Request {
|
||||
actor: doc.actor_id.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -496,9 +496,9 @@ fn handle_counters_inside_maps() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "wrens".into(),
|
||||
child: None,
|
||||
|
@ -509,7 +509,7 @@ fn handle_counters_inside_maps() {
|
|||
};
|
||||
assert_eq!(req1, expected_change_request_1);
|
||||
|
||||
let expected_change_request_2 = amp::ChangeRequest {
|
||||
let expected_change_request_2 = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -517,9 +517,9 @@ fn handle_counters_inside_maps() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Inc,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Inc,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "wrens".into(),
|
||||
child: None,
|
||||
|
@ -580,7 +580,7 @@ fn handle_counters_inside_lists() {
|
|||
|
||||
let counts_id = doc.get_object_id(&Path::root().key("counts")).unwrap();
|
||||
|
||||
let expected_change_request_1 = amp::ChangeRequest {
|
||||
let expected_change_request_1 = amp::Request {
|
||||
actor: doc.actor_id.clone(),
|
||||
seq: 1,
|
||||
version: 0,
|
||||
|
@ -588,10 +588,10 @@ fn handle_counters_inside_lists() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![
|
||||
amp::OpRequest {
|
||||
action: amp::ReqOpType::MakeList,
|
||||
amp::Op {
|
||||
action: amp::OpType::MakeList,
|
||||
obj: amp::ObjectID::Root.to_string(),
|
||||
key: "counts".into(),
|
||||
child: Some(counts_id.to_string()),
|
||||
|
@ -599,8 +599,8 @@ fn handle_counters_inside_lists() {
|
|||
value: None,
|
||||
datatype: None,
|
||||
},
|
||||
amp::OpRequest {
|
||||
action: amp::ReqOpType::Set,
|
||||
amp::Op {
|
||||
action: amp::OpType::Set,
|
||||
obj: counts_id.to_string(),
|
||||
key: 0.into(),
|
||||
child: None,
|
||||
|
@ -612,7 +612,7 @@ fn handle_counters_inside_lists() {
|
|||
};
|
||||
assert_eq!(req1, expected_change_request_1);
|
||||
|
||||
let expected_change_request_2 = amp::ChangeRequest {
|
||||
let expected_change_request_2 = amp::Request {
|
||||
actor: doc.actor_id,
|
||||
seq: 2,
|
||||
version: 0,
|
||||
|
@ -620,9 +620,9 @@ fn handle_counters_inside_lists() {
|
|||
message: None,
|
||||
undoable: true,
|
||||
deps: None,
|
||||
request_type: amp::ChangeRequestType::Change,
|
||||
ops: Some(vec![amp::OpRequest {
|
||||
action: amp::ReqOpType::Inc,
|
||||
request_type: amp::RequestType::Change,
|
||||
ops: Some(vec![amp::Op {
|
||||
action: amp::OpType::Inc,
|
||||
obj: counts_id.to_string(),
|
||||
key: 0.into(),
|
||||
child: None,
|
||||
|
|
|
@ -12,6 +12,10 @@ pub struct InvalidObjectID(pub String);
|
|||
#[error("Invalid element ID: {0}")]
|
||||
pub struct InvalidElementID(pub String);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Invalid actor ID: {0}")]
|
||||
pub struct InvalidActorID(pub String);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Invalid change hash slice: {0:?}")]
|
||||
pub struct InvalidChangeHashSlice(pub Vec<u8>);
|
||||
|
|
|
@ -1,32 +1,28 @@
|
|||
mod error;
|
||||
pub mod error;
|
||||
mod serde_impls;
|
||||
mod utility_impls;
|
||||
|
||||
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// TODO make this an opaque tuple struct. I'm waiting on hearing what the
|
||||
// constraints are on the possible values of ActorIDs
|
||||
#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone, PartialOrd, Ord)]
|
||||
pub struct ActorID(pub String);
|
||||
#[derive(Eq, PartialEq, Hash, Debug, Clone, PartialOrd, Ord)]
|
||||
pub struct ActorID(Vec<u8>);
|
||||
|
||||
impl ActorID {
|
||||
pub fn random() -> ActorID {
|
||||
ActorID(hex::encode(uuid::Uuid::new_v4().as_bytes()))
|
||||
ActorID(uuid::Uuid::new_v4().as_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
// FIXME - I should be storing u8 internally - not strings
|
||||
// i need proper error handling for non-hex strings
|
||||
hex::decode(&self.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn to_hex_string(&self) -> String {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> ActorID {
|
||||
ActorID(hex::encode(bytes))
|
||||
ActorID(bytes.to_vec())
|
||||
}
|
||||
|
||||
pub fn to_hex_string(&self) -> String {
|
||||
hex::encode(&self.0)
|
||||
}
|
||||
|
||||
pub fn op_id_at(&self, seq: u64) -> OpID {
|
||||
|
@ -48,7 +44,7 @@ pub struct OpID(pub u64, pub String);
|
|||
|
||||
impl OpID {
|
||||
pub fn new(seq: u64, actor: &ActorID) -> OpID {
|
||||
OpID(seq, actor.0.clone())
|
||||
OpID(seq, actor.to_hex_string())
|
||||
}
|
||||
|
||||
pub fn counter(&self) -> u64 {
|
||||
|
@ -181,7 +177,7 @@ pub enum RequestKey {
|
|||
|
||||
#[derive(Deserialize, PartialEq, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ReqOpType {
|
||||
pub enum OpType {
|
||||
MakeMap,
|
||||
MakeTable,
|
||||
MakeList,
|
||||
|
@ -193,8 +189,8 @@ pub enum ReqOpType {
|
|||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq, Debug, Clone)]
|
||||
pub struct OpRequest {
|
||||
pub action: ReqOpType,
|
||||
pub struct Op {
|
||||
pub action: OpType,
|
||||
pub obj: String,
|
||||
pub key: RequestKey,
|
||||
pub child: Option<String>,
|
||||
|
@ -204,7 +200,7 @@ pub struct OpRequest {
|
|||
pub insert: bool,
|
||||
}
|
||||
|
||||
impl OpRequest {
|
||||
impl Op {
|
||||
pub fn primitive_value(&self) -> Value {
|
||||
match (self.value.as_ref().and_then(|v| v.to_i64()), self.datatype) {
|
||||
(Some(n), Some(DataType::Counter)) => Value::Counter(n),
|
||||
|
@ -215,10 +211,10 @@ impl OpRequest {
|
|||
|
||||
pub fn obj_type(&self) -> Option<ObjType> {
|
||||
match self.action {
|
||||
ReqOpType::MakeMap => Some(ObjType::Map),
|
||||
ReqOpType::MakeTable => Some(ObjType::Table),
|
||||
ReqOpType::MakeList => Some(ObjType::List),
|
||||
ReqOpType::MakeText => Some(ObjType::Text),
|
||||
OpType::MakeMap => Some(ObjType::Map),
|
||||
OpType::MakeTable => Some(ObjType::Table),
|
||||
OpType::MakeList => Some(ObjType::List),
|
||||
OpType::MakeText => Some(ObjType::Text),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -228,127 +224,12 @@ impl OpRequest {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum OpType {
|
||||
Make(ObjType),
|
||||
Del,
|
||||
Link(ObjectID),
|
||||
Inc(i64),
|
||||
Set(Value),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Operation {
|
||||
pub action: OpType,
|
||||
pub obj: ObjectID,
|
||||
pub key: Key,
|
||||
pub pred: Vec<OpID>,
|
||||
pub insert: bool,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn set(obj: ObjectID, key: Key, value: Value, pred: Vec<OpID>) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Set(value),
|
||||
obj,
|
||||
key,
|
||||
insert: false,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(obj: ObjectID, key: Key, value: Value, pred: Vec<OpID>) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Set(value),
|
||||
obj,
|
||||
key,
|
||||
insert: true,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc(obj: ObjectID, key: Key, value: i64, pred: Vec<OpID>) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Inc(value),
|
||||
obj,
|
||||
key,
|
||||
insert: false,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn del(obj: ObjectID, key: Key, pred: Vec<OpID>) -> Operation {
|
||||
Operation {
|
||||
action: OpType::Del,
|
||||
obj,
|
||||
key,
|
||||
insert: false,
|
||||
pred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_make(&self) -> bool {
|
||||
self.obj_type().is_some()
|
||||
}
|
||||
|
||||
pub fn is_basic_assign(&self) -> bool {
|
||||
!self.insert
|
||||
&& match self.action {
|
||||
OpType::Del | OpType::Set(_) | OpType::Inc(_) | OpType::Link(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_inc(&self) -> bool {
|
||||
match self.action {
|
||||
OpType::Inc(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn obj_type(&self) -> Option<ObjType> {
|
||||
match self.action {
|
||||
OpType::Make(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone, PartialOrd, Ord, Copy)]
|
||||
pub struct ChangeHash(pub [u8; 32]);
|
||||
|
||||
impl ChangeHash {
|
||||
/// A ChangeHash which is all zeros
|
||||
pub fn zero() -> Self {
|
||||
ChangeHash([0; 32])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
|
||||
pub struct Change {
|
||||
#[serde(rename = "ops")]
|
||||
pub operations: Vec<Operation>,
|
||||
#[serde(rename = "actor")]
|
||||
pub actor_id: ActorID,
|
||||
pub hash: ChangeHash,
|
||||
pub seq: u64,
|
||||
#[serde(rename = "startOp")]
|
||||
pub start_op: u64,
|
||||
pub time: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
pub deps: Vec<ChangeHash>,
|
||||
}
|
||||
|
||||
impl Change {
|
||||
pub fn max_op(&self) -> u64 {
|
||||
self.start_op + (self.operations.len() as u64) - 1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChangeRequest {
|
||||
pub struct Request {
|
||||
pub actor: ActorID,
|
||||
pub seq: u64,
|
||||
pub version: u64,
|
||||
|
@ -357,13 +238,13 @@ pub struct ChangeRequest {
|
|||
pub undoable: bool,
|
||||
pub time: Option<i64>,
|
||||
pub deps: Option<Vec<ChangeHash>>,
|
||||
pub ops: Option<Vec<OpRequest>>,
|
||||
pub request_type: ChangeRequestType,
|
||||
pub ops: Option<Vec<Op>>,
|
||||
pub request_type: RequestType,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ChangeRequestType {
|
||||
pub enum RequestType {
|
||||
Change,
|
||||
Undo,
|
||||
Redo,
|
||||
|
|
24
automerge-protocol/src/serde_impls/actor_id.rs
Normal file
24
automerge-protocol/src/serde_impls/actor_id.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use crate::ActorID;
|
||||
use serde::de;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
impl<'de> Deserialize<'de> for ActorID {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
ActorID::from_str(&s)
|
||||
.map_err(|_| de::Error::invalid_value(de::Unexpected::Str(&s), &"A valid ActorID"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ActorID {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_hex_string().as_str())
|
||||
}
|
||||
}
|
|
@ -3,13 +3,12 @@ use serde::{
|
|||
Deserialize,
|
||||
};
|
||||
|
||||
mod actor_id;
|
||||
mod change_hash;
|
||||
mod diff;
|
||||
mod element_id;
|
||||
mod key;
|
||||
mod object_id;
|
||||
mod op_type;
|
||||
mod operation;
|
||||
mod opid;
|
||||
mod request_key;
|
||||
mod value;
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
use super::read_field;
|
||||
use crate::{DataType, Key, ObjType, ObjectID, OpID, OpType, Operation, ReqOpType, Value};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{
|
||||
de::{Error, MapAccess, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
impl Serialize for Operation {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut fields = 4;
|
||||
|
||||
if self.insert {
|
||||
fields += 1
|
||||
}
|
||||
|
||||
match &self.action {
|
||||
OpType::Set(Value::Timestamp(_)) => fields += 2,
|
||||
OpType::Set(Value::Counter(_)) => fields += 2,
|
||||
OpType::Link(_) | OpType::Inc(_) | OpType::Set(_) => fields += 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut op = serializer.serialize_struct("Operation", fields)?;
|
||||
op.serialize_field("action", &self.action)?;
|
||||
op.serialize_field("obj", &self.obj)?;
|
||||
op.serialize_field("key", &self.key)?;
|
||||
if self.insert {
|
||||
op.serialize_field("insert", &self.insert)?;
|
||||
}
|
||||
match &self.action {
|
||||
OpType::Link(child) => op.serialize_field("child", &child)?,
|
||||
OpType::Inc(n) => op.serialize_field("value", &n)?,
|
||||
OpType::Set(Value::Counter(value)) => {
|
||||
op.serialize_field("value", &value)?;
|
||||
op.serialize_field("datatype", &DataType::Counter)?;
|
||||
}
|
||||
OpType::Set(Value::Timestamp(value)) => {
|
||||
op.serialize_field("value", &value)?;
|
||||
op.serialize_field("datatype", &DataType::Timestamp)?;
|
||||
}
|
||||
OpType::Set(value) => op.serialize_field("value", &value)?,
|
||||
_ => {}
|
||||
}
|
||||
op.serialize_field("pred", &self.pred)?;
|
||||
op.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Operation {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
const FIELDS: &[&str] = &["ops", "deps", "message", "seq", "actor", "requestType"];
|
||||
struct OperationVisitor;
|
||||
impl<'de> Visitor<'de> for OperationVisitor {
|
||||
type Value = Operation;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("An operation object")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Operation, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut action: Option<ReqOpType> = None;
|
||||
let mut obj: Option<ObjectID> = None;
|
||||
let mut key: Option<Key> = None;
|
||||
let mut pred: Option<Vec<OpID>> = None;
|
||||
let mut insert: Option<bool> = None;
|
||||
let mut datatype: Option<DataType> = None;
|
||||
let mut value: Option<Value> = None;
|
||||
let mut child: Option<ObjectID> = None;
|
||||
while let Some(field) = map.next_key::<String>()? {
|
||||
match field.as_ref() {
|
||||
"action" => read_field("action", &mut action, &mut map)?,
|
||||
"obj" => read_field("obj", &mut obj, &mut map)?,
|
||||
"key" => read_field("key", &mut key, &mut map)?,
|
||||
"pred" => read_field("pred", &mut pred, &mut map)?,
|
||||
"insert" => read_field("insert", &mut insert, &mut map)?,
|
||||
"datatype" => read_field("datatype", &mut datatype, &mut map)?,
|
||||
"value" => read_field("value", &mut value, &mut map)?,
|
||||
"child" => read_field("child", &mut child, &mut map)?,
|
||||
_ => return Err(Error::unknown_field(&field, FIELDS)),
|
||||
}
|
||||
}
|
||||
let action = action.ok_or_else(|| Error::missing_field("action"))?;
|
||||
let obj = obj.ok_or_else(|| Error::missing_field("obj"))?;
|
||||
let key = key.ok_or_else(|| Error::missing_field("key"))?;
|
||||
let pred = pred.ok_or_else(|| Error::missing_field("pred"))?;
|
||||
let insert = insert.unwrap_or(false);
|
||||
let value = Value::from(value, datatype);
|
||||
let action = match action {
|
||||
ReqOpType::MakeMap => OpType::Make(ObjType::Map),
|
||||
ReqOpType::MakeTable => OpType::Make(ObjType::Table),
|
||||
ReqOpType::MakeList => OpType::Make(ObjType::List),
|
||||
ReqOpType::MakeText => OpType::Make(ObjType::Text),
|
||||
ReqOpType::Del => OpType::Del,
|
||||
ReqOpType::Link => {
|
||||
OpType::Link(child.ok_or_else(|| Error::missing_field("pred"))?)
|
||||
}
|
||||
ReqOpType::Set => OpType::Set(
|
||||
Value::from(value, datatype)
|
||||
.ok_or_else(|| Error::missing_field("value"))?,
|
||||
),
|
||||
ReqOpType::Inc => match value {
|
||||
Some(Value::Int(n)) => Ok(OpType::Inc(n)),
|
||||
Some(Value::Uint(n)) => Ok(OpType::Inc(n as i64)),
|
||||
Some(Value::F64(n)) => Ok(OpType::Inc(n as i64)),
|
||||
Some(Value::F32(n)) => Ok(OpType::Inc(n as i64)),
|
||||
Some(Value::Counter(n)) => Ok(OpType::Inc(n)),
|
||||
Some(Value::Timestamp(n)) => Ok(OpType::Inc(n)),
|
||||
Some(Value::Str(s)) => {
|
||||
Err(Error::invalid_value(Unexpected::Str(&s), &"a number"))
|
||||
}
|
||||
Some(Value::Boolean(b)) => {
|
||||
Err(Error::invalid_value(Unexpected::Bool(b), &"a number"))
|
||||
}
|
||||
Some(Value::Null) => {
|
||||
Err(Error::invalid_value(Unexpected::Other("null"), &"a number"))
|
||||
}
|
||||
None => Err(Error::missing_field("value")),
|
||||
}?,
|
||||
};
|
||||
Ok(Operation {
|
||||
action,
|
||||
obj,
|
||||
key,
|
||||
insert,
|
||||
pred,
|
||||
})
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_struct("Operation", &FIELDS, OperationVisitor)
|
||||
}
|
||||
}
|
|
@ -1,22 +1,40 @@
|
|||
use crate::error::InvalidActorID;
|
||||
use crate::ActorID;
|
||||
use std::{convert::Infallible, fmt, str::FromStr};
|
||||
use std::convert::TryFrom;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
impl From<&str> for ActorID {
|
||||
fn from(s: &str) -> Self {
|
||||
ActorID(s.into())
|
||||
impl TryFrom<&str> for ActorID {
|
||||
type Error = InvalidActorID;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
hex::decode(s)
|
||||
.map(ActorID)
|
||||
.map_err(|_| InvalidActorID(s.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for ActorID {
|
||||
fn from(b: &[u8]) -> Self {
|
||||
ActorID(b.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ActorID {
|
||||
fn from(b: Vec<u8>) -> Self {
|
||||
ActorID(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ActorID {
|
||||
type Err = Infallible;
|
||||
type Err = InvalidActorID;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(ActorID(s.into()))
|
||||
ActorID::try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ActorID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
write!(f, "{}", self.to_hex_string())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue