From a2e433348ae4365526304202a24fb881088467b3 Mon Sep 17 00:00:00 2001 From: Orion Henry <orion.henry@gmail.com> Date: Fri, 28 Jan 2022 12:24:58 -0500 Subject: [PATCH] mark encode/decode/serde --- automerge-wasm/test/test.js | 22 +++++- automerge/src/automerge.rs | 19 ++--- automerge/src/columnar.rs | 52 ++++++++++++-- automerge/src/legacy/serde_impls/op.rs | 42 +++++++++++ automerge/src/legacy/serde_impls/op_type.rs | 4 +- automerge/src/legacy/utility_impls/mod.rs | 1 - .../src/legacy/utility_impls/scalar_value.rs | 57 --------------- automerge/src/query.rs | 2 - automerge/src/query/mark.rs | 71 ------------------ automerge/src/query/spans.rs | 4 +- automerge/src/types.rs | 6 ++ automerge/src/value.rs | 72 +++++++++++++++++++ 12 files changed, 203 insertions(+), 149 deletions(-) delete mode 100644 automerge/src/legacy/utility_impls/scalar_value.rs delete mode 100644 automerge/src/query/mark.rs diff --git a/automerge-wasm/test/test.js b/automerge-wasm/test/test.js index 7d1519c1..38d68eba 100644 --- a/automerge-wasm/test/test.js +++ b/automerge-wasm/test/test.js @@ -3,7 +3,7 @@ const assert = require('assert') const util = require('util') const { BloomFilter } = require('./helpers/sync') const Automerge = require('..') -const { MAP, LIST, TEXT, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState }= Automerge +const { MAP, LIST, TEXT, encodeChange, decodeChange, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState }= Automerge // str to uint8array function en(str) { @@ -460,6 +460,15 @@ describe('Automerge', () => { doc.insert(list, 2, "A") spans = doc.spans(list); assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'AbA', [], 'cc' ]) + + // make sure save/load can handle marks + + let doc2 = Automerge.load(doc.save()) + spans = doc2.spans(list); + assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'AbA', [], 'cc' ]) + + assert.deepStrictEqual(doc.getHeads(), doc2.getHeads()) + assert.deepStrictEqual(doc.save(), doc2.save()) }) it.only('should handle overlapping marks', () => { @@ -489,6 +498,17 @@ describe('Automerge', () => { [], ] ) + + // mark sure encode decode can handle marks + + let all = doc.getChanges([]) + let decoded = all.map((c) => decodeChange(c)) + let encoded = decoded.map((c) => encodeChange(c)) + let doc2 = Automerge.init(); + doc2.applyChanges(encoded) + + assert.deepStrictEqual(doc.spans(list) , doc2.spans(list)) + assert.deepStrictEqual(doc.save(), doc2.save()) }) }) diff --git a/automerge/src/automerge.rs b/automerge/src/automerge.rs index f74a21d3..afed341e 100644 --- a/automerge/src/automerge.rs +++ b/automerge/src/automerge.rs @@ -6,7 +6,7 @@ use crate::exid::ExId; use crate::op_set::OpSet; use crate::types::{ ActorId, ChangeHash, Clock, ElemId, Export, Exportable, Key, ObjId, Op, OpId, OpType, Patch, - ScalarValue, Value, MarkData, + ScalarValue, Value, }; use crate::{legacy, query, types, ObjType}; use crate::{AutomergeError, Change, Prop}; @@ -322,26 +322,25 @@ impl Automerge { value: V, ) -> Result<Option<ExId>, AutomergeError> { let obj = self.exid_to_obj(obj)?; - if let Some(id) = self.do_insert(obj, index, value)? { + let value = value.into(); + if let Some(id) = self.do_insert(obj, index, value.into())? { Ok(Some(self.id_to_exid(id))) } else { Ok(None) } } - fn do_insert<V: Into<Value>>( + fn do_insert( &mut self, obj: ObjId, index: usize, - value: V, + action: OpType, ) -> Result<Option<OpId>, AutomergeError> { let id = self.next_id(); - let value = value.into(); let query = self.ops.search(obj, query::InsertNth::new(index)); let key = query.key()?; - let action = value.into(); let is_make = matches!(&action, OpType::Make(_)); let op = Op { @@ -399,7 +398,7 @@ impl Automerge { let mut results = Vec::new(); for v in vals { // insert() - let id = self.do_insert(obj, pos, v.clone())?; + let id = self.do_insert(obj, pos, v.into())?; if let Some(id) = id { results.push(self.id_to_exid(id)); } @@ -465,8 +464,11 @@ impl Automerge { value: ScalarValue, ) -> Result<(), AutomergeError> { let obj = self.exid_to_obj(obj)?; - let query = self.ops.search(obj, query::Mark::new(start, end)); + self.do_insert(obj, start, OpType::mark(mark.into(), start_sticky, value))?; + self.do_insert(obj, end, OpType::Unmark(end_sticky))?; + +/* let (a, b) = query.ops()?; let (pos, key) = a; let id = self.next_id(); @@ -497,6 +499,7 @@ impl Automerge { }; self.ops.insert(pos, op.clone()); self.tx().operations.push(op); +*/ Ok(()) } diff --git a/automerge/src/columnar.rs b/automerge/src/columnar.rs index 15ba749d..f64f8186 100644 --- a/automerge/src/columnar.rs +++ b/automerge/src/columnar.rs @@ -11,7 +11,7 @@ use std::{ str, }; -use crate::types::{ActorId, ElemId, Key, ObjId, ObjType, Op, OpId, OpType, ScalarValue}; +use crate::types::{ActorId, ElemId, Key, ObjId, ObjType, Op, OpId, OpType, ScalarValue, MarkData }; use crate::legacy as amp; use amp::SortedVec; @@ -134,6 +134,15 @@ impl<'a> Iterator for OperationIterator<'a> { Action::MakeTable => OpType::Make(ObjType::Table), Action::Del => OpType::Del, Action::Inc => OpType::Inc(value.to_i64()?), + Action::Mark => { + // mark has 3 things in the val column + let name = value.to_string()?; + let sticky = self.value.next()?.to_bool()?; + let value = self.value.next()?; + OpType::Mark(MarkData { name, sticky, value }) + } + Action::Unmark => OpType::Unmark(value.to_bool()?), + Action::Unused => panic!("invalid action"), }; Some(amp::Op { action, @@ -175,6 +184,15 @@ impl<'a> Iterator for DocOpIterator<'a> { Action::MakeTable => OpType::Make(ObjType::Table), Action::Del => OpType::Del, Action::Inc => OpType::Inc(value.to_i64()?), + Action::Mark => { + // mark has 3 things in the val column + let name = value.to_string()?; + let sticky = self.value.next()?.to_bool()?; + let value = self.value.next()?; + OpType::Mark(MarkData { name, sticky, value }) + } + Action::Unmark => OpType::Unmark(value.to_bool()?), + Action::Unused => panic!("invalid action"), }; Some(DocOp { actor, @@ -1064,8 +1082,16 @@ impl DocOpEncoder { self.val.append_null(); Action::Del } - amp::OpType::Mark(_) => unimplemented!(), - amp::OpType::Unmark(_) => unimplemented!(), + amp::OpType::Mark(m) => { + self.val.append_value(&m.name.clone().into(), actors); + self.val.append_value(&m.sticky.into(), actors); + self.val.append_value(&m.value.clone().into(), actors); + Action::Mark + } + amp::OpType::Unmark(s) => { + self.val.append_value(&(*s).into(), actors); + Action::Unmark + } amp::OpType::Make(kind) => { self.val.append_null(); match kind { @@ -1172,8 +1198,16 @@ impl ColumnEncoder { self.val.append_null(); Action::Del } - OpType::Mark(_) => unimplemented!(), - OpType::Unmark(_) => unimplemented!(), + OpType::Mark(m) => { + self.val.append_value2(&m.name.clone().into(), actors); + self.val.append_value2(&m.sticky.into(), actors); + self.val.append_value2(&m.value.clone().into(), actors); + Action::Mark + } + OpType::Unmark(s) => { + self.val.append_value2(&(*s).into(), actors); + Action::Unmark + } OpType::Make(kind) => { self.val.append_null(); match kind { @@ -1279,8 +1313,11 @@ pub(crate) enum Action { MakeText, Inc, MakeTable, + Mark, + Unused, // final bit is used to mask `Make` actions + Unmark, } -const ACTIONS: [Action; 7] = [ +const ACTIONS: [Action; 10] = [ Action::MakeMap, Action::Set, Action::MakeList, @@ -1288,6 +1325,9 @@ const ACTIONS: [Action; 7] = [ Action::MakeText, Action::Inc, Action::MakeTable, + Action::Mark, + Action::Unused, + Action::Unmark, ]; impl Decodable for Action { diff --git a/automerge/src/legacy/serde_impls/op.rs b/automerge/src/legacy/serde_impls/op.rs index 5f0db62d..298464b0 100644 --- a/automerge/src/legacy/serde_impls/op.rs +++ b/automerge/src/legacy/serde_impls/op.rs @@ -49,6 +49,12 @@ impl Serialize for Op { match &self.action { OpType::Inc(n) => op.serialize_field("value", &n)?, OpType::Set(value) => op.serialize_field("value", &value)?, + OpType::Mark(m) => { + op.serialize_field("name", &m.name)?; + op.serialize_field("sticky", &m.sticky)?; + op.serialize_field("value", &m.value)?; + } + OpType::Unmark(s) => op.serialize_field("sticky", &s)?, _ => {} } op.serialize_field("pred", &self.pred)?; @@ -70,6 +76,8 @@ pub(crate) enum RawOpType { Del, Inc, Set, + Mark, + Unmark, } impl Serialize for RawOpType { @@ -85,6 +93,8 @@ impl Serialize for RawOpType { RawOpType::Del => "del", RawOpType::Inc => "inc", RawOpType::Set => "set", + RawOpType::Mark => "mark", + RawOpType::Unmark => "unmark", }; serializer.serialize_str(s) } @@ -103,6 +113,8 @@ impl<'de> Deserialize<'de> for RawOpType { "del", "inc", "set", + "mark", + "unmark", ]; // TODO: Probably more efficient to deserialize to a `&str` let raw_type = String::deserialize(deserializer)?; @@ -114,6 +126,8 @@ impl<'de> Deserialize<'de> for RawOpType { "del" => Ok(RawOpType::Del), "inc" => Ok(RawOpType::Inc), "set" => Ok(RawOpType::Set), + "mark" => Ok(RawOpType::Mark), + "unmark" => Ok(RawOpType::Unmark), other => Err(Error::unknown_variant(other, VARIANTS)), } } @@ -144,6 +158,8 @@ impl<'de> Deserialize<'de> for Op { let mut insert: Option<bool> = None; let mut datatype: Option<DataType> = None; let mut value: Option<Option<ScalarValue>> = None; + let mut name: Option<String> = None; + let mut sticky: Option<bool> = None; let mut ref_id: Option<OpId> = None; while let Some(field) = map.next_key::<String>()? { match field.as_ref() { @@ -167,6 +183,8 @@ impl<'de> Deserialize<'de> for Op { "insert" => read_field("insert", &mut insert, &mut map)?, "datatype" => read_field("datatype", &mut datatype, &mut map)?, "value" => read_field("value", &mut value, &mut map)?, + "name" => read_field("name", &mut name, &mut map)?, + "sticky" => read_field("sticky", &mut sticky, &mut map)?, "ref" => read_field("ref", &mut ref_id, &mut map)?, _ => return Err(Error::unknown_field(&field, FIELDS)), } @@ -182,6 +200,30 @@ impl<'de> Deserialize<'de> for Op { RawOpType::MakeList => OpType::Make(ObjType::List), RawOpType::MakeText => OpType::Make(ObjType::Text), RawOpType::Del => OpType::Del, + RawOpType::Mark => { + let name = name.ok_or_else(|| Error::missing_field("mark(name)"))?; + let sticky = sticky.unwrap_or(false); + let value = if let Some(datatype) = datatype { + let raw_value = value + .ok_or_else(|| Error::missing_field("value"))? + .unwrap_or(ScalarValue::Null); + raw_value.as_datatype(datatype).map_err(|e| { + Error::invalid_value( + Unexpected::Other(e.unexpected.as_str()), + &e.expected.as_str(), + ) + })? + } else { + value + .ok_or_else(|| Error::missing_field("value"))? + .unwrap_or(ScalarValue::Null) + }; + OpType::mark(name, sticky, value) + } + RawOpType::Unmark => { + let sticky = sticky.unwrap_or(true); + OpType::Unmark(sticky) + } RawOpType::Set => { let value = if let Some(datatype) = datatype { let raw_value = value diff --git a/automerge/src/legacy/serde_impls/op_type.rs b/automerge/src/legacy/serde_impls/op_type.rs index 01041ef7..a36355e2 100644 --- a/automerge/src/legacy/serde_impls/op_type.rs +++ b/automerge/src/legacy/serde_impls/op_type.rs @@ -15,8 +15,8 @@ impl Serialize for OpType { OpType::Make(ObjType::Table) => RawOpType::MakeTable, OpType::Make(ObjType::List) => RawOpType::MakeList, OpType::Make(ObjType::Text) => RawOpType::MakeText, - OpType::Mark(_) => unimplemented!(), - OpType::Unmark(_) => unimplemented!(), + OpType::Mark(_) => RawOpType::Mark, + OpType::Unmark(_) => RawOpType::Unmark, OpType::Del => RawOpType::Del, OpType::Inc(_) => RawOpType::Inc, OpType::Set(_) => RawOpType::Set, diff --git a/automerge/src/legacy/utility_impls/mod.rs b/automerge/src/legacy/utility_impls/mod.rs index 3476b8da..99fa1750 100644 --- a/automerge/src/legacy/utility_impls/mod.rs +++ b/automerge/src/legacy/utility_impls/mod.rs @@ -2,4 +2,3 @@ mod element_id; mod key; mod object_id; mod opid; -mod scalar_value; diff --git a/automerge/src/legacy/utility_impls/scalar_value.rs b/automerge/src/legacy/utility_impls/scalar_value.rs deleted file mode 100644 index ef0a3305..00000000 --- a/automerge/src/legacy/utility_impls/scalar_value.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fmt; - -use smol_str::SmolStr; - -use crate::value::ScalarValue; - -impl From<&str> for ScalarValue { - fn from(s: &str) -> Self { - ScalarValue::Str(s.into()) - } -} - -impl From<i64> for ScalarValue { - fn from(n: i64) -> Self { - ScalarValue::Int(n) - } -} - -impl From<u64> for ScalarValue { - fn from(n: u64) -> Self { - ScalarValue::Uint(n) - } -} - -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 From<char> for ScalarValue { - fn from(c: char) -> Self { - ScalarValue::Str(SmolStr::new(c.to_string())) - } -} - -impl fmt::Display for ScalarValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ScalarValue::Bytes(b) => write!(f, "\"{:?}\"", b), - ScalarValue::Str(s) => write!(f, "\"{}\"", s), - ScalarValue::Int(i) => write!(f, "{}", i), - ScalarValue::Uint(i) => write!(f, "{}", i), - 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"), - } - } -} diff --git a/automerge/src/query.rs b/automerge/src/query.rs index 80af06b2..7bd50158 100644 --- a/automerge/src/query.rs +++ b/automerge/src/query.rs @@ -12,7 +12,6 @@ mod len; mod len_at; mod list_vals; mod list_vals_at; -mod mark; mod nth; mod nth_at; mod prop; @@ -27,7 +26,6 @@ pub(crate) use len::Len; pub(crate) use len_at::LenAt; pub(crate) use list_vals::ListVals; pub(crate) use list_vals_at::ListValsAt; -pub(crate) use mark::Mark; pub(crate) use nth::Nth; pub(crate) use nth_at::NthAt; pub(crate) use prop::Prop; diff --git a/automerge/src/query/mark.rs b/automerge/src/query/mark.rs deleted file mode 100644 index 3757677b..00000000 --- a/automerge/src/query/mark.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::AutomergeError; -use crate::query::{QueryResult, TreeQuery}; -use crate::types::{ElemId, Key, Op}; -use std::fmt::Debug; - -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct Mark<const B: usize> { - start: usize, - end: usize, - pos: usize, - seen: usize, - _ops: Vec<(usize, Key)>, - count: usize, - last_seen: Option<ElemId>, - last_insert: Option<ElemId>, -} - -impl<const B: usize> Mark<B> { - pub fn new(start: usize, end: usize) -> Self { - Mark { - start, - end, - pos: 0, - seen: 0, - _ops: Vec::new(), - count: 0, - last_seen: None, - last_insert: None, - } - } - - pub fn ops(&self) -> Result<((usize,Key),(usize,Key)),AutomergeError> { - if self._ops.len() == 2 { - Ok((self._ops[0], self._ops[1])) - } else if self._ops.len() == 1 { - Ok((self._ops[0], (self.pos + 1, self.last_insert.into()))) - } else { - Err(AutomergeError::Fail) - } - } -} - -impl<const B: usize> TreeQuery<B> for Mark<B> { - /* - fn query_node(&mut self, _child: &OpTreeNode<B>) -> QueryResult { - unimplemented!() - } - */ - - fn query_element(&mut self, element: &Op) -> QueryResult { - // find location to insert - // mark or set - if element.insert { - if self.seen >= self.end { - self._ops.push((self.pos + 1, self.last_insert.into())); - return QueryResult::Finish; - } - if self.seen >= self.start && self._ops.is_empty() { - self._ops.push((self.pos, self.last_insert.into())); - } - self.last_seen = None; - self.last_insert = element.elemid(); - } - if self.last_seen.is_none() && element.visible() { - self.seen += 1; - self.last_seen = element.elemid() - } - self.pos += 1; - QueryResult::Next - } -} diff --git a/automerge/src/query/spans.rs b/automerge/src/query/spans.rs index 461c4a70..f39f3fc1 100644 --- a/automerge/src/query/spans.rs +++ b/automerge/src/query/spans.rs @@ -53,9 +53,11 @@ impl<const B: usize> Spans<B> { if self.changed && (self.seen_at_last_mark != self.seen_at_this_mark || self.seen_at_last_mark.is_none() && self.seen_at_this_mark.is_none()) { self.changed = false; self.seen_at_last_mark = self.seen_at_this_mark; + let mut marks : Vec<_> = self.marks.iter().map(|(key, val)| (key.clone(), val.clone())).collect(); + marks.sort_by(|(k1,_),(k2,_)| k1.cmp(k2)); self.spans.push(Span { pos: self.seen, - marks: self.marks.iter().map(|(key, val)| (key.clone(), val.clone())).collect() + marks, }); } } diff --git a/automerge/src/types.rs b/automerge/src/types.rs index 6b883cfa..91a45781 100644 --- a/automerge/src/types.rs +++ b/automerge/src/types.rs @@ -164,6 +164,12 @@ pub enum OpType { Unmark(bool), } +impl OpType { + pub (crate) fn mark(name: String, sticky: bool, value: ScalarValue) -> Self { + OpType::Mark(MarkData { name, sticky, value }) + } +} + #[derive(PartialEq, Debug, Clone)] pub struct MarkData { pub name: String, diff --git a/automerge/src/value.rs b/automerge/src/value.rs index eef757fd..9ade57ef 100644 --- a/automerge/src/value.rs +++ b/automerge/src/value.rs @@ -375,7 +375,79 @@ impl ScalarValue { } } + pub fn to_bool(self) -> Option<bool> { + match self { + ScalarValue::Boolean(b) => Some(b), + _ => None, + } + } + + pub fn to_string(self) -> Option<String> { + match self { + ScalarValue::Str(s) => Some(s.to_string()), + _ => None, + } + } + pub fn counter(n: i64) -> ScalarValue { ScalarValue::Counter(n.into()) } } + +impl From<&str> for ScalarValue { + fn from(s: &str) -> Self { + ScalarValue::Str(s.into()) + } +} + +impl From<String> for ScalarValue { + fn from(s: String) -> Self { + ScalarValue::Str(s.into()) + } +} + +impl From<i64> for ScalarValue { + fn from(n: i64) -> Self { + ScalarValue::Int(n) + } +} + +impl From<u64> for ScalarValue { + fn from(n: u64) -> Self { + ScalarValue::Uint(n) + } +} + +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 From<char> for ScalarValue { + fn from(c: char) -> Self { + ScalarValue::Str(SmolStr::new(c.to_string())) + } +} + +impl fmt::Display for ScalarValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ScalarValue::Bytes(b) => write!(f, "\"{:?}\"", b), + ScalarValue::Str(s) => write!(f, "\"{}\"", s), + ScalarValue::Int(i) => write!(f, "{}", i), + ScalarValue::Uint(i) => write!(f, "{}", i), + 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"), + } + } +}