diff --git a/Cargo.toml b/Cargo.toml index abcbd25..cbc0e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,19 @@ [package] -authors = ["Marcel Müller "] +authors = [ + "Marcel Müller ", + "DecDuck ", +] categories = ["database-implementations"] description = "A modular and configurable database" documentation = "https://docs.rs/rustbreak" edition = "2018" -homepage = "https://github.com/TheNeikos/rustbreak" +homepage = "https://github.com/Drop-OSS/dropbreak" keywords = ["database", "simple", "fast", "rustbreak"] license = "MPL-2.0" -name = "rustbreak" +name = "dropbreak" readme = "README.md" -repository = "https://github.com/TheNeikos/rustbreak" -version = "2.0.0" +repository = "https://github.com/Drop-OSS/dropbreak" +version = "2.0.0-drop" [package.metadata.docs.rs] all-features = true @@ -20,6 +23,10 @@ serde = "1" tempfile = "3" thiserror = "1.0.20" +[dependencies.tokio] +version = "1.46.1" +features = ["sync", "rt", "macros", "fs", "io-util"] + [dependencies.ron] optional = true version = "0.6" @@ -49,10 +56,9 @@ lazy_static = "1" serde_derive = "1" [features] -default = [] +default = ["ron_enc"] ron_enc = ["ron"] bin_enc = ["bincode", "base64"] yaml_enc = ["serde_yaml"] other_errors = ["anyhow"] mmap = ["memmap"] - diff --git a/README.md b/README.md index 2d39a5a..fb89aab 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -Rustbreak -========= +# Dropbreak (Rustbreak) -[![Build Status](https://travis-ci.org/TheNeikos/rustbreak.svg?branch=master)](https://travis-ci.org/TheNeikos/rustbreak) -[![Crates Link](https://img.shields.io/crates/v/rustbreak.svg)](https://crates.io/crates/rustbreak) +# About this fork + +We forked Rustbreak because it was 1. no longer maintained, and 2. we needed it to be `async`. Feel free to use this fork in your own code, though. No promise of maintenance or warranty is made. **[Documentation][doc]** @@ -11,8 +11,7 @@ database. It is meant to be fast and simple to use. You add it to your application and it should just work for you. The only thing you will have to take care of is saving. -When to use it --------------- +## When to use it This library started out because of a need to be able to quickly write an application in rust that needed some persistence while still being able to write @@ -21,24 +20,21 @@ arbitrary data to it. In Ruby there is [Daybreak] however for Rust there was no similar crate, until now! -When not to use it ------------------- +## When not to use it -Rustbreak makes several trade-offs to be easy to use and extend, so knowing of these drawbacks is important if +Rustbreak makes several trade-offs to be easy to use and extend, so knowing of these drawbacks is important if you wish to use the library: - The Database needs to fit into memory (Rustbreak cannot do partial loads/saves, so if the Database exceeds your available memory you will run OOM) - Not all backends support atomic saves, so if your program crashes while it is saving you might save incomplete data (Notably only `PathBackend` supports atomic saves) -Features --------- +## Features - Simple To Use, Fast, Secure - Threadsafe - Serde compatible storage (ron, bincode, or yaml included) -Quickstart ----------- +## Quickstart Add this to your `Cargo.toml`: @@ -73,20 +69,19 @@ fn main() -> rustbreak::Result<()> { } ``` -Usage ------ +## Usage Usage is quite simple: - Create/open a database using one of the Database constructors: - - Create a `FileDatabase` with `FileDatabase::from_path`. - - Create a `MemoryDatabase` with `MemoryDatabase::memory`. - - Create a `MmapDatabase` with `MmapDatabase::mmap` or `MmapDatabase::mmap_with_size` with `mmap` feature. - - Create a `Database` with `Database::from_parts`. + - Create a `FileDatabase` with `FileDatabase::from_path`. + - Create a `MemoryDatabase` with `MemoryDatabase::memory`. + - Create a `MmapDatabase` with `MmapDatabase::mmap` or `MmapDatabase::mmap_with_size` with `mmap` feature. + - Create a `Database` with `Database::from_parts`. - `Write`/`Read` data from the Database - Don't forget to run `save` periodically, or whenever it makes sense. - - You can save in parallel to using the Database. However you will lock - write acess while it is being written to storage. + - You can save in parallel to using the Database. However you will lock + write acess while it is being written to storage. ```rust # use std::collections::HashMap; @@ -149,6 +144,5 @@ features = ["bin_enc"] You can now use `rustbreak::deser::Bincode` as deserialization struct. - -[doc]:http://neikos.me/rustbreak/rustbreak/index.html -[Daybreak]:https://propublica.github.io/daybreak/ +[doc]: http://neikos.me/rustbreak/rustbreak/index.html +[Daybreak]: https://propublica.github.io/daybreak/ diff --git a/examples/config.rs b/examples/config.rs deleted file mode 100644 index 511154f..0000000 --- a/examples/config.rs +++ /dev/null @@ -1,61 +0,0 @@ -// This just reads an example configuration. -// If it doesn't find one, it uses your default configuration -// -// You can create one by writing this file to `/tmp/config.ron`: -// ``` -// --- -// user_path: /tmp/nope -// allow_overwrite: true -// ``` -// - -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate lazy_static; - -use rustbreak::deser::Ron; -use rustbreak::FileDatabase; -use std::default::Default; -use std::path::PathBuf; - -type DB = FileDatabase; - -lazy_static! { - static ref CONFIG: DB = { - let db = FileDatabase::load_from_path_or_default("/tmp/config.ron") - .expect("Create database from path"); - db.load().expect("Config to load"); - db - }; -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -struct Config { - user_path: PathBuf, - allow_overwrite: bool, -} - -impl Default for Config { - fn default() -> Config { - Config { - user_path: PathBuf::from("/tmp"), - allow_overwrite: false, - } - } -} - -fn main() { - let _conf: Config = CONFIG - .read(|conf| conf.clone()) - .expect("Reading configuration"); - - let (user_path, allow_overwrite) = CONFIG - .read(|conf| (conf.user_path.clone(), conf.allow_overwrite.clone())) - .expect("Read config"); - - println!( - "The current configuration is: {:?} and {}", - user_path, allow_overwrite - ); -} diff --git a/examples/full.rs b/examples/full.rs deleted file mode 100644 index 62eb894..0000000 --- a/examples/full.rs +++ /dev/null @@ -1,63 +0,0 @@ -#[macro_use] -extern crate serde_derive; - -use rustbreak::deser::Ron; -use rustbreak::FileDatabase; - -#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] -enum Country { - Italy, - UnitedKingdom, -} - -#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] -struct Person { - name: String, - country: Country, -} - -fn do_main() -> Result<(), rustbreak::RustbreakError> { - use std::collections::HashMap; - - let db = FileDatabase::, Ron>::load_from_path_or_default("test.ron")?; - - println!("Writing to Database"); - db.write(|db| { - db.insert( - "john".into(), - Person { - name: String::from("John Andersson"), - country: Country::Italy, - }, - ); - db.insert( - "fred".into(), - Person { - name: String::from("Fred Johnson"), - country: Country::UnitedKingdom, - }, - ); - println!("Entries: \n{:#?}", db); - })?; - - println!("Syncing Database"); - db.save()?; - - println!("Loading Database"); - db.load()?; - - println!("Reading from Database"); - db.read(|db| { - println!("Results:"); - println!("{:#?}", db); - })?; - - Ok(()) -} - -fn main() { - if let Err(e) = do_main() { - eprintln!("An error has occurred at: \n{}", e); - std::process::exit(1); - } -} diff --git a/examples/migration.rs.incomplete b/examples/migration.rs.incomplete deleted file mode 100644 index d6b0f92..0000000 --- a/examples/migration.rs.incomplete +++ /dev/null @@ -1,183 +0,0 @@ -/* This file is a complete work in progress! And is meant to illustrate a possible way of - * migrating databases with Rustbreak. - * - * Currently the major challenge is to statically define the upgrade process. - * - * This is currently done by using the `upgrading_macro`. - * - * The idea is to read the 'version' tag in the file, load it into a simple tag struct and - * read the version from there. This version is then used to dynamically get the corresponding - * struct type. This then gets converted to the latest version. - */ - - -extern crate rustbreak; -#[macro_use] extern crate serde_derive; - -use rustbreak::FileDatabase; -use rustbreak::deser::Ron; - -mod data { - #[derive(Debug, Serialize, Deserialize, Clone, Copy)] - pub enum Version { - First, Second, Third, - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct Versioning { - pub version: Version - } - - pub mod v1 { - use data::Version; - use std::collections::HashMap; - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct Players { - version: Version, - pub players: HashMap, - } - - impl Players { - pub fn new() -> Players { - Players { - version: Version::First, - players: HashMap::new(), - } - } - } - } - - pub mod v2 { - use data::Version; - use std::collections::HashMap; - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct Player { - pub name: String, - pub level: i32, - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct Players { - version: Version, - pub players: HashMap, - } - - impl Players { - pub fn new() -> Players { - Players { - version: Version::Second, - players: HashMap::new(), - } - } - } - } - - pub mod v3 { - use data::Version; - use std::collections::HashMap; - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct Player { - pub name: String, - pub score: i64 - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct Players { - version: Version, - pub players: HashMap, - } - - impl Players { - pub fn new() -> Players { - Players { - version: Version::Third, - players: HashMap::new(), - } - } - } - } - - pub fn convert_v1_v2(input: v1::Players) -> v2::Players { - let mut new = v2::Players::new(); - for (key, player) in input.players { - new.players.insert(key, - v2::Player { - name: player, - level: 1 - }); - } - - new - } - - pub fn convert_v2_v3(input: v2::Players) -> v3::Players { - let mut new = v3::Players::new(); - for (key, player) in input.players { - new.players.insert(key, - v3::Player { - name: player.name, - score: 0 - }); - } - - new - } -} - -macro_rules! migration_rules { - ( $name:ident -> $res:ty { $( $t:ty => $conv:path ),* $(,)* } ) => { - fn $name(input: T) -> Option<$res> { - fn inner(mut input: Box) -> Option<$res> { - println!("We have: {:#?}", input); - $( - { - let mut maybe_in = None; - if let Some(out) = input.downcast_ref::<$t>() { - maybe_in = Some(Box::new($conv(out.clone()))); - } - - if let Some(inp) = maybe_in { - input = inp; - } - } - )* - - if let Ok(out) = input.downcast::<$res>() { - return Some(*out); - } - - None - } - inner(Box::new(input)) - } - } -} - -migration_rules! { - update_database -> data::v3::Players { - data::v1::Players => data::convert_v1_v2, - data::v2::Players => data::convert_v2_v3, - } -} - -fn get_version() -> data::Version { - let db = FileDatabase::::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap(); - db.load().unwrap(); - db.read(|ver| ver.version).unwrap() -} - -fn main() { - use data::v3::Players; - - let version = get_version(); - - - // Let's check if there are any updates - let updated_data = update_database(database.get_data(false).unwrap()).unwrap(); - let database = FileDatabase::::from_path("migration.ron", updated_data).unwrap(); - - println!("{:#?}", database); -} diff --git a/examples/server/Cargo.toml b/examples/server/Cargo.toml deleted file mode 100644 index 68b9f0a..0000000 --- a/examples/server/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -authors = ["Marcel Müller "] -name = "server" -version = "0.1.0" - -[dependencies] -rocket = "0.3.3" -rocket_codegen = "0.3.3" -serde = "1.0.23" -serde_derive = "1.0.23" - -[dependencies.rocket_contrib] -default-features = false -features = ["handlebars_templates"] -version = "0.3.3" - -[dependencies.rustbreak] -path = "../.." -features = ["ron_enc"] diff --git a/examples/server/server.ron b/examples/server/server.ron deleted file mode 100644 index 27f0fd0..0000000 --- a/examples/server/server.ron +++ /dev/null @@ -1,26 +0,0 @@ -( - pastes: [ - ( - user: "neikos@neikos.email", - body: "Hello :)", - ), - ( - user: "neikos@neikos.email", - body: "This works great!", - ), - ( - user: "neikos@neikos.email", - body: "Hehe", - ), - ( - user: "neikos@neikos.email", - body: "Hello World :)", - ), - ], - users: { - "neikos@neikos.email": ( - username: "neikos@neikos.email", - password: "asdf", - ), - }, -) \ No newline at end of file diff --git a/examples/server/src/main.rs b/examples/server/src/main.rs deleted file mode 100644 index 519861a..0000000 --- a/examples/server/src/main.rs +++ /dev/null @@ -1,159 +0,0 @@ -#![feature(plugin, decl_macro, custom_derive)] -#![plugin(rocket_codegen)] - -extern crate rustbreak; -extern crate rocket; -extern crate rocket_contrib; -#[macro_use] extern crate serde_derive; - -use std::collections::HashMap; - -use rocket::{State, Outcome}; -use rocket::http::{Cookies, Cookie}; -use rocket::request::{self, Request, FromRequest, Form}; -use rocket::response::Redirect; -use rocket_contrib::Template; -use rustbreak::FileDatabase; -use rustbreak::deser::Ron; - -// We create a type alias so that we always associate the same types to it -type DB = FileDatabase; - -#[derive(Debug, Serialize, Deserialize, Clone)] -struct Paste { - user: String, - body: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone, FromForm)] -struct NewPaste { - body: String -} - -#[derive(Debug, Serialize, Deserialize, Clone, FromForm)] -struct User { - username: String, - password: String, -} - -impl <'a, 'r> FromRequest<'a, 'r> for User { - type Error = (); - - fn from_request(request: &'a Request<'r>) -> request::Outcome { - let mut cookies = request.cookies(); - let db = request.guard::>()?; - match cookies.get_private("user_id") { - Some(cookie) => { - let mut outcome = Outcome::Forward(()); - let _ = db.read(|db| { - if db.users.contains_key(cookie.value()) { - outcome = Outcome::Success(db.users.get(cookie.value()).unwrap().clone()); - } - }); - return outcome; - } - None => return Outcome::Forward(()) - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -struct ServerData { - pastes: Vec, - users: HashMap -} - -#[derive(Debug, Serialize)] -struct TemplateData { - pastes: Vec, - logged_in: bool, - user: String -} - - -// Routing - -#[get("/")] -fn index(db: State, user: Option) -> Template { - let mut data = TemplateData { - logged_in: user.is_some(), - user: user.map(|u| u.username).unwrap_or_else(|| String::new()), - pastes: vec![], - }; - let _ = db.read(|db| { - data.pastes = db.pastes.clone(); - }); - - return Template::render("index", &data); -} - -#[post("/register", data = "")] -fn post_register(db: State, req_user: Form, mut cookies: Cookies) -> Redirect { - let user = req_user.into_inner(); - let _ = db.write(|db| { - if db.users.contains_key(&user.username) { - return; - } - db.users.insert(user.username.clone(), user.clone()); - cookies.add_private( - Cookie::build("user_id", user.username.clone()).http_only(true).finish() - ); - }); - let _ = db.save(); - - Redirect::to("/") -} - -#[post("/login", data = "")] -fn post_login(db: State, req_user: Form, mut cookies: Cookies) -> Redirect { - let user = req_user.into_inner(); - let _ = db.read(|db| { - match db.users.get(&user.username) { - Some(u) => { - if u.password == user.password { - cookies.add_private( - Cookie::build("user_id", user.username.clone()).http_only(true).finish() - ); - } - } - None => () - } - }); - - Redirect::to("/") -} - -#[post("/paste", data = "")] -fn post_paste(db: State, user: User, paste: Form) -> Redirect { - let body : String = paste.into_inner().body.clone(); - let _ = db.write(|db| { - let paste = Paste { - body: body, - user: user.username.clone(), - }; - db.pastes.push(paste); - }); - let _ = db.save(); - - Redirect::to("/") -} - -#[get("/dummy")] -fn get_dummy() -> &'static str { - "Hello World" -} - -fn main() { - let db : DB = FileDatabase::from_path("server.ron", ServerData { - pastes: vec![], - users: HashMap::new(), - }).unwrap(); - let _ = db.load(); - - - rocket::ignite() - .mount("/", routes![index, post_login, post_paste, post_register, get_dummy]) - .attach(Template::fairing()) - .manage(db) - .launch(); -} diff --git a/examples/server/templates/index.hbs b/examples/server/templates/index.hbs deleted file mode 100644 index 3f926ad..0000000 --- a/examples/server/templates/index.hbs +++ /dev/null @@ -1,105 +0,0 @@ - - - - Rustbreak Pastes - - - - - {{#if logged_in }} -
-
- You are logged in as: {{user}} -
-
-
- -
- -
-
-
-
- -
-
-
-
- {{else}} -
-
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
-
- {{/if}} - -
- -
-

