Stub out backend interface

This commit is contained in:
Alex Good 2020-02-13 13:28:44 +00:00
parent 4c428702f6
commit 2afcd1bbb6
5 changed files with 560 additions and 2 deletions

View file

@ -3,5 +3,5 @@
members = [
"automerge",
"automerge-backend",
"automege-wasm",
"automerge-wasm",
]

View file

@ -1,9 +1,12 @@
[package]
name = "automerge-backend"
version = "0.1.0"
version = "0.0.1"
authors = ["Alex Good <alex@memoryandthought.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "^1.0", features=["derive"] }
serde_json = "^1.0"
uuid = { version = "^0.5.1", features=["v4"] }

View file

@ -0,0 +1,42 @@
use crate::protocol::ObjectID;
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum AutomergeError {
DuplicateObjectError,
MissingObjectError(ObjectID),
InvalidLinkTarget,
NotImplemented(String),
InvalidChange(String),
}
impl fmt::Display for AutomergeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl Error for AutomergeError {}
#[derive(Debug)]
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 {}
#[derive(Debug)]
pub struct InvalidChangeRequest(pub String);
impl Error for InvalidChangeRequest {}
impl fmt::Display for InvalidChangeRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}

View file

@ -1,3 +1,52 @@
mod protocol;
mod error;
use crate::protocol::{Change, ActorID, Clock};
pub struct Backend {
}
pub struct Patch {}
impl Backend {
pub fn init() -> Backend {
Backend{}
}
pub fn apply_changes(&mut self, _changes: Vec<Change>) -> Patch {
Patch{}
}
pub fn apply_local_change(&mut self, _change: Change) -> Patch {
Patch{}
}
pub fn get_patch(&self) -> Patch {
Patch{}
}
pub fn get_changes(&self) -> Vec<Change> {
Vec::new()
}
pub fn get_changes_for_actor_id(&self, _actor_id: ActorID) -> Vec<Change> {
Vec::new()
}
pub fn get_missing_changes(&self, _clock: Clock) -> Vec<Change> {
Vec::new()
}
pub fn get_missing_deps(&self) -> Clock {
Clock::empty()
}
pub fn merge(&mut self, _remote: &Backend) -> Patch {
Patch{}
}
}
#[cfg(test)]
mod tests {
#[test]

View file

@ -0,0 +1,464 @@
//! 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 = "<paste the contents of the output here>";
//! let changes: Vec<Change> = 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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(ObjectID::parse(&s))
}
}
impl Serialize for ObjectID {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<ActorID, u32>);
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<ActorID, u32> = 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 ElementID {
pub fn as_key(&self) -> Key {
match self {
ElementID::Head => Key("_head".to_string()),
ElementID::SpecificElementID(actor_id, elem) => Key(format!("{}:{}", actor_id.0, elem)),
}
}
}
impl<'de> Deserialize<'de> for ElementID {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<Self, Self::Err> {
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<Ordering> {
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<DataType>,
},
#[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, Clone)]
pub struct Change {
#[serde(rename = "actor")]
pub(crate) actor_id: ActorID,
#[serde(rename = "ops")]
pub(crate) operations: Vec<Operation>,
pub(crate) seq: u32,
pub(crate) message: Option<String>,
#[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()));
}
}