use strings as values, with option to allow removing

This commit is contained in:
Dustin J. Mitchell
2019-12-29 11:50:05 -05:00
parent e5bd258e84
commit e83bdc28cd
6 changed files with 89 additions and 39 deletions

View File

@@ -1,17 +1,23 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
/// An Operation defines a single change to the task database
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum Operation {
Create {
uuid: Uuid,
},
/// Create a new task; if the task already exists in the DB.
///
/// On application, if the task already exists, the operation does nothing.
Create { uuid: Uuid },
/// Update an existing task, setting the given property to the given value. If the value is
/// None, then the corresponding property is deleted.
///
/// If the given task does not exist, the operation does nothing.
Update {
uuid: Uuid,
property: String,
value: Value,
value: Option<String>,
timestamp: DateTime<Utc>,
},
}
@@ -142,25 +148,25 @@ mod test {
Update {
uuid,
property: "abc".into(),
value: true.into(),
value: Some("true".into()),
timestamp,
},
Update {
uuid,
property: "def".into(),
value: false.into(),
value: Some("false".into()),
timestamp,
},
Some(Update {
uuid,
property: "abc".into(),
value: true.into(),
value: Some("true".into()),
timestamp,
}),
Some(Update {
uuid,
property: "def".into(),
value: false.into(),
value: Some("false".into()),
timestamp,
}),
);
@@ -176,20 +182,20 @@ mod test {
Update {
uuid,
property: "abc".into(),
value: true.into(),
value: Some("true".into()),
timestamp: timestamp1,
},
Update {
uuid,
property: "abc".into(),
value: false.into(),
value: Some("false".into()),
timestamp: timestamp2,
},
None,
Some(Update {
uuid,
property: "abc".into(),
value: false.into(),
value: Some("false".into()),
timestamp: timestamp2,
}),
);
@@ -204,19 +210,19 @@ mod test {
Update {
uuid,
property: "abc".into(),
value: true.into(),
value: Some("true".into()),
timestamp,
},
Update {
uuid,
property: "abc".into(),
value: false.into(),
value: Some("false".into()),
timestamp,
},
Some(Update {
uuid,
property: "abc".into(),
value: true.into(),
value: Some("true".into()),
timestamp,
}),
None,

View File

@@ -1,16 +1,17 @@
use crate::operation::Operation;
use crate::server::{Server, VersionAdd};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::str;
use uuid::Uuid;
type TaskMap = HashMap<String, String>;
#[derive(PartialEq, Debug, Clone)]
pub struct DB {
// The current state, with all pending operations applied
tasks: HashMap<Uuid, HashMap<String, Value>>,
tasks: HashMap<Uuid, TaskMap>,
// The version at which `operations` begins
base_version: u64,
@@ -56,17 +57,24 @@ impl DB {
} => {
// update if this task exists, otherwise ignore
if let Some(task) = self.tasks.get_mut(uuid) {
task.insert(property.clone(), value.clone());
DB::apply_update(task, property, value);
}
}
};
self.operations.push(op);
}
fn apply_update(task: &mut TaskMap, property: &str, value: &Option<String>) {
match value {
Some(ref val) => task.insert(property.to_string(), val.clone()),
None => task.remove(property),
};
}
/// Get a read-only reference to the underlying set of tasks.
///
/// This API is temporary, but provides query access to the DB.
pub fn tasks(&self) -> &HashMap<Uuid, HashMap<String, Value>> {
pub fn tasks(&self) -> &HashMap<Uuid, TaskMap> {
&self.tasks
}
@@ -194,19 +202,58 @@ mod tests {
let op2 = Operation::Update {
uuid,
property: String::from("title"),
value: Value::from("\"my task\""),
value: Some("my task".into()),
timestamp: Utc::now(),
};
db.apply(op2.clone());
let mut exp = HashMap::new();
let mut task = HashMap::new();
task.insert(String::from("title"), Value::from("\"my task\""));
task.insert(String::from("title"), String::from("my task"));
exp.insert(uuid, task);
assert_eq!(db.tasks(), &exp);
assert_eq!(db.operations, vec![op1, op2]);
}
#[test]
fn test_apply_create_update_delete_prop() {
let mut db = DB::new();
let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid };
db.apply(op1.clone());
let op2 = Operation::Update {
uuid,
property: String::from("title"),
value: Some("my task".into()),
timestamp: Utc::now(),
};
db.apply(op2.clone());
let op3 = Operation::Update {
uuid,
property: String::from("priority"),
value: Some("H".into()),
timestamp: Utc::now(),
};
db.apply(op3.clone());
let op4 = Operation::Update {
uuid,
property: String::from("title"),
value: None,
timestamp: Utc::now(),
};
db.apply(op4.clone());
let mut exp = HashMap::new();
let mut task = HashMap::new();
task.insert(String::from("priority"), String::from("H"));
exp.insert(uuid, task);
assert_eq!(db.tasks(), &exp);
assert_eq!(db.operations, vec![op1, op2, op3, op4]);
}
#[test]
fn test_apply_update_does_not_exist() {
let mut db = DB::new();
@@ -214,7 +261,7 @@ mod tests {
let op = Operation::Update {
uuid,
property: String::from("title"),
value: Value::from("\"my task\""),
value: Some("my task".into()),
timestamp: Utc::now(),
};
db.apply(op.clone());