From 5b1b911bf7db9419e366270293512002df58d080 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 19:44:30 -0500 Subject: [PATCH] Move add_version and get_child_version to server module ..and add some tests specifically for those functions, in the absence of all the HTTP nonsense. --- sync-server/src/api/add_version.rs | 30 +--- sync-server/src/api/get_child_version.rs | 18 +-- sync-server/src/server.rs | 197 +++++++++++++++++++++++ sync-server/src/server/mod.rs | 25 --- sync-server/src/storage/mod.rs | 4 +- 5 files changed, 201 insertions(+), 73 deletions(-) create mode 100644 sync-server/src/server.rs delete mode 100644 sync-server/src/server/mod.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 2413c5fce..770ce2abc 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -2,12 +2,9 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{AddVersionResult, ClientId, HistorySegment, VersionId, NO_VERSION_ID}; -use crate::storage::{Client, StorageTxn}; +use crate::server::{add_version, AddVersionResult, ClientId, VersionId}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; -use failure::Fallible; use futures::StreamExt; -use taskchampion::Uuid; /// Max history segment size: 100MB const MAX_SIZE: usize = 100 * 1024 * 1024; @@ -71,31 +68,6 @@ pub(crate) async fn service( }) } -fn add_version<'a>( - mut txn: Box, - client_id: ClientId, - client: Client, - parent_version_id: VersionId, - history_segment: HistorySegment, -) -> Fallible { - // check if this version is acceptable, under the protection of the transaction - if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion( - client.latest_version_id, - )); - } - - // invent a version ID - let version_id = Uuid::new_v4(); - - // update the DB - txn.add_version(client_id, version_id, parent_version_id, history_segment)?; - txn.set_client_latest_version_id(client_id, version_id)?; - txn.commit()?; - - Ok(AddVersionResult::Ok(version_id)) -} - #[cfg(test)] mod test { use crate::api::ServerState; diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 84af0b179..83f9e2224 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -2,10 +2,8 @@ use crate::api::{ failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, }; -use crate::server::{ClientId, GetVersionResult, VersionId}; -use crate::storage::StorageTxn; +use crate::server::{get_child_version, ClientId, VersionId}; use actix_web::{error, get, web, HttpResponse, Result}; -use failure::Fallible; /// Get a child version. /// @@ -41,20 +39,6 @@ pub(crate) async fn service( } } -fn get_child_version<'a>( - mut txn: Box, - client_id: ClientId, - parent_version_id: VersionId, -) -> Fallible> { - Ok(txn - .get_version_by_parent(client_id, parent_version_id)? - .map(|version| GetVersionResult { - version_id: version.version_id, - parent_version_id: version.parent_version_id, - history_segment: version.history_segment, - })) -} - #[cfg(test)] mod test { use crate::api::ServerState; diff --git a/sync-server/src/server.rs b/sync-server/src/server.rs new file mode 100644 index 000000000..0a404172d --- /dev/null +++ b/sync-server/src/server.rs @@ -0,0 +1,197 @@ +//! This module implements the core logic of the server: handling transactions, upholding +//! invariants, and so on. +use crate::storage::{Client, StorageTxn}; +use failure::Fallible; +use taskchampion::Uuid; + +/// The distinguished value for "no version" +pub const NO_VERSION_ID: VersionId = Uuid::nil(); + +pub(crate) type HistorySegment = Vec; +pub(crate) type ClientId = Uuid; +pub(crate) type VersionId = Uuid; + +/// Response to get_child_version +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct GetVersionResult { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: HistorySegment, +} + +pub(crate) fn get_child_version<'a>( + mut txn: Box, + client_id: ClientId, + parent_version_id: VersionId, +) -> Fallible> { + Ok(txn + .get_version_by_parent(client_id, parent_version_id)? + .map(|version| GetVersionResult { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment, + })) +} + +/// Response to add_version +#[derive(Clone, PartialEq, Debug)] +pub(crate) enum AddVersionResult { + /// OK, version added with the given ID + Ok(VersionId), + /// Rejected; expected a version with the given parent version + ExpectedParentVersion(VersionId), +} + +pub(crate) fn add_version<'a>( + mut txn: Box, + client_id: ClientId, + client: Client, + parent_version_id: VersionId, + history_segment: HistorySegment, +) -> Fallible { + // check if this version is acceptable, under the protection of the transaction + if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + client.latest_version_id, + )); + } + + // invent a version ID + let version_id = Uuid::new_v4(); + + // update the DB + txn.add_version(client_id, version_id, parent_version_id, history_segment)?; + txn.set_client_latest_version_id(client_id, version_id)?; + txn.commit()?; + + Ok(AddVersionResult::Ok(version_id)) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::storage::{InMemoryStorage, Storage}; + + #[test] + fn gcv_not_found() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + assert_eq!(get_child_version(txn, client_id, parent_version_id)?, None); + Ok(()) + } + + #[test] + fn gcv_found() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let version_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + + txn.add_version( + client_id, + version_id, + parent_version_id, + history_segment.clone(), + )?; + + assert_eq!( + get_child_version(txn, client_id, parent_version_id)?, + Some(GetVersionResult { + version_id, + parent_version_id, + history_segment, + }) + ); + Ok(()) + } + + #[test] + fn av_conflict() -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + let existing_parent_version_id = Uuid::new_v4(); + let client = Client { + latest_version_id: existing_parent_version_id, + }; + + assert_eq!( + add_version( + txn, + client_id, + client, + parent_version_id, + history_segment.clone() + )?, + AddVersionResult::ExpectedParentVersion(existing_parent_version_id) + ); + + // verify that the storage wasn't updated + txn = storage.txn()?; + assert_eq!(txn.get_client(client_id)?, None); + assert_eq!( + txn.get_version_by_parent(client_id, parent_version_id)?, + None + ); + + Ok(()) + } + + fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> { + let storage = InMemoryStorage::new(); + let mut txn = storage.txn()?; + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let history_segment = b"abcd".to_vec(); + let client = Client { + latest_version_id: if latest_version_id_nil { + Uuid::nil() + } else { + parent_version_id + }, + }; + + let result = add_version( + txn, + client_id, + client, + parent_version_id, + history_segment.clone(), + )?; + if let AddVersionResult::Ok(new_version_id) = result { + // check that it invented a new version ID + assert!(new_version_id != parent_version_id); + + // verify that the storage was updated + txn = storage.txn()?; + let client = txn.get_client(client_id)?.unwrap(); + assert_eq!(client.latest_version_id, new_version_id); + let version = txn + .get_version_by_parent(client_id, parent_version_id)? + .unwrap(); + assert_eq!(version.version_id, new_version_id); + assert_eq!(version.parent_version_id, parent_version_id); + assert_eq!(version.history_segment, history_segment); + } else { + panic!("did not get Ok from add_version"); + } + + Ok(()) + } + + #[test] + fn av_success_with_existing_history() -> Fallible<()> { + test_av_success(true) + } + + #[test] + fn av_success_nil_latest_version_id() -> Fallible<()> { + test_av_success(false) + } +} diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs deleted file mode 100644 index 9e2412ba3..000000000 --- a/sync-server/src/server/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -use taskchampion::Uuid; - -/// The distinguished value for "no version" -pub const NO_VERSION_ID: VersionId = Uuid::nil(); - -pub(crate) type HistorySegment = Vec; -pub(crate) type ClientId = Uuid; -pub(crate) type VersionId = Uuid; - -/// Response to get_child_version -#[derive(Clone)] -pub(crate) struct GetVersionResult { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: HistorySegment, -} - -/// Response to add_version -#[derive(Clone)] -pub(crate) enum AddVersionResult { - /// OK, version added with the given ID - Ok(VersionId), - /// Rejected; expected a version with the given parent version - ExpectedParentVersion(VersionId), -} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index 2b9bb4dc0..2915f3c4d 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -4,12 +4,12 @@ use taskchampion::Uuid; mod inmemory; pub(crate) use inmemory::InMemoryStorage; -#[derive(Clone)] +#[derive(Clone, PartialEq, Debug)] pub(crate) struct Client { pub(crate) latest_version_id: Uuid, } -#[derive(Clone)] +#[derive(Clone, PartialEq, Debug)] pub(crate) struct Version { pub(crate) version_id: Uuid, pub(crate) parent_version_id: Uuid,