//! This module contains types which are deserialized from the changes which //! are produced by the automerge JS library. Given the following code //! //! ```javascript //! doc = ... // create and edit an automerge document //! let changes = Automerge.getHistory(doc).map(h => h.change) //! console.log(JSON.stringify(changes, null, 4)) //! ``` //! //! The output of this can then be deserialized like so //! //! ```rust,no_run //! # use automerge::Change; //! let changes_str = ""; //! let changes: Vec = serde_json::from_str(changes_str).unwrap(); //! ``` use core::cmp::max; use serde::de; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cmp::{Ordering, PartialOrd}; use std::collections::HashMap; use std::str::FromStr; use crate::error; #[derive(Eq, PartialEq, Debug, Hash, Clone)] pub enum ObjectID { ID(String), Root, } impl ObjectID { fn parse(s: &str) -> ObjectID { match s { "00000000-0000-0000-0000-000000000000" => ObjectID::Root, _ => ObjectID::ID(s.into()), } } } impl<'de> Deserialize<'de> for ObjectID { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Ok(ObjectID::parse(&s)) } } impl Serialize for ObjectID { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let id_str = match self { ObjectID::Root => "00000000-0000-0000-0000-000000000000", ObjectID::ID(id) => id, }; serializer.serialize_str(id_str) } } #[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Hash, Clone)] pub struct Key(pub String); #[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone, PartialOrd, Ord)] pub struct ActorID(pub String); #[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)] pub struct Clock(pub HashMap); impl Clock { pub fn empty() -> Clock { Clock(HashMap::new()) } pub(crate) fn with_dependency(&self, actor_id: &ActorID, new_seq: u32) -> Clock { let mut result = self.0.clone(); result.insert(actor_id.clone(), new_seq); Clock(result) } pub fn upper_bound(&self, other: &Clock) -> Clock { let mut result: HashMap = HashMap::new(); self.0.iter().for_each(|(actor_id, seq)| { result.insert( actor_id.clone(), max(*seq, *other.0.get(actor_id).unwrap_or(&(0 as u32))), ); }); other.0.iter().for_each(|(actor_id, seq)| { result.insert( actor_id.clone(), max(*seq, *self.0.get(actor_id).unwrap_or(&(0 as u32))), ); }); Clock(result) } pub fn is_before_or_concurrent_with(&self, other: &Clock) -> bool { other .0 .iter() .all(|(actor_id, seq)| self.0.get(actor_id).unwrap_or(&0) >= seq) } pub(crate) fn seq_for(&self, actor_id: &ActorID) -> u32 { *self.0.get(actor_id).unwrap_or(&0) } } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] #[serde(untagged)] pub enum PrimitiveValue { Str(String), Number(f64), Boolean(bool), Null, } #[derive(PartialEq, Eq, Debug, Hash, Clone)] pub enum ElementID { Head, SpecificElementID(ActorID, u32), } impl<'de> Deserialize<'de> for ElementID { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; ElementID::from_str(&s).map_err(|_| de::Error::custom("invalid element ID")) } } impl Serialize for ElementID { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self { ElementID::Head => serializer.serialize_str("_head"), ElementID::SpecificElementID(actor_id, elem) => { serializer.serialize_str(&format!("{}:{}", actor_id.0, elem)) } } } } impl FromStr for ElementID { type Err = error::InvalidElementID; fn from_str(s: &str) -> Result { match s { "_head" => Ok(ElementID::Head), id => { let components: Vec<&str> = id.split(':').collect(); match components.as_slice() { [actor_id, elem_str] => { let elem = u32::from_str(elem_str) .map_err(|_| error::InvalidElementID(id.to_string()))?; Ok(ElementID::SpecificElementID( ActorID((*actor_id).to_string()), elem, )) } _ => Err(error::InvalidElementID(id.to_string())), } } } } } impl PartialOrd for ElementID { fn partial_cmp(&self, other: &ElementID) -> Option { Some(self.cmp(other)) } } impl Ord for ElementID { fn cmp(&self, other: &ElementID) -> Ordering { match (self, other) { (ElementID::Head, ElementID::Head) => Ordering::Equal, (ElementID::Head, _) => Ordering::Less, (_, ElementID::Head) => Ordering::Greater, ( ElementID::SpecificElementID(self_actor, self_elem), ElementID::SpecificElementID(other_actor, other_elem), ) => { if self_elem == other_elem { self_actor.cmp(other_actor) } else { self_elem.cmp(other_elem) } } } } } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub enum DataType { #[serde(rename = "counter")] Counter, #[serde(rename = "timestamp")] Timestamp, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] #[serde(tag = "action")] pub enum Operation { #[serde(rename = "makeMap")] MakeMap { #[serde(rename = "obj")] object_id: ObjectID, }, #[serde(rename = "makeList")] MakeList { #[serde(rename = "obj")] object_id: ObjectID, }, #[serde(rename = "makeText")] MakeText { #[serde(rename = "obj")] object_id: ObjectID, }, #[serde(rename = "makeTable")] MakeTable { #[serde(rename = "obj")] object_id: ObjectID, }, #[serde(rename = "ins")] Insert { #[serde(rename = "obj")] list_id: ObjectID, key: ElementID, elem: u32, }, #[serde(rename = "set")] Set { #[serde(rename = "obj")] object_id: ObjectID, key: Key, value: PrimitiveValue, #[serde(skip_serializing_if = "Option::is_none", default)] datatype: Option, }, #[serde(rename = "link")] Link { #[serde(rename = "obj")] object_id: ObjectID, key: Key, value: ObjectID, }, #[serde(rename = "del")] Delete { #[serde(rename = "obj")] object_id: ObjectID, key: Key, }, #[serde(rename = "inc")] Increment { #[serde(rename = "obj")] object_id: ObjectID, key: Key, value: f64, }, } #[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct Change { #[serde(rename = "actor")] pub(crate) actor_id: ActorID, #[serde(rename = "ops")] pub(crate) operations: Vec, pub(crate) seq: u32, pub(crate) message: Option, #[serde(rename = "deps")] pub(crate) dependencies: Clock, } #[cfg(test)] mod tests { use super::*; use serde_json; use std::iter::FromIterator; #[test] fn test_deserializing_operations() { let json_str = r#"{ "ops": [ { "action": "makeMap", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945" }, { "action": "makeList", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945" }, { "action": "makeText", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945" }, { "action": "makeTable", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945" }, { "action": "ins", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "someactorid:6", "elem": 5 }, { "action": "ins", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "_head", "elem": 6 }, { "action": "set", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "sometimestamp", "value": 123456, "datatype": "timestamp" }, { "action": "set", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "somekeyid", "value": true }, { "action": "set", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "somekeyid", "value": 123 }, { "action": "set", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "somekeyid", "value": null }, { "action": "link", "obj": "00000000-0000-0000-0000-000000000000", "key": "cards", "value": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945" }, { "action": "del", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "somekey" }, { "action": "inc", "obj": "2ed3ffe8-0ff3-4671-9777-aa16c3e09945", "key": "somekey", "value": 123 } ], "actor": "741e7221-11cc-4ef8-86ee-4279011569fd", "seq": 1, "deps": { "someid": 0 }, "message": "Initialization" }"#; let change: Change = serde_json::from_str(&json_str).unwrap(); assert_eq!( change, Change { actor_id: ActorID("741e7221-11cc-4ef8-86ee-4279011569fd".to_string()), operations: vec![ Operation::MakeMap { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()) }, Operation::MakeList { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()) }, Operation::MakeText { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()) }, Operation::MakeTable { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()) }, Operation::Insert { list_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: ElementID::SpecificElementID(ActorID("someactorid".to_string()), 6), elem: 5, }, Operation::Insert { list_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: ElementID::Head, elem: 6, }, Operation::Set { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: Key("sometimestamp".to_string()), value: PrimitiveValue::Number(123_456.0), datatype: Some(DataType::Timestamp) }, Operation::Set { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: Key("somekeyid".to_string()), value: PrimitiveValue::Boolean(true), datatype: None }, Operation::Set { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: Key("somekeyid".to_string()), value: PrimitiveValue::Number(123.0), datatype: None, }, Operation::Set { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: Key("somekeyid".to_string()), value: PrimitiveValue::Null, datatype: None, }, Operation::Link { object_id: ObjectID::Root, key: Key("cards".to_string()), value: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()) }, Operation::Delete { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: Key("somekey".to_string()) }, Operation::Increment { object_id: ObjectID::ID("2ed3ffe8-0ff3-4671-9777-aa16c3e09945".to_string()), key: Key("somekey".to_string()), value: 123.0, } ], seq: 1, message: Some("Initialization".to_string()), dependencies: Clock(HashMap::from_iter(vec![(ActorID("someid".to_string()), 0)])) } ); } #[test] fn test_deserialize_elementid() { let json_str = "\"_head\""; let elem: ElementID = serde_json::from_str(json_str).unwrap(); assert_eq!(elem, ElementID::Head); } #[test] fn test_serialize_elementid() { let result = serde_json::to_value(ElementID::Head).unwrap(); assert_eq!(result, serde_json::Value::String("_head".to_string())); } }