From ebcf9527dcaad95981e9b975fadf85ef6311abd9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 7 Sep 2021 02:44:38 +0000 Subject: [PATCH] refactor sync-server into a lib crate with a binary --- sync-server/src/api/add_version.rs | 31 ++++++------ sync-server/src/api/get_child_version.rs | 24 ++++----- sync-server/src/api/mod.rs | 2 +- .../taskchampion-sync-server.rs} | 49 +++++-------------- sync-server/src/lib.rs | 37 ++++++++++++++ sync-server/src/storage/inmemory.rs | 5 +- sync-server/src/storage/mod.rs | 25 +++++----- sync-server/src/storage/sqlite.rs | 2 +- 8 files changed, 93 insertions(+), 82 deletions(-) rename sync-server/src/{main.rs => bin/taskchampion-sync-server.rs} (52%) create mode 100644 sync-server/src/lib.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 60db1f385..9804258a6 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -77,9 +77,8 @@ pub(crate) async fn service( #[cfg(test)] mod test { - use crate::api::ServerState; - use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; use actix_web::{http::StatusCode, test, App}; use uuid::Uuid; @@ -88,16 +87,16 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::nil()).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -125,16 +124,16 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, version_id).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -159,9 +158,9 @@ mod test { async fn test_bad_content_type() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -178,9 +177,9 @@ mod test { async fn test_empty_body() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index a38113803..f5d67184e 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -45,8 +45,8 @@ pub(crate) async fn service( #[cfg(test)] mod test { use crate::api::ServerState; - use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; use actix_web::{http::StatusCode, test, App}; use uuid::Uuid; @@ -55,18 +55,18 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec()) .unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -97,9 +97,9 @@ mod test { async fn test_client_not_found() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -116,15 +116,15 @@ mod test { async fn test_version_not_found() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // create the client, but not the version { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 26aa8eba0..917dda83e 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -20,7 +20,7 @@ pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key"; pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; /// The type containing a reference to the Storage object in the Actix state. -pub(crate) type ServerState = Arc>; +pub(crate) type ServerState = Arc; pub(crate) fn api_scope() -> Scope { web::scope("") diff --git a/sync-server/src/main.rs b/sync-server/src/bin/taskchampion-sync-server.rs similarity index 52% rename from sync-server/src/main.rs rename to sync-server/src/bin/taskchampion-sync-server.rs index 394031738..88f0bb180 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -1,29 +1,9 @@ #![deny(clippy::all)] -use crate::storage::{SqliteStorage, Storage}; -use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; -use api::{api_scope, ServerState}; +use actix_web::{middleware::Logger, App, HttpServer}; use clap::Arg; - -mod api; -mod server; -mod storage; - -// TODO: use hawk to sign requests - -#[get("/")] -async fn index() -> impl Responder { - format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) -} - -/// Return a scope defining the URL rules for this server, with access to -/// the given ServerState. -pub(crate) fn app_scope(server_state: ServerState) -> Scope { - web::scope("") - .data(server_state) - .service(index) - .service(api_scope()) -} +use taskchampion_sync_server::storage::SqliteStorage; +use taskchampion_sync_server::Server; #[actix_web::main] async fn main() -> anyhow::Result<()> { @@ -56,33 +36,26 @@ async fn main() -> anyhow::Result<()> { let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); - let server_box: Box = Box::new(SqliteStorage::new(data_dir)?); - let server_state = ServerState::new(server_box); + let server = Server::new(Box::new(SqliteStorage::new(data_dir)?)); log::warn!("Serving on port {}", port); - HttpServer::new(move || { - App::new() - .wrap(Logger::default()) - .service(app_scope(server_state.clone())) - }) - .bind(format!("0.0.0.0:{}", port))? - .run() - .await?; + HttpServer::new(move || App::new().wrap(Logger::default()).service(server.service())) + .bind(format!("0.0.0.0:{}", port))? + .run() + .await?; Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::api::ServerState; - use crate::storage::{InMemoryStorage, Storage}; use actix_web::{test, App}; + use taskchampion_sync_server::storage::{InMemoryStorage, Storage}; #[actix_rt::test] async fn test_index_get() { - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(Box::new(InMemoryStorage::new())); + let mut app = test::init_service(App::new().service(server.service())).await; let req = test::TestRequest::get().uri("/").to_request(); let resp = test::call_service(&mut app, req).await; diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs new file mode 100644 index 000000000..c219f2e1b --- /dev/null +++ b/sync-server/src/lib.rs @@ -0,0 +1,37 @@ +#![deny(clippy::all)] + +mod api; +mod server; +pub mod storage; + +use crate::storage::Storage; +use actix_web::{get, web, Responder, Scope}; +use api::{api_scope, ServerState}; + +#[get("/")] +async fn index() -> impl Responder { + format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) +} + +/// A Server represents a sync server. +#[derive(Clone)] +pub struct Server { + storage: ServerState, +} + +impl Server { + /// Create a new sync server with the given storage implementation. + pub fn new(storage: Box) -> Self { + Self { + storage: storage.into(), + } + } + + /// Get an Actix-web service for this server. + pub fn service(&self) -> Scope { + web::scope("") + .data(self.storage.clone()) + .service(index) + .service(api_scope()) + } +} diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index e37abb07a..143b2e760 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -10,10 +10,11 @@ struct Inner { versions: HashMap<(Uuid, Uuid), Version>, } -pub(crate) struct InMemoryStorage(Mutex); +pub struct InMemoryStorage(Mutex); impl InMemoryStorage { - pub(crate) fn new() -> Self { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { Self(Mutex::new(Inner { clients: HashMap::new(), versions: HashMap::new(), diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index f9a2fc699..30b961fa1 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,27 +1,28 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[cfg(test)] +#[cfg(debug_assertions)] mod inmemory; -#[cfg(test)] -pub(crate) use inmemory::InMemoryStorage; + +#[cfg(debug_assertions)] +pub use inmemory::InMemoryStorage; mod sqlite; -pub(crate) use self::sqlite::SqliteStorage; +pub use self::sqlite::SqliteStorage; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub(crate) struct Client { - pub(crate) latest_version_id: Uuid, +pub struct Client { + pub latest_version_id: Uuid, } #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub(crate) struct Version { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: Vec, +pub struct Version { + pub version_id: Uuid, + pub parent_version_id: Uuid, + pub history_segment: Vec, } -pub(crate) trait StorageTxn { +pub trait StorageTxn { /// Get information about the given client fn get_client(&mut self, client_key: Uuid) -> anyhow::Result>; @@ -58,7 +59,7 @@ pub(crate) trait StorageTxn { /// A trait for objects able to act as storage. Most of the interesting behavior is in the /// [`crate::storage::StorageTxn`] trait. -pub(crate) trait Storage: Send + Sync { +pub trait Storage: Send + Sync { /// Begin a transaction fn txn<'a>(&'a self) -> anyhow::Result>; } diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 34cdd05b7..ba56cb0a6 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -49,7 +49,7 @@ impl ToSql for Client { } /// An on-disk storage backend which uses SQLite -pub(crate) struct SqliteStorage { +pub struct SqliteStorage { db_file: std::path::PathBuf, }