Move automerge_backend::UnencodedChange -> automerge_protocol::UncompressedChange
This commit is contained in:
parent
c859b24a12
commit
a3bd0a79d6
25 changed files with 1179 additions and 1001 deletions
automerge-backend-wasm/src
automerge-backend
Cargo.toml
src
actor_map.rsbackend.rschange.rscolumnar.rsconcurrent_operations.rserror.rsinternal.rslib.rsobject_store.rsop.rsop_handle.rsop_set.rsop_type.rsordered_set.rspending_diff.rs
tests
automerge-c/src
automerge-protocol/src
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
|
|
|
@ -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: &::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: &::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: &::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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<&::UncompressedChange> for Change {
|
||||
type Error = AutomergeError;
|
||||
|
||||
fn try_from(value: &::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: &::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: &::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: &::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(())
|
||||
|
|
|
@ -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: &::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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<&::Op> for Operation {
|
||||
type Error = InvalidChangeError;
|
||||
fn try_from(op: &::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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<&::Op> for OpType {
|
||||
type Error = error::InvalidChangeError;
|
||||
fn try_from(op: &::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(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue