Move automerge_backend::UnencodedChange -> automerge_protocol::UncompressedChange

This commit is contained in:
Alex Good 2021-01-13 13:22:51 +00:00
parent c859b24a12
commit a3bd0a79d6
25 changed files with 1179 additions and 1001 deletions

View file

@ -1,7 +1,7 @@
//#![feature(set_stdio)]
use automerge_backend::{Backend, Change, UnencodedChange };
use automerge_protocol::{ActorID, ChangeHash };
use automerge_backend::{Backend, Change, UncompressedChange};
use automerge_protocol::{ActorID, ChangeHash};
use js_sys::{Array, Uint8Array};
use serde::de::DeserializeOwned;
use serde::Serialize;
@ -69,7 +69,7 @@ impl State {
#[wasm_bindgen(js_name = applyLocalChange)]
pub fn apply_local_change(&mut self, change: JsValue) -> Result<Array, JsValue> {
let c: UnencodedChange = js_to_rust(change)?;
let c: UncompressedChange = js_to_rust(change)?;
let (patch, change) = self.backend.apply_local_change(c).map_err(to_js_err)?;
let heads = self.backend.get_heads();
let result = Array::new();

View file

@ -19,6 +19,7 @@ sha2 = "^0.8.1"
leb128 = "^0.2.4"
automerge-protocol = { path = "../automerge-protocol" }
fxhash = "^0.2.1"
thiserror = "1.0.16"
[dependencies.web-sys]
version = "0.3"

View file

@ -1,6 +1,4 @@
use crate::internal::{
ActorID, ElementID, InternalOp, InternalOpType, Key, ObjectID, OpID,
};
use crate::internal::{ActorID, ElementID, InternalOp, InternalOpType, Key, ObjectID, OpID};
use crate::op_type::OpType;
use crate::Operation;
use automerge_protocol as amp;
@ -66,7 +64,6 @@ impl ActorMap {
match optype {
OpType::Make(val) => InternalOpType::Make(*val),
OpType::Del => InternalOpType::Del,
OpType::Link(obj) => InternalOpType::Link(self.import_obj(&obj)),
OpType::Inc(val) => InternalOpType::Inc(*val),
OpType::Set(val) => InternalOpType::Set(val.clone()),
}

View file

@ -4,10 +4,11 @@ use crate::internal::ObjectID;
use crate::op_handle::OpHandle;
use crate::op_set::OpSet;
use crate::pending_diff::PendingDiff;
use crate::{Change, UnencodedChange};
use crate::Change;
use automerge_protocol as amp;
use std::collections::{HashMap, HashSet};
use core::cmp::max;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::rc::Rc;
#[derive(Debug, PartialEq, Clone)]
@ -38,9 +39,14 @@ impl Backend {
diffs: Option<amp::Diff>,
actor_seq: Option<(amp::ActorID, u64)>,
) -> Result<amp::Patch, AutomergeError> {
let mut deps : Vec<_> = if let Some((ref actor,ref seq)) = actor_seq {
let mut deps: Vec<_> = if let Some((ref actor, ref seq)) = actor_seq {
let last_hash = self.get_hash(actor, *seq)?;
self.op_set.deps.iter().cloned().filter(|dep| dep != &last_hash).collect()
self.op_set
.deps
.iter()
.cloned()
.filter(|dep| dep != &last_hash)
.collect()
} else {
self.op_set.deps.iter().cloned().collect()
};
@ -92,13 +98,17 @@ impl Backend {
self.make_patch(diffs, actor)
}
fn get_hash(&self, actor: &amp::ActorID, seq: u64) -> Result<amp::ChangeHash,AutomergeError> {
self.states.get(actor).and_then(|v| v.get(seq as usize - 1)).map(|c| c.hash).ok_or(AutomergeError::InvalidSeq(seq))
fn get_hash(&self, actor: &amp::ActorID, seq: u64) -> Result<amp::ChangeHash, AutomergeError> {
self.states
.get(actor)
.and_then(|v| v.get(seq as usize - 1))
.map(|c| c.hash)
.ok_or(AutomergeError::InvalidSeq(seq))
}
pub fn apply_local_change(
&mut self,
mut change: UnencodedChange
mut change: amp::UncompressedChange,
) -> Result<(amp::Patch, Rc<Change>), AutomergeError> {
self.check_for_duplicate(&change)?; // Change has already been applied
@ -111,13 +121,13 @@ impl Backend {
}
}
let bin_change : Rc<Change> = Rc::new(change.into());
let patch : amp::Patch = self.apply(vec![ bin_change.clone() ], Some(actor_seq))?;
let bin_change: Rc<Change> = Rc::new(change.try_into()?);
let patch: amp::Patch = self.apply(vec![bin_change.clone()], Some(actor_seq))?;
Ok((patch, bin_change))
}
fn check_for_duplicate(&self, change: &UnencodedChange) -> Result<(), AutomergeError> {
fn check_for_duplicate(&self, change: &amp::UncompressedChange) -> Result<(), AutomergeError> {
if self
.states
.get(&change.actor_id)
@ -179,11 +189,7 @@ impl Backend {
op_set.max_op = max(op_set.max_op, start_op + (ops.len() as u64) - 1);
op_set.apply_ops(
ops,
diffs,
&self.actors,
)?;
op_set.apply_ops(ops, diffs, &self.actors)?;
Ok(())
}
@ -271,4 +277,3 @@ impl Backend {
.collect()
}
}

View file

@ -3,16 +3,14 @@ use crate::columnar::{
ColumnEncoder, KeyIterator, ObjIterator, OperationIterator, PredIterator, ValueIterator,
};
use crate::encoding::{Decodable, Encodable};
use crate::error::AutomergeError;
use crate::error::{AutomergeError, InvalidChangeError};
use crate::op::Operation;
use automerge_protocol as amp;
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;
@ -20,83 +18,95 @@ 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: amp::ActorID,
//pub hash: amp::ChangeHash,
pub seq: u64,
#[serde(rename = "startOp")]
pub start_op: u64,
pub time: i64,
pub message: Option<String>,
pub deps: Vec<amp::ChangeHash>,
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
pub extra_bytes: Vec<u8>,
impl TryFrom<&amp::UncompressedChange> for Change {
type Error = AutomergeError;
fn try_from(value: &amp::UncompressedChange) -> Result<Self, Self::Error> {
encode(value).map_err(|e| AutomergeError::InvalidChange { source: e })
}
//pub fn max_op(&self) -> u64 {
//self.start_op + (self.operations.len() as u64) - 1
//}
}
impl UnencodedChange {
pub fn max_op(&self) -> u64 {
self.start_op + (self.operations.len() as u64) - 1
impl TryFrom<amp::UncompressedChange> for Change {
type Error = InvalidChangeError;
fn try_from(value: amp::UncompressedChange) -> Result<Self, Self::Error> {
encode(&value)
}
}
pub fn encode(&self) -> Change {
let mut buf = Vec::new();
let mut hasher = Sha256::new();
fn encode(uncompressed_change: &amp::UncompressedChange) -> Result<Change, InvalidChangeError> {
let mut buf: Vec<u8> = Vec::new();
let mut hasher = Sha256::new();
let chunk = self.encode_chunk();
let chunk = encode_chunk(uncompressed_change)?;
hasher.input(&chunk);
hasher.input(&chunk);
buf.extend(&MAGIC_BYTES);
buf.extend(&hasher.result()[0..4]);
buf.extend(&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
// ---
// 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
// ---
Ok(Change::from_bytes(buf).unwrap())
}
Change::from_bytes(buf).unwrap()
fn encode_chunk(
uncompressed_change: &amp::UncompressedChange,
) -> Result<Vec<u8>, InvalidChangeError> {
let mut chunk = vec![CHUNK_TYPE]; // chunk type is always 1
let data = encode_chunk_body(uncompressed_change)?;
// Unwrap is fine as we're writing to in memory data
leb128::write::unsigned(&mut chunk, data.len() as u64).unwrap();
chunk.extend(&data);
Ok(chunk)
}
fn encode_chunk_body(
uncompressed_change: &amp::UncompressedChange,
) -> Result<Vec<u8>, InvalidChangeError> {
let mut buf = Vec::new();
let mut deps = uncompressed_change.deps.clone();
deps.sort_unstable();
deps.len().encode(&mut buf).unwrap();
for hash in deps.iter() {
buf.write_all(&hash.0).unwrap();
}
let mut actors = Vec::new();
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
}
actors.push(uncompressed_change.actor_id.clone());
fn encode_chunk_body(&self) -> io::Result<Vec<u8>> {
let mut buf = Vec::new();
let mut deps = self.deps.clone();
deps.sort_unstable();
deps.len().encode(&mut buf)?;
for hash in deps.iter() {
buf.write_all(&hash.0)?;
}
// All these unwraps are okay because we're writing to an in memory buffer
uncompressed_change
.actor_id
.to_bytes()
.encode(&mut buf)
.unwrap();
uncompressed_change.seq.encode(&mut buf).unwrap();
uncompressed_change.start_op.encode(&mut buf).unwrap();
uncompressed_change.time.encode(&mut buf).unwrap();
uncompressed_change.message.encode(&mut buf).unwrap();
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: Vec<Operation> = uncompressed_change
.operations
.iter()
.map(Operation::try_from)
.collect::<Result<Vec<Operation>, InvalidChangeError>>()?;
let mut actors = Vec::new();
actors.push(self.actor_id.clone());
let ops_buf = ColumnEncoder::encode_ops(&self.operations, &mut actors);
actors[1..].encode(&mut buf)?;
let ops_buf = ColumnEncoder::encode_ops(&ops, &mut actors);
buf.write_all(&ops_buf)?;
buf.write_all(&self.extra_bytes)?;
actors[1..].encode(&mut buf).unwrap();
Ok(buf)
}
buf.write_all(&ops_buf).unwrap();
Ok(buf)
}
#[derive(PartialEq, Debug, Clone)]
@ -158,7 +168,9 @@ impl Change {
let mut hasher = Sha256::new();
hasher.input(&bytes[PREAMBLE_BYTES..]);
let hash = hasher.result()[..].try_into()?;
let hash = hasher.result()[..]
.try_into()
.map_err(InvalidChangeError::from)?;
let mut cursor = body.clone();
let mut deps = Vec::new();
@ -167,7 +179,7 @@ impl Change {
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()?);
deps.push(bytes[hash].try_into().map_err(InvalidChangeError::from)?);
}
let actor = amp::ActorID::from(&bytes[slice_bytes(&bytes, &mut cursor)?]);
let seq = read_slice(&bytes, &mut cursor)?;
@ -233,16 +245,15 @@ impl Change {
}
}
pub fn decode(&self) -> UnencodedChange {
UnencodedChange {
pub fn decode(&self) -> amp::UncompressedChange {
amp::UncompressedChange {
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(),
extra_bytes: self.bytes[self.extra_bytes.clone()].to_vec()
operations: self.iter_ops().map(|o| (&o).into()).collect(),
}
}
@ -263,11 +274,6 @@ impl Change {
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),
@ -290,20 +296,8 @@ impl Change {
}
}
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 {
impl From<&Change> for amp::UncompressedChange {
fn from(change: &Change) -> amp::UncompressedChange {
change.decode()
}
}
@ -349,12 +343,11 @@ const HEADER_BYTES: usize = PREAMBLE_BYTES + 1;
#[cfg(test)]
mod tests {
use super::*;
use crate::op_type::OpType;
use std::str::FromStr;
#[test]
fn test_empty_change() {
let change1 = UnencodedChange {
let change1 = amp::UncompressedChange {
start_op: 1,
seq: 2,
time: 1234,
@ -362,11 +355,10 @@ mod tests {
actor_id: amp::ActorID::from_str("deadbeefdeadbeef").unwrap(),
deps: vec![],
operations: vec![],
extra_bytes: vec![],
};
let bin1 = change1.encode();
let bin1: Change = change1.clone().try_into().unwrap();
let change2 = bin1.decode();
let bin2 = change2.encode();
let bin2 = Change::try_from(change2.clone()).unwrap();
assert_eq!(bin1, bin2);
assert_eq!(change1, change2);
}
@ -391,7 +383,7 @@ mod tests {
let keyseq1 = amp::Key::from(&opid1);
let keyseq2 = amp::Key::from(&opid2);
let insert = false;
let change1 = UnencodedChange {
let change1 = amp::UncompressedChange {
start_op: 123,
seq: 29291,
time: 12_341_231,
@ -399,89 +391,101 @@ mod tests {
actor_id: actor1,
deps: vec![],
operations: vec![
Operation {
action: OpType::Set(amp::ScalarValue::F64(10.0)),
key: key1.clone(),
obj: obj1.clone(),
amp::Op {
action: amp::OpType::Set,
key: key1,
obj: obj1.to_string(),
value: Some(amp::ScalarValue::F64(10.0)),
insert,
pred: vec![opid1.clone(), opid2.clone()],
datatype: Some(amp::DataType::Undefined),
},
Operation {
action: OpType::Set(amp::ScalarValue::Counter(-11)),
amp::Op {
action: amp::OpType::Set,
value: Some(amp::ScalarValue::Counter(-11)),
datatype: Some(amp::DataType::Counter),
key: key2.clone(),
obj: obj1.clone(),
obj: obj1.to_string(),
insert,
pred: vec![opid1.clone(), opid2.clone()],
},
Operation {
action: OpType::Set(amp::ScalarValue::Timestamp(20)),
amp::Op {
action: amp::OpType::Set,
value: Some(amp::ScalarValue::Timestamp(20)),
datatype: Some(amp::DataType::Timestamp),
key: key3,
obj: obj1.clone(),
obj: obj1.to_string(),
insert,
pred: vec![opid1.clone(), opid2],
},
Operation {
action: OpType::Set(amp::ScalarValue::Str("some value".into())),
amp::Op {
action: amp::OpType::Set,
value: Some(amp::ScalarValue::Str("some value".into())),
datatype: Some(amp::DataType::Undefined),
key: key2.clone(),
obj: obj2.clone(),
obj: obj2.to_string(),
insert,
pred: vec![opid3.clone(), opid4.clone()],
},
Operation {
action: OpType::Make(amp::ObjType::list()),
amp::Op {
action: amp::OpType::MakeMap,
value: None,
datatype: None,
key: key2.clone(),
obj: obj2.clone(),
obj: obj2.to_string(),
insert,
pred: vec![opid3.clone(), opid4.clone()],
},
Operation {
action: OpType::Set(amp::ScalarValue::Str("val1".into())),
amp::Op {
action: amp::OpType::Set,
value: Some(amp::ScalarValue::Str("val1".into())),
datatype: Some(amp::DataType::Undefined),
key: head.clone(),
obj: obj3.clone(),
obj: obj3.to_string(),
insert: true,
pred: vec![opid3.clone(), opid4.clone()],
pred: vec![opid3, opid4.clone()],
},
Operation {
action: OpType::Set(amp::ScalarValue::Str("val2".into())),
amp::Op {
action: amp::OpType::Set,
value: Some(amp::ScalarValue::Str("val2".into())),
datatype: Some(amp::DataType::Undefined),
key: head,
obj: obj3.clone(),
obj: obj3.to_string(),
insert: true,
pred: vec![opid4.clone(), opid5.clone()],
},
Operation {
action: OpType::Inc(10),
amp::Op {
action: amp::OpType::Inc,
value: Some(amp::ScalarValue::Counter(10)),
datatype: Some(amp::DataType::Counter),
key: key2,
obj: obj2,
obj: obj2.to_string(),
insert,
pred: vec![opid1.clone(), opid5.clone()],
pred: vec![opid1, opid5.clone()],
},
Operation {
action: OpType::Link(obj3.clone()),
obj: obj1,
key: key1,
insert,
pred: vec![opid1, opid3],
},
Operation {
action: OpType::Del,
obj: obj3.clone(),
amp::Op {
action: amp::OpType::Del,
value: None,
datatype: None,
obj: obj3.to_string(),
key: keyseq1,
insert: true,
pred: vec![opid4.clone(), opid5.clone()],
},
Operation {
action: OpType::Del,
obj: obj3,
amp::Op {
action: amp::OpType::Del,
value: None,
datatype: None,
obj: obj3.to_string(),
key: keyseq2,
insert: true,
pred: vec![opid4, opid5],
},
],
extra_bytes: vec![],
};
let bin1 = change1.encode();
let bin1 = Change::try_from(change1.clone()).unwrap();
let change2 = bin1.decode();
let bin2 = change2.encode();
let bin2 = Change::try_from(change2.clone()).unwrap();
assert_eq!(bin1, bin2);
assert_eq!(change1, change2);
Ok(())

View file

@ -60,7 +60,6 @@ impl Encodable for &[u8] {
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>,
@ -226,7 +225,6 @@ impl<'a> Iterator for OperationIterator<'a> {
let key = self.keys.next()?;
let pred = self.pred.next()?;
let value = self.value.next()?;
let child = self.chld.next()?;
let action = match action {
Action::Set => OpType::Set(value),
Action::MakeList => OpType::Make(amp::ObjType::list()),
@ -235,7 +233,6 @@ impl<'a> Iterator for OperationIterator<'a> {
Action::MakeTable => OpType::Make(amp::ObjType::table()),
Action::Del => OpType::Del,
Action::Inc => OpType::Inc(value.to_i64()?),
Action::Link => OpType::Link(child),
};
Some(Operation {
action,
@ -454,16 +451,6 @@ impl ChildEncoder {
self.ctr.append_null();
}
fn append(&mut self, obj: &amp::ObjectID, actors: &mut Vec<amp::ActorID>) {
match obj {
amp::ObjectID::Root => self.append_null(),
amp::ObjectID::ID(amp::OpID(ctr, actor)) => {
self.actor.append_value(map_actor(&actor, actors));
self.ctr.append_value(*ctr);
}
}
}
fn finish(self) -> Vec<ColData> {
vec![
self.actor.finish(COL_CHILD_ACTOR),
@ -483,7 +470,10 @@ pub(crate) struct ColumnEncoder {
}
impl ColumnEncoder {
pub fn encode_ops(ops: &[Operation], actors: &mut Vec<amp::ActorID>) -> Vec<u8> {
pub fn encode_ops<'a, 'b, I>(ops: I, actors: &'a mut Vec<amp::ActorID>) -> Vec<u8>
where
I: IntoIterator<Item = &'b Operation>,
{
let mut e = Self::new();
e.encode(ops, actors);
e.finish()
@ -501,7 +491,10 @@ impl ColumnEncoder {
}
}
fn encode(&mut self, ops: &[Operation], actors: &mut Vec<amp::ActorID>) {
fn encode<'a, 'b, 'c, I>(&'a mut self, ops: I, actors: &'b mut Vec<amp::ActorID>)
where
I: IntoIterator<Item = &'c Operation>,
{
for op in ops {
self.append(op, actors)
}
@ -528,11 +521,6 @@ impl ColumnEncoder {
self.chld.append_null();
Action::Del
}
OpType::Link(child) => {
self.val.append_null();
self.chld.append(child, actors);
Action::Link
}
OpType::Make(kind) => {
self.val.append_null();
self.chld.append_null();
@ -602,9 +590,8 @@ pub(crate) enum Action {
MakeText,
Inc,
MakeTable,
Link,
}
const ACTIONS: [Action; 8] = [
const ACTIONS: [Action; 7] = [
Action::MakeMap,
Action::Set,
Action::MakeList,
@ -612,7 +599,6 @@ const ACTIONS: [Action; 8] = [
Action::MakeText,
Action::Inc,
Action::MakeTable,
Action::Link,
];
impl Decodable for Action {

View file

@ -59,7 +59,7 @@ impl ConcurrentOperations {
}
match new_op.action {
InternalOpType::Set(_) | InternalOpType::Link(_) | InternalOpType::Make(_) => {
InternalOpType::Set(_) | InternalOpType::Make(_) => {
self.ops.push(new_op.clone());
}
_ => {}

View file

@ -1,64 +1,74 @@
use automerge_protocol as amp;
use std::error::Error;
use std::fmt;
//use std::error::Error;
use std::fmt::Debug;
use thiserror::Error;
#[derive(Debug, PartialEq)]
#[derive(Error, Debug, PartialEq)]
pub enum AutomergeError {
#[error("Missing object ID")]
MissingObjectError,
#[error("Missing index in op {0}")]
MissingIndex(amp::OpID),
MissingChildID(String),
MissingElement(amp::ObjectID, amp::OpID),
#[error("Missing element ID: {0}")]
MissingElement(amp::ObjectID, amp::ElementID),
#[error("No path to object: {0}")]
NoPathToObject(amp::ObjectID),
#[error("Cant extract object: {0}")]
CantExtractObject(amp::ObjectID),
LinkMissingChild,
#[error("Skiplist error: {0}")]
SkipListError(String),
#[error("Index out of bounds: {0}")]
IndexOutOfBounds(usize),
#[error("Invalid op id: {0}")]
InvalidOpID(String),
#[error("Invalid object ID: {0}")]
InvalidObjectID(String),
#[error("Missing value")]
MissingValue,
#[error("Unknown error: {0}")]
GeneralError(String),
#[error("Missing number value")]
MissingNumberValue,
#[error("Unknown version: {0}")]
UnknownVersion(u64),
#[error("Duplicate change {0}")]
DuplicateChange(String),
#[error("Diverged state {0}")]
DivergedState(String),
#[error("Change decompression error: {0}")]
ChangeDecompressError(String),
#[error("Invalid seq {0}")]
InvalidSeq(u64),
#[error("Map key in seq")]
MapKeyInSeq,
#[error("Head to opid")]
HeadToOpID,
#[error("Doc format not implemented yet")]
DocFormatUnimplemented,
#[error("Divergeentchange {0}")]
DivergentChange(String),
#[error("Encode failed")]
EncodeFailed,
#[error("Decode failed")]
DecodeFailed,
InvalidChange,
ChangeBadFormat,
#[error("Invalid change")]
InvalidChange {
#[from]
source: InvalidChangeError,
},
#[error("Change bad format: {source}")]
ChangeBadFormat {
#[source]
source: amp::error::InvalidChangeHashSlice,
},
#[error("Encoding error")]
EncodingError,
}
impl From<amp::error::InvalidChangeHashSlice> for AutomergeError {
fn from(_: amp::error::InvalidChangeHashSlice) -> AutomergeError {
AutomergeError::ChangeBadFormat
}
}
impl fmt::Display for AutomergeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for AutomergeError {}
#[derive(Debug)]
#[derive(Error, Debug)]
#[error("Invalid element ID: {0}")]
pub struct InvalidElementID(pub String);
impl fmt::Display for InvalidElementID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for InvalidElementID {}
impl From<leb128::read::Error> for AutomergeError {
fn from(_err: leb128::read::Error) -> Self {
AutomergeError::EncodingError
@ -70,3 +80,21 @@ impl From<std::io::Error> for AutomergeError {
AutomergeError::EncodingError
}
}
#[derive(Error, Debug, PartialEq)]
pub enum InvalidChangeError {
#[error("Change contained an operation with action 'set' which did not have a 'value'")]
SetOpWithoutValue,
#[error("Received an inc operation which had an invalid value, value was: {op_value:?}")]
IncOperationWithInvalidValue { op_value: Option<amp::ScalarValue> },
#[error("Change contained an invalid object id: {}", source.0)]
InvalidObjectID {
#[from]
source: amp::error::InvalidObjectID,
},
#[error("Change contained an invalid hash: {:?}", source.0)]
InvalidChangeHash {
#[from]
source: amp::error::InvalidChangeHashSlice,
},
}

View file

@ -44,14 +44,12 @@ impl InternalOp {
pub fn is_inc(&self) -> bool {
matches!(self.action, InternalOpType::Inc(_))
}
}
#[derive(PartialEq, Debug, Clone)]
pub(crate) enum InternalOpType {
Make(amp::ObjType),
Del,
Link(ObjectID),
Inc(i64),
Set(amp::ScalarValue),
}

View file

@ -31,7 +31,7 @@ mod pending_diff;
mod time;
pub use backend::Backend;
pub use change::{Change, UnencodedChange};
pub use change::Change;
pub use error::AutomergeError;
pub use op::Operation;
pub use op_type::OpType;

View file

@ -1,13 +1,12 @@
use crate::actor_map::ActorMap;
use crate::concurrent_operations::ConcurrentOperations;
use crate::error::AutomergeError;
use crate::internal::{ElementID, Key, OpID};
use crate::op_handle::OpHandle;
use crate::ordered_set::{OrderedSet, SkipList};
use fxhash::FxBuildHasher;
use automerge_protocol as amp;
use fxhash::FxBuildHasher;
//use im_rc::{HashMap, HashSet};
use std::collections::{ HashMap, HashSet };
use std::collections::{HashMap, HashSet};
/// ObjectHistory is what the OpSet uses to store operations for a particular
/// key, they represent the two possible container types in automerge, a map or
@ -52,38 +51,44 @@ impl ObjState {
}
// this is the efficient way to do it for a SkipList
pub fn index_of(&self, id: OpID) -> Result<usize, AutomergeError> {
pub fn index_of(&self, id: OpID) -> Option<usize> {
let mut prev_id = id.into();
let mut index = None;
// reverse walk through the following/insertions and looking for something that not deleted
while index.is_none() {
prev_id = self.get_previous(&prev_id)?;
prev_id = match self.get_previous(&prev_id) {
Some(p) => p,
None => return None,
};
match prev_id {
ElementID::ID(id) => {
// FIXME maybe I can speed this up with self.props.get before looking for
index = self.seq.index_of(&id)
}
ElementID::Head => break,
ElementID::Head => return None,
}
}
Ok(index.map(|i| i + 1).unwrap_or(0))
index.map(|i| i + 1)
}
fn get_previous(&self, element: &ElementID) -> Result<ElementID, AutomergeError> {
let parent_id = self.get_parent(element).unwrap();
fn get_previous(&self, element: &ElementID) -> Option<ElementID> {
let parent_id = match self.get_parent(element) {
Some(p) => p,
None => return None,
};
let children = self.insertions_after(&parent_id);
let pos = children
.iter()
.position(|k| k == element)
.ok_or_else(|| AutomergeError::GeneralError("get_previous".to_string()))?;
let pos = match children.iter().position(|k| k == element) {
Some(p) => p,
None => return None,
};
if pos == 0 {
Ok(parent_id)
Some(parent_id)
} else {
let mut prev_id = children[pos - 1]; // FIXME - use refs here
loop {
match self.insertions_after(&prev_id).last() {
Some(id) => prev_id = *id,
None => return Ok(prev_id),
None => return Some(prev_id),
}
}
}

View file

@ -1,4 +1,5 @@
// FIXME
use crate::error::InvalidChangeError;
use crate::op_type::OpType;
use automerge_protocol as amp;
use serde::ser::SerializeStruct;
@ -6,6 +7,8 @@ use serde::{
de::{Error, MapAccess, Unexpected, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::convert::TryFrom;
use std::str::FromStr;
fn read_field<'de, T, M>(
name: &'static str,
@ -106,7 +109,7 @@ impl Serialize for Operation {
match &self.action {
OpType::Set(amp::ScalarValue::Timestamp(_)) => fields += 2,
OpType::Set(amp::ScalarValue::Counter(_)) => fields += 2,
OpType::Link(_) | OpType::Inc(_) | OpType::Set(_) => fields += 1,
OpType::Inc(_) | OpType::Set(_) => fields += 1,
_ => {}
}
@ -118,7 +121,6 @@ impl Serialize for Operation {
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::ScalarValue::Counter(value)) => {
op.serialize_field("value", &value)?;
@ -161,7 +163,6 @@ impl<'de> Deserialize<'de> for Operation {
let mut insert: Option<bool> = None;
let mut datatype: Option<amp::DataType> = None;
let mut value: Option<Option<amp::ScalarValue>> = 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)?,
@ -172,7 +173,6 @@ impl<'de> Deserialize<'de> for Operation {
"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)),
}
}
@ -192,9 +192,6 @@ impl<'de> Deserialize<'de> for Operation {
OpType::Make(amp::ObjType::Sequence(amp::SequenceType::Text))
}
amp::OpType::Del => OpType::Del,
amp::OpType::Link => {
OpType::Link(child.ok_or_else(|| Error::missing_field("pred"))?)
}
amp::OpType::Set => OpType::Set(value.unwrap_or(amp::ScalarValue::Null)),
amp::OpType::Inc => match value {
Some(amp::ScalarValue::Int(n)) => Ok(OpType::Inc(n)),
@ -228,3 +225,56 @@ impl<'de> Deserialize<'de> for Operation {
}
}
impl TryFrom<&amp::Op> for Operation {
type Error = InvalidChangeError;
fn try_from(op: &amp::Op) -> Result<Self, Self::Error> {
let op_type = OpType::try_from(op)?;
let obj_id = amp::ObjectID::from_str(&op.obj)?;
Ok(Operation {
action: op_type,
obj: obj_id,
key: op.key.clone(),
pred: op.pred.clone(),
insert: op.insert,
})
}
}
impl Into<amp::Op> for &Operation {
fn into(self) -> amp::Op {
let value = match &self.action {
OpType::Del => None,
OpType::Set(v) => Some(v.clone()),
OpType::Make(_) => None,
OpType::Inc(i) => Some(amp::ScalarValue::Counter(*i)),
};
let datatype = value
.as_ref()
.and_then(|v| match (v.datatype(), &self.action) {
(Some(d), _) => Some(d),
(None, OpType::Set(..)) => Some(amp::DataType::Undefined),
_ => None,
});
amp::Op {
obj: self.obj.to_string(),
value,
action: match self.action {
OpType::Inc(_) => amp::OpType::Inc,
OpType::Make(amp::ObjType::Map(amp::MapType::Map)) => amp::OpType::MakeMap,
OpType::Make(amp::ObjType::Map(amp::MapType::Table)) => amp::OpType::MakeTable,
OpType::Make(amp::ObjType::Sequence(amp::SequenceType::List)) => {
amp::OpType::MakeList
}
OpType::Make(amp::ObjType::Sequence(amp::SequenceType::Text)) => {
amp::OpType::MakeText
}
OpType::Set(..) => amp::OpType::Set,
OpType::Del => amp::OpType::Del,
},
pred: self.pred.clone(),
insert: self.insert,
key: self.key.clone(),
datatype,
}
}
}

View file

@ -44,7 +44,6 @@ impl OpHandle {
pub fn child(&self) -> Option<ObjectID> {
match &self.action {
InternalOpType::Make(_) => Some(self.id.into()),
InternalOpType::Link(obj) => Some(*obj),
_ => None,
}
}

View file

@ -75,9 +75,9 @@ impl OpSet {
}
pub fn heads(&self) -> Vec<amp::ChangeHash> {
let mut deps: Vec<_> = self.deps.iter().cloned().collect();
deps.sort_unstable();
deps
let mut deps: Vec<_> = self.deps.iter().cloned().collect();
deps.sort_unstable();
deps
}
pub fn apply_op(
@ -122,9 +122,9 @@ impl OpSet {
.operation_key()
.to_opid()
.ok_or(AutomergeError::HeadToOpID)?;
let index = object.index_of(id)?;
let index = object.index_of(id).unwrap_or(0);
object.seq.insert_index(index, id);
Some(PendingDiff::SeqInsert(op.clone(), index, op.id ))
Some(PendingDiff::SeqInsert(op.clone(), index, op.id))
}
(false, false) => None,
};
@ -314,7 +314,6 @@ impl OpSet {
InternalOpType::Make(_) => {
self.gen_obj_diff(&op.id.into(), pending_diffs, actors)?
}
InternalOpType::Link(ref child) => self.construct_object(&child, actors)?,
_ => panic!("del or inc found in field_operations"),
};
opid_to_value.insert(actors.export_opid(&op.id), link);
@ -357,9 +356,6 @@ impl OpSet {
// FIXME
self.gen_obj_diff(&op.id.into(), pending_diffs, actors)?
}
InternalOpType::Link(ref child_id) => {
self.construct_object(&child_id, actors)?
}
_ => panic!("del or inc found in field_operations"),
};
opid_to_value.insert(actors.export_opid(&op.id), link);

View file

@ -1,11 +1,12 @@
use crate::error;
use automerge_protocol as amp;
use serde::{Serialize, Serializer};
use std::convert::TryFrom;
#[derive(PartialEq, Debug, Clone)]
pub enum OpType {
Make(amp::ObjType),
Del,
Link(amp::ObjectID),
Inc(i64),
Set(amp::ScalarValue),
}
@ -21,10 +22,39 @@ impl Serialize for OpType {
OpType::Make(amp::ObjType::Sequence(amp::SequenceType::List)) => "makeList",
OpType::Make(amp::ObjType::Sequence(amp::SequenceType::Text)) => "makeText",
OpType::Del => "del",
OpType::Link(_) => "link",
OpType::Inc(_) => "inc",
OpType::Set(_) => "set",
};
serializer.serialize_str(s)
}
}
impl TryFrom<&amp::Op> for OpType {
type Error = error::InvalidChangeError;
fn try_from(op: &amp::Op) -> Result<Self, Self::Error> {
match op.action {
amp::OpType::MakeMap => Ok(OpType::Make(amp::ObjType::Map(amp::MapType::Map))),
amp::OpType::MakeTable => Ok(OpType::Make(amp::ObjType::Map(amp::MapType::Table))),
amp::OpType::MakeList => Ok(OpType::Make(amp::ObjType::Sequence(
amp::SequenceType::List,
))),
amp::OpType::MakeText => Ok(OpType::Make(amp::ObjType::Sequence(
amp::SequenceType::Text,
))),
amp::OpType::Del => Ok(OpType::Del),
amp::OpType::Set => op
.value
.as_ref()
.map(|v| OpType::Set(v.clone()))
.ok_or(error::InvalidChangeError::SetOpWithoutValue),
amp::OpType::Inc => match &op.value {
Some(amp::ScalarValue::Int(i)) => Ok(OpType::Inc(*i)),
Some(amp::ScalarValue::Uint(u)) => Ok(OpType::Inc(*u as i64)),
Some(amp::ScalarValue::Counter(i)) => Ok(OpType::Inc(*i)),
val => Err(error::InvalidChangeError::IncOperationWithInvalidValue {
op_value: val.clone(),
}),
},
}
}
}

View file

@ -2,10 +2,10 @@
use fxhash::FxBuildHasher;
//use im_rc::HashMap;
use std::collections::HashMap;
use rand::rngs::ThreadRng;
use rand::Rng;
use std::cmp::{max, min};
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::iter::Iterator;
@ -822,25 +822,33 @@ mod tests {
#[test]
fn test_remove_key_big() {
let mut s = SkipList::<String>::new();
let mut strings: Vec<String> = Vec::new();
for i in 0..10000 {
let j = 9999 - i;
s.insert_head(format!("a{}", j));
strings.push(format!("a{}", j));
}
let mut s = SkipList::<&str>::new();
for string in strings.iter() {
s.insert_head(string);
}
//for i in 0..10000 {
//let j = 9999 - i;
//s.insert_head(format!("a{}", j));
//}
assert_eq!(s.index_of(&"a20".to_string()), Some(20));
assert_eq!(s.index_of(&"a500".to_string()), Some(500));
assert_eq!(s.index_of(&"a1000".to_string()), Some(1000));
assert_eq!(s.index_of(&"a20"), Some(20));
assert_eq!(s.index_of(&"a500"), Some(500));
assert_eq!(s.index_of(&"a1000"), Some(1000));
for i in 0..5000 {
let j = (4999 - i) * 2 + 1;
s.remove_index(j);
}
assert_eq!(s.index_of(&"a4000".to_string()), Some(2000));
assert_eq!(s.index_of(&"a1000".to_string()), Some(500));
assert_eq!(s.index_of(&"a500".to_string()), Some(250));
assert_eq!(s.index_of(&"a20".to_string()), Some(10));
assert_eq!(s.index_of(&"a4000"), Some(2000));
assert_eq!(s.index_of(&"a1000"), Some(500));
assert_eq!(s.index_of(&"a500"), Some(250));
assert_eq!(s.index_of(&"a20"), Some(10));
}
#[test]

View file

@ -1,6 +1,6 @@
use crate::actor_map::ActorMap;
use crate::internal::{Key, OpID};
use crate::op_handle::OpHandle;
use crate::actor_map::ActorMap;
use automerge_protocol as amp;
#[derive(Debug, Clone, PartialEq)]
@ -21,7 +21,10 @@ impl PendingDiff {
pub fn edit(&self, actors: &ActorMap) -> Option<amp::DiffEdit> {
match *self {
Self::SeqInsert(_, index, opid ) => Some(amp::DiffEdit::Insert { index , elem_id: actors.export_opid(&opid).into() }),
Self::SeqInsert(_, index, opid) => Some(amp::DiffEdit::Insert {
index,
elem_id: actors.export_opid(&opid).into(),
}),
Self::SeqRemove(_, index) => Some(amp::DiffEdit::Remove { index }),
_ => None,
}

File diff suppressed because it is too large Load diff

View file

@ -1,71 +1,70 @@
extern crate automerge_backend;
use automerge_backend::{Backend, UnencodedChange};
use automerge_backend::{OpType, Operation};
use automerge_backend::Backend;
use automerge_backend::Change;
use automerge_protocol as protocol;
use automerge_protocol as amp;
use automerge_protocol::{
ActorID, ChangeHash, DataType, Diff, DiffEdit, ElementID, MapDiff, MapType, ObjType, ObjectID,
Op, Patch, Request, RequestType, SeqDiff, SequenceType,
ActorID, ChangeHash, DataType, Diff, DiffEdit, ElementID, MapDiff, MapType, ObjectID, Op,
Patch, SeqDiff, SequenceType, UncompressedChange,
};
use maplit::hashmap;
use std::collections::HashSet;
use std::convert::TryInto;
use std::{collections::HashSet, str::FromStr};
#[test]
fn test_apply_local_change() {
let actor: ActorID = "eb738e04ef8848ce8b77309b6c7f7e39".try_into().unwrap();
let change_request = Request {
actor: actor.clone(),
seq: 1,
version: 0,
let change_request = UncompressedChange {
actor_id: actor.clone(),
time: 0,
message: None,
undoable: false,
time: None,
deps: None,
ops: Some(vec![Op {
seq: 1,
deps: Vec::new(),
start_op: 1,
operations: vec![Op {
action: protocol::OpType::Set,
value: Some("magpie".into()),
datatype: Some(DataType::Undefined),
key: "bird".into(),
obj: ObjectID::Root.to_string(),
child: None,
insert: false,
}]),
request_type: RequestType::Change,
pred: Vec::new(),
}],
};
let mut backend = Backend::init();
let patch = backend.apply_local_change(change_request).unwrap();
let patch = backend.apply_local_change(change_request).unwrap().0;
let changes = backend.get_changes(&[]);
let expected_change = UnencodedChange {
let expected_change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
time: changes[0].time,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set("magpie".into()),
obj: ObjectID::Root,
operations: vec![Op {
action: amp::OpType::Set,
value: Some("magpie".into()),
datatype: Some(DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "bird".into(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
assert_eq!(changes[0], &expected_change);
let expected_patch = Patch {
actor: Some(actor.clone()),
max_op: 1,
seq: Some(1),
version: 1,
clock: hashmap! {
actor => 1,
},
can_undo: false,
can_redo: false,
deps: vec![changes[0].hash],
deps: Vec::new(),
diffs: Some(Diff::Map(MapDiff {
object_id: ObjectID::Root,
obj_type: MapType::Map,
@ -82,44 +81,40 @@ fn test_apply_local_change() {
#[test]
fn test_error_on_duplicate_requests() {
let actor: ActorID = "37704788917a499cb0206fa8519ac4d9".try_into().unwrap();
let change_request1 = Request {
actor: actor.clone(),
let change_request1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
version: 0,
message: None,
undoable: false,
time: None,
deps: None,
ops: Some(vec![Op {
time: 0,
deps: Vec::new(),
start_op: 1,
operations: vec![Op {
action: protocol::OpType::Set,
obj: ObjectID::Root.to_string(),
key: "bird".into(),
child: None,
value: Some("magpie".into()),
datatype: Some(DataType::Undefined),
insert: false,
}]),
request_type: RequestType::Change,
pred: Vec::new(),
}],
};
let change_request2 = Request {
actor,
let change_request2 = UncompressedChange {
actor_id: actor,
seq: 2,
version: 0,
message: None,
undoable: false,
time: None,
deps: None,
ops: Some(vec![Op {
time: 0,
deps: Vec::new(),
start_op: 2,
operations: vec![Op {
action: protocol::OpType::Set,
obj: ObjectID::Root.to_string(),
key: "bird".into(),
value: Some("jay".into()),
child: None,
insert: false,
datatype: Some(DataType::Undefined),
}]),
request_type: RequestType::Change,
pred: Vec::new(),
}],
};
let mut backend = Backend::init();
backend.apply_local_change(change_request1.clone()).unwrap();
@ -131,107 +126,112 @@ fn test_error_on_duplicate_requests() {
#[test]
fn test_handle_concurrent_frontend_and_backend_changes() {
let actor: ActorID = "cb55260e9d7e457886a4fc73fd949202".try_into().unwrap();
let local1 = Request {
actor: actor.clone(),
let local1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
version: 0,
time: None,
deps: None,
time: 0,
deps: Vec::new(),
message: None,
undoable: false,
request_type: RequestType::Change,
ops: Some(vec![Op {
start_op: 1,
operations: vec![Op {
action: protocol::OpType::Set,
obj: ObjectID::Root.to_string(),
key: "bird".into(),
value: Some("magpie".into()),
child: None,
datatype: Some(DataType::Undefined),
insert: false,
}]),
pred: Vec::new(),
}],
};
let local2 = Request {
actor: actor.clone(),
let local2 = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
version: 0,
time: None,
deps: None,
start_op: 2,
time: 0,
deps: Vec::new(),
message: None,
request_type: RequestType::Change,
undoable: false,
ops: Some(vec![Op {
operations: vec![Op {
action: protocol::OpType::Set,
obj: ObjectID::Root.to_string(),
key: "bird".into(),
value: Some("jay".into()),
child: None,
datatype: Some(DataType::Undefined),
insert: false,
}]),
pred: vec![actor.op_id_at(1)],
}],
};
let remote_actor: ActorID = "6d48a01318644eed90455d2cb68ac657".try_into().unwrap();
let remote1 = UnencodedChange {
let remote1 = UncompressedChange {
actor_id: remote_actor.clone(),
seq: 1,
start_op: 1,
time: 0,
deps: Vec::new(),
message: None,
operations: vec![Operation {
action: OpType::Set("goldfish".into()),
obj: ObjectID::Root,
operations: vec![Op {
action: protocol::OpType::Set,
value: Some("goldfish".into()),
datatype: Some(DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "fish".into(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let mut expected_change1 = UnencodedChange {
let mut expected_change1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set("magpie".into()),
obj: ObjectID::Root,
operations: vec![Op {
action: protocol::OpType::Set,
value: Some("magpie".into()),
datatype: Some(DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "bird".into(),
pred: Vec::new(),
insert: false,
}],
};
let mut expected_change2 = UnencodedChange {
let mut expected_change2 = UncompressedChange {
actor_id: remote_actor,
seq: 1,
start_op: 1,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set("goldfish".into()),
operations: vec![Op {
action: protocol::OpType::Set,
value: Some("goldfish".into()),
datatype: Some(DataType::Undefined),
key: "fish".into(),
obj: ObjectID::Root,
obj: ObjectID::Root.to_string(),
pred: Vec::new(),
insert: false,
}],
};
let mut expected_change3 = UnencodedChange {
actor_id: actor,
let mut expected_change3 = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
start_op: 2,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set("jay".into()),
obj: ObjectID::Root,
operations: vec![Op {
action: protocol::OpType::Set,
value: Some("jay".into()),
datatype: Some(DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "bird".into(),
pred: vec!["1@cb55260e9d7e457886a4fc73fd949202".try_into().unwrap()],
pred: vec![actor.op_id_at(1)],
insert: false,
}],
};
@ -255,175 +255,178 @@ fn test_handle_concurrent_frontend_and_backend_changes() {
expected_change3.time = change23.time;
expected_change3.deps = vec![change01.hash];
assert_eq!(change01, &&expected_change1.encode());
assert_eq!(change12, &expected_change2.encode());
assert_eq!(change23, &&expected_change3.encode());
assert_eq!(change01, &&expected_change1.try_into().unwrap());
assert_eq!(change12, &expected_change2.try_into().unwrap());
assert_changes_equal(change23.decode(), expected_change3.clone());
assert_eq!(change23, &&expected_change3.try_into().unwrap());
}
#[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 remote1 = UnencodedChange {
let remote1: Change = UncompressedChange {
actor_id: remote_actor.clone(),
seq: 1,
start_op: 1,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Make(ObjType::Sequence(SequenceType::List)),
operations: vec![Op {
action: protocol::OpType::MakeList,
value: None,
datatype: Some(DataType::Undefined),
key: "birds".into(),
obj: ObjectID::Root,
obj: ObjectID::Root.to_string(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let remote2 = UnencodedChange {
actor_id: remote_actor,
let remote2: Change = UncompressedChange {
actor_id: remote_actor.clone(),
seq: 2,
start_op: 2,
time: 0,
message: None,
deps: vec![remote1.hash],
operations: vec![Operation {
action: OpType::Set("magpie".into()),
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
operations: vec![Op {
action: protocol::OpType::Set,
value: Some("magpie".into()),
datatype: Some(DataType::Undefined),
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
key: ElementID::Head.into(),
insert: true,
pred: Vec::new(),
}],
}
.encode();
.try_into()
.unwrap();
let local1 = Request {
actor: actor.clone(),
let local1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
version: 1,
message: None,
time: None,
deps: None,
undoable: false,
request_type: RequestType::Change,
ops: Some(vec![Op {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
time: 0,
deps: vec![remote1.hash],
start_op: 2,
operations: vec![Op {
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("goldfinch".into()),
key: 0.into(),
key: ElementID::Head.into(),
datatype: Some(DataType::Undefined),
insert: true,
child: None,
}]),
pred: Vec::new(),
}],
};
let local2 = Request {
actor: actor.clone(),
let local2 = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
version: 1,
message: None,
deps: None,
time: None,
undoable: false,
request_type: RequestType::Change,
ops: Some(vec![Op {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
deps: Vec::new(),
time: 0,
start_op: 3,
operations: vec![Op {
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("wagtail".into()),
key: 1.into(),
key: actor.op_id_at(2).into(),
insert: true,
pred: Vec::new(),
datatype: Some(DataType::Undefined),
child: None,
}]),
}],
};
let local3 = Request {
actor: actor.clone(),
let local3 = UncompressedChange {
actor_id: actor.clone(),
seq: 3,
version: 4,
message: None,
deps: None,
time: None,
undoable: false,
request_type: RequestType::Change,
ops: Some(vec![
deps: vec![remote2.hash],
time: 0,
start_op: 4,
operations: vec![
Op {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
key: 0.into(),
key: remote_actor.op_id_at(2).into(),
value: Some("Magpie".into()),
insert: false,
child: None,
datatype: Some(DataType::Undefined),
pred: vec![remote_actor.op_id_at(2)],
},
Op {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".into(),
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
key: 1.into(),
key: actor.op_id_at(2).into(),
value: Some("Goldfinch".into()),
child: None,
insert: false,
datatype: Some(DataType::Undefined),
pred: vec![actor.op_id_at(2)],
},
]),
],
};
let mut expected_change1 = UnencodedChange {
let mut expected_change1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 2,
time: 0,
message: None,
deps: vec![remote1.hash],
operations: vec![Operation {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
action: OpType::Set("goldfinch".into()),
operations: vec![Op {
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("goldfinch".into()),
datatype: Some(DataType::Undefined),
key: ElementID::Head.into(),
insert: true,
pred: Vec::new(),
}],
};
let mut expected_change2 = UnencodedChange {
let mut expected_change2 = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
start_op: 3,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
action: OpType::Set("wagtail".into()),
key: ElementID::from_str("2@8f389df8fecb4ddc989102321af3578e")
.unwrap()
.into(),
operations: vec![Op {
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("wagtail".into()),
datatype: Some(DataType::Undefined),
key: actor.op_id_at(2).into(),
insert: true,
pred: Vec::new(),
}],
};
let mut expected_change3 = UnencodedChange {
actor_id: actor,
let mut expected_change3 = UncompressedChange {
actor_id: actor.clone(),
seq: 3,
start_op: 4,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![
Operation {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
action: OpType::Set("Magpie".into()),
key: ElementID::from_str("2@9ba21574dc44411b8ce37bc6037a9687")
.unwrap()
.into(),
pred: vec!["2@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap()],
Op {
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("Magpie".into()),
datatype: Some(DataType::Undefined),
key: remote_actor.op_id_at(2).into(),
pred: vec![remote_actor.op_id_at(2)],
insert: false,
},
Operation {
obj: "1@9ba21574dc44411b8ce37bc6037a9687".try_into().unwrap(),
action: OpType::Set("Goldfinch".into()),
key: ElementID::from_str("2@8f389df8fecb4ddc989102321af3578e")
.unwrap()
.into(),
pred: vec!["2@8f389df8fecb4ddc989102321af3578e".try_into().unwrap()],
Op {
obj: ObjectID::from(remote_actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("Goldfinch".into()),
datatype: Some(DataType::Undefined),
key: actor.op_id_at(2).into(),
pred: vec![actor.op_id_at(2)],
insert: false,
},
],
@ -452,85 +455,80 @@ fn test_transform_list_indexes_into_element_ids() {
expected_change3.time = change34.time;
expected_change3.deps = vec![remote2.hash, change23.hash];
assert_eq!(change12, &expected_change1.encode());
assert_eq!(change23, &expected_change2.encode());
assert_changes_equal(change34, expected_change3);
assert_eq!(change12, &expected_change1.try_into().unwrap());
assert_changes_equal(change23.decode(), expected_change2.clone());
assert_eq!(change23, &expected_change2.try_into().unwrap());
}
#[test]
fn test_handle_list_insertion_and_deletion_in_same_change() {
let actor: ActorID = "0723d2a1940744868ffd6b294ada813f".try_into().unwrap();
let local1 = Request {
actor: actor.clone(),
let local1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
version: 0,
request_type: RequestType::Change,
message: None,
time: None,
undoable: false,
deps: None,
ops: Some(vec![Op {
time: 0,
deps: Vec::new(),
start_op: 1,
operations: vec![Op {
obj: ObjectID::Root.to_string(),
action: protocol::OpType::MakeList,
key: "birds".into(),
child: None,
datatype: None,
value: None,
insert: false,
}]),
pred: Vec::new(),
}],
};
let local2 = Request {
actor: actor.clone(),
let local2 = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
version: 0,
request_type: RequestType::Change,
message: None,
time: None,
undoable: false,
deps: None,
ops: Some(vec![
time: 0,
deps: Vec::new(),
start_op: 2,
operations: vec![
Op {
obj: "1@0723d2a1940744868ffd6b294ada813f".into(),
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
key: 0.into(),
key: ElementID::Head.into(),
insert: true,
value: Some("magpie".into()),
child: None,
datatype: Some(DataType::Undefined),
pred: Vec::new(),
},
Op {
obj: "1@0723d2a1940744868ffd6b294ada813f".into(),
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: protocol::OpType::Del,
key: 0.into(),
child: None,
key: actor.op_id_at(2).into(),
insert: false,
value: None,
datatype: None,
pred: vec![actor.op_id_at(2)],
},
]),
],
};
let mut expected_patch = Patch {
actor: Some(actor.clone()),
seq: Some(2),
version: 2,
max_op: 3,
clock: hashmap! {
"0723d2a1940744868ffd6b294ada813f".try_into().unwrap() => 2
actor.clone() => 2
},
can_undo: false,
can_redo: false,
deps: Vec::new(),
diffs: Some(Diff::Map(MapDiff {
object_id: ObjectID::Root,
obj_type: MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
"1@0723d2a1940744868ffd6b294ada813f".try_into().unwrap() => Diff::Seq(SeqDiff{
object_id: "1@0723d2a1940744868ffd6b294ada813f".try_into().unwrap(),
actor.op_id_at(1) => Diff::Seq(SeqDiff{
object_id: ObjectID::from(actor.op_id_at(1)),
obj_type: SequenceType::List,
edits: vec![
DiffEdit::Insert{index: 0},
DiffEdit::Insert{index: 0, elem_id: actor.op_id_at(2).into()},
DiffEdit::Remove{index: 0},
],
props: hashmap!{},
@ -542,7 +540,7 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
let mut backend = Backend::init();
backend.apply_local_change(local1).unwrap();
let patch = backend.apply_local_change(local2).unwrap();
let patch = backend.apply_local_change(local2).unwrap().0;
expected_patch.deps = patch.deps.clone();
assert_eq!(patch, expected_patch);
@ -551,50 +549,56 @@ fn test_handle_list_insertion_and_deletion_in_same_change() {
let change1 = changes[0].clone();
let change2 = changes[1].clone();
let expected_change1 = UnencodedChange {
let expected_change1 = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
time: change1.time,
message: None,
deps: Vec::new(),
operations: vec![Operation {
obj: ObjectID::Root,
action: OpType::Make(ObjType::Sequence(SequenceType::List)),
operations: vec![Op {
obj: ObjectID::Root.to_string(),
action: protocol::OpType::MakeList,
value: None,
datatype: None,
key: "birds".into(),
insert: false,
pred: Vec::new(),
}],
}
.encode();
.try_into()
.unwrap();
let expected_change2 = UnencodedChange {
actor_id: actor,
let expected_change2 = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
start_op: 2,
time: change2.time,
message: None,
deps: vec![change1.hash],
operations: vec![
Operation {
obj: "1@0723d2a1940744868ffd6b294ada813f".try_into().unwrap(),
action: OpType::Set("magpie".into()),
Op {
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: protocol::OpType::Set,
value: Some("magpie".into()),
datatype: Some(DataType::Undefined),
key: ElementID::Head.into(),
insert: true,
pred: Vec::new(),
},
Operation {
obj: "1@0723d2a1940744868ffd6b294ada813f".try_into().unwrap(),
action: OpType::Del,
key: ElementID::from_str("2@0723d2a1940744868ffd6b294ada813f")
.unwrap()
.into(),
pred: vec!["2@0723d2a1940744868ffd6b294ada813f".try_into().unwrap()],
Op {
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: protocol::OpType::Del,
value: None,
datatype: None,
key: actor.op_id_at(2).into(),
pred: vec![actor.op_id_at(2)],
insert: false,
},
],
}
.encode();
.try_into()
.unwrap();
assert_eq!(change1, expected_change1);
assert_eq!(change2, expected_change2);
@ -602,7 +606,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: UnencodedChange, change2: UnencodedChange) {
fn assert_changes_equal(mut change1: UncompressedChange, change2: UncompressedChange) {
let change2_clone = change2.clone();
let deps1: HashSet<&ChangeHash> = change1.deps.iter().collect();
let deps2: HashSet<&ChangeHash> = change2.deps.iter().collect();
@ -614,81 +618,3 @@ fn assert_changes_equal(mut change1: UnencodedChange, change2: UnencodedChange)
change1.deps = change2.deps;
assert_eq!(change1, change2_clone)
}
#[test]
fn valid_objectid_as_child_works() {
let actor = ActorID::from_str("1928d3c61735456993ffe94d6b44b9f3").unwrap();
let cr = Request {
actor: actor.clone(),
seq: 1,
version: 0,
message: Some("Initialization".to_string()),
undoable: false,
time: Some(1_590_236_501_416),
deps: None,
ops: Some(vec![
Op {
action: amp::OpType::MakeMap,
obj: amp::ObjectID::Root.to_string(),
key: "birds".into(),
child: Some("1@8ee3b3c4587245a684af1b121361141d".to_string()),
value: None,
datatype: None,
insert: false,
},
Op {
action: amp::OpType::Set,
obj: "1@8ee3b3c4587245a684af1b121361141d".to_string(),
key: "wrens".into(),
child: None,
value: Some(amp::ScalarValue::F64(3.0)),
datatype: Some(amp::DataType::Undefined),
insert: false,
},
Op {
action: amp::OpType::Set,
obj: "1@8ee3b3c4587245a684af1b121361141d".to_string(),
key: "sparrows".into(),
child: None,
value: Some(amp::ScalarValue::F64(15.0)),
datatype: Some(amp::DataType::Undefined),
insert: false,
},
]),
request_type: amp::RequestType::Change,
};
let mut expected_patch = amp::Patch {
actor: Some(actor.clone()),
seq: Some(1),
can_undo: false,
can_redo: false,
version: 1,
clock: hashmap! {cr.actor.clone() => 1},
deps: Vec::new(),
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectID::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".to_string() => hashmap!{
cr.actor.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(1).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"wrens".to_string() => hashmap!{
actor.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::F64(3.0)),
},
"sparrows".to_string() => hashmap!{
actor.op_id_at(3) => amp::Diff::Value(amp::ScalarValue::F64(15.0))
}
}
})
}
},
})),
};
let mut backend = Backend::init();
let patch = backend.apply_local_change(cr).unwrap();
expected_patch.deps = patch.clone().deps;
assert_eq!(patch, expected_patch);
}

View file

@ -1,9 +1,9 @@
extern crate automerge_backend;
use automerge_backend::{Backend, UnencodedChange};
use automerge_backend::{OpType, Operation};
use automerge_backend::{Backend, Change};
use automerge_protocol as amp;
use automerge_protocol::{
ActorID, Diff, DiffEdit, ElementID, MapDiff, MapType, ObjType, ObjectID, Patch, ScalarValue,
SeqDiff, SequenceType,
ActorID, Diff, DiffEdit, ElementID, MapDiff, MapType, ObjectID, Op, Patch, ScalarValue,
SeqDiff, SequenceType, UncompressedChange,
};
use maplit::hashmap;
use std::convert::TryInto;
@ -11,56 +11,60 @@ use std::convert::TryInto;
#[test]
fn test_include_most_recent_value_for_key() {
let actor: ActorID = "ec28cfbcdb9e4f32ad24b3c776e651b0".try_into().unwrap();
let change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
time: 0,
deps: Vec::new(),
message: None,
operations: vec![Operation {
action: OpType::Set("magpie".into()),
operations: vec![Op {
action: amp::OpType::Set,
value: Some("magpie".into()),
datatype: None,
key: "bird".into(),
obj: ObjectID::Root,
obj: ObjectID::Root.to_string(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let change2 = UnencodedChange {
let change2: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
start_op: 2,
time: 0,
message: None,
deps: vec![change1.hash],
operations: vec![Operation {
obj: ObjectID::Root,
action: OpType::Set("blackbird".into()),
operations: vec![Op {
obj: ObjectID::Root.to_string(),
action: amp::OpType::Set,
value: Some("blackbird".into()),
datatype: Some(amp::DataType::Undefined),
key: "bird".into(),
pred: vec!["1@ec28cfbcdb9e4f32ad24b3c776e651b0".try_into().unwrap()],
pred: vec![actor.op_id_at(1)],
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
actor: None,
seq: None,
version: 0,
max_op: 2,
clock: hashmap! {
actor => 2,
actor.clone() => 2,
},
can_undo: false,
can_redo: false,
deps: vec![change2.hash],
diffs: Some(Diff::Map(MapDiff {
object_id: ObjectID::Root,
obj_type: MapType::Map,
props: hashmap! {
"bird".into() => hashmap!{
"2@ec28cfbcdb9e4f32ad24b3c776e651b0".try_into().unwrap() => Diff::Value("blackbird".into())
actor.op_id_at(2) => Diff::Value("blackbird".into()),
}
},
})),
@ -76,58 +80,62 @@ 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 change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor1.clone(),
seq: 1,
start_op: 1,
time: 0,
deps: Vec::new(),
message: None,
operations: vec![Operation {
action: OpType::Set("magpie".into()),
obj: ObjectID::Root,
operations: vec![Op {
action: amp::OpType::Set,
value: Some("magpie".into()),
datatype: Some(amp::DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "bird".into(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let change2 = UnencodedChange {
let change2: Change = UncompressedChange {
actor_id: actor2.clone(),
seq: 1,
start_op: 1,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set("blackbird".into()),
operations: vec![Op {
action: amp::OpType::Set,
value: Some("blackbird".into()),
datatype: Some(amp::DataType::Undefined),
key: "bird".into(),
obj: ObjectID::Root,
obj: ObjectID::Root.to_string(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
clock: hashmap! {
actor1 => 1,
actor2 => 1,
actor1.clone() => 1,
actor2.clone() => 1,
},
max_op: 1,
seq: None,
actor: None,
can_undo: false,
can_redo: false,
deps: vec![change2.hash, change1.hash],
diffs: Some(Diff::Map(MapDiff {
object_id: ObjectID::Root,
obj_type: MapType::Map,
props: hashmap! {
"bird".into() => hashmap!{
"1@111111".try_into().unwrap() => Diff::Value("magpie".into()),
"1@222222".try_into().unwrap() => Diff::Value("blackbird".into()),
actor1.op_id_at(1) => Diff::Value("magpie".into()),
actor2.op_id_at(1) => Diff::Value("blackbird".into()),
},
},
})),
@ -142,56 +150,60 @@ 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 change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set(ScalarValue::Counter(1)),
obj: ObjectID::Root,
operations: vec![Op {
action: amp::OpType::Set,
value: Some(ScalarValue::Counter(1)),
datatype: Some(amp::DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "counter".into(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let change2 = UnencodedChange {
let change2: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
start_op: 2,
time: 0,
deps: vec![change1.hash],
message: None,
operations: vec![Operation {
action: OpType::Inc(2),
obj: ObjectID::Root,
operations: vec![Op {
action: amp::OpType::Inc,
value: Some(2.into()),
datatype: Some(amp::DataType::Undefined),
obj: ObjectID::Root.to_string(),
key: "counter".into(),
pred: vec!["1@46c92088e4484ae5945dc63bf606a4a5".try_into().unwrap()],
pred: vec![actor.op_id_at(1)],
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
seq: None,
actor: None,
clock: hashmap! {
actor => 2,
actor.clone() => 2,
},
can_undo: false,
can_redo: false,
max_op: 2,
deps: vec![change2.hash],
diffs: Some(Diff::Map(MapDiff {
object_id: ObjectID::Root,
obj_type: MapType::Map,
props: hashmap! {
"counter".into() => hashmap!{
"1@46c92088e4484ae5945dc63bf606a4a5".try_into().unwrap() => Diff::Value(ScalarValue::Counter(3))
actor.op_id_at(1) => Diff::Value(ScalarValue::Counter(3))
}
},
})),
@ -206,7 +218,7 @@ fn test_handles_counter_increment_at_keys_in_a_map() {
#[test]
fn test_creates_nested_maps() {
let actor: ActorID = "06148f9422cb40579fd02f1975c34a51".try_into().unwrap();
let change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
@ -214,25 +226,30 @@ fn test_creates_nested_maps() {
message: None,
deps: Vec::new(),
operations: vec![
Operation {
action: OpType::Make(ObjType::Map(MapType::Map)),
obj: ObjectID::Root,
Op {
action: amp::OpType::MakeMap,
value: None,
datatype: None,
obj: ObjectID::Root.to_string(),
key: "birds".into(),
pred: Vec::new(),
insert: false,
},
Operation {
action: OpType::Set(ScalarValue::F64(3.0)),
Op {
action: amp::OpType::Set,
value: Some(ScalarValue::F64(3.0)),
datatype: Some(amp::DataType::Undefined),
key: "wrens".into(),
obj: "1@06148f9422cb40579fd02f1975c34a51".try_into().unwrap(),
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
pred: Vec::new(),
insert: false,
},
],
}
.encode();
.try_into()
.unwrap();
let change2 = UnencodedChange {
let change2: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 2,
start_op: 3,
@ -240,45 +257,48 @@ fn test_creates_nested_maps() {
deps: vec![change1.hash],
message: None,
operations: vec![
Operation {
action: OpType::Del,
obj: "1@06148f9422cb40579fd02f1975c34a51".try_into().unwrap(),
Op {
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: amp::OpType::Del,
value: None,
datatype: None,
key: "wrens".into(),
pred: vec!["2@06148f9422cb40579fd02f1975c34a51".try_into().unwrap()],
pred: vec![actor.op_id_at(2)],
insert: false,
},
Operation {
action: OpType::Set(ScalarValue::F64(15.0)),
obj: "1@06148f9422cb40579fd02f1975c34a51".try_into().unwrap(),
Op {
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: amp::OpType::Set,
value: Some(ScalarValue::F64(15.0)),
datatype: None,
key: "sparrows".into(),
pred: Vec::new(),
insert: false,
},
],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
clock: hashmap! {
actor => 2,
actor.clone() => 2,
},
actor: None,
seq: None,
can_undo: false,
can_redo: false,
max_op: 4,
deps: vec![change2.hash],
diffs: Some(Diff::Map(MapDiff {
object_id: ObjectID::Root,
obj_type: MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
"1@06148f9422cb40579fd02f1975c34a51".try_into().unwrap() => Diff::Map(MapDiff{
object_id: "1@06148f9422cb40579fd02f1975c34a51".try_into().unwrap(),
actor.op_id_at(1) => Diff::Map(MapDiff{
object_id: ObjectID::from(actor.op_id_at(1)),
obj_type: MapType::Map,
props: hashmap!{
"sparrows".into() => hashmap!{
"4@06148f9422cb40579fd02f1975c34a51".try_into().unwrap() => Diff::Value(ScalarValue::F64(15.0))
actor.op_id_at(4) => Diff::Value(ScalarValue::F64(15.0))
}
}
})
@ -296,7 +316,7 @@ fn test_creates_nested_maps() {
#[test]
fn test_create_lists() {
let actor: ActorID = "90bf7df682f747fa82ac604b35010906".try_into().unwrap();
let change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
@ -304,31 +324,34 @@ fn test_create_lists() {
message: None,
deps: Vec::new(),
operations: vec![
Operation {
action: OpType::Make(ObjType::Sequence(SequenceType::List)),
obj: ObjectID::Root,
Op {
action: amp::OpType::MakeList,
value: None,
datatype: None,
obj: ObjectID::Root.to_string(),
key: "birds".into(),
pred: Vec::new(),
insert: false,
},
Operation {
action: OpType::Set("chaffinch".into()),
obj: "1@90bf7df682f747fa82ac604b35010906".try_into().unwrap(),
Op {
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: amp::OpType::Set,
value: Some("chaffinch".into()),
datatype: Some(amp::DataType::Undefined),
key: ElementID::Head.into(),
insert: true,
pred: Vec::new(),
},
],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
clock: hashmap! {
actor => 1,
actor.clone() => 1,
},
can_undo: false,
can_redo: false,
max_op: 2,
actor: None,
seq: None,
deps: vec![change1.hash],
@ -337,10 +360,13 @@ fn test_create_lists() {
obj_type: MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
"1@90bf7df682f747fa82ac604b35010906".try_into().unwrap() => Diff::Seq(SeqDiff{
object_id: "1@90bf7df682f747fa82ac604b35010906".try_into().unwrap(),
actor.op_id_at(1) => Diff::Seq(SeqDiff{
object_id: ObjectID::from(actor.op_id_at(1)),
obj_type: SequenceType::List,
edits: vec![DiffEdit::Insert { index :0 }],
edits: vec![DiffEdit::Insert {
index: 0,
elem_id: actor.op_id_at(2).into()
}],
props: hashmap!{
0 => hashmap!{
"2@90bf7df682f747fa82ac604b35010906".try_into().unwrap() => Diff::Value("chaffinch".into())
@ -361,7 +387,7 @@ fn test_create_lists() {
#[test]
fn test_includes_latests_state_of_list() {
let actor: ActorID = "6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap();
let change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
@ -369,45 +395,52 @@ fn test_includes_latests_state_of_list() {
message: None,
deps: Vec::new(),
operations: vec![
Operation {
action: OpType::Make(ObjType::Sequence(SequenceType::List)),
obj: ObjectID::Root,
Op {
action: amp::OpType::MakeList,
value: None,
datatype: None,
obj: ObjectID::Root.to_string(),
key: "todos".into(),
pred: Vec::new(),
insert: false,
},
Operation {
action: OpType::Make(ObjType::Map(MapType::Map)),
obj: "1@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap(),
Op {
action: amp::OpType::MakeMap,
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
value: None,
datatype: None,
key: ElementID::Head.into(),
insert: true,
pred: Vec::new(),
},
Operation {
action: OpType::Set("water plants".into()),
obj: "2@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap(),
Op {
obj: ObjectID::from(actor.op_id_at(2)).to_string(),
action: amp::OpType::Set,
value: Some("water plants".into()),
datatype: Some(amp::DataType::Undefined),
key: "title".into(),
pred: Vec::new(),
insert: false,
},
Operation {
action: OpType::Set(false.into()),
obj: "2@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap(),
Op {
obj: ObjectID::from(actor.op_id_at(2)).to_string(),
action: amp::OpType::Set,
value: Some(false.into()),
datatype: Some(amp::DataType::Undefined),
key: "done".into(),
pred: Vec::new(),
insert: false,
},
],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
clock: hashmap! {
actor => 1
actor.clone() => 1
},
can_undo: false,
can_redo: false,
max_op: 4,
actor: None,
seq: None,
deps: vec![change1.hash],
@ -416,21 +449,21 @@ fn test_includes_latests_state_of_list() {
obj_type: MapType::Map,
props: hashmap! {
"todos".into() => hashmap!{
"1@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap() => Diff::Seq(SeqDiff{
object_id: "1@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap(),
actor.op_id_at(1) => Diff::Seq(SeqDiff{
object_id: ObjectID::from(actor.op_id_at(1)),
obj_type: SequenceType::List,
edits: vec![DiffEdit::Insert{index: 0}],
edits: vec![DiffEdit::Insert{index: 0, elem_id: actor.op_id_at(2).into()}],
props: hashmap!{
0 => hashmap!{
"2@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap() => Diff::Map(MapDiff{
actor.op_id_at(2) => Diff::Map(MapDiff{
object_id: "2@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap(),
obj_type: MapType::Map,
props: hashmap!{
"title".into() => hashmap!{
"3@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap() => Diff::Value("water plants".into()),
actor.op_id_at(3) => Diff::Value("water plants".into()),
},
"done".into() => hashmap!{
"4@6caaa2e433de42ae9c3fa65c9ff3f03e".try_into().unwrap() => Diff::Value(false.into())
actor.op_id_at(4) => Diff::Value(false.into())
}
}
})
@ -451,30 +484,31 @@ fn test_includes_latests_state_of_list() {
#[test]
fn test_includes_date_objects_at_root() {
let actor: ActorID = "90f5dd5d4f524e95ad5929e08d1194f1".try_into().unwrap();
let change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
time: 0,
message: None,
deps: Vec::new(),
operations: vec![Operation {
action: OpType::Set(ScalarValue::Timestamp(1_586_541_033_457)),
obj: ObjectID::Root,
operations: vec![Op {
obj: ObjectID::Root.to_string(),
action: amp::OpType::Set,
value: Some(ScalarValue::Timestamp(1_586_541_033_457)),
datatype: Some(amp::DataType::Timestamp),
key: "now".into(),
pred: Vec::new(),
insert: false,
}],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
clock: hashmap! {
actor => 1,
actor.clone() => 1,
},
can_undo: false,
can_redo: false,
max_op: 1,
actor: None,
seq: None,
deps: vec![change1.hash],
@ -483,7 +517,7 @@ fn test_includes_date_objects_at_root() {
obj_type: MapType::Map,
props: hashmap! {
"now".into() => hashmap!{
"1@90f5dd5d4f524e95ad5929e08d1194f1".try_into().unwrap() => Diff::Value(ScalarValue::Timestamp(1_586_541_033_457))
actor.op_id_at(1) => Diff::Value(ScalarValue::Timestamp(1_586_541_033_457))
}
},
})),
@ -498,7 +532,7 @@ fn test_includes_date_objects_at_root() {
#[test]
fn test_includes_date_objects_in_a_list() {
let actor: ActorID = "08b050f976a249349021a2e63d99c8e8".try_into().unwrap();
let change1 = UnencodedChange {
let change1: Change = UncompressedChange {
actor_id: actor.clone(),
seq: 1,
start_op: 1,
@ -506,31 +540,34 @@ fn test_includes_date_objects_in_a_list() {
message: None,
deps: Vec::new(),
operations: vec![
Operation {
action: OpType::Make(ObjType::Sequence(SequenceType::List)),
obj: ObjectID::Root,
Op {
obj: ObjectID::Root.to_string(),
action: amp::OpType::MakeList,
value: None,
datatype: None,
key: "list".into(),
pred: Vec::new(),
insert: false,
},
Operation {
action: OpType::Set(ScalarValue::Timestamp(1_586_541_089_595)),
obj: "1@08b050f976a249349021a2e63d99c8e8".try_into().unwrap(),
Op {
obj: ObjectID::from(actor.op_id_at(1)).to_string(),
action: amp::OpType::Set,
value: Some(ScalarValue::Timestamp(1_586_541_089_595)),
datatype: Some(amp::DataType::Timestamp),
key: ElementID::Head.into(),
insert: true,
pred: Vec::new(),
},
],
}
.encode();
.try_into()
.unwrap();
let expected_patch = Patch {
version: 0,
clock: hashmap! {
actor => 1,
actor.clone() => 1,
},
can_undo: false,
can_redo: false,
max_op: 2,
actor: None,
seq: None,
deps: vec![change1.hash],
@ -539,13 +576,13 @@ fn test_includes_date_objects_in_a_list() {
obj_type: MapType::Map,
props: hashmap! {
"list".into() => hashmap!{
"1@08b050f976a249349021a2e63d99c8e8".try_into().unwrap() => Diff::Seq(SeqDiff{
object_id: "1@08b050f976a249349021a2e63d99c8e8".try_into().unwrap(),
actor.op_id_at(1) => Diff::Seq(SeqDiff{
object_id: ObjectID::from(actor.op_id_at(1)),
obj_type: SequenceType::List,
edits: vec![DiffEdit::Insert {index: 0}],
edits: vec![DiffEdit::Insert {index: 0, elem_id: actor.op_id_at(2).into()}],
props: hashmap!{
0 => hashmap!{
"2@08b050f976a249349021a2e63d99c8e8".try_into().unwrap() => Diff::Value(ScalarValue::Timestamp(1_586_541_089_595))
actor.op_id_at(2) => Diff::Value(ScalarValue::Timestamp(1_586_541_089_595))
}
}
})

View file

@ -3,7 +3,8 @@ extern crate errno;
extern crate libc;
extern crate serde;
use automerge_backend::{AutomergeError, Change, UnencodedChange};
use automerge_backend::{AutomergeError, Change};
use automerge_protocol::UncompressedChange;
use errno::{set_errno, Errno};
use serde::ser::Serialize;
use std::convert::TryInto;
@ -134,10 +135,10 @@ 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: Result<UnencodedChange, _> = serde_json::from_str(&request);
let request: Result<UncompressedChange, _> = serde_json::from_str(&request);
if let Ok(request) = request {
// FIXME - need to update the c api to all receiving the binary change here
if let Ok((patch,_change)) = (*backend).apply_local_change(request) {
if let Ok((patch, _change)) = (*backend).apply_local_change(request) {
(*backend).generate_json(Ok(patch))
} else {
-1
@ -276,8 +277,8 @@ pub unsafe extern "C" fn automerge_encode_change(
) -> isize {
let change: &CStr = CStr::from_ptr(change);
let change = change.to_string_lossy();
let change: Result<UnencodedChange, _> = serde_json::from_str(&change);
let change = change.unwrap().encode();
let uncomp_change: UncompressedChange = serde_json::from_str(&change).unwrap();
let change: Change = uncomp_change.try_into().unwrap();
(*backend).handle_binary(Ok(change.bytes))
}

View file

@ -4,7 +4,7 @@ use thiserror::Error;
#[error("Invalid OpID: {0}")]
pub struct InvalidOpID(pub String);
#[derive(Error, Debug)]
#[derive(Error, Debug, PartialEq)]
#[error("Invalid object ID: {0}")]
pub struct InvalidObjectID(pub String);
@ -16,6 +16,6 @@ pub struct InvalidElementID(pub String);
#[error("Invalid actor ID: {0}")]
pub struct InvalidActorID(pub String);
#[derive(Error, Debug)]
#[derive(Error, Debug, PartialEq)]
#[error("Invalid change hash slice: {0:?}")]
pub struct InvalidChangeHashSlice(pub Vec<u8>);

View file

@ -1,6 +1,7 @@
pub mod error;
mod serde_impls;
mod utility_impls;
use std::convert::TryFrom;
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
use std::collections::HashMap;
@ -205,6 +206,14 @@ impl ScalarValue {
_ => None,
}
}
pub fn datatype(&self) -> Option<DataType> {
match self {
ScalarValue::Counter(..) => Some(DataType::Counter),
ScalarValue::Timestamp(..) => Some(DataType::Timestamp),
_ => None,
}
}
}
#[derive(Serialize, Debug, PartialEq, Clone)]
@ -213,7 +222,7 @@ pub enum RequestKey {
Num(u64),
}
#[derive(Deserialize, PartialEq, Debug, Clone, Copy)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub enum OpType {
MakeMap,
@ -221,21 +230,22 @@ pub enum OpType {
MakeList,
MakeText,
Del,
Link,
Inc,
Set,
}
#[derive(Deserialize, PartialEq, Debug, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Op {
pub action: OpType,
//TODO make this an ObjectID
pub obj: String,
pub key: RequestKey,
pub child: Option<String>,
pub key: Key,
pub value: Option<ScalarValue>,
pub datatype: Option<DataType>,
#[serde(default = "serde_impls::make_false")]
pub insert: bool,
#[serde(default)]
pub pred: Vec<OpID>,
}
impl Op {
@ -265,21 +275,6 @@ impl Op {
#[derive(Eq, PartialEq, Debug, Hash, Clone, PartialOrd, Ord, Copy)]
pub struct ChangeHash(pub [u8; 32]);
#[derive(Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Request {
pub actor: ActorID,
pub seq: u64,
pub version: u64,
pub message: Option<String>,
#[serde(default = "serde_impls::make_true")]
pub undoable: bool,
pub time: Option<i64>,
pub deps: Option<Vec<ChangeHash>>,
pub ops: Option<Vec<Op>>,
//pub request_type: RequestType,
}
// The Diff Structure Maps on to the Patch Diffs the Frontend is expecting
// Diff {
// object_id: 123,
@ -347,9 +342,11 @@ pub enum DiffEdit {
Insert {
index: usize,
#[serde(rename = "elemId")]
elem_id: ElementID
elem_id: ElementID,
},
Remove {
index: usize,
},
Remove { index: usize },
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
@ -362,13 +359,39 @@ pub struct Patch {
pub clock: HashMap<ActorID, u64>,
pub deps: Vec<ChangeHash>,
pub max_op: u64,
// pub can_undo: bool,
// pub can_redo: bool,
// pub version: u64,
// pub can_undo: bool,
// pub can_redo: bool,
// pub version: u64,
#[serde(serialize_with = "Patch::top_level_serialize")]
pub diffs: Option<Diff>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
pub struct UncompressedChange {
#[serde(rename = "ops")]
pub operations: Vec<Op>,
#[serde(rename = "actor")]
pub actor_id: ActorID,
//pub hash: amp::ChangeHash,
pub seq: u64,
#[serde(rename = "startOp")]
pub start_op: u64,
pub time: i64,
pub message: Option<String>,
pub deps: Vec<ChangeHash>,
}
impl UncompressedChange {
pub fn op_id_of(&self, index: u64) -> Option<OpID> {
if let Ok(index_usize) = usize::try_from(index) {
if index_usize < self.operations.len() {
return Some(self.actor_id.op_id_at(self.start_op + index));
}
}
None
}
}
impl Patch {
// the default behavior is to return {} for an empty patch
// this patch implementation comes with ObjectID::Root baked in so this covered

View file

@ -18,11 +18,6 @@ pub(crate) fn make_false() -> bool {
false
}
// Factory method for use in #[serde(default=..)] annotations
pub(crate) fn make_true() -> bool {
true
}
// Helper method for use in custom deserialize impls
pub(crate) fn read_field<'de, T, M>(
name: &'static str,

View file

@ -1,4 +1,5 @@
use crate::ScalarValue;
use std::fmt;
impl From<&str> for ScalarValue {
fn from(s: &str) -> Self {
@ -18,8 +19,30 @@ impl From<u64> for ScalarValue {
}
}
impl From<i32> for ScalarValue {
fn from(n: i32) -> Self {
ScalarValue::Int(n as i64)
}
}
impl From<bool> for ScalarValue {
fn from(b: bool) -> Self {
ScalarValue::Boolean(b)
}
}
impl fmt::Display for ScalarValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarValue::Str(s) => write!(f, "\"{}\"", s),
ScalarValue::Int(i) => write!(f, "{}", i),
ScalarValue::Uint(i) => write!(f, "{}", i),
ScalarValue::F32(n) => write!(f, "{:.32}", n),
ScalarValue::F64(n) => write!(f, "{:.324}", n),
ScalarValue::Counter(c) => write!(f, "Counter: {}", c),
ScalarValue::Timestamp(i) => write!(f, "Timestamp: {}", i),
ScalarValue::Boolean(b) => write!(f, "{}", b),
ScalarValue::Null => write!(f, "null"),
}
}
}