diff --git a/src/lib.rs b/src/lib.rs index c2c80cfef..7ec4cd83c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod errors; mod operation; +mod replica; mod server; mod taskdb; diff --git a/src/replica.rs b/src/replica.rs new file mode 100644 index 000000000..c0b147997 --- /dev/null +++ b/src/replica.rs @@ -0,0 +1,99 @@ +use crate::errors::Error; +use crate::operation::Operation; +use crate::taskdb::DB; +use chrono::Utc; +use std::collections::HashMap; +use uuid::Uuid; + +/// A replica represents an instance of a user's task data. +struct Replica { + taskdb: Box, +} + +impl Replica { + pub fn new(taskdb: Box) -> Replica { + return Replica { taskdb }; + } + + /// Create a new task. The task must not already exist. + pub fn create_task(&mut self, uuid: Uuid) -> Result<(), Error> { + self.taskdb.apply(Operation::Create { uuid }) + } + + /// Delete a task. The task must exist. + pub fn delete_task(&mut self, uuid: Uuid) -> Result<(), Error> { + self.taskdb.apply(Operation::Delete { uuid }) + } + + /// Update an existing task. If the value is Some, the property is added or updated. If the + /// value is None, the property is deleted. It is not an error to delete a nonexistent + /// property. + pub fn update_task( + &mut self, + uuid: Uuid, + property: S1, + value: Option, + ) -> Result<(), Error> + where + S1: Into, + S2: Into, + { + self.taskdb.apply(Operation::Update { + uuid, + property: property.into(), + value: value.map(|v| v.into()), + timestamp: Utc::now(), + }) + } + + /// Get an existing task by its UUID + pub fn get_task(&self, uuid: &Uuid) -> Option<&HashMap> { + self.taskdb.tasks().get(&uuid) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::taskdb::DB; + use uuid::Uuid; + + #[test] + fn create() { + let mut rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + + rep.create_task(uuid.clone()).unwrap(); + assert_eq!(rep.get_task(&uuid), Some(&HashMap::new())); + } + + #[test] + fn delete() { + let mut rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + + rep.create_task(uuid.clone()).unwrap(); + rep.delete_task(uuid.clone()).unwrap(); + assert_eq!(rep.get_task(&uuid), None); + } + + #[test] + fn update() { + let mut rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + + rep.create_task(uuid.clone()).unwrap(); + rep.update_task(uuid.clone(), "title", Some("snarsblat")) + .unwrap(); + let mut task = HashMap::new(); + task.insert("title".into(), "snarsblat".into()); + assert_eq!(rep.get_task(&uuid), Some(&task)); + } + + #[test] + fn get_does_not_exist() { + let rep = Replica::new(DB::new().into()); + let uuid = Uuid::new_v4(); + assert_eq!(rep.get_task(&uuid), None); + } +} diff --git a/src/taskdb.rs b/src/taskdb.rs index 1c1cec9e9..34506450d 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -39,9 +39,9 @@ impl DB { } } - /// Apply an operation to the DB. Aside from synchronization operations, this - /// is the only way to modify the DB. In cases where an operation does not - /// make sense, this function will ignore the operation. + /// Apply an operation to the DB. Aside from synchronization operations, this is the only way + /// to modify the DB. In cases where an operation does not make sense, this function will do + /// nothing and return an error (but leave the DB in a consistent state). pub fn apply(&mut self, op: Operation) -> Result<(), Error> { if let err @ Err(_) = self.apply_op(&op) { return err;