Pastes

- - {{#each pastes as |p| ~}} -
-
-
-

- {{p.user}}
- {{p.body}} -

-

-
-
- {{/each~}} -
-
-
-
-

- Made with love in Germany — Created using Rocket, Handlebars and Rustbreak -

- - Made with Bulma - -
-
-
- - diff --git a/examples/switching.rs b/examples/switching.rs deleted file mode 100644 index 514ec0f..0000000 --- a/examples/switching.rs +++ /dev/null @@ -1,61 +0,0 @@ -#[macro_use] -extern crate serde_derive; - -use rustbreak::deser::{Ron, Yaml}; -use rustbreak::{backend::FileBackend, FileDatabase}; - -#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] -enum Country { - Italy, - UnitedKingdom, -} - -#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] -struct Person { - name: String, - country: Country, -} - -fn do_main() -> Result<(), rustbreak::RustbreakError> { - use std::collections::HashMap; - - let db = FileDatabase::, Ron>::load_from_path_or_default("test.ron")?; - - println!("Writing to Database"); - db.write(|db| { - db.insert( - "john".into(), - Person { - name: String::from("John Andersson"), - country: Country::Italy, - }, - ); - db.insert( - "fred".into(), - Person { - name: String::from("Fred Johnson"), - country: Country::UnitedKingdom, - }, - ); - println!("Entries: \n{:#?}", db); - })?; - - println!("Syncing Database"); - db.save()?; - - // Now lets switch it - - let db = db - .with_deser(Yaml) - .with_backend(FileBackend::from_path_or_create("test.yml").map(|p| p.0)?); - db.save()?; - - Ok(()) -} - -fn main() { - if let Err(e) = do_main() { - eprintln!("An error has occurred at: \n{}", e); - std::process::exit(1); - } -} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 23bc7de..f63c89f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -21,33 +21,26 @@ use crate::error; /// **Important**: You can only return custom errors if the `other_errors` feature is enabled pub trait Backend { /// Read the all data from the backend. - fn get_data(&mut self) -> error::BackendResult>; + fn get_data( + &mut self, + ) -> impl std::future::Future>> + Send; /// Write the whole slice to the backend. - fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()>; + fn put_data(&mut self, data: &[u8]) -> impl std::future::Future> + Send; } -impl Backend for Box { - fn get_data(&mut self) -> error::BackendResult> { +impl Backend for Box +where + T: Send, +{ + async fn get_data(&mut self) -> error::BackendResult> { use std::ops::DerefMut; - self.deref_mut().get_data() + self.deref_mut().get_data().await } - fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { + async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { use std::ops::DerefMut; - self.deref_mut().put_data(data) - } -} - -impl Backend for Box { - fn get_data(&mut self) -> error::BackendResult> { - use std::ops::DerefMut; - self.deref_mut().get_data() - } - - fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { - use std::ops::DerefMut; - self.deref_mut().put_data(data) + self.deref_mut().put_data(data).await } } @@ -64,7 +57,7 @@ pub use path::PathBackend; pub struct FileBackend(std::fs::File); impl Backend for FileBackend { - fn get_data(&mut self) -> error::BackendResult> { + async fn get_data(&mut self) -> error::BackendResult> { use std::io::{Read, Seek, SeekFrom}; let mut buffer = vec![]; @@ -73,7 +66,7 @@ impl Backend for FileBackend { Ok(buffer) } - fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { + async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { use std::io::{Seek, SeekFrom, Write}; self.0.seek(SeekFrom::Start(0))?; @@ -160,18 +153,20 @@ impl MemoryBackend { } impl Backend for MemoryBackend { - fn get_data(&mut self) -> error::BackendResult> { + async fn get_data(&mut self) -> error::BackendResult> { println!("Returning data: {:?}", &self.0); Ok(self.0.clone()) } - fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { + async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { println!("Writing data: {:?}", data); self.0 = data.to_owned(); Ok(()) } } +/* + #[cfg(test)] mod tests { use super::{Backend, FileBackend, MemoryBackend}; @@ -323,3 +318,5 @@ mod tests { dir.close().expect("Error while deleting temp directory!"); } } + +*/ diff --git a/src/backend/path.rs b/src/backend/path.rs index 234a235..87547c0 100644 --- a/src/backend/path.rs +++ b/src/backend/path.rs @@ -7,9 +7,10 @@ use super::Backend; use crate::error; -use std::fs::OpenOptions; use std::path::{Path, PathBuf}; use tempfile::NamedTempFile; +use tokio::fs::{File, OpenOptions}; +use tokio::io::AsyncReadExt; /// A [`Backend`] using a file given the path. /// @@ -23,8 +24,8 @@ pub struct PathBackend { impl PathBackend { /// Opens a new [`PathBackend`] for a given path. /// Errors when the file doesn't yet exist. - pub fn from_path_or_fail(path: PathBuf) -> error::BackendResult { - OpenOptions::new().read(true).open(path.as_path())?; + pub async fn from_path_or_fail(path: PathBuf) -> error::BackendResult { + OpenOptions::new().read(true).open(path.as_path()).await?; Ok(Self { path }) } @@ -32,41 +33,44 @@ impl PathBackend { /// Creates a file if it doesn't yet exist. /// /// Returns the [`PathBackend`] and whether the file already existed. - pub fn from_path_or_create(path: PathBuf) -> error::BackendResult<(Self, bool)> { + pub async fn from_path_or_create(path: PathBuf) -> error::BackendResult<(Self, bool)> { let exists = path.as_path().is_file(); OpenOptions::new() .write(true) .create(true) - .open(path.as_path())?; + .open(path.as_path()) + .await?; Ok((Self { path }, exists)) } /// Opens a new [`PathBackend`] for a given path. /// Creates a file if it doesn't yet exist, and calls `closure` with it. - pub fn from_path_or_create_and(path: PathBuf, closure: C) -> error::BackendResult + pub async fn from_path_or_create_and(path: PathBuf, closure: C) -> error::BackendResult where - C: FnOnce(&mut std::fs::File), + C: AsyncFnOnce(&mut File), { let exists = path.as_path().is_file(); let mut file = OpenOptions::new() .read(true) .write(true) .create(true) - .open(path.as_path())?; + .open(path.as_path()) + .await?; if !exists { - closure(&mut file) + closure(&mut file).await } Ok(Self { path }) } } impl Backend for PathBackend { - fn get_data(&mut self) -> error::BackendResult> { - use std::io::Read; - - let mut file = OpenOptions::new().read(true).open(self.path.as_path())?; + async fn get_data(&mut self) -> error::BackendResult> { + let mut file = OpenOptions::new() + .read(true) + .open(self.path.as_path()) + .await?; let mut buffer = vec![]; - file.read_to_end(&mut buffer)?; + file.read_to_end(&mut buffer).await?; Ok(buffer) } @@ -74,7 +78,7 @@ impl Backend for PathBackend { /// /// This won't corrupt the existing database file if the program panics /// during the save. - fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { + async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { use std::io::Write; #[allow(clippy::or_fun_call)] // `Path::new` is a zero cost conversion @@ -89,58 +93,59 @@ impl Backend for PathBackend { #[cfg(test)] mod tests { use super::{Backend, PathBackend}; - use std::io::Write; use tempfile::NamedTempFile; + use tokio::io::AsyncWriteExt; - #[test] + #[tokio::test] #[cfg_attr(miri, ignore)] - fn test_path_backend_existing() { + async fn test_path_backend_existing() { let file = NamedTempFile::new().expect("could not create temporary file"); let (mut backend, existed) = PathBackend::from_path_or_create(file.path().to_owned()) + .await .expect("could not create backend"); assert!(existed); let data = [4, 5, 1, 6, 8, 1]; - backend.put_data(&data).expect("could not put data"); - assert_eq!(backend.get_data().expect("could not get data"), data); + backend.put_data(&data).await.expect("could not put data"); + assert_eq!(backend.get_data().await.expect("could not get data"), data); } - #[test] + #[tokio::test] #[cfg_attr(miri, ignore)] - fn test_path_backend_new() { + async fn test_path_backend_new() { let dir = tempfile::tempdir().expect("could not create temporary directory"); let mut file_path = dir.path().to_owned(); file_path.push("rustbreak_path_db.db"); let (mut backend, existed) = - PathBackend::from_path_or_create(file_path).expect("could not create backend"); + PathBackend::from_path_or_create(file_path).await.expect("could not create backend"); assert!(!existed); let data = [4, 5, 1, 6, 8, 1]; - backend.put_data(&data).expect("could not put data"); - assert_eq!(backend.get_data().expect("could not get data"), data); + backend.put_data(&data).await.expect("could not put data"); + assert_eq!(backend.get_data().await.expect("could not get data"), data); dir.close().expect("Error while deleting temp directory!"); } - #[test] + #[tokio::test] #[cfg_attr(miri, ignore)] - fn test_path_backend_nofail() { + async fn test_path_backend_nofail() { let file = NamedTempFile::new().expect("could not create temporary file"); let file_path = file.path().to_owned(); - let mut backend = PathBackend::from_path_or_fail(file_path).expect("should not fail"); + let mut backend = PathBackend::from_path_or_fail(file_path).await.expect("should not fail"); let data = [4, 5, 1, 6, 8, 1]; - backend.put_data(&data).expect("could not put data"); - assert_eq!(backend.get_data().expect("could not get data"), data); + backend.put_data(&data).await.expect("could not put data"); + assert_eq!(backend.get_data().await.expect("could not get data"), data); } - #[test] + #[tokio::test] #[cfg_attr(miri, ignore)] - fn test_path_backend_fail_notfound() { + async fn test_path_backend_fail_notfound() { let dir = tempfile::tempdir().expect("could not create temporary directory"); let mut file_path = dir.path().to_owned(); file_path.push("rustbreak_path_db.db"); let err = - PathBackend::from_path_or_fail(file_path).expect_err("should fail with file not found"); + PathBackend::from_path_or_fail(file_path).await.expect_err("should fail with file not found"); if let crate::error::BackendError::Io(io_err) = &err { assert_eq!(std::io::ErrorKind::NotFound, io_err.kind()); } else { @@ -150,40 +155,40 @@ mod tests { } // If the file already exists, the closure shouldn't be called. - #[test] + #[tokio::test] #[cfg_attr(miri, ignore)] - fn test_path_backend_create_and_existing_nocall() { + async fn test_path_backend_create_and_existing_nocall() { let file = NamedTempFile::new().expect("could not create temporary file"); - let mut backend = PathBackend::from_path_or_create_and(file.path().to_owned(), |_| { + let mut backend = PathBackend::from_path_or_create_and(file.path().to_owned(), async |_| { panic!("Closure called but file already existed"); }) - .expect("could not create backend"); + .await.expect("could not create backend"); let data = [4, 5, 1, 6, 8, 1]; - backend.put_data(&data).expect("could not put data"); - assert_eq!(backend.get_data().expect("could not get data"), data); + backend.put_data(&data).await.expect("could not put data"); + assert_eq!(backend.get_data().await.expect("could not get data"), data); } // If the file does not yet exist, the closure should be called. - #[test] + #[tokio::test] #[cfg_attr(miri, ignore)] - fn test_path_backend_create_and_new() { + async fn test_path_backend_create_and_new() { let dir = tempfile::tempdir().expect("could not create temporary directory"); let mut file_path = dir.path().to_owned(); file_path.push("rustbreak_path_db.db"); - let mut backend = PathBackend::from_path_or_create_and(file_path, |f| { + let mut backend = PathBackend::from_path_or_create_and(file_path, async |f| { f.write_all(b"this is a new file") - .expect("could not write to file") + .await.expect("could not write to file") }) - .expect("could not create backend"); + .await.expect("could not create backend"); assert_eq!( - backend.get_data().expect("could not get data"), + backend.get_data().await.expect("could not get data"), b"this is a new file" ); let data = [4, 5, 1, 6, 8, 1]; - backend.put_data(&data).expect("could not put data"); - assert_eq!(backend.get_data().expect("could not get data"), data); + backend.put_data(&data).await.expect("could not put data"); + assert_eq!(backend.get_data().await.expect("could not get data"), data); dir.close().expect("Error while deleting temp directory!"); } } diff --git a/src/lib.rs b/src/lib.rs index 02f6673..2092270 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,10 +203,10 @@ pub use crate::deser::DeSerializer; use std::fmt::Debug; use std::ops::Deref; use std::path::PathBuf; -use std::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use serde::de::DeserializeOwned; use serde::Serialize; +use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; #[cfg(feature = "mmap")] use crate::backend::MmapStorage; @@ -291,11 +291,11 @@ where /// # func().unwrap(); /// # } /// ``` - pub fn write(&self, task: T) -> error::Result + pub async fn write(&self, task: T) -> error::Result where T: FnOnce(&mut Data) -> R, { - let mut lock = self.data.write().map_err(|_| RustbreakError::Poison)?; + let mut lock = self.data.write().await; Ok(task(&mut lock)) } @@ -373,11 +373,11 @@ where /// # func().unwrap(); /// # } /// ``` - pub fn write_safe(&self, task: T) -> error::Result<()> + pub async fn write_safe(&self, task: T) -> error::Result<()> where T: FnOnce(&mut Data) + std::panic::UnwindSafe, { - let mut lock = self.data.write().map_err(|_| RustbreakError::Poison)?; + let mut lock = self.data.write().await; let mut data = lock.clone(); std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { task(&mut data); @@ -404,11 +404,11 @@ where /// any subsequent writes/reads will fail with an /// [`error::RustbreakError::Poison`]. You can only recover from /// this by re-creating the Database Object. - pub fn read(&self, task: T) -> error::Result + pub async fn read(&self, task: T) -> error::Result where T: FnOnce(&Data) -> R, { - let mut lock = self.data.read().map_err(|_| RustbreakError::Poison)?; + let mut lock = self.data.read().await; Ok(task(&mut lock)) } @@ -448,8 +448,8 @@ where /// # func().unwrap(); /// # } /// ``` - pub fn borrow_data<'a>(&'a self) -> error::Result> { - self.data.read().map_err(|_| RustbreakError::Poison) + pub async fn borrow_data<'a>(&'a self) -> RwLockReadGuard<'a, Data> { + self.data.read().await } /// Write lock the database and get access to the underlying struct. @@ -500,59 +500,60 @@ where /// # func().unwrap(); /// # } /// ``` - pub fn borrow_data_mut<'a>(&'a self) -> error::Result> { - self.data.write().map_err(|_| RustbreakError::Poison) + pub async fn borrow_data_mut<'a>(&'a self) -> RwLockWriteGuard<'a, Data> { + self.data.write().await } /// Load data from backend and return this data. - fn load_from_backend(backend: &mut Back, deser: &DeSer) -> error::Result { - let new_data = deser.deserialize(&backend.get_data()?[..])?; + async fn load_from_backend(backend: &mut Back, deser: &DeSer) -> error::Result { + let new_data = deser.deserialize(&backend.get_data().await?[..])?; Ok(new_data) } /// Like [`Self::load`] but returns the write lock to data it used. - fn load_get_data_lock(&self) -> error::Result> { - let mut backend_lock = self.backend.lock().map_err(|_| RustbreakError::Poison)?; + async fn load_get_data_lock(&self) -> error::Result> { + let mut backend_lock = self.backend.lock().await; - let fresh_data = Self::load_from_backend(&mut backend_lock, &self.deser)?; + let fresh_data = Self::load_from_backend(&mut backend_lock, &self.deser).await?; drop(backend_lock); - let mut data_write_lock = self.data.write().map_err(|_| RustbreakError::Poison)?; + let mut data_write_lock = self.data.write().await; *data_write_lock = fresh_data; Ok(data_write_lock) } /// Load the data from the backend. - pub fn load(&self) -> error::Result<()> { - self.load_get_data_lock().map(|_| ()) + pub async fn load(&self) -> error::Result<()> { + let _ = self.load_get_data_lock().await?; + Ok(()) } /// Like [`Self::save`] but with explicit read (or write) lock to data. - fn save_data_locked>(&self, lock: L) -> error::Result<()> { + async fn save_data_locked>(&self, lock: L) -> error::Result<()> { let ser = self.deser.serialize(lock.deref())?; drop(lock); - let mut backend = self.backend.lock().map_err(|_| RustbreakError::Poison)?; - backend.put_data(&ser)?; + let mut backend = self.backend.lock().await; + backend.put_data(&ser).await?; Ok(()) } /// Flush the data structure to the backend. - pub fn save(&self) -> error::Result<()> { - let data = self.data.read().map_err(|_| RustbreakError::Poison)?; - self.save_data_locked(data) + pub async fn save(&self) -> error::Result<()> { + let data = self.data.read().await; + self.save_data_locked(data).await } /// Get a clone of the data as it is in memory right now. /// /// To make sure you have the latest data, call this method with `load` /// true. - pub fn get_data(&self, load: bool) -> error::Result { + pub async fn get_data(&self, load: bool) -> error::Result { let data = if load { - self.load_get_data_lock()? + self.load_get_data_lock().await? } else { - self.data.write().map_err(|_| RustbreakError::Poison)? + self.data.write().await }; Ok(data.clone()) } @@ -560,11 +561,11 @@ where /// Puts the data as is into memory. /// /// To save the data afterwards, call with `save` true. - pub fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> { - let mut data = self.data.write().map_err(|_| RustbreakError::Poison)?; + pub async fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> { + let mut data = self.data.write().await; *data = new_data; if save { - self.save_data_locked(data) + self.save_data_locked(data).await } else { Ok(()) } @@ -582,10 +583,8 @@ where /// Break a database into its individual parts. pub fn into_inner(self) -> error::Result<(Data, Back, DeSer)> { Ok(( - self.data.into_inner().map_err(|_| RustbreakError::Poison)?, - self.backend - .into_inner() - .map_err(|_| RustbreakError::Poison)?, + self.data.into_inner(), + self.backend.into_inner(), self.deser, )) } @@ -632,8 +631,8 @@ where /// # func().unwrap(); /// # } /// ``` - pub fn try_clone(&self) -> error::Result> { - let lock = self.data.read().map_err(|_| RustbreakError::Poison)?; + pub async fn try_clone(&self) -> error::Result> { + let lock = self.data.read().await; Ok(Database { data: RwLock::new(lock.clone()), @@ -653,13 +652,13 @@ where { /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. - pub fn load_from_path(path: S) -> error::Result + pub async fn load_from_path(path: S) -> error::Result where S: AsRef, { let mut backend = FileBackend::from_path_or_fail(path)?; let deser = DeSer::default(); - let data = Self::load_from_backend(&mut backend, &deser)?; + let data = Self::load_from_backend(&mut backend, &deser).await?; let db = Self { data: RwLock::new(data), @@ -674,7 +673,7 @@ where /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. If the file does not exist, initialise with /// `data`. - pub fn load_from_path_or(path: S, data: Data) -> error::Result + pub async fn load_from_path_or(path: S, data: Data) -> error::Result where S: AsRef, { @@ -682,7 +681,7 @@ where let deser = DeSer::default(); if !exists { let ser = deser.serialize(&data)?; - backend.put_data(&ser)?; + backend.put_data(&ser).await?; } let db = Self { @@ -692,7 +691,7 @@ where }; if exists { - db.load()?; + db.load().await?; } Ok(db) @@ -703,7 +702,7 @@ where /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. If the file does not exist, `closure` is /// called and the database is initialised with it's return value. - pub fn load_from_path_or_else(path: S, closure: C) -> error::Result + pub async fn load_from_path_or_else(path: S, closure: C) -> error::Result where S: AsRef, C: FnOnce() -> Data, @@ -711,12 +710,12 @@ where let (mut backend, exists) = FileBackend::from_path_or_create(path)?; let deser = DeSer::default(); let data = if exists { - Self::load_from_backend(&mut backend, &deser)? + Self::load_from_backend(&mut backend, &deser).await? } else { let data = closure(); let ser = deser.serialize(&data)?; - backend.put_data(&ser)?; + backend.put_data(&ser).await?; data }; @@ -735,7 +734,7 @@ where /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path). /// Contents are not loaded. If the file does not exist, it is /// initialised with `data`. Frontend is always initialised with `data`. - pub fn create_at_path(path: S, data: Data) -> error::Result + pub async fn create_at_path(path: S, data: Data) -> error::Result where S: AsRef, { @@ -743,7 +742,7 @@ where let deser = DeSer::default(); if !exists { let ser = deser.serialize(&data)?; - backend.put_data(&ser)?; + backend.put_data(&ser).await?; } let db = Self { @@ -776,11 +775,11 @@ where /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. If the file does not exist, initialise with /// `Data::default`. - pub fn load_from_path_or_default(path: S) -> error::Result + pub async fn load_from_path_or_default(path: S) -> error::Result where S: AsRef, { - Self::load_from_path_or_else(path, Data::default) + Self::load_from_path_or_else(path, Data::default).await } } @@ -794,10 +793,10 @@ where { /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. - pub fn load_from_path(path: PathBuf) -> error::Result { - let mut backend = PathBackend::from_path_or_fail(path)?; + pub async fn load_from_path(path: PathBuf) -> error::Result { + let mut backend = PathBackend::from_path_or_fail(path).await?; let deser = DeSer::default(); - let data = Self::load_from_backend(&mut backend, &deser)?; + let data = Self::load_from_backend(&mut backend, &deser).await?; let db = Self { data: RwLock::new(data), @@ -812,12 +811,12 @@ where /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. If the file does not exist, initialise with /// `data`. - pub fn load_from_path_or(path: PathBuf, data: Data) -> error::Result { - let (mut backend, exists) = PathBackend::from_path_or_create(path)?; + pub async fn load_from_path_or(path: PathBuf, data: Data) -> error::Result { + let (mut backend, exists) = PathBackend::from_path_or_create(path).await?; let deser = DeSer::default(); if !exists { let ser = deser.serialize(&data)?; - backend.put_data(&ser)?; + backend.put_data(&ser).await?; } let db = Self { @@ -827,7 +826,7 @@ where }; if exists { - db.load()?; + db.load().await?; } Ok(db) @@ -838,19 +837,19 @@ where /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. If the file does not exist, `closure` is /// called and the database is initialised with it's return value. - pub fn load_from_path_or_else(path: PathBuf, closure: C) -> error::Result + pub async fn load_from_path_or_else(path: PathBuf, closure: C) -> error::Result where C: FnOnce() -> Data, { - let (mut backend, exists) = PathBackend::from_path_or_create(path)?; + let (mut backend, exists) = PathBackend::from_path_or_create(path).await?; let deser = DeSer::default(); let data = if exists { - Self::load_from_backend(&mut backend, &deser)? + Self::load_from_backend(&mut backend, &deser).await? } else { let data = closure(); let ser = deser.serialize(&data)?; - backend.put_data(&ser)?; + backend.put_data(&ser).await?; data }; @@ -869,12 +868,12 @@ where /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path). /// Contents are not loaded. If the file does not exist, it is /// initialised with `data`. Frontend is always initialised with `data`. - pub fn create_at_path(path: PathBuf, data: Data) -> error::Result { - let (mut backend, exists) = PathBackend::from_path_or_create(path)?; + pub async fn create_at_path(path: PathBuf, data: Data) -> error::Result { + let (mut backend, exists) = PathBackend::from_path_or_create(path).await?; let deser = DeSer::default(); if !exists { let ser = deser.serialize(&data)?; - backend.put_data(&ser)?; + backend.put_data(&ser).await?; } let db = Self { @@ -896,8 +895,8 @@ where /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), /// and load the contents. If the file does not exist, initialise with /// `Data::default`. - pub fn load_from_path_or_default(path: PathBuf) -> error::Result { - Self::load_from_path_or_else(path, Data::default) + pub async fn load_from_path_or_default(path: PathBuf) -> error::Result { + Self::load_from_path_or_else(path, Data::default).await } } @@ -1009,8 +1008,8 @@ where #[cfg(test)] mod tests { use super::*; + use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; - use tempfile::NamedTempFile; type TestData = HashMap; type TestDb = Database; @@ -1024,7 +1023,7 @@ mod tests { } /// Used to test that `Default::default` isn't called. - #[derive(Clone, Debug, Serialize, serde::Deserialize)] + #[derive(Clone, Debug, Serialize, Deserialize)] struct PanicDefault; impl Default for PanicDefault { fn default() -> Self { @@ -1032,131 +1031,150 @@ mod tests { } } - #[test] - fn create_db_and_read() { + #[tokio::test] + async fn create_db_and_read() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); } - #[test] - fn write_twice() { + #[tokio::test] + async fn write_twice() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); db.write(|d| d.insert(3, "Write to db".to_string())) + .await .expect("Rustbreak write error"); db.write(|d| d.insert(3, "Second write".to_string())) + .await .expect("Rustbreak write error"); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Second write", db.read(|d| d.get(&3).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); } - #[test] - fn save_load() { + #[tokio::test] + async fn save_load() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); - db.save().expect("Rustbreak save error"); - db.write(|d| d.clear()).expect("Rustbreak write error"); - db.load().expect("Rustbreak load error"); + db.save().await.expect("Rustbreak save error"); + db.write(|d| d.clear()) + .await + .expect("Rustbreak write error"); + db.load().await.expect("Rustbreak load error"); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); } - #[test] - fn writesafe_twice() { + #[tokio::test] + async fn writesafe_twice() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); db.write_safe(|d| { d.insert(3, "Write to db".to_string()); }) + .await .expect("Rustbreak write error"); db.write_safe(|d| { d.insert(3, "Second write".to_string()); }) + .await .expect("Rustbreak write error"); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Second write", db.read(|d| d.get(&3).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); } - #[test] - fn writesafe_panic() { + #[tokio::test] + async fn writesafe_panic() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); let err = db .write_safe(|d| { d.clear(); panic!("Panic should be catched") }) + .await .expect_err("Did not error on panic in safe write!"); assert!(matches!(err, RustbreakError::WritePanic)); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); } - #[test] - fn borrow_data_twice() { + #[tokio::test] + async fn borrow_data_twice() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); - let readlock1 = db.borrow_data().expect("Rustbreak readlock error"); - let readlock2 = db.borrow_data().expect("Rustbreak readlock error"); + let readlock1 = db.borrow_data().await; + let readlock2 = db.borrow_data().await; assert_eq!( "Hello World", readlock1.get(&1).expect("Should be `Some` but was `None`") @@ -1180,89 +1198,103 @@ mod tests { assert_eq!(*readlock1, *readlock2); } - #[test] - fn borrow_data_mut() { + #[tokio::test] + async fn borrow_data_mut() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); - let mut writelock = db.borrow_data_mut().expect("Rustbreak writelock error"); + let mut writelock = db.borrow_data_mut().await; writelock.insert(3, "Write to db".to_string()); drop(writelock); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Write to db", db.read(|d| d.get(&3).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); } - #[test] - fn get_data_mem() { + #[tokio::test] + async fn get_data_mem() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); - let data = db.get_data(false).expect("could not get data"); + let data = db.get_data(false).await.expect("could not get data"); assert_eq!(test_data(), data); } - #[test] - fn get_data_load() { + #[tokio::test] + async fn get_data_load() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); - db.save().expect("Rustbreak save error"); - db.write(|d| d.clear()).expect("Rustbreak write error"); - let data = db.get_data(true).expect("could not get data"); + db.save().await.expect("Rustbreak save error"); + db.write(|d| d.clear()) + .await + .expect("Rustbreak write error"); + let data = db.get_data(true).await.expect("could not get data"); assert_eq!(test_data(), data); } - #[test] - fn put_data_mem() { + #[tokio::test] + async fn put_data_mem() { let db = TestMemDb::memory(TestData::default()).expect("Could not create database"); - db.put_data(test_data(), false).expect("could not put data"); + db.put_data(test_data(), false) + .await + .expect("could not put data"); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); - let data = db.get_data(false).expect("could not get data"); + let data = db.get_data(false).await.expect("could not get data"); assert_eq!(test_data(), data); } - #[test] - fn put_data_save() { + #[tokio::test] + async fn put_data_save() { let db = TestMemDb::memory(TestData::default()).expect("Could not create database"); - db.put_data(test_data(), true).expect("could not put data"); - db.load().expect("Rustbreak load error"); + db.put_data(test_data(), true) + .await + .expect("could not put data"); + db.load().await.expect("Rustbreak load error"); assert_eq!( "Hello World", db.read(|d| d.get(&1).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); assert_eq!( "Rustbreak", db.read(|d| d.get(&100).cloned()) + .await .expect("Rustbreak read error") .expect("Should be `Some` but was `None`") ); - let data = db.get_data(false).expect("could not get data"); + let data = db.get_data(false).await.expect("could not get data"); assert_eq!(test_data(), data); } + /* #[test] fn save_and_into_inner() { let db = TestMemDb::memory(test_data()).expect("Could not create database"); @@ -1475,4 +1507,6 @@ mod tests { let readlock = db.borrow_data().expect("Rustbreak readlock error"); assert_eq!(test_data(), *readlock); } + + */ } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs deleted file mode 100644 index a092c22..0000000 --- a/tests/integration_tests.rs +++ /dev/null @@ -1,178 +0,0 @@ -use rustbreak::backend::Backend; -use rustbreak::deser::{Bincode, DeSerializer, Ron, Yaml}; -use rustbreak::{Database, FileDatabase, MemoryDatabase, MmapDatabase, PathDatabase}; -use std::fmt::Debug; -use std::ops::Deref; -use tempfile::tempfile; - -type Data = std::collections::HashMap; - -fn conv(mut data: Data) -> Data { - data.remove(&2); - data.insert(0, "Newly inserted".to_string()); - data.insert(16, "Convertion succesful".to_string()); - data -} - -fn test_basic_save_load + Debug>( - db: &Database, -) { - db.write(|db| { - db.insert(2, "Hello world!".to_string()); - }) - .expect("rustbreak write error"); - db.write_safe(|db| { - db.insert(5, "Hello again".to_string()); - }) - .expect("rustbreak write error"); - db.save().expect("error while saving"); - let saved_state = db.get_data(false).expect("could not get data"); - - // test that loading correctly restores the data - db.write(|db| { - db.clear(); - }) - .expect("rustbreak write error"); - db.load().expect("rustbreak load error"); - - let len = db.read(|db| db.len()).expect("rustbreak read error"); - assert_eq!(len, 2); - - let second = db - .read(|db| db.get(&2).cloned()) - .expect("rustbreak read error"); - assert_eq!(second, Some(String::from("Hello world!"))); - - let fith = db - .read(|db| db.get(&5).cloned()) - .expect("rustbreak read error"); - assert_eq!(fith, Some(String::from("Hello again"))); - - let data = db.borrow_data().expect("rustbreak borrow error"); - assert_eq!(&saved_state, data.deref()); -} - -fn test_multi_borrow + Debug>(db: &Database) { - let data1 = db.borrow_data().expect("rustbreak borrow error"); - let data2 = db.borrow_data().expect("rustbreak borrow error"); - let data3 = db.borrow_data().expect("rustbreak borrow error"); - assert_eq!(data1.deref(), data2.deref()); - assert_eq!(data1.deref(), data3.deref()); -} - -fn test_put_data + Debug>(db: &Database) { - let backup = db.get_data(true).expect("could not get data"); - - let mut other_state = Data::new(); - other_state.insert(3, "Foo".to_string()); - other_state.insert(7, "Bar".to_string()); - other_state.insert(19, "Bazz".to_string()); - - db.put_data(other_state.clone(), true) - .expect("could not put data"); - let data = db.borrow_data().expect("rustbreak borrow error"); - assert_eq!(&other_state, data.deref()); - // If we do not explicitly drop `data` here, the subsequent write will freeze - drop(data); - - db.write(|db| { - db.clear(); - }) - .expect("rustbreak write error"); - db.load().expect("rustbreak load error"); - - let data = db.borrow_data().expect("rustbreak borrow error"); - assert_eq!(&other_state, data.deref()); - drop(data); - - db.put_data(backup, false).expect("could not put data"); -} - -fn test_convert_data + Debug>(db: Database) { - let db = db.convert_data(conv).expect("Could not convert data"); - - let mut expected_state = Data::new(); - expected_state.insert(0, "Newly inserted".to_string()); - expected_state.insert(5, "Hello again".to_string()); - expected_state.insert(16, "Convertion succesful".to_string()); - assert_eq!( - &expected_state, - db.borrow_data().expect("rustbreak borrow error").deref() - ); -} - -fn create_filedb + Debug>() -> FileDatabase { - FileDatabase::from_file(tempfile().expect("could not create file"), Data::default()) - .expect("could not create database") -} - -fn create_filedb_from_path + Debug>() -> FileDatabase { - let file = tempfile::NamedTempFile::new().expect("could not create temporary file"); - FileDatabase::create_at_path(file.path(), Data::default()).expect("could not create database") -} - -fn create_memdb + Debug>() -> MemoryDatabase { - MemoryDatabase::memory(Data::default()).expect("could not create database") -} - -fn create_mmapdb + Debug>() -> MmapDatabase { - MmapDatabase::mmap(Data::default()).expect("could not create database") -} - -fn create_mmapdb_with_size + Debug>(size: usize) -> MmapDatabase { - MmapDatabase::mmap_with_size(Data::default(), size).expect("could not create database") -} - -fn create_pathdb + Debug>() -> PathDatabase { - let file = tempfile::NamedTempFile::new().expect("could not create temporary file"); - PathDatabase::create_at_path(file.path().to_owned(), Data::default()) - .expect("could not create database") -} - -macro_rules! test_basic_save_load { - ($name:ident, $db:expr, $enc:ty) => { - #[test] - #[cfg_attr(miri, ignore)] - fn $name() { - let db: Database = $db; - test_basic_save_load(&db); - test_put_data(&db); - test_multi_borrow(&db); - test_convert_data(db); - } - }; - ($name:ident, $db:expr, $enc:ty, miri=true) => { - #[test] - fn $name() { - let db: Database = $db; - test_basic_save_load(&db); - test_put_data(&db); - test_multi_borrow(&db); - test_convert_data(db); - } - }; -} - -test_basic_save_load!(file_ron, create_filedb(), Ron); -test_basic_save_load!(file_yaml, create_filedb(), Yaml); -test_basic_save_load!(file_bincode, create_filedb(), Bincode); - -test_basic_save_load!(filepath_ron, create_filedb_from_path(), Ron); -test_basic_save_load!(filepath_yaml, create_filedb_from_path(), Yaml); -test_basic_save_load!(filepath_bincode, create_filedb_from_path(), Bincode); - -test_basic_save_load!(mem_ron, create_memdb(), Ron, miri = true); -test_basic_save_load!(mem_yaml, create_memdb(), Yaml, miri = true); -test_basic_save_load!(mem_bincode, create_memdb(), Bincode, miri = true); - -test_basic_save_load!(mmap_ron, create_mmapdb(), Ron); -test_basic_save_load!(mmap_yaml, create_mmapdb(), Yaml); -test_basic_save_load!(mmap_bincode, create_mmapdb(), Bincode); - -test_basic_save_load!(mmapsize_ron, create_mmapdb_with_size(10), Ron); -test_basic_save_load!(mmapsize_yaml, create_mmapdb_with_size(10), Yaml); -test_basic_save_load!(mmapsize_bincode, create_mmapdb_with_size(10), Bincode); - -test_basic_save_load!(path_ron, create_pathdb(), Ron); -test_basic_save_load!(path_yaml, create_pathdb(), Yaml); -test_basic_save_load!(path_bincode, create_pathdb(), Bincode);