From 9e4f8a6a997ae1ef6b51a1f75530719f0d51a305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 6 Oct 2017 19:21:39 +0200 Subject: [PATCH 01/40] Rewrite to v2 --- Cargo.toml | 14 +- examples/full.rs | 73 ++--- examples/simple.rs | 15 - src/backend.rs | 76 ++++++ src/bincode_enc.rs | 18 -- src/deser.rs | 59 ++++ src/error.rs | 25 +- src/lib.rs | 668 ++++++++++++--------------------------------- src/yaml_enc.rs | 23 -- 9 files changed, 364 insertions(+), 607 deletions(-) delete mode 100644 examples/simple.rs create mode 100644 src/backend.rs delete mode 100644 src/bincode_enc.rs create mode 100644 src/deser.rs delete mode 100644 src/yaml_enc.rs diff --git a/Cargo.toml b/Cargo.toml index 3de90ac..80a6a0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Marcel Müller "] -description = "A single file database" +description = "A single point database" documentation = "http://neikos.me/rustbreak/rustbreak/index.html" homepage = "https://github.com/TheNeikos/rustbreak" keywords = ["database", "simple", "fast", "daybreak", "rustbreak"] @@ -8,22 +8,20 @@ license = "MPL-2.0" name = "rustbreak" readme = "README.md" repository = "https://github.com/TheNeikos/rustbreak" -version = "1.4.0" +version = "2.0.0" [dependencies] -bincode = { version = "0.8", optional = true } -serde_yaml = { version = "0.7", optional = true } -ron = { version = "0.1.3", optional = true } fs2 = "0.4" quick-error = "1.1.0" +ron = "0.1.2" +serde_yaml = { version = "0.7", optional = true } serde = "1" [dev-dependencies] -tempfile = "2.1" lazy_static = "0.2.1" +tempfile = "2.1" +serde_derive = "1" [features] -default = ["bin"] -bin = ["bincode"] yaml = ["serde_yaml"] ron_enc = ["ron"] diff --git a/examples/full.rs b/examples/full.rs index 8e0743c..c9064d9 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -1,46 +1,51 @@ extern crate rustbreak; -#[macro_use] extern crate lazy_static; +#[macro_use] extern crate serde_derive; -use rustbreak::{Database, Result as BreakResult}; +use rustbreak::Database; -lazy_static! { - static ref DB: Database = { - Database::open("music").unwrap() - }; +#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] +enum Country { + Italy, UnitedKingdom } -type Artist = String; -type Album = String; -type AlbumArtist = (Album, Artist); - -fn add_album_to_artist(name: Artist, album: Album) -> BreakResult<()> { - let mut lock = try!(DB.lock()); - let mut map: Vec = lock.retrieve(&name).unwrap_or_else(|_| vec![]); - map.push(album); - try!(lock.insert(&name, map)); - Ok(()) -} - -fn get_albums(name: &str) -> BreakResult> { - DB.retrieve(name) +#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] +struct Person { + name: String, + country: Country, } fn main() { - let albums = [ - ("The Queenstons", "What you do EP"), - ("The Queenstons", "Figurehead"), - ("The Queenstons", "Undertones"), - ("System of a Down", "Toxicity II"), - ("System of a Down", "Mezmerize"), - ]; + let db = Database::from_path("test.yaml").unwrap().with_deser(rustbreak::deser::Yaml); - for &(artist, album) in albums.iter() { - add_album_to_artist(artist.to_owned(), album.to_owned()).unwrap(); - } + println!("Writing to Database"); + db.write(|mut 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 + }); + }).unwrap(); - for al in get_albums("The Queenstons").unwrap() { - println!("{}", al); - } + println!("Syncing Database"); + db.sync().unwrap(); + + println!("Reloading Database"); + db.reload().unwrap(); + + let mut john = None; + let mut fred = None; + + println!("Reading from Database"); + db.read(|db| { + // We want to take things out of the Database, so we clone + john = db.get("john").cloned(); + fred = db.get("fred").cloned(); + }).unwrap(); + + println!("Results:"); + println!("{:#?}, {:#?}", john, fred); - DB.flush().unwrap(); } diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index a1e3ee6..0000000 --- a/examples/simple.rs +++ /dev/null @@ -1,15 +0,0 @@ -extern crate rustbreak; - -use rustbreak::Database; - -fn main() { - let db = Database::open("my_contacts").unwrap(); - - db.insert("Lapfox", "lapfoxtrax.com").unwrap(); - db.insert("Rust", "github.com/rust-lang/rust").unwrap(); - - // we need to be explicit about the kind of type we want as println! is generic - let rust : String = db.retrieve("Rust").unwrap(); - println!("You can find Rust at: {}", rust); - db.flush().unwrap(); -} diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..1ef0eaa --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io::{Seek, SeekFrom, Read, Write, Result as IOResult}; + +/// Marker for Resizing action +/// +/// This is used to truncate the backend to a given size +pub trait Resizable { + /// Sets the backend to the given size + fn resize(&mut self, size: usize) -> IOResult<()>; + /// Syncs the given backend to the support it uses + fn sync(&mut self) -> IOResult<()>; +} + +impl Resizable for ::std::fs::File { + fn resize(&mut self, size: usize) -> IOResult<()> { + self.set_len(size as u64) + } + fn sync(&mut self) -> IOResult<()> { + self.sync_all() + } +} + +#[derive(Debug)] +pub struct RWVec(Vec, usize); + +impl Resizable for RWVec { + fn resize(&mut self, size: usize) -> IOResult<()> { + self.0.resize(size, 0); + Ok(()) + } + fn sync(&mut self) -> IOResult<()> { + Ok(()) + } +} + +impl<'r> Read for &'r mut RWVec { + fn read(&mut self, buf: &mut [u8]) -> IOResult { + println!("Called with: len: {} pos: {}", self.0.len(), self.1); + let len = (&self.0[(self.1)..]).read(buf)?; + self.1 += len; + Ok(len) + } +} + +impl Write for RWVec { + fn write(&mut self, buf: &[u8]) -> IOResult { + self.0.write(buf) + } + + fn flush(&mut self) -> IOResult<()> { + self.0.flush() + } +} + +impl Seek for RWVec { + fn seek(&mut self, seek: SeekFrom) -> IOResult { + match seek { + SeekFrom::Start(start) => self.1 = start as usize, + SeekFrom::End(end) => self.1 = (self.0.len() as i64 - end) as usize, + SeekFrom::Current(off) => self.1 = match self.1 as i64 - off { + x if x < 0 => 0, + x => x as usize + } + } + Ok(self.1 as u64) + } +} + +impl RWVec { + pub fn new() -> RWVec { + RWVec(vec![], 0) + } +} diff --git a/src/bincode_enc.rs b/src/bincode_enc.rs deleted file mode 100644 index 3fcd9b0..0000000 --- a/src/bincode_enc.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::{Serialize, Deserialize}; -use bincode::{self, serialize as bin_serialize, deserialize as bin_deserialize}; - -pub type Repr = Vec; -pub type SerializeError = bincode::Error; -pub type DeserializeError = (); - -pub fn serialize(value: &T) -> bincode::Result> - where T: Serialize -{ - bin_serialize(value, bincode::Infinite) -} - -pub fn deserialize<'a, T>(bytes: &'a [u8]) -> Result - where T: Deserialize<'a> -{ - bin_deserialize(bytes) -} diff --git a/src/deser.rs b/src/deser.rs new file mode 100644 index 0000000..3e3cb0c --- /dev/null +++ b/src/deser.rs @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io::Read; + +use serde::Serialize; +use serde::de::DeserializeOwned; + +use ron::ser::pretty::to_string as to_ron_string; +use ron::de::from_reader as from_ron_string; + +#[cfg(feature = "yaml")] +use serde_yaml::{to_string as to_yaml_string, from_reader as from_yaml_string}; + +/// A trait to bundle serializer and deserializer +pub trait DeSerializer { + /// Associated error with serialization + type SerError; + /// Associated error with deserialization + type DeError; + + /// Serializes a given value to a String + fn serialize(&self, val: &T) -> Result; + /// Deserializes a String to a value + fn deserialize(&self, s: R) -> Result; +} + +/// The Struct that allows you to use `ron` the Rusty Object Notation +#[derive(Debug)] +pub struct Ron; + +impl DeSerializer for Ron { + type SerError = ::ron::ser::Error; + type DeError = ::ron::de::Error; + fn serialize(&self, val: &T) -> Result { + to_ron_string(val) + } + fn deserialize(&self, s: R) -> Result { + from_ron_string(s) + } +} + +#[cfg(feature = "yaml")] +/// The struct that allows you to use yaml +#[derive(Debug)] +pub struct Yaml; + +#[cfg(feature = "yaml")] +impl DeSerializer for Yaml { + type SerError = ::serde_yaml::Error; + type DeError = ::serde_yaml::Error; + fn serialize(&self, val: &T) -> Result { + to_yaml_string(val) + } + fn deserialize(&self, s: R) -> Result { + from_yaml_string(s) + } +} diff --git a/src/error.rs b/src/error.rs index 1f884ca..8807852 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ quick_error! { /// The Error type exported by BreakError, usually you only need to check against NotFound, @@ -9,24 +12,16 @@ quick_error! { Io(err: ::std::io::Error) { from() } - /// This error happens if Bincode cannot deserialize a given file. If you get this error - /// check your database is not corrupt. (This includes non-empty files **not** created by - /// RustBreak! - Deserialize(err: ::enc::DeserializeError) { - from() - } - /// This error happens if bincode cannot serialize the given type at runtime - Serialize(err: ::enc::SerializeError) { - from() - } /// Error when reading a formatted String Format(err: ::std::string::FromUtf8Error) { from() } - /// Poisoned, you can recover from this by running `recover_poison` on the database - Poison {} - /// This simply means your key could not be found in the database - NotFound {} + + Serialize { } + + Deserialize { } + + Poison { } } } @@ -35,3 +30,5 @@ impl From<::std::sync::PoisonError> for BreakError { BreakError::Poison } } + +pub type BreakResult = Result; diff --git a/src/lib.rs b/src/lib.rs index fc03d5b..c5effde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,537 +22,215 @@ //! # Rustbreak //! //! Rustbreak is a [Daybreak][daybreak] inspiried single file Database. -//! It uses [bincode][bincode] or yaml to compactly save data. -//! It is thread safe and very fast due to staying in memory until flushed to disk. //! -//! It can be used for short-lived processes or with long-lived ones: +//! See the [examples][examples] to get an idea how this library is used. //! -//! ```rust -//! use rustbreak::{Database, Result}; -//! -//! fn get_data(key: &str) -> Result { -//! let db = try!(Database::::open("/tmp/database")); -//! db.retrieve(key) -//! } -//! ``` -//! -//! ```rust -//! # #[macro_use] extern crate lazy_static; -//! # extern crate rustbreak; -//! use rustbreak::{Database, Result}; -//! -//! lazy_static! { -//! static ref DB: Database = { -//! Database::open("/tmp/more_data").unwrap() -//! }; -//! } -//! -//! fn get_data(key: &str) -> Result { -//! DB.retrieve(key) -//! } -//! -//! fn set_data(key: &str, d: u64) -> Result<()> { -//! let mut lock = try!(DB.lock()); -//! let old_data : u64 = try!(lock.retrieve(key)); -//! lock.insert(key, d + old_data) -//! } -//! -//! # fn main() {} -//! ``` -//! -//! [daybreak]:https://propublica.github.io/daybreak/ -//! [bincode]:https://github.com/TyOverby/bincode +//! [daybreak]:https://propublica.github.io/daybreak +//! [examples]: https://github.com/TheNeikos/rustbreak extern crate serde; +extern crate ron; +#[cfg(feature = "yaml")] +extern crate serde_yaml; #[macro_use] extern crate quick_error; -extern crate fs2; -#[cfg(feature = "bin")] extern crate bincode; -#[cfg(feature = "yaml")] extern crate serde_yaml; -#[cfg(feature = "ron_enc")] extern crate ron; -#[cfg(test)] extern crate tempfile; mod error; -#[cfg(feature = "bin")] mod bincode_enc; -#[cfg(feature = "yaml")] mod yaml_enc; -#[cfg(feature = "ron_enc")] mod ron_enc; - -mod enc { - #[cfg(feature = "bin")] pub use bincode_enc::*; - #[cfg(feature = "yaml")] pub use yaml_enc::*; - #[cfg(feature = "ron_enc")] pub use ron_enc::*; -} +mod backend; +/// Differend serialization and deserialization methods one can use +pub mod deser; use std::collections::HashMap; -use std::fs::File; +use std::fs::{OpenOptions, File}; use std::path::Path; -use std::sync::{RwLock, RwLockWriteGuard, Mutex}; +use std::io::{Seek, SeekFrom, Read, Write}; +use std::sync::{Mutex, RwLock}; use std::hash::Hash; -use std::borrow::Borrow; +use std::fmt::Debug; +use std::marker::PhantomData; use serde::Serialize; use serde::de::DeserializeOwned; -pub use error::BreakError; +use backend::RWVec; +pub use backend::Resizable; +use error::{BreakResult, BreakError}; +use deser::{DeSerializer, Ron}; -/// Alias for our Result Type -pub type Result = ::std::result::Result; +/// A container is a Key/Value storage +pub trait Container : Debug { + /// Insert the value at the given key + /// + /// Returns optionally an existing value that was replaced + fn insert(&mut self, key: K, value: V) -> Option; -/// The Database structure + /// Removes the given value from the Container + fn remove>(&mut self, key: T) -> Option; + + /// Borrows the given value from the Container + fn get>(&self, key: T) -> Option<&V>; +} + +impl Container for HashMap { + fn insert(&mut self, key: K, value: V) -> Option { + self.insert(key, value) + } + fn remove>(&mut self, key: T) -> Option { + self.remove(key.as_ref()) + } + fn get>(&self, key: T) -> Option<&V> { + self.get(key.as_ref()) + } +} + + +type StringMap = HashMap; + +/// The Central Database to RustBreak /// -/// # Notes -/// One should create this once for each Database instance. -/// Subsequent tries to open the same file should fail or worse, could break the database. +/// It has 4 Type Generics: /// -/// # Example -/// -/// ``` -/// use rustbreak::Database; -/// -/// let db = Database::open("/tmp/artists").unwrap(); -/// -/// let albums = vec![ -/// ("What you do", "The Queenstons"), -/// ("Experience", "The Prodigy"), -/// ]; -/// -/// for (album, artist) in albums { -/// db.insert(&format!("album_{}",album), artist).unwrap(); -/// } -/// db.flush().unwrap(); -/// ``` +/// - D: Is the Data, you must specify this (usually inferred by the compiler) +/// - C: Is the backing Container, per default HashMap +/// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used +/// - F: The file backend. Per default it is in memory, but can be easily used with a file #[derive(Debug)] -pub struct Database { - file: Mutex, - data: RwLock>, +pub struct Database, S = Ron, F = RWVec> + where + D: Serialize + DeserializeOwned + Debug, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + DeSerializer + Sync + Send + Debug, + F: Write + Resizable + Debug, + for<'r> &'r mut F: Read +{ + backing: Mutex, + data: RwLock, + deser: S, + key: PhantomData } -impl Database { - /// Opens a new Database - /// - /// This might fail if the file is non-empty and was not created by RustBreak, or if the file - /// is already being used by another RustBreak instance. - /// - /// # Example - /// - /// ``` - /// use rustbreak::Database; - /// - /// let db = Database::open("/tmp/more_artists").unwrap(); - /// - /// let albums = vec![ - /// ("What you do", "The Queenstons"), - /// ("Experience", "The Prodigy"), - /// ]; - /// - /// for (album, artist) in albums { - /// db.insert(&format!("album_{}",album), artist).unwrap(); - /// } - /// db.flush().unwrap(); - /// ``` - pub fn open>(path: P) -> Result> { - use std::fs::OpenOptions; - use fs2::FileExt; - use std::io::Read; - use enc::deserialize; +impl Database> + where + D: Serialize + DeserializeOwned + Debug, +{ + /// Constructs a `Database` with in-memory Storage + pub fn memory() -> Database, Ron, RWVec> + { + Database { + backing: Mutex::new(RWVec::new()), + data: RwLock::new(HashMap::new()), + deser: Ron, + key: PhantomData, + } + } +} - let mut file = try!(OpenOptions::new().read(true).write(true).create(true).open(path)); - try!(file.try_lock_exclusive()); +impl Database> + where + D: Serialize + DeserializeOwned + Debug, +{ + /// Constructs a `Database` with file-backed storage + pub fn from_file(file: File) -> Database, Ron, File> { + Database { + backing: Mutex::new(file), + data: RwLock::new(HashMap::new()), + deser: Ron, + key: PhantomData, + } + } - let mut buf = Vec::new(); - try!(file.read_to_end(&mut buf)); - let map : HashMap = if !buf.is_empty() { - try!(deserialize(&buf)) - } else { - HashMap::new() + /// Constructs a `Database` with file-backed storage from a given path + pub fn from_path>(path: P) -> BreakResult, Ron, File>> { + let file = OpenOptions::new().read(true).write(true).create(true).open(path)?; + Ok(Self::from_file(file)) + } +} + +impl Database + where + D: Serialize + DeserializeOwned + Debug, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + DeSerializer + Sync + Send + Debug, + F: Write + Seek + Resizable + Debug, + >::SerError: std::error::Error + Send + Sync + 'static, + >::DeError: std::fmt::Debug, + for<'r> &'r mut F: Read +{ + /// Write locks the database and gives you write access to the underlying `Container` + pub fn write(&self, mut task: T) -> BreakResult<()> + where T: FnMut(&mut C) + { + let mut lock = self.data.write()?; + task(&mut lock); + Ok(()) + } + + /// Read locks the database and gives you read access to the underlying `Container` + pub fn read(&self, mut task: T) -> BreakResult<()> + where T: FnMut(&C) + { + let lock = self.data.read()?; + task(&lock); + Ok(()) + } + + /// Syncs the Database to the backing storage + /// + /// # Attention + /// You __have__ to call this method yourself! Per default Rustbreak buffers everything + /// in-memory and lets you decide when to write + pub fn sync(&self) -> BreakResult<()> { + let mut backing = self.backing.lock()?; + let data = self.data.read()?; + let s = match self.deser.serialize(&*data) { + Ok(s) => s, + Err(_) => return Err(BreakError::Serialize), }; - - Ok(Database { - file: Mutex::new(file), - data: RwLock::new(map), - }) - } - - /// Insert a given Object into the Database at that key - /// - /// This will overwrite any existing objects. - /// - /// The Object has to be serializable. - pub fn insert(&self, key: &K, obj: S) -> Result<()> - where T: Borrow, K: Hash + PartialEq + ToOwned - { - use enc::serialize; - let mut map = try!(self.data.write()); - map.insert(key.to_owned(), try!(serialize(&obj))); + backing.seek(SeekFrom::Start(0))?; + backing.resize(0)?; + backing.write_all(&s.as_bytes())?; + backing.sync()?; Ok(()) } - /// Remove an Object at that key - pub fn delete(&self, key: &K) -> Result<()> - where T: Borrow, K: Hash + Eq - { - let mut map = try!(self.data.write()); - map.remove(key.to_owned()); - Ok(()) - } - - /// Retrieves an Object from the Database + /// Reloads the internal storage from the backing storage /// - /// # Errors - /// - /// This will return an `Err(BreakError::NotFound)` if there is no key behind the object. - /// If you tried to request something that can't be serialized to then - /// `Err(BreakError::Deserialize)` will be returned. - /// - /// # Example - /// - /// ``` - /// use rustbreak::{Database, BreakError}; - /// - /// let db = Database::open("/tmp/stuff").unwrap(); - /// - /// for i in 0..5i64 { - /// db.insert(&format!("num_{}", i), i*i*i).unwrap(); - /// } - /// - /// let num : i64 = db.retrieve::("num_0").unwrap(); - /// assert_eq!(num, 0); - /// match db.retrieve::("non-existent") { - /// Err(BreakError::NotFound) => {}, - /// _ => panic!("Was still found?"), - /// } - /// - /// match db.retrieve::, str>("num_1") { - /// Err(_) => {}, - /// _ => panic!("Was deserialized?"), - /// } - /// ``` - pub fn retrieve(&self, key: &K) -> Result - where T: Borrow, K: Hash + Eq - { - use enc::deserialize; - let map = try!(self.data.read()); - match map.get(key.borrow()) { - Some(t) => Ok(try!(deserialize(t))), - None => Err(BreakError::NotFound), - } - } - - /// Checks wether a given key exists in the Database - pub fn contains_key(&self, key: &K) -> Result - where T: Borrow, K: Hash + Eq - { - let map = try!(self.data.read()); - Ok(map.get(key.borrow()).is_some()) - } - - /// Flushes the Database to disk - pub fn flush(&self) -> Result<()> { - use enc::serialize; - use std::io::{Write, Seek, SeekFrom}; - - let map = try!(self.data.read()); - - let mut file = try!(self.file.lock()); - - let buf = try!(serialize(&*map)); - try!(file.set_len(0)); - try!(file.seek(SeekFrom::Start(0))); - try!(file.write(&buf.as_ref())); - try!(file.sync_all()); - Ok(()) - } - - /// Starts a transaction - /// - /// A transaction passes through reads but caches writes. This means that if changes do happen - /// they are processed at the same time. To run them you have to call `run` on the - /// `Transaction` object. - pub fn transaction(&self) -> Transaction { - Transaction { - lock: &self.data, - data: RwLock::new(HashMap::new()), - } - } - - /// Locks the Database, making sure only the caller can change it - /// - /// This write-locks the Database until the `Lock` has been dropped. - /// - /// # Panics - /// - /// If you panic while holding the lock it will get poisoned and subsequent calls to it will - /// fail. You will have to re-open the Database to be able to continue accessing it. - pub fn lock(&self) -> Result> { - let map = try!(self.data.write()); - Ok(Lock { - lock: map, - }) - } -} - -/// Structure representing a lock of the Database -pub struct Lock<'a, T: Serialize + DeserializeOwned + Eq + Hash + 'a> { - lock: RwLockWriteGuard<'a, HashMap>, -} - -impl<'a, T: Serialize + DeserializeOwned + Eq + Hash + 'a> Lock<'a, T> { - /// Insert a given Object into the Database at that key - /// - /// See `Database::insert` for details - pub fn insert(&mut self, key: &K, obj: S) -> Result<()> - where T: Borrow, K: Hash + PartialEq + ToOwned - { - use enc::serialize; - self.lock.insert(key.to_owned(), try!(serialize(&obj))); - Ok(()) - } - - /// Retrieves an Object from the Database - /// - /// See `Database::retrieve` for details - pub fn retrieve(&mut self, key: &K) -> Result - where T: Borrow, K: Hash + Eq - { - use enc::deserialize; - match self.lock.get(key.borrow()) { - Some(t) => Ok(try!(deserialize(t))), - None => Err(BreakError::NotFound), - } - } - - /// Starts a transaction - /// - /// See `Database::transaction` for details - pub fn transaction<'b>(&'b mut self) -> TransactionLock<'a, 'b, T> { - TransactionLock { - lock: self, - data: RwLock::new(HashMap::new()), - } - } - -} - -/// A `TransactionLock` that is atomic in writes and defensive -/// -/// You generate this by calling `transaction` on a `Lock` -/// The transactionlock does not get automatically applied when it is dropped, you have to `run` it. -/// This allows for defensive programming where the values are only applied once it is `run`. -pub struct TransactionLock<'a: 'b, 'b, T: Serialize + DeserializeOwned + Eq + Hash + 'a> { - lock: &'b mut Lock<'a, T>, - data: RwLock>, -} - -impl<'a: 'b, 'b, T: Serialize + DeserializeOwned + Eq + Hash + 'a> TransactionLock<'a, 'b, T> { - /// Insert a given Object into the Database at that key - /// - /// See `Database::insert` for details - pub fn insert(&mut self, key: &K, obj: S) -> Result<()> - where T: Borrow, K: Hash + PartialEq + ToOwned - { - use enc::serialize; - - let mut map = try!(self.data.write()); - - map.insert(key.to_owned(), try!(serialize(&obj))); - - Ok(()) - } - - /// Retrieves an Object from the Database - /// - /// See `Database::retrieve` for details - pub fn retrieve(&mut self, key: &K) -> Result - where T: Borrow, K: Hash + Eq - { - use enc::deserialize; - let other_map = &mut self.lock.lock; - if other_map.contains_key(key) { - match other_map.get(key.borrow()) { - Some(t) => Ok(try!(deserialize(t))), - None => Err(BreakError::NotFound), + /// This is useful to call at startup, or your Database might be empty! + pub fn reload(&self) -> BreakResult<()> { + let mut backing = self.backing.lock()?; + let mut data = self.data.write()?; + backing.seek(SeekFrom::Start(0))?; + let mut new_data = match self.deser.deserialize(&mut *backing) { + Ok(s) => s, + Err(e) => { + println!("{:?}", e); + return Err(BreakError::Deserialize); } - } else { - let map = try!(self.data.read()); - match map.get(key.borrow()) { - Some(t) => Ok(try!(deserialize(t))), - None => Err(BreakError::NotFound), - } - } - } - - /// Consumes the TransactionLock and runs it - pub fn run(self) -> Result<()> { - let other_map = &mut self.lock.lock; - - let mut map = try!(self.data.write()); - - for (k, v) in map.drain() { - other_map.insert(k, v); - } - + }; + ::std::mem::swap(&mut *data, &mut new_data); Ok(()) } + } -/// A Transaction that is atomic in writes -/// -/// You generate this by calling `transaction` on a `Database` -/// The transaction does not get automatically applied when it is dropped, you have to `run` it. -/// This allows for defensive programming where the values are only applied once it is `run`. -pub struct Transaction<'a, T: Serialize + DeserializeOwned + Eq + Hash + 'a> { - lock: &'a RwLock>, - data: RwLock>, -} - -impl<'a, T: Serialize + DeserializeOwned + Eq + Hash + 'a> Transaction<'a, T> { - /// Insert a given Object into the Database at that key - /// - /// See `Database::insert` for details - pub fn insert(&mut self, key: &K, obj: S) -> Result<()> - where T: Borrow, K: Hash + PartialEq + ToOwned +impl Database + where + D: Serialize + DeserializeOwned + Debug, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + DeSerializer + Sync + Send + Debug, + F: Write + Resizable + Debug, + for<'r> &'r mut F: Read +{ + /// Exchanges a given deserialization method with another + pub fn with_deser(self, deser: T) -> Database + where + T: DeSerializer + DeSerializer + Sync + Send + Debug, { - use enc::serialize; - - let mut map = try!(self.data.write()); - - map.insert(key.to_owned(), try!(serialize(&obj))); - - Ok(()) - } - - /// Retrieves an Object from the Database - /// - /// See `Database::retrieve` for details - pub fn retrieve(&self, key: &K) -> Result - where T: Borrow, K: Hash + Eq - { - use enc::deserialize; - let other_map = try!(self.lock.read()); - if other_map.contains_key(key) { - match other_map.get(key.borrow()) { - Some(t) => Ok(try!(deserialize(t))), - None => Err(BreakError::NotFound), - } - } else { - let map = try!(self.data.read()); - match map.get(key.borrow()) { - Some(t) => Ok(try!(deserialize(t))), - None => Err(BreakError::NotFound), - } + Database { + backing: self.backing, + data: self.data, + deser: deser, + key: PhantomData, } } - - /// Consumes the Transaction and runs it - pub fn run(self) -> Result<()> { - let mut other_map = try!(self.lock.write()); - - let mut map = try!(self.data.write()); - - for (k, v) in map.drain() { - other_map.insert(k, v); - } - - Ok(()) - } } -#[cfg(test)] -mod test { - use super::{Database,BreakError}; - use tempfile::NamedTempFile; - - #[test] - fn insert_and_delete() { - let tmpf = NamedTempFile::new().unwrap(); - let db = Database::open(tmpf.path()).unwrap(); - - db.insert("test", "Hello World!").unwrap(); - db.delete("test").unwrap(); - let hello : Result = db.retrieve("test"); - assert!(hello.is_err()) - } - - #[test] - fn simple_insert_and_retrieve() { - let tmpf = NamedTempFile::new().unwrap(); - let db = Database::open(tmpf.path()).unwrap(); - - db.insert("test", "Hello World!").unwrap(); - let hello : String = db.retrieve("test").unwrap(); - assert_eq!(hello, "Hello World!"); - } - - #[test] - fn simple_insert_and_retrieve_borrow() { - let tmpf = NamedTempFile::new().unwrap(); - let db = Database::open(tmpf.path()).unwrap(); - - db.insert("test", &25).unwrap(); - let hello : u32 = db.retrieve("test").unwrap(); - assert_eq!(hello, 25); - } - - #[test] - fn test_persistence() { - let tmpf = NamedTempFile::new().unwrap(); - let db = Database::open(tmpf.path()).unwrap(); - db.insert("test", "Hello World!").unwrap(); - db.flush().unwrap(); - drop(db); - let db : Database = Database::open(tmpf.path()).unwrap(); - let hello : String = db.retrieve("test").unwrap(); - assert_eq!(hello, "Hello World!"); - } - - #[test] - fn simple_transaction() { - let tmpf = NamedTempFile::new().unwrap(); - let db = Database::open(tmpf.path()).unwrap(); - assert!(db.retrieve::("test").is_err()); - { - let mut trans = db.transaction(); - trans.insert("test", "Hello World!").unwrap(); - trans.run().unwrap(); - } - { - let mut trans = db.transaction(); - trans.insert("test", "Hello World too!!").unwrap(); - drop(trans); - } - let hello : String = db.retrieve("test").unwrap(); - assert_eq!(hello, "Hello World!"); - } - - #[test] - fn multithreaded_locking() { - use std::sync::Arc; - let tmpf = NamedTempFile::new().unwrap(); - let db = Arc::new(Database::open(tmpf.path()).unwrap()); - db.insert("value", 0i64).unwrap(); - let mut threads = vec![]; - for _ in 0..10 { - use std::thread; - let a = db.clone(); - threads.push(thread::spawn(move || { - let mut lock = a.lock().unwrap(); - { - let mut trans = lock.transaction(); - let x = trans.retrieve::("value").unwrap(); - trans.insert("value", x + 1).unwrap(); - trans.run().unwrap(); - } - { - let mut trans = lock.transaction(); - let x = trans.retrieve::("value").unwrap(); - trans.insert("value", x - 1).unwrap(); - drop(trans); - } - })); - } - for thr in threads { - thr.join().unwrap(); - } - let x = db.retrieve::("value").unwrap(); - assert_eq!(x, 10); - } -} diff --git a/src/yaml_enc.rs b/src/yaml_enc.rs deleted file mode 100644 index 04122fd..0000000 --- a/src/yaml_enc.rs +++ /dev/null @@ -1,23 +0,0 @@ -use serde::Serialize; -use serde::de::DeserializeOwned; -use serde_yaml::Result as YamlResult; -use serde_yaml::Error; - -pub type Repr = String; -pub type SerializeError = Error; -pub type DeserializeError = (); - -pub fn serialize(value: &T) -> YamlResult - where T: Serialize -{ - ::serde_yaml::to_string(value) -} - -pub fn deserialize>(bytes: &I) -> Result - where T: DeserializeOwned -{ - - let string = try!(String::from_utf8(bytes.as_ref().to_vec())); - let des = try!(::serde_yaml::from_str(&string)); - Ok(des) -} From 2713e966b42f5f83f2826cef1cfb8338650228a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 11 Oct 2017 21:11:10 +0200 Subject: [PATCH 02/40] Add helper methods --- src/lib.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c5effde..044eaba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ pub struct Database, S = Ron, F = RWVec> impl Database> where - D: Serialize + DeserializeOwned + Debug, + D: Serialize + DeserializeOwned + Debug + Clone, { /// Constructs a `Database` with in-memory Storage pub fn memory() -> Database, Ron, RWVec> @@ -126,7 +126,7 @@ impl Database> impl Database> where - D: Serialize + DeserializeOwned + Debug, + D: Serialize + DeserializeOwned + Debug + Clone, { /// Constructs a `Database` with file-backed storage pub fn from_file(file: File) -> Database, Ron, File> { @@ -147,7 +147,7 @@ impl Database> impl Database where - D: Serialize + DeserializeOwned + Debug, + D: Serialize + DeserializeOwned + Debug + Clone, C: Serialize + DeserializeOwned + Container + Debug, S: DeSerializer + DeSerializer + Sync + Send + Debug, F: Write + Seek + Resizable + Debug, @@ -173,6 +173,25 @@ impl Database Ok(()) } + /// Directly inserts a given piece of data into the Database + pub fn insert(&self, key: String, data: D) -> BreakResult<()> { + let mut lock = self.data.write()?; + lock.insert(key, data); + Ok(()) + } + + /// Directly removes a given piece of data from the Database + pub fn remove>(&self, key: K) -> BreakResult> { + let mut lock = self.data.write()?; + Ok(lock.remove(key)) + } + + /// Directly removes a given piece of data from the Database + pub fn get>(&self, key: K) -> BreakResult> { + let lock = self.data.read()?; + Ok(lock.get(key).cloned()) + } + /// Syncs the Database to the backing storage /// /// # Attention From b4eeea38cf3864bb56a5dac2c71db461a7eeac34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 11 Oct 2017 22:18:39 +0200 Subject: [PATCH 03/40] Add bincode --- Cargo.toml | 16 +++++++++++-- src/deser.rs | 68 ++++++++++++++++++++++++++++++++++++++++++---------- src/lib.rs | 4 ++++ 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80a6a0f..ee9d2d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,14 +14,26 @@ version = "2.0.0" fs2 = "0.4" quick-error = "1.1.0" ron = "0.1.2" -serde_yaml = { version = "0.7", optional = true } serde = "1" +[dependencies.bincode] +optional = true +version = "0.8.1" + +[dependencies.base64] +optional = true +version = "0.7.0" + +[dependencies.serde_yaml] +optional = true +version = "0.7" + [dev-dependencies] lazy_static = "0.2.1" -tempfile = "2.1" serde_derive = "1" +tempfile = "2.1" [features] +bin = ["bincode", "base64"] yaml = ["serde_yaml"] ron_enc = ["ron"] diff --git a/src/deser.rs b/src/deser.rs index 3e3cb0c..1d89afd 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -11,7 +11,10 @@ use ron::ser::pretty::to_string as to_ron_string; use ron::de::from_reader as from_ron_string; #[cfg(feature = "yaml")] -use serde_yaml::{to_string as to_yaml_string, from_reader as from_yaml_string}; +pub use yaml::Yaml; + +#[cfg(feature = "bin")] +pub use bincode::Bincode; /// A trait to bundle serializer and deserializer pub trait DeSerializer { @@ -42,18 +45,57 @@ impl DeSerializer for Ron { } #[cfg(feature = "yaml")] -/// The struct that allows you to use yaml -#[derive(Debug)] -pub struct Yaml; +mod yaml { + use std::io::Read; -#[cfg(feature = "yaml")] -impl DeSerializer for Yaml { - type SerError = ::serde_yaml::Error; - type DeError = ::serde_yaml::Error; - fn serialize(&self, val: &T) -> Result { - to_yaml_string(val) - } - fn deserialize(&self, s: R) -> Result { - from_yaml_string(s) + use serde_yaml::{to_string as to_yaml_string, from_reader as from_yaml_string}; + use serde::Serialize; + use serde::de::DeserializeOwned; + + use deser::DeSerializer; + + /// The struct that allows you to use yaml + #[derive(Debug)] + pub struct Yaml; + + impl DeSerializer for Yaml { + type SerError = ::serde_yaml::Error; + type DeError = ::serde_yaml::Error; + fn serialize(&self, val: &T) -> Result { + to_yaml_string(val) + } + fn deserialize(&self, s: R) -> Result { + from_yaml_string(s) + } + } +} + +#[cfg(feature = "bin")] +mod bincode { + use std::io::Read; + + use bincode::{serialize as to_bincode_string, deserialize as from_bincode_string}; + use base64::{encode, decode}; + use serde::Serialize; + use serde::de::DeserializeOwned; + + use deser::DeSerializer; + + /// The struct that allows you to use bincode + #[derive(Debug)] + pub struct Bincode; + + impl DeSerializer for Bincode { + type SerError = ::bincode::Error; + type DeError = ::bincode::Error; + fn serialize(&self, val: &T) -> Result { + let res = to_bincode_string(val, ::bincode::Infinite)?; + Ok(encode(&res)) + } + fn deserialize(&self, s: R) -> Result { + let mut string = String::new(); + s.read_to_string(&mut string)?; + Ok(from_bincode_string(String::from_utf8(decode(&string)?)?.as_bytes())?) + } } } diff --git a/src/lib.rs b/src/lib.rs index 044eaba..fdad0ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,10 @@ extern crate serde; extern crate ron; #[cfg(feature = "yaml")] extern crate serde_yaml; +#[cfg(feature = "bin")] +extern crate bincode; +#[cfg(feature = "bin")] +extern crate base64; #[macro_use] extern crate quick_error; mod error; From 47f9852ffe8b95b4bfd80c892d33a54bc24367a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 11 Oct 2017 23:01:52 +0200 Subject: [PATCH 04/40] Setup actual error handling --- Cargo.toml | 10 ++++++---- examples/full.rs | 2 +- src/deser.rs | 23 ++++++++++++++++++----- src/error.rs | 46 ++++++++++++++++++++++------------------------ src/lib.rs | 42 ++++++++++++++++++++++++++---------------- 5 files changed, 73 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee9d2d1..3ccfff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,18 +11,20 @@ repository = "https://github.com/TheNeikos/rustbreak" version = "2.0.0" [dependencies] -fs2 = "0.4" -quick-error = "1.1.0" ron = "0.1.2" serde = "1" +[dependencies.base64] +optional = true +version = "0.7.0" + [dependencies.bincode] optional = true version = "0.8.1" -[dependencies.base64] +[dependencies.error-chain] optional = true -version = "0.7.0" +version = "0.11.0" [dependencies.serde_yaml] optional = true diff --git a/examples/full.rs b/examples/full.rs index c9064d9..288594e 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -15,7 +15,7 @@ struct Person { } fn main() { - let db = Database::from_path("test.yaml").unwrap().with_deser(rustbreak::deser::Yaml); + let db = Database::from_path("test.yaml").unwrap(); println!("Writing to Database"); db.write(|mut db| { diff --git a/src/deser.rs b/src/deser.rs index 1d89afd..5f2218d 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -11,10 +11,10 @@ use ron::ser::pretty::to_string as to_ron_string; use ron::de::from_reader as from_ron_string; #[cfg(feature = "yaml")] -pub use yaml::Yaml; +pub use self::yaml::Yaml; #[cfg(feature = "bin")] -pub use bincode::Bincode; +pub use self::bincode::Bincode; /// A trait to bundle serializer and deserializer pub trait DeSerializer { @@ -81,18 +81,31 @@ mod bincode { use deser::DeSerializer; + error_chain! { + types { + Error, ErrorKind, ResultExt; + } + + foreign_links { + Bincode(::bincode::Error); + Io(::std::io::Error); + Base64(::base64::DecodeError); + Format(::std::string::FromUtf8Error); + } + } + /// The struct that allows you to use bincode #[derive(Debug)] pub struct Bincode; impl DeSerializer for Bincode { - type SerError = ::bincode::Error; - type DeError = ::bincode::Error; + type SerError = Error; + type DeError = Error; fn serialize(&self, val: &T) -> Result { let res = to_bincode_string(val, ::bincode::Infinite)?; Ok(encode(&res)) } - fn deserialize(&self, s: R) -> Result { + fn deserialize(&self, mut s: R) -> Result { let mut string = String::new(); s.read_to_string(&mut string)?; Ok(from_bincode_string(String::from_utf8(decode(&string)?)?.as_bytes())?) diff --git a/src/error.rs b/src/error.rs index 8807852..60817e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,33 +2,31 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -quick_error! { - /// The Error type exported by BreakError, usually you only need to check against NotFound, - /// however it might be useful sometimes to get other errors. - #[derive(Debug)] - pub enum BreakError { - /// An error returned when doing file operations, this might happen by opening, closing, - /// locking or flushing - Io(err: ::std::io::Error) { - from() - } - /// Error when reading a formatted String - Format(err: ::std::string::FromUtf8Error) { - from() - } - - Serialize { } - - Deserialize { } - - Poison { } - } +/// The Error type exported by BreakError, usually you only need to check against NotFound, +/// however it might be useful sometimes to get other errors. +#[derive(Debug)] +pub enum BreakError { + /// An error returned when doing file operations, this might happen by opening, closing, + /// locking or flushing + Io(::std::io::Error), + /// Error when reading a formatted String + Format(::std::string::FromUtf8Error), + Serialize(Se), + Deserialize(De), + Poison } -impl From<::std::sync::PoisonError> for BreakError { - fn from(_: ::std::sync::PoisonError) -> BreakError { +impl From<::std::sync::PoisonError> for BreakError { + fn from(_: ::std::sync::PoisonError) -> BreakError { BreakError::Poison } } -pub type BreakResult = Result; +impl From<::std::io::Error> for BreakError { + fn from(e: ::std::io::Error) -> BreakError { + BreakError::Io(e) + } +} + + +pub type BreakResult = Result>; diff --git a/src/lib.rs b/src/lib.rs index fdad0ca..e68cda7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,8 @@ extern crate serde_yaml; extern crate bincode; #[cfg(feature = "bin")] extern crate base64; -#[macro_use] extern crate quick_error; +#[cfg(feature = "bin")] +#[macro_use] extern crate error_chain; mod error; mod backend; @@ -143,7 +144,9 @@ impl Database> } /// Constructs a `Database` with file-backed storage from a given path - pub fn from_path>(path: P) -> BreakResult, Ron, File>> { + pub fn from_path>(path: P) + -> BreakResult, Ron, File>, + >::SerError, >::DeError> { let file = OpenOptions::new().read(true).write(true).create(true).open(path)?; Ok(Self::from_file(file)) } @@ -155,12 +158,11 @@ impl Database C: Serialize + DeserializeOwned + Container + Debug, S: DeSerializer + DeSerializer + Sync + Send + Debug, F: Write + Seek + Resizable + Debug, - >::SerError: std::error::Error + Send + Sync + 'static, - >::DeError: std::fmt::Debug, for<'r> &'r mut F: Read { /// Write locks the database and gives you write access to the underlying `Container` - pub fn write(&self, mut task: T) -> BreakResult<()> + pub fn write(&self, mut task: T) + -> BreakResult<(), >::SerError, >::DeError> where T: FnMut(&mut C) { let mut lock = self.data.write()?; @@ -169,7 +171,8 @@ impl Database } /// Read locks the database and gives you read access to the underlying `Container` - pub fn read(&self, mut task: T) -> BreakResult<()> + pub fn read(&self, mut task: T) + -> BreakResult<(), >::SerError, >::DeError> where T: FnMut(&C) { let lock = self.data.read()?; @@ -178,20 +181,26 @@ impl Database } /// Directly inserts a given piece of data into the Database - pub fn insert(&self, key: String, data: D) -> BreakResult<()> { + pub fn insert(&self, key: String, data: D) + -> BreakResult<(), >::SerError, >::DeError> + { let mut lock = self.data.write()?; lock.insert(key, data); Ok(()) } /// Directly removes a given piece of data from the Database - pub fn remove>(&self, key: K) -> BreakResult> { + pub fn remove>(&self, key: K) + -> BreakResult, >::SerError, >::DeError> + { let mut lock = self.data.write()?; Ok(lock.remove(key)) } /// Directly removes a given piece of data from the Database - pub fn get>(&self, key: K) -> BreakResult> { + pub fn get>(&self, key: K) + -> BreakResult, >::SerError, >::DeError> + { let lock = self.data.read()?; Ok(lock.get(key).cloned()) } @@ -201,12 +210,14 @@ impl Database /// # Attention /// You __have__ to call this method yourself! Per default Rustbreak buffers everything /// in-memory and lets you decide when to write - pub fn sync(&self) -> BreakResult<()> { + pub fn sync(&self) + -> BreakResult<(), >::SerError, >::DeError> + { let mut backing = self.backing.lock()?; let data = self.data.read()?; let s = match self.deser.serialize(&*data) { Ok(s) => s, - Err(_) => return Err(BreakError::Serialize), + Err(e) => return Err(BreakError::Serialize(e)), }; backing.seek(SeekFrom::Start(0))?; backing.resize(0)?; @@ -218,16 +229,15 @@ impl Database /// Reloads the internal storage from the backing storage /// /// This is useful to call at startup, or your Database might be empty! - pub fn reload(&self) -> BreakResult<()> { + pub fn reload(&self) + -> BreakResult<(), >::SerError, >::DeError> + { let mut backing = self.backing.lock()?; let mut data = self.data.write()?; backing.seek(SeekFrom::Start(0))?; let mut new_data = match self.deser.deserialize(&mut *backing) { Ok(s) => s, - Err(e) => { - println!("{:?}", e); - return Err(BreakError::Deserialize); - } + Err(e) => return Err(BreakError::Deserialize(e)), }; ::std::mem::swap(&mut *data, &mut new_data); Ok(()) From 8810bc3b29ea60f21338e5752e2cd4d878d92716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 11 Oct 2017 23:06:48 +0200 Subject: [PATCH 05/40] Make bincode error public --- Cargo.toml | 2 +- src/deser.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ccfff9..85ca117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,6 @@ serde_derive = "1" tempfile = "2.1" [features] -bin = ["bincode", "base64"] +bin = ["bincode", "base64", "error-chain"] yaml = ["serde_yaml"] ron_enc = ["ron"] diff --git a/src/deser.rs b/src/deser.rs index 5f2218d..f7c6f74 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -14,7 +14,7 @@ use ron::de::from_reader as from_ron_string; pub use self::yaml::Yaml; #[cfg(feature = "bin")] -pub use self::bincode::Bincode; +pub use self::bincode::{Bincode, Error as BincodeError}; /// A trait to bundle serializer and deserializer pub trait DeSerializer { From 294b7de5a20bfdbb2e57ca0441c0a0857dc08af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 11 Oct 2017 23:22:07 +0200 Subject: [PATCH 06/40] Add borrow{_mut} to container --- examples/full.rs | 5 +++++ src/lib.rs | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/examples/full.rs b/examples/full.rs index 288594e..9deb164 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -15,6 +15,9 @@ struct Person { } fn main() { + use std::collections::HashMap; + + use rustbreak::Container; let db = Database::from_path("test.yaml").unwrap(); println!("Writing to Database"); @@ -27,6 +30,8 @@ fn main() { name: String::from("Fred Johnson"), country: Country::UnitedKingdom }); + let map : &HashMap<_, _> = db.borrow(); + println!("Values: \n{:#?}", map.values()); }).unwrap(); println!("Syncing Database"); diff --git a/src/lib.rs b/src/lib.rs index e68cda7..cdd50e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,16 @@ pub trait Container : Debug { /// Borrows the given value from the Container fn get>(&self, key: T) -> Option<&V>; + + /// Gets the underlying storage container mutably + fn borrow_mut(&mut self) -> &mut Self { + self + } + + /// Gets the underlying storage container + fn borrow(&self) -> &Self { + self + } } impl Container for HashMap { @@ -97,7 +107,7 @@ type StringMap = HashMap; /// - D: Is the Data, you must specify this (usually inferred by the compiler) /// - C: Is the backing Container, per default HashMap /// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used -/// - F: The file backend. Per default it is in memory, but can be easily used with a file +/// - F: The storage backend. Per default it is in memory, but can be easily used with a file #[derive(Debug)] pub struct Database, S = Ron, F = RWVec> where From 48739de03e1f7d95f4205b03a890d572a5054b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 12 Oct 2017 20:32:27 +0200 Subject: [PATCH 07/40] Add ability to exchange backing storage --- src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index cdd50e3..44a5960 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -277,3 +277,25 @@ impl Database } } +impl Database + where + D: Serialize + DeserializeOwned + Debug, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + DeSerializer + Sync + Send + Debug, + F: Write + Resizable + Debug, + for<'r> &'r mut F: Read +{ + /// Exchanges a given deserialization method with another + pub fn with_backing(self, backing: T) -> Database + where + T: Write + Resizable + Debug, + for<'r> &'r mut T: Read + { + Database { + backing: Mutex::new(backing), + data: self.data, + deser: self.deser, + key: PhantomData, + } + } +} From 6f0b39b269bc3c428ac26f0a892172fed4eb7526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 12 Oct 2017 20:36:02 +0200 Subject: [PATCH 08/40] Add ability to change storage container --- src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 44a5960..5aa7330 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -299,3 +299,26 @@ impl Database } } } + +impl Database + where + D: Serialize + DeserializeOwned + Debug, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + DeSerializer + Sync + Send + Debug, + F: Write + Resizable + Debug, + for<'r> &'r mut F: Read +{ + /// Exchanges a given deserialization method with another + pub fn with_storage(self, data: T) -> Database + where + T: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + DeSerializer + Sync + Send + Debug, + { + Database { + backing: self.backing, + data: RwLock::new(data), + deser: self.deser, + key: PhantomData, + } + } +} From 732fabde29d6a5370da77e4478dd4398bd22e94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 12 Oct 2017 20:39:04 +0200 Subject: [PATCH 09/40] Fix bincode version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 85ca117..a5bd8ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ version = "0.7.0" [dependencies.bincode] optional = true -version = "0.8.1" +version = "0.8.0" [dependencies.error-chain] optional = true From 7ddbe9dcc6756e39cbc4809e11d4dce9677ff41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 12 Oct 2017 21:00:18 +0200 Subject: [PATCH 10/40] Make the documentation better --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5aa7330..8eda640 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -285,7 +285,7 @@ impl Database F: Write + Resizable + Debug, for<'r> &'r mut F: Read { - /// Exchanges a given deserialization method with another + /// Exchanges a given backing method with another pub fn with_backing(self, backing: T) -> Database where T: Write + Resizable + Debug, @@ -308,8 +308,8 @@ impl Database F: Write + Resizable + Debug, for<'r> &'r mut F: Read { - /// Exchanges a given deserialization method with another - pub fn with_storage(self, data: T) -> Database + /// Exchanges a given container method with another + pub fn with_container(self, data: T) -> Database where T: Serialize + DeserializeOwned + Container + Debug, S: DeSerializer + DeSerializer + Sync + Send + Debug, From 531dc44da3e4477ac9ffe9ca4b7ea26605acddfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 12 Oct 2017 22:03:48 +0200 Subject: [PATCH 11/40] Add some documentation --- src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8eda640..5ffa59f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,52 @@ //! //! Rustbreak is a [Daybreak][daybreak] inspiried single file Database. //! -//! See the [examples][examples] to get an idea how this library is used. +//! You will find an overview here in the docs, but to give you a more complete tale of how this is +//! used please check the [examples][examples]. +//! +//! At its core, Rustbreak is an attempt at making a configurable key-value store Database. +//! It features the possibility of: +//! - Choosing what kind of Data is stored in it +//! - What kind of Container is storing it +//! - Which kind of Serialization is used for persistence +//! - Which kind of persistence is used +//! +//! Per default these options are used: +//! - The Container is a HashMap, leaving you the choice what you want to store in it, and +//! can access it with a String key. +//! - The Serialization is [Ron][ron], a familiar notation for Rust +//! - The persistence is in-memory, allowing for quick prototyping +//! +//! Later in the development process, the Serialization and the Persistence can be exchanged without +//! breaking the code, allowing you to be flexible. +//! +//! If you have any questions feel free to ask at the main [repo][repo]. +//! +//! ## Quickstart +//! +//! ```rust +//! use rustbreak::Database; +//! +//! let db = Database::memory(); +//! +//! println!("Writing to Database"); +//! db.write(|mut db| { +//! db.insert("hello".into(), String::from("world")); +//! db.insert("foo".into(), String::from("bar")); +//! }); +//! +//! db.read(|db| { +//! // db.insert("foo".into(), String::from("bar")); +//! // The above line will not compile since we are only reading +//! println!("Hello: {:?}", db.get("hello")); +//! }); +//! ``` //! //! [daybreak]:https://propublica.github.io/daybreak -//! [examples]: https://github.com/TheNeikos/rustbreak +//! [examples]: https://github.com/TheNeikos/rustbreak/tree/master/examples +//! [examples]: https://github.com/TheNeikos/rustbreak/ +//! [ron]: https://github.com/ron-rs/ron + extern crate serde; extern crate ron; From 19890501845a05d93f761b83503b234611b3ebe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 13 Oct 2017 22:40:17 +0200 Subject: [PATCH 12/40] Parametrize over Key --- README.md | 6 +- examples/full.rs | 2 +- src/lib.rs | 189 ++++++++++++++++++++++++++--------------------- 3 files changed, 110 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index b603043..07476f0 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,10 @@ Usage is quite simple: use rustbreak::Database; fn main() { - let db = Database::open("my_contacts").unwrap(); + let db = Database::memory(); - db.insert("Lapfox", "lapfoxtrax.com").unwrap(); - db.insert("Rust", "github.com/rust-lang/rust").unwrap(); + db.insert("Lapfox".into(), "lapfoxtrax.com") + db.insert("Rust".into(), "github.com/rust-lang/rust") // we need to be explicit about the kind of type we want as println! is // generic diff --git a/examples/full.rs b/examples/full.rs index 9deb164..004b46c 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -18,7 +18,7 @@ fn main() { use std::collections::HashMap; use rustbreak::Container; - let db = Database::from_path("test.yaml").unwrap(); + let db = Database::::from_path("test.yaml").unwrap(); println!("Writing to Database"); db.write(|mut db| { diff --git a/src/lib.rs b/src/lib.rs index 5ffa59f..8c326cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,11 +104,11 @@ use error::{BreakResult, BreakError}; use deser::{DeSerializer, Ron}; /// A container is a Key/Value storage -pub trait Container : Debug { +pub trait Container : Debug { /// Insert the value at the given key /// /// Returns optionally an existing value that was replaced - fn insert(&mut self, key: K, value: V) -> Option; + fn insert>(&mut self, key: T, value: V) -> Option; /// Removes the given value from the Container fn remove>(&mut self, key: T) -> Option; @@ -127,9 +127,9 @@ pub trait Container : Debug { } } -impl Container for HashMap { - fn insert(&mut self, key: K, value: V) -> Option { - self.insert(key, value) +impl Container for HashMap { + fn insert>(&mut self, key: T, value: V) -> Option { + self.insert(key.into(), value) } fn remove>(&mut self, key: T) -> Option { self.remove(key.as_ref()) @@ -140,75 +140,81 @@ impl Container for HashMap { } -type StringMap = HashMap; - /// The Central Database to RustBreak /// -/// It has 4 Type Generics: +/// It has 5 Type Generics: /// -/// - D: Is the Data, you must specify this (usually inferred by the compiler) +/// - V: Is the Data, you must specify this (usually inferred by the compiler) +/// - K: Is the Key, you must specify this (usually inferred by the compiler) /// - C: Is the backing Container, per default HashMap /// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used /// - F: The storage backend. Per default it is in memory, but can be easily used with a file #[derive(Debug)] -pub struct Database, S = Ron, F = RWVec> +pub struct Database, S = Ron, F = RWVec> where - D: Serialize + DeserializeOwned + Debug, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + DeSerializer + Sync + Send + Debug, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { backing: Mutex, data: RwLock, deser: S, - key: PhantomData + data_type: PhantomData, + key_type: PhantomData, } -impl Database> +impl Database> where - D: Serialize + DeserializeOwned + Debug + Clone, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, { /// Constructs a `Database` with in-memory Storage - pub fn memory() -> Database, Ron, RWVec> + pub fn memory() -> Database, Ron, RWVec> { Database { backing: Mutex::new(RWVec::new()), data: RwLock::new(HashMap::new()), deser: Ron, - key: PhantomData, + data_type: PhantomData, + key_type: PhantomData, } } } -impl Database> +impl Database> where - D: Serialize + DeserializeOwned + Debug + Clone, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, { /// Constructs a `Database` with file-backed storage - pub fn from_file(file: File) -> Database, Ron, File> { + pub fn from_file(file: File) -> Database, Ron, File> { Database { backing: Mutex::new(file), data: RwLock::new(HashMap::new()), deser: Ron, - key: PhantomData, + data_type: PhantomData, + key_type: PhantomData, } } /// Constructs a `Database` with file-backed storage from a given path pub fn from_path>(path: P) - -> BreakResult, Ron, File>, - >::SerError, >::DeError> { + -> BreakResult, Ron, File>, + >::SerError, >::DeError> { let file = OpenOptions::new().read(true).write(true).create(true).open(path)?; Ok(Self::from_file(file)) } } -impl Database +impl Database where - D: Serialize + DeserializeOwned + Debug + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + DeSerializer + Sync + Send + Debug, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Seek + Resizable + Debug, for<'r> &'r mut F: Read { @@ -222,41 +228,6 @@ impl Database Ok(()) } - /// Read locks the database and gives you read access to the underlying `Container` - pub fn read(&self, mut task: T) - -> BreakResult<(), >::SerError, >::DeError> - where T: FnMut(&C) - { - let lock = self.data.read()?; - task(&lock); - Ok(()) - } - - /// Directly inserts a given piece of data into the Database - pub fn insert(&self, key: String, data: D) - -> BreakResult<(), >::SerError, >::DeError> - { - let mut lock = self.data.write()?; - lock.insert(key, data); - Ok(()) - } - - /// Directly removes a given piece of data from the Database - pub fn remove>(&self, key: K) - -> BreakResult, >::SerError, >::DeError> - { - let mut lock = self.data.write()?; - Ok(lock.remove(key)) - } - - /// Directly removes a given piece of data from the Database - pub fn get>(&self, key: K) - -> BreakResult, >::SerError, >::DeError> - { - let lock = self.data.read()?; - Ok(lock.get(key).cloned()) - } - /// Syncs the Database to the backing storage /// /// # Attention @@ -297,38 +268,87 @@ impl Database } -impl Database + +impl Database where - D: Serialize + DeserializeOwned + Debug, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + DeSerializer + Sync + Send + Debug, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + Sync + Send + Debug, + F: Write + Resizable + Debug, + for<'r> &'r mut F: Read +{ + /// Read locks the database and gives you read access to the underlying `Container` + pub fn read(&self, mut task: T) + -> BreakResult<(), >::SerError, >::DeError> + where T: FnMut(&C) + { + let lock = self.data.read()?; + task(&lock); + Ok(()) + } + + /// Directly inserts a given piece of data into the Database + pub fn insert>(&self, key: T, data: V) + -> BreakResult<(), >::SerError, >::DeError> + { + let mut lock = self.data.write()?; + lock.insert(key, data); + Ok(()) + } + + /// Directly removes a given piece of data from the Database + pub fn remove>(&self, key: KK) + -> BreakResult, >::SerError, >::DeError> + { + let mut lock = self.data.write()?; + Ok(lock.remove(key)) + } + + /// Directly removes a given piece of data from the Database + pub fn get>(&self, key: KK) + -> BreakResult, >::SerError, >::DeError> + { + let lock = self.data.read()?; + Ok(lock.get(key).cloned()) + } +} + +impl Database + where + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { /// Exchanges a given deserialization method with another - pub fn with_deser(self, deser: T) -> Database + pub fn with_deser(self, deser: T) -> Database where - T: DeSerializer + DeSerializer + Sync + Send + Debug, + T: DeSerializer + Sync + Send + Debug, { Database { backing: self.backing, data: self.data, deser: deser, - key: PhantomData, + data_type: PhantomData, + key_type: PhantomData, } } } -impl Database +impl Database where - D: Serialize + DeserializeOwned + Debug, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + DeSerializer + Sync + Send + Debug, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { /// Exchanges a given backing method with another - pub fn with_backing(self, backing: T) -> Database + pub fn with_backing(self, backing: T) -> Database where T: Write + Resizable + Debug, for<'r> &'r mut T: Read @@ -337,30 +357,33 @@ impl Database backing: Mutex::new(backing), data: self.data, deser: self.deser, - key: PhantomData, + data_type: PhantomData, + key_type: PhantomData, } } } -impl Database +impl Database where - D: Serialize + DeserializeOwned + Debug, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + DeSerializer + Sync + Send + Debug, + V: Serialize + DeserializeOwned + Debug + Clone, + K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, + C: Serialize + DeserializeOwned + Container + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { /// Exchanges a given container method with another - pub fn with_container(self, data: T) -> Database + pub fn with_container(self, data: T) -> Database where - T: Serialize + DeserializeOwned + Container + Debug, + T: Serialize + DeserializeOwned + Container + Debug, S: DeSerializer + DeSerializer + Sync + Send + Debug, { Database { backing: self.backing, data: RwLock::new(data), deser: self.deser, - key: PhantomData, + data_type: PhantomData, + key_type: PhantomData, } } } From 1368d8c299dbc52321339c0bdcb5926663adb215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 3 Dec 2017 21:42:41 +0100 Subject: [PATCH 13/40] Small fixes and typos --- examples/full.rs | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/full.rs b/examples/full.rs index 004b46c..9d627b5 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -21,7 +21,7 @@ fn main() { let db = Database::::from_path("test.yaml").unwrap(); println!("Writing to Database"); - db.write(|mut db| { + db.write(|db| { db.insert("john".into(), Person { name: String::from("John Andersson"), country: Country::Italy diff --git a/src/lib.rs b/src/lib.rs index 8c326cc..f8619c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ //! ```rust //! use rustbreak::Database; //! -//! let db = Database::memory(); +//! let db = Database::::memory(); //! //! println!("Writing to Database"); //! db.write(|mut db| { @@ -83,7 +83,7 @@ extern crate base64; mod error; mod backend; -/// Differend serialization and deserialization methods one can use +/// Different serialization and deserialization methods one can use pub mod deser; use std::collections::HashMap; From 2c9cd5888c2d31ffdefd7bf6bb4362216fc3040e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 6 Dec 2017 20:02:15 +0100 Subject: [PATCH 14/40] Add extensive example --- Cargo.toml | 7 ++ examples/full.rs | 6 +- examples/server.rs | 155 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 188 +++++++++----------------------------------- templates/index.hbs | 105 +++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 155 deletions(-) create mode 100644 examples/server.rs create mode 100644 templates/index.hbs diff --git a/Cargo.toml b/Cargo.toml index a5bd8ac..5c0b96f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,9 +32,16 @@ version = "0.7" [dev-dependencies] lazy_static = "0.2.1" +rocket = "0.3.3" +rocket_codegen = "0.3.3" serde_derive = "1" tempfile = "2.1" +[dev-dependencies.rocket_contrib] +version = "0.3.3" +default-features = false +features = ["handlebars_templates"] + [features] bin = ["bincode", "base64", "error-chain"] yaml = ["serde_yaml"] diff --git a/examples/full.rs b/examples/full.rs index 9d627b5..17b1729 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -17,8 +17,7 @@ struct Person { fn main() { use std::collections::HashMap; - use rustbreak::Container; - let db = Database::::from_path("test.yaml").unwrap(); + let db = Database::>::from_path(HashMap::new(), "test.yaml").unwrap(); println!("Writing to Database"); db.write(|db| { @@ -30,8 +29,7 @@ fn main() { name: String::from("Fred Johnson"), country: Country::UnitedKingdom }); - let map : &HashMap<_, _> = db.borrow(); - println!("Values: \n{:#?}", map.values()); + println!("Values: \n{:#?}", db.values()); }).unwrap(); println!("Syncing Database"); diff --git a/examples/server.rs b/examples/server.rs new file mode 100644 index 0000000..74f76c6 --- /dev/null +++ b/examples/server.rs @@ -0,0 +1,155 @@ +#![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 std::fs::File; + +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::Database; +use rustbreak::deser::Ron; + +// We create a type alias so that we always associate the same types to it +type DB = Database; + +#[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.sync(); + + 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.sync(); + + Redirect::to("/") +} + +fn main() { + let db : DB = Database::from_path(ServerData { + pastes: vec![], + users: HashMap::new(), + }, "server.ron").unwrap(); + let _ = db.reload(); + + + rocket::ignite() + .mount("/", routes![index, post_login, post_paste, post_register]) + .attach(Template::fairing()) + .manage(db) + .launch(); +} diff --git a/src/lib.rs b/src/lib.rs index f8619c8..49d3996 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,14 +28,13 @@ //! //! At its core, Rustbreak is an attempt at making a configurable key-value store Database. //! It features the possibility of: +//! //! - Choosing what kind of Data is stored in it -//! - What kind of Container is storing it //! - Which kind of Serialization is used for persistence //! - Which kind of persistence is used //! //! Per default these options are used: -//! - The Container is a HashMap, leaving you the choice what you want to store in it, and -//! can access it with a String key. +//! //! - The Serialization is [Ron][ron], a familiar notation for Rust //! - The persistence is in-memory, allowing for quick prototyping //! @@ -47,12 +46,13 @@ //! ## Quickstart //! //! ```rust +//! # use std::collections::HashMap; //! use rustbreak::Database; //! -//! let db = Database::::memory(); +//! let db = Database::>::memory(HashMap::new()); //! //! println!("Writing to Database"); -//! db.write(|mut db| { +//! db.write(|db| { //! db.insert("hello".into(), String::from("world")); //! db.insert("foo".into(), String::from("bar")); //! }); @@ -66,7 +66,6 @@ //! //! [daybreak]:https://propublica.github.io/daybreak //! [examples]: https://github.com/TheNeikos/rustbreak/tree/master/examples -//! [examples]: https://github.com/TheNeikos/rustbreak/ //! [ron]: https://github.com/ron-rs/ron @@ -86,14 +85,11 @@ mod backend; /// Different serialization and deserialization methods one can use pub mod deser; -use std::collections::HashMap; use std::fs::{OpenOptions, File}; use std::path::Path; use std::io::{Seek, SeekFrom, Read, Write}; use std::sync::{Mutex, RwLock}; -use std::hash::Hash; use std::fmt::Debug; -use std::marker::PhantomData; use serde::Serialize; use serde::de::DeserializeOwned; @@ -103,125 +99,75 @@ pub use backend::Resizable; use error::{BreakResult, BreakError}; use deser::{DeSerializer, Ron}; -/// A container is a Key/Value storage -pub trait Container : Debug { - /// Insert the value at the given key - /// - /// Returns optionally an existing value that was replaced - fn insert>(&mut self, key: T, value: V) -> Option; - - /// Removes the given value from the Container - fn remove>(&mut self, key: T) -> Option; - - /// Borrows the given value from the Container - fn get>(&self, key: T) -> Option<&V>; - - /// Gets the underlying storage container mutably - fn borrow_mut(&mut self) -> &mut Self { - self - } - - /// Gets the underlying storage container - fn borrow(&self) -> &Self { - self - } -} - -impl Container for HashMap { - fn insert>(&mut self, key: T, value: V) -> Option { - self.insert(key.into(), value) - } - fn remove>(&mut self, key: T) -> Option { - self.remove(key.as_ref()) - } - fn get>(&self, key: T) -> Option<&V> { - self.get(key.as_ref()) - } -} - - /// The Central Database to RustBreak /// /// It has 5 Type Generics: /// /// - V: Is the Data, you must specify this (usually inferred by the compiler) -/// - K: Is the Key, you must specify this (usually inferred by the compiler) /// - C: Is the backing Container, per default HashMap /// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used /// - F: The storage backend. Per default it is in memory, but can be easily used with a file #[derive(Debug)] -pub struct Database, S = Ron, F = RWVec> +pub struct Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + Sync + Send + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { backing: Mutex, - data: RwLock, + data: RwLock, deser: S, - data_type: PhantomData, - key_type: PhantomData, } -impl Database> +impl Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, { /// Constructs a `Database` with in-memory Storage - pub fn memory() -> Database, Ron, RWVec> + pub fn memory(initial: V) -> Database { Database { backing: Mutex::new(RWVec::new()), - data: RwLock::new(HashMap::new()), + data: RwLock::new(initial), deser: Ron, - data_type: PhantomData, - key_type: PhantomData, } } } -impl Database> +impl Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, { /// Constructs a `Database` with file-backed storage - pub fn from_file(file: File) -> Database, Ron, File> { + pub fn from_file(initial: V, file: File) -> Database { Database { backing: Mutex::new(file), - data: RwLock::new(HashMap::new()), + data: RwLock::new(initial), deser: Ron, - data_type: PhantomData, - key_type: PhantomData, } } /// Constructs a `Database` with file-backed storage from a given path - pub fn from_path>(path: P) - -> BreakResult, Ron, File>, - >::SerError, >::DeError> { + pub fn from_path>(initial: V, path: P) -> BreakResult, + >::SerError, >::DeError> + { let file = OpenOptions::new().read(true).write(true).create(true).open(path)?; - Ok(Self::from_file(file)) + Ok(Self::from_file(initial, file)) } } -impl Database +impl Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + Sync + Send + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Seek + Resizable + Debug, for<'r> &'r mut F: Read { /// Write locks the database and gives you write access to the underlying `Container` - pub fn write(&self, mut task: T) - -> BreakResult<(), >::SerError, >::DeError> - where T: FnMut(&mut C) + pub fn write(&self, task: T) + -> BreakResult<(), >::SerError, >::DeError> + where T: FnOnce(&mut V) { let mut lock = self.data.write()?; task(&mut lock); @@ -234,7 +180,7 @@ impl Database /// You __have__ to call this method yourself! Per default Rustbreak buffers everything /// in-memory and lets you decide when to write pub fn sync(&self) - -> BreakResult<(), >::SerError, >::DeError> + -> BreakResult<(), >::SerError, >::DeError> { let mut backing = self.backing.lock()?; let data = self.data.read()?; @@ -253,7 +199,7 @@ impl Database /// /// This is useful to call at startup, or your Database might be empty! pub fn reload(&self) - -> BreakResult<(), >::SerError, >::DeError> + -> BreakResult<(), >::SerError, >::DeError> { let mut backing = self.backing.lock()?; let mut data = self.data.write()?; @@ -269,86 +215,53 @@ impl Database } -impl Database +impl Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + Sync + Send + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { /// Read locks the database and gives you read access to the underlying `Container` - pub fn read(&self, mut task: T) - -> BreakResult<(), >::SerError, >::DeError> - where T: FnMut(&C) + pub fn read(&self, task: T) + -> BreakResult<(), >::SerError, >::DeError> + where T: FnOnce(&V) { let lock = self.data.read()?; task(&lock); Ok(()) } - - /// Directly inserts a given piece of data into the Database - pub fn insert>(&self, key: T, data: V) - -> BreakResult<(), >::SerError, >::DeError> - { - let mut lock = self.data.write()?; - lock.insert(key, data); - Ok(()) - } - - /// Directly removes a given piece of data from the Database - pub fn remove>(&self, key: KK) - -> BreakResult, >::SerError, >::DeError> - { - let mut lock = self.data.write()?; - Ok(lock.remove(key)) - } - - /// Directly removes a given piece of data from the Database - pub fn get>(&self, key: KK) - -> BreakResult, >::SerError, >::DeError> - { - let lock = self.data.read()?; - Ok(lock.get(key).cloned()) - } } -impl Database +impl Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + Sync + Send + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { /// Exchanges a given deserialization method with another - pub fn with_deser(self, deser: T) -> Database + pub fn with_deser(self, deser: T) -> Database where - T: DeSerializer + Sync + Send + Debug, + T: DeSerializer + Sync + Send + Debug, { Database { backing: self.backing, data: self.data, deser: deser, - data_type: PhantomData, - key_type: PhantomData, } } } -impl Database +impl Database where V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + Sync + Send + Debug, + S: DeSerializer + Sync + Send + Debug, F: Write + Resizable + Debug, for<'r> &'r mut F: Read { /// Exchanges a given backing method with another - pub fn with_backing(self, backing: T) -> Database + pub fn with_backing(self, backing: T) -> Database where T: Write + Resizable + Debug, for<'r> &'r mut T: Read @@ -357,33 +270,6 @@ impl Database backing: Mutex::new(backing), data: self.data, deser: self.deser, - data_type: PhantomData, - key_type: PhantomData, - } - } -} - -impl Database - where - V: Serialize + DeserializeOwned + Debug + Clone, - K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone, - C: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + Sync + Send + Debug, - F: Write + Resizable + Debug, - for<'r> &'r mut F: Read -{ - /// Exchanges a given container method with another - pub fn with_container(self, data: T) -> Database - where - T: Serialize + DeserializeOwned + Container + Debug, - S: DeSerializer + DeSerializer + Sync + Send + Debug, - { - Database { - backing: self.backing, - data: RwLock::new(data), - deser: self.deser, - data_type: PhantomData, - key_type: PhantomData, } } } diff --git a/templates/index.hbs b/templates/index.hbs new file mode 100644 index 0000000..3f926ad --- /dev/null +++ b/templates/index.hbs @@ -0,0 +1,105 @@ + + + + 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 + +
+
+
+ + From 13a4d37dcbcf8de488bf9a607fc74b12e0ab1541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 6 Dec 2017 20:17:33 +0100 Subject: [PATCH 15/40] Only test on nightly --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1c23677..6e85a94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_script: script: - | travis-cargo build && - travis-cargo test && + travis-cargo --only nightly test && rm /tmp/* travis-cargo test -- --no-default-features --features yaml && travis-cargo bench && From a631fc1687cd73c538c132b626aa1e20d9efe1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 10 Dec 2017 12:54:54 +0100 Subject: [PATCH 16/40] Refactor some examples --- .travis.yml | 6 +- Cargo.toml | 10 +--- examples/config.rs | 57 +++++++++++++++++++ examples/server/Cargo.toml | 18 ++++++ examples/server/server.ron | 22 +++++++ examples/{server.rs => server/src/main.rs} | 0 .../server/templates}/index.hbs | 0 src/deser.rs | 5 +- src/lib.rs | 15 +++-- 9 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 examples/config.rs create mode 100644 examples/server/Cargo.toml create mode 100644 examples/server/server.ron rename examples/{server.rs => server/src/main.rs} (100%) rename {templates => examples/server/templates}/index.hbs (100%) diff --git a/.travis.yml b/.travis.yml index 6e85a94..63587cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,11 @@ before_script: script: - | travis-cargo build && - travis-cargo --only nightly test && + travis-cargo test && rm /tmp/* - travis-cargo test -- --no-default-features --features yaml && + travis-cargo test --features yaml && travis-cargo bench && - travis-cargo --only stable doc + travis-cargo --only stable --features yaml,bin doc after_success: - travis-cargo --only stable doc-upload env: diff --git a/Cargo.toml b/Cargo.toml index 5c0b96f..0e6f316 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/TheNeikos/rustbreak" version = "2.0.0" [dependencies] +lazy_static = "1.0.0" ron = "0.1.2" serde = "1" @@ -31,17 +32,10 @@ optional = true version = "0.7" [dev-dependencies] -lazy_static = "0.2.1" -rocket = "0.3.3" -rocket_codegen = "0.3.3" +lazy_static = "1.0.0" serde_derive = "1" tempfile = "2.1" -[dev-dependencies.rocket_contrib] -version = "0.3.3" -default-features = false -features = ["handlebars_templates"] - [features] bin = ["bincode", "base64", "error-chain"] yaml = ["serde_yaml"] diff --git a/examples/config.rs b/examples/config.rs new file mode 100644 index 0000000..c523eeb --- /dev/null +++ b/examples/config.rs @@ -0,0 +1,57 @@ +// This just reads an example configuration. Don't forget to run with `--feature yaml` +// If it doesn't find one, it uses your default configuration +// +// You can create one by writing this file to `/tmp/config.yml`: +// ``` +// --- +// user_path: /tmp/nope +// allow_overwrite: true +// ``` +// + +extern crate rustbreak; +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate lazy_static; + +use std::fs::File; +use std::path::PathBuf; +use std::default::Default; +use rustbreak::Database; +use rustbreak::deser::Yaml; + +type DB = Database; + +lazy_static! { + static ref CONFIG: DB = { + let db = Database::from_path(Config::default(), "/tmp/config.yml").expect("Create database from path"); + let db = db.with_deser(Yaml); + db.reload().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/server/Cargo.toml b/examples/server/Cargo.toml new file mode 100644 index 0000000..19336f7 --- /dev/null +++ b/examples/server/Cargo.toml @@ -0,0 +1,18 @@ +[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 = "../.." diff --git a/examples/server/server.ron b/examples/server/server.ron new file mode 100644 index 0000000..0d6b061 --- /dev/null +++ b/examples/server/server.ron @@ -0,0 +1,22 @@ +( + pastes: [ + ( + user: "Hello", + body: "Wooow", + ), + ( + user: "Hello", + body: "asweasd", + ), + ( + user: "Hello", + body: "", + ), + ], + users: { + "Hello": ( + username: "Hello", + password: "Test", + ), + }, +) \ No newline at end of file diff --git a/examples/server.rs b/examples/server/src/main.rs similarity index 100% rename from examples/server.rs rename to examples/server/src/main.rs diff --git a/templates/index.hbs b/examples/server/templates/index.hbs similarity index 100% rename from templates/index.hbs rename to examples/server/templates/index.hbs diff --git a/src/deser.rs b/src/deser.rs index f7c6f74..ae7223e 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -7,7 +7,8 @@ use std::io::Read; use serde::Serialize; use serde::de::DeserializeOwned; -use ron::ser::pretty::to_string as to_ron_string; +use ron::ser::to_string_pretty as to_ron_string; +use ron::ser::PrettyConfig; use ron::de::from_reader as from_ron_string; #[cfg(feature = "yaml")] @@ -37,7 +38,7 @@ impl DeSerializer for Ron { type SerError = ::ron::ser::Error; type DeError = ::ron::de::Error; fn serialize(&self, val: &T) -> Result { - to_ron_string(val) + to_ron_string(val, PrettyConfig::default()) } fn deserialize(&self, s: R) -> Result { from_ron_string(s) diff --git a/src/lib.rs b/src/lib.rs index 49d3996..dc48ca8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,9 +104,9 @@ use deser::{DeSerializer, Ron}; /// It has 5 Type Generics: /// /// - V: Is the Data, you must specify this (usually inferred by the compiler) -/// - C: Is the backing Container, per default HashMap -/// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used -/// - F: The storage backend. Per default it is in memory, but can be easily used with a file +/// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used. Check the +/// `deser` module for other strategies. +/// - F: The storage backend. Per default it is in memory, but can be easily used with a `File`. #[derive(Debug)] pub struct Database where @@ -223,13 +223,12 @@ impl Database for<'r> &'r mut F: Read { /// Read locks the database and gives you read access to the underlying `Container` - pub fn read(&self, task: T) - -> BreakResult<(), >::SerError, >::DeError> - where T: FnOnce(&V) + pub fn read(&self, task: T) + -> BreakResult>::SerError, >::DeError> + where T: FnOnce(&V) -> R { let lock = self.data.read()?; - task(&lock); - Ok(()) + Ok(task(&lock)) } } From 2616a7cd6d19f4f04a305ada32cc4b5476541805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 10 Dec 2017 12:59:10 +0100 Subject: [PATCH 17/40] Fix travis.yml --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 63587cd..3f4ffb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,7 @@ before_script: script: - | travis-cargo build && - travis-cargo test && - rm /tmp/* travis-cargo test --features yaml && - travis-cargo bench && travis-cargo --only stable --features yaml,bin doc after_success: - travis-cargo --only stable doc-upload From 174c8186c66cc8680c286781ace55d686b81cf36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 02:13:40 +0200 Subject: [PATCH 18/40] Rewrite with a better architecture --- .travis.yml | 6 +- Cargo.toml | 1 + examples/config.rs | 2 +- examples/full.rs | 18 +-- examples/server/src/main.rs | 7 +- src/backend.rs | 76 ----------- src/deser.rs | 56 +++----- src/error.rs | 41 +++--- src/lib.rs | 265 +++++++++++++++++------------------- test.ron | 10 ++ 10 files changed, 191 insertions(+), 291 deletions(-) delete mode 100644 src/backend.rs create mode 100644 test.ron diff --git a/.travis.yml b/.travis.yml index 3f4ffb7..e275c0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ before_script: export PATH=$HOME/.local/bin:$PATH script: - | - travis-cargo build && - travis-cargo test --features yaml && - travis-cargo --only stable --features yaml,bin doc + travis-cargo build -- --features yaml && + travis-cargo test -- --features yaml && + travis-cargo --only stable -- --features yaml,bin doc after_success: - travis-cargo --only stable doc-upload env: diff --git a/Cargo.toml b/Cargo.toml index 0e6f316..ce77d57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/TheNeikos/rustbreak" version = "2.0.0" [dependencies] +failure = "0.1.1" lazy_static = "1.0.0" ron = "0.1.2" serde = "1" diff --git a/examples/config.rs b/examples/config.rs index c523eeb..b403fb9 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -19,7 +19,7 @@ use std::default::Default; use rustbreak::Database; use rustbreak::deser::Yaml; -type DB = Database; +type DB = Database; lazy_static! { static ref CONFIG: DB = { diff --git a/examples/full.rs b/examples/full.rs index 17b1729..b3c5e73 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -1,7 +1,8 @@ extern crate rustbreak; #[macro_use] extern crate serde_derive; -use rustbreak::Database; +use rustbreak::FileDatabase; +use rustbreak::deser::Ron; #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] enum Country { @@ -17,7 +18,8 @@ struct Person { fn main() { use std::collections::HashMap; - let db = Database::>::from_path(HashMap::new(), "test.yaml").unwrap(); + let db = FileDatabase::, Ron>::from_path(HashMap::new(), + Ron, "test.ron").unwrap(); println!("Writing to Database"); db.write(|db| { @@ -29,7 +31,7 @@ fn main() { name: String::from("Fred Johnson"), country: Country::UnitedKingdom }); - println!("Values: \n{:#?}", db.values()); + println!("Entries: \n{:#?}", db); }).unwrap(); println!("Syncing Database"); @@ -38,17 +40,11 @@ fn main() { println!("Reloading Database"); db.reload().unwrap(); - let mut john = None; - let mut fred = None; - println!("Reading from Database"); db.read(|db| { - // We want to take things out of the Database, so we clone - john = db.get("john").cloned(); - fred = db.get("fred").cloned(); + println!("Results:"); + println!("{:#?}", db); }).unwrap(); - println!("Results:"); - println!("{:#?}, {:#?}", john, fred); } diff --git a/examples/server/src/main.rs b/examples/server/src/main.rs index 74f76c6..44addd6 100644 --- a/examples/server/src/main.rs +++ b/examples/server/src/main.rs @@ -139,6 +139,11 @@ fn post_paste(db: State, user: User, paste: Form) -> Redirect { Redirect::to("/") } +#[get("/dummy")] +fn get_dummy() -> &'static str { + "Hello World" +} + fn main() { let db : DB = Database::from_path(ServerData { pastes: vec![], @@ -148,7 +153,7 @@ fn main() { rocket::ignite() - .mount("/", routes![index, post_login, post_paste, post_register]) + .mount("/", routes![index, post_login, post_paste, post_register, get_dummy]) .attach(Template::fairing()) .manage(db) .launch(); diff --git a/src/backend.rs b/src/backend.rs deleted file mode 100644 index 1ef0eaa..0000000 --- a/src/backend.rs +++ /dev/null @@ -1,76 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use std::io::{Seek, SeekFrom, Read, Write, Result as IOResult}; - -/// Marker for Resizing action -/// -/// This is used to truncate the backend to a given size -pub trait Resizable { - /// Sets the backend to the given size - fn resize(&mut self, size: usize) -> IOResult<()>; - /// Syncs the given backend to the support it uses - fn sync(&mut self) -> IOResult<()>; -} - -impl Resizable for ::std::fs::File { - fn resize(&mut self, size: usize) -> IOResult<()> { - self.set_len(size as u64) - } - fn sync(&mut self) -> IOResult<()> { - self.sync_all() - } -} - -#[derive(Debug)] -pub struct RWVec(Vec, usize); - -impl Resizable for RWVec { - fn resize(&mut self, size: usize) -> IOResult<()> { - self.0.resize(size, 0); - Ok(()) - } - fn sync(&mut self) -> IOResult<()> { - Ok(()) - } -} - -impl<'r> Read for &'r mut RWVec { - fn read(&mut self, buf: &mut [u8]) -> IOResult { - println!("Called with: len: {} pos: {}", self.0.len(), self.1); - let len = (&self.0[(self.1)..]).read(buf)?; - self.1 += len; - Ok(len) - } -} - -impl Write for RWVec { - fn write(&mut self, buf: &[u8]) -> IOResult { - self.0.write(buf) - } - - fn flush(&mut self) -> IOResult<()> { - self.0.flush() - } -} - -impl Seek for RWVec { - fn seek(&mut self, seek: SeekFrom) -> IOResult { - match seek { - SeekFrom::Start(start) => self.1 = start as usize, - SeekFrom::End(end) => self.1 = (self.0.len() as i64 - end) as usize, - SeekFrom::Current(off) => self.1 = match self.1 as i64 - off { - x if x < 0 => 0, - x => x as usize - } - } - Ok(self.1 as u64) - } -} - -impl RWVec { - pub fn new() -> RWVec { - RWVec(vec![], 0) - } -} diff --git a/src/deser.rs b/src/deser.rs index ae7223e..4160624 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -11,23 +11,20 @@ use ron::ser::to_string_pretty as to_ron_string; use ron::ser::PrettyConfig; use ron::de::from_reader as from_ron_string; +use error; + #[cfg(feature = "yaml")] pub use self::yaml::Yaml; #[cfg(feature = "bin")] -pub use self::bincode::{Bincode, Error as BincodeError}; +pub use self::bincode::Bincode; /// A trait to bundle serializer and deserializer -pub trait DeSerializer { - /// Associated error with serialization - type SerError; - /// Associated error with deserialization - type DeError; - +pub trait DeSerializer : ::std::fmt::Debug + Send + Sync { /// Serializes a given value to a String - fn serialize(&self, val: &T) -> Result; + fn serialize(&self, val: &T) -> error::Result; /// Deserializes a String to a value - fn deserialize(&self, s: R) -> Result; + fn deserialize(&self, s: R) -> error::Result; } /// The Struct that allows you to use `ron` the Rusty Object Notation @@ -35,13 +32,11 @@ pub trait DeSerializer { pub struct Ron; impl DeSerializer for Ron { - type SerError = ::ron::ser::Error; - type DeError = ::ron::de::Error; - fn serialize(&self, val: &T) -> Result { - to_ron_string(val, PrettyConfig::default()) + fn serialize(&self, val: &T) -> error::Result { + Ok(to_ron_string(val, PrettyConfig::default())?) } - fn deserialize(&self, s: R) -> Result { - from_ron_string(s) + fn deserialize(&self, s: R) -> error::Result { + Ok(from_ron_string(s)?) } } @@ -53,6 +48,7 @@ mod yaml { use serde::Serialize; use serde::de::DeserializeOwned; + use error; use deser::DeSerializer; /// The struct that allows you to use yaml @@ -60,13 +56,11 @@ mod yaml { pub struct Yaml; impl DeSerializer for Yaml { - type SerError = ::serde_yaml::Error; - type DeError = ::serde_yaml::Error; - fn serialize(&self, val: &T) -> Result { - to_yaml_string(val) + fn serialize(&self, val: &T) -> error::Result { + Ok(to_yaml_string(val)?) } - fn deserialize(&self, s: R) -> Result { - from_yaml_string(s) + fn deserialize(&self, s: R) -> error::Result { + Ok(from_yaml_string(s)?) } } } @@ -80,33 +74,19 @@ mod bincode { use serde::Serialize; use serde::de::DeserializeOwned; + use error; use deser::DeSerializer; - error_chain! { - types { - Error, ErrorKind, ResultExt; - } - - foreign_links { - Bincode(::bincode::Error); - Io(::std::io::Error); - Base64(::base64::DecodeError); - Format(::std::string::FromUtf8Error); - } - } - /// The struct that allows you to use bincode #[derive(Debug)] pub struct Bincode; impl DeSerializer for Bincode { - type SerError = Error; - type DeError = Error; - fn serialize(&self, val: &T) -> Result { + fn serialize(&self, val: &T) -> error::Result { let res = to_bincode_string(val, ::bincode::Infinite)?; Ok(encode(&res)) } - fn deserialize(&self, mut s: R) -> Result { + fn deserialize(&self, mut s: R) -> error::Result { let mut string = String::new(); s.read_to_string(&mut string)?; Ok(from_bincode_string(String::from_utf8(decode(&string)?)?.as_bytes())?) diff --git a/src/error.rs b/src/error.rs index 60817e0..9b3e650 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,31 +2,34 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/// The Error type exported by BreakError, usually you only need to check against NotFound, -/// however it might be useful sometimes to get other errors. +use failure::{self, Context}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] +pub enum RustbreakErrorKind { + #[fail(display = "Could not serialize the value")] + SerializationError, + #[fail(display = "Could not deserialize the value")] + DeserializationError, + #[fail(display = "The database has been poisoned")] + PoisonError +} + + #[derive(Debug)] -pub enum BreakError { - /// An error returned when doing file operations, this might happen by opening, closing, - /// locking or flushing - Io(::std::io::Error), - /// Error when reading a formatted String - Format(::std::string::FromUtf8Error), - Serialize(Se), - Deserialize(De), - Poison +pub struct RustbreakError { + inner: Context, } -impl From<::std::sync::PoisonError> for BreakError { - fn from(_: ::std::sync::PoisonError) -> BreakError { - BreakError::Poison +impl From for RustbreakError { + fn from(kind: RustbreakErrorKind) -> RustbreakError { + RustbreakError { inner: Context::new(kind) } } } -impl From<::std::io::Error> for BreakError { - fn from(e: ::std::io::Error) -> BreakError { - BreakError::Io(e) +impl From> for RustbreakError { + fn from(inner: Context) -> RustbreakError { + RustbreakError { inner: inner } } } - -pub type BreakResult = Result>; +pub type Result = ::std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index dc48ca8..42c8013 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,33 +71,89 @@ extern crate serde; extern crate ron; +#[macro_use] extern crate failure; + #[cfg(feature = "yaml")] extern crate serde_yaml; + #[cfg(feature = "bin")] extern crate bincode; #[cfg(feature = "bin")] extern crate base64; -#[cfg(feature = "bin")] -#[macro_use] extern crate error_chain; mod error; -mod backend; /// Different serialization and deserialization methods one can use pub mod deser; -use std::fs::{OpenOptions, File}; -use std::path::Path; -use std::io::{Seek, SeekFrom, Read, Write}; +use std::io::{Read, Write}; use std::sync::{Mutex, RwLock}; use std::fmt::Debug; use serde::Serialize; use serde::de::DeserializeOwned; -use backend::RWVec; -pub use backend::Resizable; -use error::{BreakResult, BreakError}; -use deser::{DeSerializer, Ron}; +// use error::{BreakResult, BreakError}; +use deser::DeSerializer; + +/// The Backend Trait +/// +/// This trait describes a simple backend, allowing users to swap it, or +/// to implement one themselves +pub trait Backend { + /// This method gets the data from the backend + fn get_data(&mut self) -> error::Result>; + + /// This method + fn put_data(&mut self, data: &[u8]) -> error::Result<()>; +} + +/// A backend using a file +pub struct FileBackend { + file: std::fs::File +} + +impl Backend for FileBackend { + fn get_data(&mut self) -> error::Result> { + use std::io::{Seek, SeekFrom}; + + let mut buffer = vec![]; + self.file.seek(SeekFrom::Start(0))?; + self.file.read_to_end(&mut buffer)?; + Ok(buffer) + } + + fn put_data(&mut self, data: &[u8]) -> error::Result<()> { + use std::io::{Seek, SeekFrom}; + + self.file.seek(SeekFrom::Start(0))?; + self.file.set_len(0)?; + self.file.write_all(data)?; + Ok(()) + } +} + +impl FileBackend { + /// Opens a new FileBackend for a given path + pub fn open>(path: P) -> error::Result { + use std::fs::OpenOptions; + + Ok(FileBackend { + file: OpenOptions::new().read(true).write(true).create(true).open(path)?, + }) + } + + /// Uses an already open File + pub fn from_file(file: std::fs::File) -> FileBackend { + FileBackend { + file: file + } + } + + /// Return the inner File + pub fn into_inner(self) -> std::fs::File { + self.file + } +} /// The Central Database to RustBreak /// @@ -108,167 +164,92 @@ use deser::{DeSerializer, Ron}; /// `deser` module for other strategies. /// - F: The storage backend. Per default it is in memory, but can be easily used with a `File`. #[derive(Debug)] -pub struct Database +pub struct Database where - V: Serialize + DeserializeOwned + Debug + Clone, - S: DeSerializer + Sync + Send + Debug, - F: Write + Resizable + Debug, - for<'r> &'r mut F: Read + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + Back: Backend, + DeSer: DeSerializer + Send + Sync { - backing: Mutex, - data: RwLock, - deser: S, + data: RwLock, + backend: Mutex, + deser: DeSer } -impl Database +impl Database where - V: Serialize + DeserializeOwned + Debug + Clone, + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + Back: Backend, + DeSer: DeSerializer + Send + Sync { - /// Constructs a `Database` with in-memory Storage - pub fn memory(initial: V) -> Database + /// Write lock the database and get write access to the `Data` container + pub fn write(&self, task: T) -> error::Result<()> + where T: FnOnce(&mut Data) { - Database { - backing: Mutex::new(RWVec::new()), - data: RwLock::new(initial), - deser: Ron, - } - } -} - -impl Database - where - V: Serialize + DeserializeOwned + Debug + Clone, -{ - /// Constructs a `Database` with file-backed storage - pub fn from_file(initial: V, file: File) -> Database { - Database { - backing: Mutex::new(file), - data: RwLock::new(initial), - deser: Ron, - } - } - - /// Constructs a `Database` with file-backed storage from a given path - pub fn from_path>(initial: V, path: P) -> BreakResult, - >::SerError, >::DeError> - { - let file = OpenOptions::new().read(true).write(true).create(true).open(path)?; - Ok(Self::from_file(initial, file)) - } -} - -impl Database - where - V: Serialize + DeserializeOwned + Debug + Clone, - S: DeSerializer + Sync + Send + Debug, - F: Write + Seek + Resizable + Debug, - for<'r> &'r mut F: Read -{ - /// Write locks the database and gives you write access to the underlying `Container` - pub fn write(&self, task: T) - -> BreakResult<(), >::SerError, >::DeError> - where T: FnOnce(&mut V) - { - let mut lock = self.data.write()?; + let mut lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; task(&mut lock); Ok(()) } - /// Syncs the Database to the backing storage - /// - /// # Attention - /// You __have__ to call this method yourself! Per default Rustbreak buffers everything - /// in-memory and lets you decide when to write - pub fn sync(&self) - -> BreakResult<(), >::SerError, >::DeError> + /// Read lock the database and get write access to the `Data` container + pub fn read(&self, task: T) -> error::Result<()> + where T: FnOnce(&Data) { - let mut backing = self.backing.lock()?; - let data = self.data.read()?; - let s = match self.deser.serialize(&*data) { - Ok(s) => s, - Err(e) => return Err(BreakError::Serialize(e)), - }; - backing.seek(SeekFrom::Start(0))?; - backing.resize(0)?; - backing.write_all(&s.as_bytes())?; - backing.sync()?; + let mut lock = self.data.read().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + task(&mut lock); Ok(()) } - /// Reloads the internal storage from the backing storage - /// - /// This is useful to call at startup, or your Database might be empty! - pub fn reload(&self) - -> BreakResult<(), >::SerError, >::DeError> - { - let mut backing = self.backing.lock()?; - let mut data = self.data.write()?; - backing.seek(SeekFrom::Start(0))?; - let mut new_data = match self.deser.deserialize(&mut *backing) { + /// Reload the Data from the backend + pub fn reload(&self) -> error::Result<()> { + use failure::ResultExt; + + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + + let mut new_data = match self.deser.deserialize(&backend.get_data()?[..]) { Ok(s) => s, - Err(e) => return Err(BreakError::Deserialize(e)), + Err(e) => Err(e).context(error::RustbreakErrorKind::DeserializationError)? }; + ::std::mem::swap(&mut *data, &mut new_data); Ok(()) } -} + /// Flush the data structure to the backend + pub fn sync(&self) -> error::Result<()> { + use failure::ResultExt; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; -impl Database - where - V: Serialize + DeserializeOwned + Debug + Clone, - S: DeSerializer + Sync + Send + Debug, - F: Write + Resizable + Debug, - for<'r> &'r mut F: Read -{ - /// Read locks the database and gives you read access to the underlying `Container` - pub fn read(&self, task: T) - -> BreakResult>::SerError, >::DeError> - where T: FnOnce(&V) -> R - { - let lock = self.data.read()?; - Ok(task(&lock)) + let ser = match self.deser.serialize(&*data) { + Ok(s) => s, + Err(e) => Err(e).context(error::RustbreakErrorKind::SerializationError)? + }; + backend.put_data(ser.as_bytes())?; + Ok(()) } } -impl Database +/// A database backed by a file +pub type FileDatabase = Database; + +impl Database where - V: Serialize + DeserializeOwned + Debug + Clone, - S: DeSerializer + Sync + Send + Debug, - F: Write + Resizable + Debug, - for<'r> &'r mut F: Read + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + DeSer: DeSerializer + Send + Sync { - /// Exchanges a given deserialization method with another - pub fn with_deser(self, deser: T) -> Database - where - T: DeSerializer + Sync + Send + Debug, + /// Create new FileDatabase from Path + pub fn from_path(data: Data, deser: DeSer, path: S) + -> error::Result> + where S: AsRef { - Database { - backing: self.backing, - data: self.data, + let b = FileBackend::open(path)?; + + Ok(Database { + data: RwLock::new(data), + backend: Mutex::new(b), deser: deser, - } - } -} - -impl Database - where - V: Serialize + DeserializeOwned + Debug + Clone, - S: DeSerializer + Sync + Send + Debug, - F: Write + Resizable + Debug, - for<'r> &'r mut F: Read -{ - /// Exchanges a given backing method with another - pub fn with_backing(self, backing: T) -> Database - where - T: Write + Resizable + Debug, - for<'r> &'r mut T: Read - { - Database { - backing: Mutex::new(backing), - data: self.data, - deser: self.deser, - } + }) } } diff --git a/test.ron b/test.ron new file mode 100644 index 0000000..16956ef --- /dev/null +++ b/test.ron @@ -0,0 +1,10 @@ +{ + "fred": ( + name: "Fred Johnson", + country: UnitedKingdom, + ), + "john": ( + name: "John Andersson", + country: Italy, + ), +} \ No newline at end of file From 52f0cb31be46951386f7cbc97d90e4ddfcad610a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 02:33:09 +0200 Subject: [PATCH 19/40] Add in-memory and fix docs --- Cargo.toml | 7 +------ examples/config.rs | 9 ++++----- src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce77d57..32ce623 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,20 +24,15 @@ version = "0.7.0" optional = true version = "0.8.0" -[dependencies.error-chain] -optional = true -version = "0.11.0" - [dependencies.serde_yaml] optional = true version = "0.7" [dev-dependencies] -lazy_static = "1.0.0" serde_derive = "1" tempfile = "2.1" [features] -bin = ["bincode", "base64", "error-chain"] +bin = ["bincode", "base64"] yaml = ["serde_yaml"] ron_enc = ["ron"] diff --git a/examples/config.rs b/examples/config.rs index b403fb9..395cf5b 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -13,18 +13,17 @@ extern crate rustbreak; #[macro_use] extern crate serde_derive; #[macro_use] extern crate lazy_static; -use std::fs::File; use std::path::PathBuf; use std::default::Default; -use rustbreak::Database; +use rustbreak::FileDatabase; use rustbreak::deser::Yaml; -type DB = Database; +type DB = FileDatabase; lazy_static! { static ref CONFIG: DB = { - let db = Database::from_path(Config::default(), "/tmp/config.yml").expect("Create database from path"); - let db = db.with_deser(Yaml); + let db = FileDatabase::from_path(Config::default(), Yaml, "/tmp/config.yml") + .expect("Create database from path"); db.reload().expect("Config to load"); db }; diff --git a/src/lib.rs b/src/lib.rs index 42c8013..7d638a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,9 +47,9 @@ //! //! ```rust //! # use std::collections::HashMap; -//! use rustbreak::Database; +//! use rustbreak::{MemoryDatabase, deser::Ron}; //! -//! let db = Database::>::memory(HashMap::new()); +//! let db = MemoryDatabase::, Ron>::memory(HashMap::new(), Ron); //! //! println!("Writing to Database"); //! db.write(|db| { @@ -191,12 +191,11 @@ impl Database } /// Read lock the database and get write access to the `Data` container - pub fn read(&self, task: T) -> error::Result<()> - where T: FnOnce(&Data) + pub fn read(&self, task: T) -> error::Result + where T: FnOnce(&Data) -> R { let mut lock = self.data.read().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - task(&mut lock); - Ok(()) + Ok(task(&mut lock)) } /// Reload the Data from the backend @@ -253,3 +252,37 @@ impl Database }) } } + +/// An in memory backend +/// +/// It is backed by a `Vec` +pub struct MemoryBackend(Vec); + +impl Backend for MemoryBackend { + fn get_data(&mut self) -> error::Result> { + Ok(self.0.clone()) + } + + fn put_data(&mut self, data: &[u8]) -> error::Result<()> { + self.0 = data.to_owned(); + Ok(()) + } +} + +/// A database backed by a file +pub type MemoryDatabase = Database; + +impl Database + where + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + DeSer: DeSerializer + Send + Sync +{ + /// Create new FileDatabase from Path + pub fn memory(data: Data, deser: DeSer) -> MemoryDatabase { + Database { + data: RwLock::new(data), + backend: Mutex::new(MemoryBackend(vec![])), + deser: deser, + } + } +} From 7e47da26ce6de5587124a85bb32de188dd22583b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 02:41:47 +0200 Subject: [PATCH 20/40] Add switching backend/encoding --- examples/switching.rs | 46 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 36 +++++++++++++++++++++++++++++++++ test.ron | 10 ---------- 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 examples/switching.rs delete mode 100644 test.ron diff --git a/examples/switching.rs b/examples/switching.rs new file mode 100644 index 0000000..762e63f --- /dev/null +++ b/examples/switching.rs @@ -0,0 +1,46 @@ +extern crate rustbreak; +#[macro_use] extern crate serde_derive; + +use rustbreak::{FileDatabase, FileBackend}; +use rustbreak::deser::{Ron, Yaml}; + +#[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 main() { + use std::collections::HashMap; + + let db = FileDatabase::, Ron>::from_path(HashMap::new(), + Ron, "test.ron").unwrap(); + + 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); + }).unwrap(); + + println!("Syncing Database"); + db.sync().unwrap(); + + // Now lets switch it + + let db = db.with_deser(Yaml).with_backend(FileBackend::open("test.yml").unwrap()); + db.sync().unwrap(); + +} + diff --git a/src/lib.rs b/src/lib.rs index 7d638a1..5b558a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -286,3 +286,39 @@ impl Database } } } + +impl Database + where + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + Back: Backend, + DeSer: DeSerializer + Send + Sync +{ + /// Exchanges the DeSerialization strategy with the given one + pub fn with_deser(self, deser: T) -> Database + where T: DeSerializer + Send + Sync + { + Database { + backend: self.backend, + data: self.data, + deser: deser, + } + } +} + +impl Database + where + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + Back: Backend, + DeSer: DeSerializer + Send + Sync +{ + /// Exchanges the Backend with the given one + pub fn with_backend(self, backend: T) -> Database + where T: Backend + { + Database { + backend: Mutex::new(backend), + data: self.data, + deser: self.deser, + } + } +} diff --git a/test.ron b/test.ron deleted file mode 100644 index 16956ef..0000000 --- a/test.ron +++ /dev/null @@ -1,10 +0,0 @@ -{ - "fred": ( - name: "Fred Johnson", - country: UnitedKingdom, - ), - "john": ( - name: "John Andersson", - country: Italy, - ), -} \ No newline at end of file From cfaf60f0ea1ecab5e19d13b9b254e6b14cbd3c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 02:49:48 +0200 Subject: [PATCH 21/40] Factor out backend.rs --- src/backend.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 91 ++++---------------------------------------------- 2 files changed, 88 insertions(+), 84 deletions(-) create mode 100644 src/backend.rs diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..684d032 --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use error; + +/// The Backend Trait +/// +/// This trait describes a simple backend, allowing users to swap it, or +/// to implement one themselves +pub trait Backend { + /// This method gets the data from the backend + fn get_data(&mut self) -> error::Result>; + + /// This method + fn put_data(&mut self, data: &[u8]) -> error::Result<()>; +} + +/// A backend using a file +pub struct FileBackend { + file: ::std::fs::File +} + +impl Backend for FileBackend { + fn get_data(&mut self) -> error::Result> { + use ::std::io::{Seek, SeekFrom, Read}; + + let mut buffer = vec![]; + self.file.seek(SeekFrom::Start(0))?; + self.file.read_to_end(&mut buffer)?; + Ok(buffer) + } + + fn put_data(&mut self, data: &[u8]) -> error::Result<()> { + use ::std::io::{Seek, SeekFrom, Write}; + + self.file.seek(SeekFrom::Start(0))?; + self.file.set_len(0)?; + self.file.write_all(data)?; + Ok(()) + } +} + +impl FileBackend { + /// Opens a new FileBackend for a given path + pub fn open>(path: P) -> error::Result { + use ::std::fs::OpenOptions; + + Ok(FileBackend { + file: OpenOptions::new().read(true).write(true).create(true).open(path)?, + }) + } + + /// Uses an already open File + pub fn from_file(file: ::std::fs::File) -> FileBackend { + FileBackend { + file: file + } + } + + /// Return the inner File + pub fn into_inner(self) -> ::std::fs::File { + self.file + } +} + +/// An in memory backend +/// +/// It is backed by a `Vec` +pub struct MemoryBackend(pub(crate) Vec); + +impl Backend for MemoryBackend { + fn get_data(&mut self) -> error::Result> { + Ok(self.0.clone()) + } + + fn put_data(&mut self, data: &[u8]) -> error::Result<()> { + self.0 = data.to_owned(); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5b558a4..12feb06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,13 +33,10 @@ //! - Which kind of Serialization is used for persistence //! - Which kind of persistence is used //! -//! Per default these options are used: +//! There are two helper type aliases `MemoryDatabase` and `FileDatabase`, each backed by their +//! respective backend. //! -//! - The Serialization is [Ron][ron], a familiar notation for Rust -//! - The persistence is in-memory, allowing for quick prototyping -//! -//! Later in the development process, the Serialization and the Persistence can be exchanged without -//! breaking the code, allowing you to be flexible. +//! Using the `with_deser` and `with_backend` one can switch between the representations one needs. //! //! If you have any questions feel free to ask at the main [repo][repo]. //! @@ -82,10 +79,11 @@ extern crate bincode; extern crate base64; mod error; +/// Different storage backends +pub mod backend; /// Different serialization and deserialization methods one can use pub mod deser; -use std::io::{Read, Write}; use std::sync::{Mutex, RwLock}; use std::fmt::Debug; @@ -94,66 +92,7 @@ use serde::de::DeserializeOwned; // use error::{BreakResult, BreakError}; use deser::DeSerializer; - -/// The Backend Trait -/// -/// This trait describes a simple backend, allowing users to swap it, or -/// to implement one themselves -pub trait Backend { - /// This method gets the data from the backend - fn get_data(&mut self) -> error::Result>; - - /// This method - fn put_data(&mut self, data: &[u8]) -> error::Result<()>; -} - -/// A backend using a file -pub struct FileBackend { - file: std::fs::File -} - -impl Backend for FileBackend { - fn get_data(&mut self) -> error::Result> { - use std::io::{Seek, SeekFrom}; - - let mut buffer = vec![]; - self.file.seek(SeekFrom::Start(0))?; - self.file.read_to_end(&mut buffer)?; - Ok(buffer) - } - - fn put_data(&mut self, data: &[u8]) -> error::Result<()> { - use std::io::{Seek, SeekFrom}; - - self.file.seek(SeekFrom::Start(0))?; - self.file.set_len(0)?; - self.file.write_all(data)?; - Ok(()) - } -} - -impl FileBackend { - /// Opens a new FileBackend for a given path - pub fn open>(path: P) -> error::Result { - use std::fs::OpenOptions; - - Ok(FileBackend { - file: OpenOptions::new().read(true).write(true).create(true).open(path)?, - }) - } - - /// Uses an already open File - pub fn from_file(file: std::fs::File) -> FileBackend { - FileBackend { - file: file - } - } - - /// Return the inner File - pub fn into_inner(self) -> std::fs::File { - self.file - } -} +use backend::{Backend, MemoryBackend, FileBackend}; /// The Central Database to RustBreak /// @@ -253,23 +192,7 @@ impl Database } } -/// An in memory backend -/// -/// It is backed by a `Vec` -pub struct MemoryBackend(Vec); - -impl Backend for MemoryBackend { - fn get_data(&mut self) -> error::Result> { - Ok(self.0.clone()) - } - - fn put_data(&mut self, data: &[u8]) -> error::Result<()> { - self.0 = data.to_owned(); - Ok(()) - } -} - -/// A database backed by a file +/// A database backed by a `Vec` pub type MemoryDatabase = Database; impl Database From 385f6d2cafe23fca62a924db8495bc086bfeda3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 10:41:31 +0200 Subject: [PATCH 22/40] Fix examples --- README.md | 24 +++++++++++++----------- examples/switching.rs | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 07476f0..440fc68 100644 --- a/README.md +++ b/README.md @@ -41,20 +41,22 @@ Usage is quite simple: - Don't forget to run `flush` periodically ```rust -use rustbreak::Database; +# use std::collections::HashMap; +use rustbreak::{MemoryDatabase, deser::Ron}; -fn main() { - let db = Database::memory(); +let db = MemoryDatabase::, Ron>::memory(HashMap::new(), Ron); - db.insert("Lapfox".into(), "lapfoxtrax.com") - db.insert("Rust".into(), "github.com/rust-lang/rust") +println!("Writing to Database"); +db.write(|db| { + db.insert("hello".into(), String::from("world")); + db.insert("foo".into(), String::from("bar")); +}); - // we need to be explicit about the kind of type we want as println! is - // generic - let rust : String = db.retrieve("Rust").unwrap(); - println!("You can find Rust at: {}", rust); - db.flush().unwrap(); -} +db.read(|db| { + // db.insert("foo".into(), String::from("bar")); + // The above line will not compile since we are only reading + println!("Hello: {:?}", db.get("hello")); +}); ``` ### Yaml diff --git a/examples/switching.rs b/examples/switching.rs index 762e63f..5f09894 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -1,7 +1,7 @@ extern crate rustbreak; #[macro_use] extern crate serde_derive; -use rustbreak::{FileDatabase, FileBackend}; +use rustbreak::{FileDatabase, backend::FileBackend}; use rustbreak::deser::{Ron, Yaml}; #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] From dfee584860ae9649dc87a959408cca79f2bb59ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 10:50:04 +0200 Subject: [PATCH 23/40] Fix travis test --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e275c0d..29dc446 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ before_script: export PATH=$HOME/.local/bin:$PATH script: - | - travis-cargo build -- --features yaml && - travis-cargo test -- --features yaml && - travis-cargo --only stable -- --features yaml,bin doc + travis-cargo build --features yaml && + travis-cargo test --features yaml && + travis-cargo --only stable doc --features yaml,bin after_success: - travis-cargo --only stable doc-upload env: From 6e87e5e1123f8c02cb7a0d5d1fd9b8d2937765d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 10:56:56 +0200 Subject: [PATCH 24/40] Fix the server example --- examples/server/server.ron | 22 ---------------------- examples/server/src/main.rs | 9 ++++----- 2 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 examples/server/server.ron diff --git a/examples/server/server.ron b/examples/server/server.ron deleted file mode 100644 index 0d6b061..0000000 --- a/examples/server/server.ron +++ /dev/null @@ -1,22 +0,0 @@ -( - pastes: [ - ( - user: "Hello", - body: "Wooow", - ), - ( - user: "Hello", - body: "asweasd", - ), - ( - user: "Hello", - body: "", - ), - ], - users: { - "Hello": ( - username: "Hello", - password: "Test", - ), - }, -) \ No newline at end of file diff --git a/examples/server/src/main.rs b/examples/server/src/main.rs index 44addd6..fb12ab1 100644 --- a/examples/server/src/main.rs +++ b/examples/server/src/main.rs @@ -7,18 +7,17 @@ extern crate rocket_contrib; #[macro_use] extern crate serde_derive; use std::collections::HashMap; -use std::fs::File; 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::Database; +use rustbreak::FileDatabase; use rustbreak::deser::Ron; // We create a type alias so that we always associate the same types to it -type DB = Database; +type DB = FileDatabase; #[derive(Debug, Serialize, Deserialize, Clone)] struct Paste { @@ -145,10 +144,10 @@ fn get_dummy() -> &'static str { } fn main() { - let db : DB = Database::from_path(ServerData { + let db : DB = FileDatabase::from_path(ServerData { pastes: vec![], users: HashMap::new(), - }, "server.ron").unwrap(); + }, Ron, "server.ron").unwrap(); let _ = db.reload(); From 093c7d8d7f7c8fc9d042cbce6b9ec822899a59ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 11:02:07 +0200 Subject: [PATCH 25/40] Remove the features from tests --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29dc446..7990c3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ before_script: export PATH=$HOME/.local/bin:$PATH script: - | - travis-cargo build --features yaml && - travis-cargo test --features yaml && - travis-cargo --only stable doc --features yaml,bin + travis-cargo build && + travis-cargo test && + travis-cargo --only stable doc after_success: - travis-cargo --only stable doc-upload env: From 1fda29e1186a1b5a82cc968da507d8541cbec96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 11:05:05 +0200 Subject: [PATCH 26/40] Add Yaml features --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7990c3e..02f63fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_script: script: - | travis-cargo build && - travis-cargo test && + travis-cargo test --features yaml && travis-cargo --only stable doc after_success: - travis-cargo --only stable doc-upload From 8074cded6ab50102d0e1a2cbd532ba4ca0ec9ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 11:13:00 +0200 Subject: [PATCH 27/40] Remove unecessary match --- src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 12feb06..3738537 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,10 +144,8 @@ impl Database let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut new_data = match self.deser.deserialize(&backend.get_data()?[..]) { - Ok(s) => s, - Err(e) => Err(e).context(error::RustbreakErrorKind::DeserializationError)? - }; + let mut new_data = self.deser.deserialize(&backend.get_data()?[..]) + .context(error::RustbreakErrorKind::DeserializationError)?; ::std::mem::swap(&mut *data, &mut new_data); Ok(()) @@ -160,10 +158,9 @@ impl Database let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; let data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let ser = match self.deser.serialize(&*data) { - Ok(s) => s, - Err(e) => Err(e).context(error::RustbreakErrorKind::SerializationError)? - }; + let ser = self.deser.serialize(&*data) + .context(error::RustbreakErrorKind::SerializationError)?; + backend.put_data(ser.as_bytes())?; Ok(()) } From de6430fabcecb7e8d5949f3b0e4f9397fcd0401e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 11:14:21 +0200 Subject: [PATCH 28/40] Don't use travis-cargo --- .travis.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 02f63fa..eb85fc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,11 @@ rust: - nightly - beta - stable -before_script: -- | - pip install 'travis-cargo<0.2' --user && - export PATH=$HOME/.local/bin:$PATH script: - | - travis-cargo build && - travis-cargo test --features yaml && - travis-cargo --only stable doc -after_success: -- travis-cargo --only stable doc-upload + cargo build && + cargo test --features yaml && + cargo doc --features yaml,bin env: global: - - TRAVIS_CARGO_NIGHTLY_FEATURE=" " - secure: RFX1Ke3uk8ZdrAGwKPP7FXqQfC6bkGMDzV5ut9s5da6ClvoafosJF4RBvXi5j8KhQP9Kyow7Z8IwtTExNHMIjlE0MguHsYdFBdkj9HdjrqBQHThx5vMOq3VY3bNq9ah83Q6idoTTpQvoZ380BSHznHz8E3rmKGbEA0Z1NigWlT8DFfgqI5nTa64/bvD8IzMTjkLax4pq1SjsG8v+UgwNxkuYE6Yl/mpQJ/xTCGYiFHLKk4EW8z2I5DcK/XqY+w42uFDP2BRvXdzuS937A0lGZtAiK3sKJCBDq/2Ulkz4+onkAo9QT0k9Eea9dSoxXDk3RJHzDb5wLANtubxAWyTKiyTlO3I81ogPmHevSv3xDXXwLBjPxwwIRI1qt/01Nod4yIT4fWfIxcW+rY/W5P4v5QbvAJ079v8be1lnwCEFP55WXfTFEypvKUmNpCTeY9nAKiLywpQuibi/TuHmVH+R9xWlJaXrJ8+fM+dkl57V7AnIKs3nnYmE/c5guUakJ46QVZZtSQRcFfD0QeZPM5WaqAjiZ52eP4y/Mi5Rjg4/noLjXoR3YWYxWZsT5b7U2hv/wEMTzERgHev3aPZttO7gr3ug/FDv0TP6PKPCLd/i+g0w4RKRGArcAXIBWHdeEhfOYuC8Q93gl3d/qutvgGdWF4sdBpaPRcwmaWD+FoEWjWQ= From b136c1900ea40b3df0aa2d32962244c2f5690f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 11:40:04 +0200 Subject: [PATCH 29/40] Update dependencies --- .travis.yml | 4 ++-- Cargo.toml | 10 ++++------ src/deser.rs | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb85fc5..fc24a8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ rust: - stable script: - | - cargo build && - cargo test --features yaml && + cargo build --features yaml,bin && + cargo test --features yaml,bin && cargo doc --features yaml,bin env: global: diff --git a/Cargo.toml b/Cargo.toml index 32ce623..d044c8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,17 +12,16 @@ version = "2.0.0" [dependencies] failure = "0.1.1" -lazy_static = "1.0.0" -ron = "0.1.2" +ron = "0.2.0" serde = "1" [dependencies.base64] optional = true -version = "0.7.0" +version = "0.9.0" [dependencies.bincode] optional = true -version = "0.8.0" +version = "1" [dependencies.serde_yaml] optional = true @@ -30,9 +29,8 @@ version = "0.7" [dev-dependencies] serde_derive = "1" -tempfile = "2.1" +lazy_static = "1" [features] bin = ["bincode", "base64"] yaml = ["serde_yaml"] -ron_enc = ["ron"] diff --git a/src/deser.rs b/src/deser.rs index 4160624..5247a56 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -83,7 +83,7 @@ mod bincode { impl DeSerializer for Bincode { fn serialize(&self, val: &T) -> error::Result { - let res = to_bincode_string(val, ::bincode::Infinite)?; + let res = to_bincode_string(val)?; Ok(encode(&res)) } fn deserialize(&self, mut s: R) -> error::Result { From e311ee9ac3e6a78f7163882a515f00befb00a037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 11:54:34 +0200 Subject: [PATCH 30/40] Add into_inner and from_parts for the database --- README.md | 22 +++++++--------------- src/lib.rs | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 440fc68..39061c4 100644 --- a/README.md +++ b/README.md @@ -26,19 +26,19 @@ Features - Simple To Use, Fast, Secure - Threadsafe -- Key/Value Storage -- bincode or yaml storage +- ron, bincode, or yaml storage Usage ----- Usage is quite simple: -- Create/open a database using `Database::open` - - You can specify the kind of Key you want using this Syntax: - `Database::::open` -- `Insert`/`Retrieve` data from the Database -- Don't forget to run `flush` periodically +- 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 `Database` with `Database::from_parts` +- `Write`/`Read` data from the Database +- Don't forget to run `sync` periodically ```rust # use std::collections::HashMap; @@ -88,14 +88,6 @@ default-features = false features = ["ron_enc"] ``` -How it works ------------- - -Internally the Database holds a Hashmap behind a RwLock. -This Hashmap is then written to/read from and safely casted to the requested -type. This works thanks to encoding/decoding traits. - - [doc]:http://neikos.me/rustbreak/rustbreak/index.html [Daybreak]:https://propublica.github.io/daybreak/ diff --git a/src/lib.rs b/src/lib.rs index 3738537..88d91f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,6 +164,22 @@ impl Database backend.put_data(ser.as_bytes())?; Ok(()) } + + /// Create a database from its constituents + pub fn from_parts(data: Data, backend: Back, deser: DeSer) -> Database { + Database { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser: deser, + } + } + + /// Break a database into its individual parts + pub fn into_inner(self) -> error::Result<(Data, Back, DeSer)> { + Ok((self.data.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?, + self.backend.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?, + self.deser)) + } } /// A database backed by a file From e0e8c542a3f6dc51cdeed805a8574f93924c38c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 12:02:31 +0200 Subject: [PATCH 31/40] Add unit tests --- Cargo.toml | 3 ++- src/backend.rs | 60 ++++++++++++++++++++++++++++++++++++-------------- src/lib.rs | 5 ++++- src/ron_enc.rs | 21 ------------------ 4 files changed, 50 insertions(+), 39 deletions(-) delete mode 100644 src/ron_enc.rs diff --git a/Cargo.toml b/Cargo.toml index d044c8c..7c03868 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,9 @@ optional = true version = "0.7" [dev-dependencies] -serde_derive = "1" lazy_static = "1" +serde_derive = "1" +tempfile = "3.0.0" [features] bin = ["bincode", "base64"] diff --git a/src/backend.rs b/src/backend.rs index 684d032..46d9ba1 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -17,26 +17,24 @@ pub trait Backend { } /// A backend using a file -pub struct FileBackend { - file: ::std::fs::File -} +pub struct FileBackend(::std::fs::File); impl Backend for FileBackend { fn get_data(&mut self) -> error::Result> { use ::std::io::{Seek, SeekFrom, Read}; let mut buffer = vec![]; - self.file.seek(SeekFrom::Start(0))?; - self.file.read_to_end(&mut buffer)?; + self.0.seek(SeekFrom::Start(0))?; + self.0.read_to_end(&mut buffer)?; Ok(buffer) } fn put_data(&mut self, data: &[u8]) -> error::Result<()> { use ::std::io::{Seek, SeekFrom, Write}; - self.file.seek(SeekFrom::Start(0))?; - self.file.set_len(0)?; - self.file.write_all(data)?; + self.0.seek(SeekFrom::Start(0))?; + self.0.set_len(0)?; + self.0.write_all(data)?; Ok(()) } } @@ -46,28 +44,33 @@ impl FileBackend { pub fn open>(path: P) -> error::Result { use ::std::fs::OpenOptions; - Ok(FileBackend { - file: OpenOptions::new().read(true).write(true).create(true).open(path)?, - }) + Ok(FileBackend( + OpenOptions::new().read(true).write(true).create(true).open(path)?, + )) } /// Uses an already open File pub fn from_file(file: ::std::fs::File) -> FileBackend { - FileBackend { - file: file - } + FileBackend(file) } /// Return the inner File pub fn into_inner(self) -> ::std::fs::File { - self.file + self.0 } } /// An in memory backend /// /// It is backed by a `Vec` -pub struct MemoryBackend(pub(crate) Vec); +pub struct MemoryBackend(Vec); + +impl MemoryBackend { + /// Construct a new Memory Database + pub fn new() -> MemoryBackend { + MemoryBackend(vec![]) + } +} impl Backend for MemoryBackend { fn get_data(&mut self) -> error::Result> { @@ -79,3 +82,28 @@ impl Backend for MemoryBackend { Ok(()) } } + +#[cfg(test)] +mod tests { + use tempfile; + use super::{Backend, MemoryBackend, FileBackend}; + + #[test] + fn test_memory_backend() { + let mut backend = MemoryBackend::new(); + let data = [4, 5, 1, 6, 8, 1]; + + backend.put_data(&data).unwrap(); + assert_eq!(backend.get_data().unwrap(), data); + } + + #[test] + fn test_file_backend() { + let file = tempfile::tempfile().unwrap(); + let mut backend = FileBackend::from_file(file); + let data = [4, 5, 1, 6, 8, 1]; + + backend.put_data(&data).unwrap(); + assert_eq!(backend.get_data().unwrap(), data); + } +} diff --git a/src/lib.rs b/src/lib.rs index 88d91f5..eefd75d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,9 @@ extern crate bincode; #[cfg(feature = "bin")] extern crate base64; +#[cfg(test)] +extern crate tempfile; + mod error; /// Different storage backends pub mod backend; @@ -217,7 +220,7 @@ impl Database pub fn memory(data: Data, deser: DeSer) -> MemoryDatabase { Database { data: RwLock::new(data), - backend: Mutex::new(MemoryBackend(vec![])), + backend: Mutex::new(MemoryBackend::new()), deser: deser, } } diff --git a/src/ron_enc.rs b/src/ron_enc.rs deleted file mode 100644 index d420fb0..0000000 --- a/src/ron_enc.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::Serialize; -use serde::de::DeserializeOwned; -use ron::{ser, de}; - -pub type Repr = String; -pub type SerializeError = ser::Error; -pub type DeserializeError = de::Error; - -pub fn serialize(value: &T) -> ser::Result - where T: Serialize -{ - ser::pretty::to_string(value) -} - -pub fn deserialize(bytes: &I) -> Result - where T: DeserializeOwned, - I: AsRef<[u8]> -{ - let string = try!(String::from_utf8(bytes.as_ref().to_vec())); - Ok(de::from_str(&string)?) -} From 586f10ced5656f0ee94a37f6f40d3cca6ce47ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 13:18:52 +0200 Subject: [PATCH 32/40] Add Documentation about Failure --- Cargo.toml | 6 +++--- src/lib.rs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c03868..9c9d075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Marcel Müller "] -description = "A single point database" -documentation = "http://neikos.me/rustbreak/rustbreak/index.html" +description = "A modular and configurable database" +documentation = "https://docs.rs/rustbreak" homepage = "https://github.com/TheNeikos/rustbreak" keywords = ["database", "simple", "fast", "daybreak", "rustbreak"] license = "MPL-2.0" @@ -30,7 +30,7 @@ version = "0.7" [dev-dependencies] lazy_static = "1" serde_derive = "1" -tempfile = "3.0.0" +tempfile = "3" [features] bin = ["bincode", "base64"] diff --git a/src/lib.rs b/src/lib.rs index eefd75d..2655352 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,9 @@ //! //! Using the `with_deser` and `with_backend` one can switch between the representations one needs. //! +//! Rustbreak uses the [failure][failure] crate. As such, to handle its errors you will need to +//! learn how to use it. +//! //! If you have any questions feel free to ask at the main [repo][repo]. //! //! ## Quickstart @@ -64,6 +67,7 @@ //! [daybreak]:https://propublica.github.io/daybreak //! [examples]: https://github.com/TheNeikos/rustbreak/tree/master/examples //! [ron]: https://github.com/ron-rs/ron +//! [failure]: https://boats.gitlab.io/failure/intro.html extern crate serde; @@ -93,7 +97,6 @@ use std::fmt::Debug; use serde::Serialize; use serde::de::DeserializeOwned; -// use error::{BreakResult, BreakError}; use deser::DeSerializer; use backend::{Backend, MemoryBackend, FileBackend}; From 1ac7eca877a910a2e039ff0c42ce8fe22feb5555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 14 Apr 2018 13:49:29 +0200 Subject: [PATCH 33/40] Add convert method --- src/lib.rs | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2655352..567459d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,12 +102,12 @@ use backend::{Backend, MemoryBackend, FileBackend}; /// The Central Database to RustBreak /// -/// It has 5 Type Generics: +/// It has 3 Type Generics: /// -/// - V: Is the Data, you must specify this (usually inferred by the compiler) -/// - S: The Serializer/Deserializer or short DeSer. Per default `deser::Ron` is used. Check the -/// `deser` module for other strategies. -/// - F: The storage backend. Per default it is in memory, but can be easily used with a `File`. +/// - Data: Is the Data, you must specify this +/// - Back: The storage backend. +/// - DeSer: The Serializer/Deserializer or short DeSer. Check the `deser` module for other +/// strategies. #[derive(Debug)] pub struct Database where @@ -264,3 +264,28 @@ impl Database } } } + +impl Database + where + Data: Serialize + DeserializeOwned + Debug + Clone + Send, + Back: Backend, + DeSer: DeSerializer + Send + Sync +{ + /// Converts from one data type to another + /// + /// This method is useful to migrate from one datatype to another + pub fn convert_data(self, convert: C) + -> error::Result> + where + OutputData: Serialize + DeserializeOwned + Debug + Clone + Send, + C: FnOnce(Data) -> OutputData, + DeSer: DeSerializer, + { + let (data, backend, deser) = self.into_inner()?; + Ok(Database { + data: RwLock::new(convert(data)), + backend: Mutex::new(backend), + deser: deser, + }) + } +} From 90baa2aaa50a2bb48c1ba3c063c578afbf9d5847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 23 Apr 2018 13:23:34 +0200 Subject: [PATCH 34/40] Add and document some more changes --- examples/config.rs | 2 +- examples/full.rs | 3 +- examples/migration.rs.incomplete | 183 +++++++++++++++++++++++++++++++ examples/switching.rs | 3 +- src/backend.rs | 2 + src/deser.rs | 8 +- src/lib.rs | 178 ++++++++++++++++++++++++------ 7 files changed, 336 insertions(+), 43 deletions(-) create mode 100644 examples/migration.rs.incomplete diff --git a/examples/config.rs b/examples/config.rs index 395cf5b..0f57b0c 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -22,7 +22,7 @@ type DB = FileDatabase; lazy_static! { static ref CONFIG: DB = { - let db = FileDatabase::from_path(Config::default(), Yaml, "/tmp/config.yml") + let db = FileDatabase::from_path("/tmp/config.yml", Config::default()) .expect("Create database from path"); db.reload().expect("Config to load"); db diff --git a/examples/full.rs b/examples/full.rs index b3c5e73..0a2bc57 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -18,8 +18,7 @@ struct Person { fn main() { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path(HashMap::new(), - Ron, "test.ron").unwrap(); + let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new()).unwrap(); println!("Writing to Database"); db.write(|db| { diff --git a/examples/migration.rs.incomplete b/examples/migration.rs.incomplete new file mode 100644 index 0000000..1d73b9f --- /dev/null +++ b/examples/migration.rs.incomplete @@ -0,0 +1,183 @@ +/* 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.reload().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/switching.rs b/examples/switching.rs index 5f09894..db9d147 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -18,8 +18,7 @@ struct Person { fn main() { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path(HashMap::new(), - Ron, "test.ron").unwrap(); + let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new()).unwrap(); println!("Writing to Database"); db.write(|db| { diff --git a/src/backend.rs b/src/backend.rs index 46d9ba1..4ae4ac2 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -17,6 +17,7 @@ pub trait Backend { } /// A backend using a file +#[derive(Debug)] pub struct FileBackend(::std::fs::File); impl Backend for FileBackend { @@ -63,6 +64,7 @@ impl FileBackend { /// An in memory backend /// /// It is backed by a `Vec` +#[derive(Debug)] pub struct MemoryBackend(Vec); impl MemoryBackend { diff --git a/src/deser.rs b/src/deser.rs index 5247a56..294199a 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -20,7 +20,7 @@ pub use self::yaml::Yaml; pub use self::bincode::Bincode; /// A trait to bundle serializer and deserializer -pub trait DeSerializer : ::std::fmt::Debug + Send + Sync { +pub trait DeSerializer : ::std::default::Default + Send + Sync + Clone { /// Serializes a given value to a String fn serialize(&self, val: &T) -> error::Result; /// Deserializes a String to a value @@ -28,7 +28,7 @@ pub trait DeSerializer : ::std::fmt::Debug + Se } /// The Struct that allows you to use `ron` the Rusty Object Notation -#[derive(Debug)] +#[derive(Debug, Default, Clone)] pub struct Ron; impl DeSerializer for Ron { @@ -52,7 +52,7 @@ mod yaml { use deser::DeSerializer; /// The struct that allows you to use yaml - #[derive(Debug)] + #[derive(Debug, Default, Clone)] pub struct Yaml; impl DeSerializer for Yaml { @@ -78,7 +78,7 @@ mod bincode { use deser::DeSerializer; /// The struct that allows you to use bincode - #[derive(Debug)] + #[derive(Debug, Default, Clone)] pub struct Bincode; impl DeSerializer for Bincode { diff --git a/src/lib.rs b/src/lib.rs index 567459d..443800c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! # Rustbreak //! -//! Rustbreak is a [Daybreak][daybreak] inspiried single file Database. +//! Rustbreak is a [Daybreak][daybreak] inspired single file Database. //! //! You will find an overview here in the docs, but to give you a more complete tale of how this is //! used please check the [examples][examples]. @@ -46,10 +46,14 @@ //! ## Quickstart //! //! ```rust +//! # extern crate failure; +//! # extern crate rustbreak; //! # use std::collections::HashMap; //! use rustbreak::{MemoryDatabase, deser::Ron}; //! -//! let db = MemoryDatabase::, Ron>::memory(HashMap::new(), Ron); +//! # fn main() { +//! # let func = || -> Result<(), failure::Error> { +//! let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; //! //! println!("Writing to Database"); //! db.write(|db| { @@ -62,6 +66,9 @@ //! // The above line will not compile since we are only reading //! println!("Hello: {:?}", db.get("hello")); //! }); +//! # return Ok(()); }; +//! # func().unwrap(); +//! # } //! ``` //! //! [daybreak]:https://propublica.github.io/daybreak @@ -112,8 +119,8 @@ use backend::{Backend, MemoryBackend, FileBackend}; pub struct Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { data: RwLock, backend: Mutex, @@ -123,8 +130,8 @@ pub struct Database impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Write lock the database and get write access to the `Data` container pub fn write(&self, task: T) -> error::Result<()> @@ -143,17 +150,21 @@ impl Database Ok(task(&mut lock)) } - /// Reload the Data from the backend - pub fn reload(&self) -> error::Result<()> { + fn load(backend: &mut Back, deser: &DeSer) -> error::Result { use failure::ResultExt; - - let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - - let mut new_data = self.deser.deserialize(&backend.get_data()?[..]) + let new_data = deser.deserialize(&backend.get_data()?[..]) .context(error::RustbreakErrorKind::DeserializationError)?; - ::std::mem::swap(&mut *data, &mut new_data); + Ok(new_data) + } + + /// Reload the Data from the backend + pub fn reload(&self) -> error::Result<()> { + + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + + *data = Self::load(&mut backend, &self.deser)?; Ok(()) } @@ -171,6 +182,39 @@ impl Database Ok(()) } + /// 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 `reload` true + pub fn get_data(&self, reload: bool) -> error::Result { + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + if reload { + *data = Self::load(&mut backend, &self.deser)?; + drop(backend); + } + Ok(data.clone()) + } + + /// Puts the data as is into memory + /// + /// To sync the data afterwards, call with `sync` true. + pub fn put_data(&self, new_data: Data, sync: bool) -> error::Result<()> { + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + if sync { + // TODO: Spin this into its own method + use failure::ResultExt; + + let ser = self.deser.serialize(&*data) + .context(error::RustbreakErrorKind::SerializationError)?; + + backend.put_data(ser.as_bytes())?; + drop(backend); + } + *data = new_data; + Ok(()) + } + /// Create a database from its constituents pub fn from_parts(data: Data, backend: Back, deser: DeSer) -> Database { Database { @@ -186,6 +230,58 @@ impl Database self.backend.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?, self.deser)) } + + /// Tries to clone the Data in the Database. + /// + /// This method returns a `MemoryDatabase` which has an empty vector as a + /// backend initially. This means that the user is responsible for assigning a new backend + /// if an alternative is wanted. + /// + /// # Examples + /// + /// ```rust + /// # #[macro_use] extern crate serde_derive; + /// # extern crate rustbreak; + /// # extern crate serde; + /// # extern crate tempfile; + /// # extern crate failure; + /// + /// use rustbreak::{FileDatabase, deser::Ron}; + /// + /// #[derive(Debug, Serialize, Deserialize, Clone)] + /// struct Data { + /// level: u32, + /// } + /// + /// # fn main() { + /// # let func = || -> Result<(), failure::Error> { + /// # let file = tempfile::tempfile()?; + /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; + /// + /// db.write(|db| { + /// db.level = 42; + /// })?; + /// + /// db.sync()?; + /// + /// let other_db = db.try_clone()?; + /// + /// let value = other_db.read(|db| db.level)?; + /// assert_eq!(42, value); + /// # return Ok(()); + /// # }; + /// # func().unwrap(); + /// # } + /// ``` + pub fn try_clone(&self) -> error::Result> { + let lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + + Ok(Database { + data: RwLock::new(lock.clone()), + backend: Mutex::new(MemoryBackend::new()), + deser: self.deser.clone(), + }) + } } /// A database backed by a file @@ -194,19 +290,31 @@ pub type FileDatabase = Database; impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - DeSer: DeSerializer + Send + Sync + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Create new FileDatabase from Path - pub fn from_path(data: Data, deser: DeSer, path: S) + pub fn from_path(path: S, data: Data) -> error::Result> where S: AsRef { - let b = FileBackend::open(path)?; + let backend = FileBackend::open(path)?; Ok(Database { data: RwLock::new(data), - backend: Mutex::new(b), - deser: deser, + backend: Mutex::new(backend), + deser: DeSer::default(), + }) + } + + /// Create new FileDatabase from a file + pub fn from_file(file: ::std::fs::File, data: Data) -> error::Result> + { + let backend = FileBackend::from_file(file); + + Ok(Database { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser: DeSer::default(), }) } } @@ -217,27 +325,29 @@ pub type MemoryDatabase = Database; impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - DeSer: DeSerializer + Send + Sync + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Create new FileDatabase from Path - pub fn memory(data: Data, deser: DeSer) -> MemoryDatabase { - Database { + pub fn memory(data: Data) -> error::Result> { + let backend = MemoryBackend::new(); + + Ok(Database { data: RwLock::new(data), - backend: Mutex::new(MemoryBackend::new()), - deser: deser, - } + backend: Mutex::new(backend), + deser: DeSer::default(), + }) } } impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Exchanges the DeSerialization strategy with the given one pub fn with_deser(self, deser: T) -> Database - where T: DeSerializer + Send + Sync + where T: DeSerializer + Debug + Send + Sync { Database { backend: self.backend, @@ -250,12 +360,12 @@ impl Database impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Exchanges the Backend with the given one pub fn with_backend(self, backend: T) -> Database - where T: Backend + where T: Backend + Debug { Database { backend: Mutex::new(backend), @@ -268,8 +378,8 @@ impl Database impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Converts from one data type to another /// @@ -279,7 +389,7 @@ impl Database where OutputData: Serialize + DeserializeOwned + Debug + Clone + Send, C: FnOnce(Data) -> OutputData, - DeSer: DeSerializer, + DeSer: DeSerializer + Debug + Send + Sync, { let (data, backend, deser) = self.into_inner()?; Ok(Database { From 59bd14f5a1e90cf577ce756f83bb35934b3177c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 26 Apr 2018 10:18:09 +0200 Subject: [PATCH 35/40] Add more documentation --- Cargo.toml | 11 ++-- examples/config.rs | 10 ++-- examples/server/server.ron | 22 ++++++++ examples/server/src/main.rs | 4 +- src/deser.rs | 50 +++++++++++------- src/error.rs | 17 +++++- src/lib.rs | 100 +++++++++++++++++++++++++++++++++--- 7 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 examples/server/server.ron diff --git a/Cargo.toml b/Cargo.toml index 9c9d075..f5a886f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,12 @@ version = "2.0.0" [dependencies] failure = "0.1.1" -ron = "0.2.0" serde = "1" +[dependencies.ron] +optional = true +version = "0.2.0" + [dependencies.base64] optional = true version = "0.9.0" @@ -33,5 +36,7 @@ serde_derive = "1" tempfile = "3" [features] -bin = ["bincode", "base64"] -yaml = ["serde_yaml"] +default = ["ron_enc"] +ron_enc = ["ron"] +bin_enc = ["bincode", "base64"] +yaml_enc = ["serde_yaml"] diff --git a/examples/config.rs b/examples/config.rs index 0f57b0c..f715824 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -1,7 +1,7 @@ -// This just reads an example configuration. Don't forget to run with `--feature yaml` +// 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.yml`: +// You can create one by writing this file to `/tmp/config.ron`: // ``` // --- // user_path: /tmp/nope @@ -16,13 +16,13 @@ extern crate rustbreak; use std::path::PathBuf; use std::default::Default; use rustbreak::FileDatabase; -use rustbreak::deser::Yaml; +use rustbreak::deser::Ron; -type DB = FileDatabase; +type DB = FileDatabase; lazy_static! { static ref CONFIG: DB = { - let db = FileDatabase::from_path("/tmp/config.yml", Config::default()) + let db = FileDatabase::from_path("/tmp/config.ron", Config::default()) .expect("Create database from path"); db.reload().expect("Config to load"); db diff --git a/examples/server/server.ron b/examples/server/server.ron new file mode 100644 index 0000000..f1925c0 --- /dev/null +++ b/examples/server/server.ron @@ -0,0 +1,22 @@ +( + pastes: [ + ( + user: "neikos@neikos.email", + body: "Hello :)", + ), + ( + user: "neikos@neikos.email", + body: "This works great!", + ), + ( + user: "neikos@neikos.email", + body: "Hehe", + ), + ], + 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 index fb12ab1..e4d32d0 100644 --- a/examples/server/src/main.rs +++ b/examples/server/src/main.rs @@ -144,10 +144,10 @@ fn get_dummy() -> &'static str { } fn main() { - let db : DB = FileDatabase::from_path(ServerData { + let db : DB = FileDatabase::from_path("server.ron", ServerData { pastes: vec![], users: HashMap::new(), - }, Ron, "server.ron").unwrap(); + }).unwrap(); let _ = db.reload(); diff --git a/src/deser.rs b/src/deser.rs index 294199a..8b1d0de 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -1,22 +1,20 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - use std::io::Read; use serde::Serialize; use serde::de::DeserializeOwned; -use ron::ser::to_string_pretty as to_ron_string; -use ron::ser::PrettyConfig; -use ron::de::from_reader as from_ron_string; - use error; -#[cfg(feature = "yaml")] +#[cfg(feature = "ron_enc")] +pub use self::ron::Ron; + +#[cfg(feature = "yaml_enc")] pub use self::yaml::Yaml; -#[cfg(feature = "bin")] +#[cfg(feature = "bin_enc")] pub use self::bincode::Bincode; /// A trait to bundle serializer and deserializer @@ -27,20 +25,36 @@ pub trait DeSerializer : ::std::default::Defaul fn deserialize(&self, s: R) -> error::Result; } -/// The Struct that allows you to use `ron` the Rusty Object Notation -#[derive(Debug, Default, Clone)] -pub struct Ron; -impl DeSerializer for Ron { - fn serialize(&self, val: &T) -> error::Result { - Ok(to_ron_string(val, PrettyConfig::default())?) - } - fn deserialize(&self, s: R) -> error::Result { - Ok(from_ron_string(s)?) +#[cfg(feature = "ron_enc")] +mod ron { + use std::io::Read; + + use serde::Serialize; + use serde::de::DeserializeOwned; + + use ron::ser::to_string_pretty as to_ron_string; + use ron::ser::PrettyConfig; + use ron::de::from_reader as from_ron_string; + + use error; + use deser::DeSerializer; + + /// The Struct that allows you to use `ron` the Rusty Object Notation + #[derive(Debug, Default, Clone)] + pub struct Ron; + + impl DeSerializer for Ron { + fn serialize(&self, val: &T) -> error::Result { + Ok(to_ron_string(val, PrettyConfig::default())?) + } + fn deserialize(&self, s: R) -> error::Result { + Ok(from_ron_string(s)?) + } } } -#[cfg(feature = "yaml")] +#[cfg(feature = "yaml_enc")] mod yaml { use std::io::Read; @@ -65,7 +79,7 @@ mod yaml { } } -#[cfg(feature = "bin")] +#[cfg(feature = "bin_enc")] mod bincode { use std::io::Read; diff --git a/src/error.rs b/src/error.rs index 9b3e650..a57d99a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,17 +4,31 @@ use failure::{self, Context}; +/// The different kinds of errors that can be returned #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] pub enum RustbreakErrorKind { + /// A context error when a serialization failed #[fail(display = "Could not serialize the value")] SerializationError, + /// A context error when a deserialization failed #[fail(display = "Could not deserialize the value")] DeserializationError, + /// This error is returned if the `Database` is poisoned. See `Database::write` for details #[fail(display = "The database has been poisoned")] - PoisonError + PoisonError, + /// If `Database::write_safe` is used and the closure panics, this error is returned + #[fail(display = "The write operation paniced but got caught")] + WritePanicError, + /// This variant should never be used. It is meant to keep this enum forward compatible. + #[doc(hidden)] + #[fail(display = "You have found a secret message, please report it to the Rustbreak maintainer")] + __Nonexhaustive, } + +/// The main error type that gets returned for errors that happen while interacting with a +/// `Database`. #[derive(Debug)] pub struct RustbreakError { inner: Context, @@ -32,4 +46,5 @@ impl From> for RustbreakError { } } +/// A simple type alias for errors pub type Result = ::std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index 443800c..92e04dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,28 +71,66 @@ //! # } //! ``` //! +//! ## Panics +//! +//! This Database implementation uses `RwLock` and `Mutex` under the hood. If either the closures +//! given to `Database::write` or any of the Backend implementation methods panic the respective +//! objects are then poisoned. This means that you *cannot panic* under any circumstances in your +//! closures or custom backends. +//! +//! Currently there is no way to recover from a poisoned `Database` other than re-creating it. +//! +//! ## Examples +//! +//! There are several more or less in-depth example programs you can check out! +//! Check them out here: https://github.com/TheNeikos/rustbreak/tree/master/examples +//! +//! - `config.rs` shows you how a possible configuration file could be managed with rustbreak +//! - `full.rs` shows you how the database can be used as a hashmap store +//! - `switching.rs` show you how you can easily swap out different parts of the Database +//! *Note*: To run this example you need to enable the feature `yaml` like so: +//! `cargo run --example switching --features yaml` +//! - `server/` is a fully fledged example app written with the Rocket framework to make a form of +//! micro-blogging website. You will need rust nightly to start it. +//! +//! ## Features +//! +//! Rustbreak comes with three optional features: +//! +//! - `ron_enc` which enables the [Ron][ron] de/serialization +//! - `yaml_enc` which enables the Yaml de/serialization +//! - `bin_enc` which enables the Bincode de/serialization +//! +//! [Enable them in your `Cargo.toml` file to use them.][features] You can safely have them all +//! turned on per-default. The default feature is `ron` +//! +//! //! [daybreak]:https://propublica.github.io/daybreak //! [examples]: https://github.com/TheNeikos/rustbreak/tree/master/examples //! [ron]: https://github.com/ron-rs/ron //! [failure]: https://boats.gitlab.io/failure/intro.html +//! [features]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features extern crate serde; -extern crate ron; #[macro_use] extern crate failure; -#[cfg(feature = "yaml")] +#[cfg(feature = "ron_enc")] +extern crate ron; + +#[cfg(feature = "yaml_enc")] extern crate serde_yaml; -#[cfg(feature = "bin")] +#[cfg(feature = "bin_enc")] extern crate bincode; -#[cfg(feature = "bin")] +#[cfg(feature = "bin_enc")] extern crate base64; #[cfg(test)] extern crate tempfile; -mod error; +/// The rustbreak errors that can be returned +pub mod error; /// Different storage backends pub mod backend; /// Different serialization and deserialization methods one can use @@ -114,7 +152,7 @@ use backend::{Backend, MemoryBackend, FileBackend}; /// - Data: Is the Data, you must specify this /// - Back: The storage backend. /// - DeSer: The Serializer/Deserializer or short DeSer. Check the `deser` module for other -/// strategies. +/// strategies. #[derive(Debug)] pub struct Database where @@ -134,6 +172,18 @@ impl Database DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Write lock the database and get write access to the `Data` container + /// + /// This gives you an exclusive lock on the memory object. Trying to open the database in + /// writing will block if it is currently being written to. + /// + /// # Panics + /// + /// If you panic in the closure, the database is poisoned. This means that any + /// subsequent writes/reads will fail with an `std::sync::PoisonError`. + /// You can only recover from this by re-creating the Database Object. + /// + /// If you do not have full control over the code being written, and cannot incur the cost of + /// having a single operation panicking then use `Database::write_safe`. pub fn write(&self, task: T) -> error::Result<()> where T: FnOnce(&mut Data) { @@ -142,7 +192,45 @@ impl Database Ok(()) } + /// Write lock the database and get write access to the `Data` container in a safe way + /// + /// This gives you an exclusive lock on the memory object. Trying to open the database in + /// writing will block if it is currently being written to. + /// + /// This differs to `Database::write` in that a clone of the internal data is made, which is + /// then passed to the closure. Only if the closure doesn't panic is the internal model + /// updated. + /// + /// Depending on the size of the database this can be very costly. This is a tradeoff to make + /// for panic safety. + /// + /// # Panics + /// + /// When the closure panics, it is caught and a `error::RustbreakErrorkind::WritePanic` will be + /// returned. + pub fn write_safe(&self, task: T) -> error::Result<()> + where T: FnOnce(&mut Data) + std::panic::UnwindSafe, + for<'r> &'r mut Data: std::panic::UnwindSafe + { + let mut lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut data : Data = lock.clone(); + ::std::panic::catch_unwind(|| { + task(&mut data); + }).map_err(|_| error::RustbreakErrorKind::WritePanicError)?; + *lock = data; + Ok(()) + } + /// Read lock the database and get write access to the `Data` container + /// + /// This gives you a read-only lock on the database. You can have as many readers in parallel + /// as you wish. + /// + /// # Panics + /// + /// If you panic in the closure, the database is poisoned. This means that any + /// subsequent writes/reads will fail with an `std::sync::PoisonError`. + /// You can only recover from this by re-creating the Database Object. pub fn read(&self, task: T) -> error::Result where T: FnOnce(&Data) -> R { From 3168f8dcf5a9f8361016b9ab6af6608bdb52c7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 4 May 2018 21:09:04 +0200 Subject: [PATCH 36/40] Finalize for realease of v2 --- examples/full.rs | 21 ++-- examples/switching.rs | 20 +++- src/backend.rs | 33 ++++-- src/deser.rs | 35 +++--- src/error.rs | 39 +++++- src/lib.rs | 270 +++++++++++++++++++++++++++++++----------- 6 files changed, 302 insertions(+), 116 deletions(-) diff --git a/examples/full.rs b/examples/full.rs index 0a2bc57..7404e44 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -1,5 +1,6 @@ extern crate rustbreak; #[macro_use] extern crate serde_derive; +extern crate failure; use rustbreak::FileDatabase; use rustbreak::deser::Ron; @@ -15,10 +16,10 @@ struct Person { country: Country, } -fn main() { +fn do_main() -> Result<(), failure::Error> { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new()).unwrap(); + let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new())?; println!("Writing to Database"); db.write(|db| { @@ -31,19 +32,25 @@ fn main() { country: Country::UnitedKingdom }); println!("Entries: \n{:#?}", db); - }).unwrap(); + })?; println!("Syncing Database"); - db.sync().unwrap(); + db.sync()?; println!("Reloading Database"); - db.reload().unwrap(); + db.reload()?; println!("Reading from Database"); db.read(|db| { println!("Results:"); println!("{:#?}", db); - }).unwrap(); - + })?; + Ok(()) +} + +fn main() { + if let Err(e) = do_main() { + println!("An error has occurred at: \n{}", e.backtrace()); + } } diff --git a/examples/switching.rs b/examples/switching.rs index db9d147..08da877 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -1,5 +1,6 @@ extern crate rustbreak; #[macro_use] extern crate serde_derive; +extern crate failure; use rustbreak::{FileDatabase, backend::FileBackend}; use rustbreak::deser::{Ron, Yaml}; @@ -15,10 +16,10 @@ struct Person { country: Country, } -fn main() { +fn do_main() -> Result<(), failure::Error> { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new()).unwrap(); + let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new())?; println!("Writing to Database"); db.write(|db| { @@ -31,15 +32,22 @@ fn main() { country: Country::UnitedKingdom }); println!("Entries: \n{:#?}", db); - }).unwrap(); + })?; println!("Syncing Database"); - db.sync().unwrap(); + db.sync()?; // Now lets switch it - let db = db.with_deser(Yaml).with_backend(FileBackend::open("test.yml").unwrap()); - db.sync().unwrap(); + let db = db.with_deser(Yaml).with_backend(FileBackend::open("test.yml")?); + db.sync()?; + Ok(()) +} + +fn main() { + if let Err(e) = do_main() { + println!("An error has occurred at: \n{}", e.backtrace()); + } } diff --git a/src/backend.rs b/src/backend.rs index 4ae4ac2..1212c08 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -2,17 +2,26 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +//! The persistence backends of the Database +//! +//! A file is a `Backend` through the `FileBackend`, so is a `Vec` with a `MemoryBackend`. +//! +//! Implementing your own Backend should be straightforward. Check the `Backend` documentation for +//! details. + +use failure::ResultExt; + use error; /// The Backend Trait /// -/// This trait describes a simple backend, allowing users to swap it, or -/// to implement one themselves +/// It should always read and save in full the data that it is passed. This means that a write to +/// the backend followed by a read __must__ return the same dataset. pub trait Backend { - /// This method gets the data from the backend + /// Read the all data from the backend fn get_data(&mut self) -> error::Result>; - /// This method + /// Write the whole slice to the backend fn put_data(&mut self, data: &[u8]) -> error::Result<()>; } @@ -25,32 +34,32 @@ impl Backend for FileBackend { use ::std::io::{Seek, SeekFrom, Read}; let mut buffer = vec![]; - self.0.seek(SeekFrom::Start(0))?; - self.0.read_to_end(&mut buffer)?; + self.0.seek(SeekFrom::Start(0)).context(error::RustbreakErrorKind::Backend)?; + self.0.read_to_end(&mut buffer).context(error::RustbreakErrorKind::Backend)?; Ok(buffer) } fn put_data(&mut self, data: &[u8]) -> error::Result<()> { use ::std::io::{Seek, SeekFrom, Write}; - self.0.seek(SeekFrom::Start(0))?; - self.0.set_len(0)?; - self.0.write_all(data)?; + self.0.seek(SeekFrom::Start(0)).context(error::RustbreakErrorKind::Backend)?; + self.0.set_len(0).context(error::RustbreakErrorKind::Backend)?; + self.0.write_all(data).context(error::RustbreakErrorKind::Backend)?; Ok(()) } } impl FileBackend { - /// Opens a new FileBackend for a given path + /// Opens a new FileBackend for a given path, will create it if the file doesn't exist. pub fn open>(path: P) -> error::Result { use ::std::fs::OpenOptions; Ok(FileBackend( - OpenOptions::new().read(true).write(true).create(true).open(path)?, + OpenOptions::new().read(true).write(true).create(true).open(path).context(error::RustbreakErrorKind::Backend)?, )) } - /// Uses an already open File + /// Use an already open File as the backend pub fn from_file(file: ::std::fs::File) -> FileBackend { FileBackend(file) } diff --git a/src/deser.rs b/src/deser.rs index 8b1d0de..78b27f5 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -20,7 +20,7 @@ pub use self::bincode::Bincode; /// A trait to bundle serializer and deserializer pub trait DeSerializer : ::std::default::Default + Send + Sync + Clone { /// Serializes a given value to a String - fn serialize(&self, val: &T) -> error::Result; + fn serialize(&self, val: &T) -> error::Result>; /// Deserializes a String to a value fn deserialize(&self, s: R) -> error::Result; } @@ -32,6 +32,7 @@ mod ron { use serde::Serialize; use serde::de::DeserializeOwned; + use failure::ResultExt; use ron::ser::to_string_pretty as to_ron_string; use ron::ser::PrettyConfig; @@ -45,11 +46,12 @@ mod ron { pub struct Ron; impl DeSerializer for Ron { - fn serialize(&self, val: &T) -> error::Result { - Ok(to_ron_string(val, PrettyConfig::default())?) + fn serialize(&self, val: &T) -> error::Result> { + Ok(to_ron_string(val, PrettyConfig::default()).map(String::into_bytes) + .context(error::RustbreakErrorKind::Serialization)?) } fn deserialize(&self, s: R) -> error::Result { - Ok(from_ron_string(s)?) + Ok(from_ron_string(s).context(error::RustbreakErrorKind::Deserialization)?) } } } @@ -61,6 +63,7 @@ mod yaml { use serde_yaml::{to_string as to_yaml_string, from_reader as from_yaml_string}; use serde::Serialize; use serde::de::DeserializeOwned; + use failure::ResultExt; use error; use deser::DeSerializer; @@ -70,11 +73,13 @@ mod yaml { pub struct Yaml; impl DeSerializer for Yaml { - fn serialize(&self, val: &T) -> error::Result { - Ok(to_yaml_string(val)?) + fn serialize(&self, val: &T) -> error::Result> { + Ok(to_yaml_string(val) + .map(String::into_bytes) + .context(error::RustbreakErrorKind::Serialization)?) } fn deserialize(&self, s: R) -> error::Result { - Ok(from_yaml_string(s)?) + Ok(from_yaml_string(s).context(error::RustbreakErrorKind::Deserialization)?) } } } @@ -83,10 +88,10 @@ mod yaml { mod bincode { use std::io::Read; - use bincode::{serialize as to_bincode_string, deserialize as from_bincode_string}; - use base64::{encode, decode}; + use bincode::{deserialize_from, serialize}; use serde::Serialize; use serde::de::DeserializeOwned; + use failure::ResultExt; use error; use deser::DeSerializer; @@ -96,14 +101,12 @@ mod bincode { pub struct Bincode; impl DeSerializer for Bincode { - fn serialize(&self, val: &T) -> error::Result { - let res = to_bincode_string(val)?; - Ok(encode(&res)) + fn serialize(&self, val: &T) -> error::Result> { + let res = serialize(val).context(error::RustbreakErrorKind::Serialization)?; + Ok(res) } - fn deserialize(&self, mut s: R) -> error::Result { - let mut string = String::new(); - s.read_to_string(&mut string)?; - Ok(from_bincode_string(String::from_utf8(decode(&string)?)?.as_bytes())?) + fn deserialize(&self, s: R) -> error::Result { + Ok(deserialize_from(s).context(error::RustbreakErrorKind::Deserialization)?) } } } diff --git a/src/error.rs b/src/error.rs index a57d99a..320f810 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,23 +2,27 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use failure::{self, Context}; +use std::fmt::{self, Display}; +use failure::{Context, Fail, Backtrace}; /// The different kinds of errors that can be returned #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] pub enum RustbreakErrorKind { /// A context error when a serialization failed #[fail(display = "Could not serialize the value")] - SerializationError, + Serialization, /// A context error when a deserialization failed #[fail(display = "Could not deserialize the value")] - DeserializationError, + Deserialization, /// This error is returned if the `Database` is poisoned. See `Database::write` for details #[fail(display = "The database has been poisoned")] - PoisonError, + Poison, + /// An error in the backend happened + #[fail(display = "The backend has encountered an error")] + Backend, /// If `Database::write_safe` is used and the closure panics, this error is returned #[fail(display = "The write operation paniced but got caught")] - WritePanicError, + WritePanic, /// This variant should never be used. It is meant to keep this enum forward compatible. #[doc(hidden)] #[fail(display = "You have found a secret message, please report it to the Rustbreak maintainer")] @@ -34,6 +38,29 @@ pub struct RustbreakError { inner: Context, } +impl Fail for RustbreakError { + fn cause(&self) -> Option<&Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl Display for RustbreakError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl RustbreakError { + /// Get the kind of this error + pub fn kind(&self) -> RustbreakErrorKind { + *self.inner.get_context() + } +} + impl From for RustbreakError { fn from(kind: RustbreakErrorKind) -> RustbreakError { RustbreakError { inner: Context::new(kind) } @@ -47,4 +74,4 @@ impl From> for RustbreakError { } /// A simple type alias for errors -pub type Result = ::std::result::Result; +pub type Result = ::std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index 92e04dd..9db44da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,30 +21,48 @@ //! # Rustbreak //! -//! Rustbreak is a [Daybreak][daybreak] inspired single file Database. +//! Rustbreak was a [Daybreak][daybreak] inspired single file Database. +//! It has now since evolved into something else. Please check v1 for a more similar version. //! //! You will find an overview here in the docs, but to give you a more complete tale of how this is //! used please check the [examples][examples]. //! -//! At its core, Rustbreak is an attempt at making a configurable key-value store Database. +//! At its core, Rustbreak is an attempt at making a configurable general-purpose store Database. //! It features the possibility of: //! //! - Choosing what kind of Data is stored in it //! - Which kind of Serialization is used for persistence //! - Which kind of persistence is used //! +//! This means you can take any struct you can serialize and deserialize and stick it into this +//! Database. It is then encoded with Ron, Yaml, JSON, Bincode, anything really that uses Serde +//! operations! +//! //! There are two helper type aliases `MemoryDatabase` and `FileDatabase`, each backed by their //! respective backend. //! -//! Using the `with_deser` and `with_backend` one can switch between the representations one needs. +//! The `MemoryBackend` saves its data into a `Vec`, which is not that useful on its own, but +//! is needed for compatibility with the rest of the Library. //! -//! Rustbreak uses the [failure][failure] crate. As such, to handle its errors you will need to -//! learn how to use it. +//! The `FileDatabase` is a classical file based database. You give it a path or a file, and it +//! will use it as its storage. You still get to pick what encoding it uses. +//! +//! Using the `with_deser` and `with_backend` one can switch between the representations one needs. +//! Even at runtime! However this is only useful in a few scenarios. //! //! If you have any questions feel free to ask at the main [repo][repo]. //! //! ## Quickstart //! +//! Add this to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies.rustbreak] +//! version = "2" +//! features = ["ron_enc"] # You can also use "yaml_enc" or "bin_enc" +//! # Check the documentation to add your own! +//! ``` +//! //! ```rust //! # extern crate failure; //! # extern crate rustbreak; @@ -53,24 +71,49 @@ //! //! # fn main() { //! # let func = || -> Result<(), failure::Error> { -//! let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; +//! let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; //! //! println!("Writing to Database"); //! db.write(|db| { -//! db.insert("hello".into(), String::from("world")); -//! db.insert("foo".into(), String::from("bar")); +//! db.insert(0, String::from("world")); +//! db.insert(1, String::from("bar")); //! }); //! //! db.read(|db| { //! // db.insert("foo".into(), String::from("bar")); //! // The above line will not compile since we are only reading -//! println!("Hello: {:?}", db.get("hello")); -//! }); +//! println!("Hello: {:?}", db.get(&0)); +//! })?; //! # return Ok(()); }; //! # func().unwrap(); //! # } //! ``` //! +//! ## Error Handling +//! +//! Handling errors in Rustbreak is straightforward. Every `Result` has as its fail case as +//! `error::RustbreakError`. This means that you can now either continue bubbling up said error +//! case, or handle it yourself. +//! +//! You can simply call its `.kind()` method, giving you all the information you need to continue. +//! +//! ```rust +//! use rustbreak::{ +//! MemoryDatabase, +//! deser::Ron, +//! error::{ +//! RustbreakError, +//! } +//! }; +//! let db = match MemoryDatabase::::memory(0) { +//! Ok(db) => db, +//! Err(e) => { +//! // Do something with `e` here +//! ::std::process::exit(1); +//! } +//! }; +//! ``` +//! //! ## Panics //! //! This Database implementation uses `RwLock` and `Mutex` under the hood. If either the closures @@ -83,7 +126,7 @@ //! ## Examples //! //! There are several more or less in-depth example programs you can check out! -//! Check them out here: https://github.com/TheNeikos/rustbreak/tree/master/examples +//! Check them out here: [Examples][examples] //! //! - `config.rs` shows you how a possible configuration file could be managed with rustbreak //! - `full.rs` shows you how the database can be used as a hashmap store @@ -105,7 +148,8 @@ //! turned on per-default. The default feature is `ron` //! //! -//! [daybreak]:https://propublica.github.io/daybreak +//! [repo]: https://github.com/TheNeikos/rustbreak +//! [daybreak]: https://propublica.github.io/daybreak //! [examples]: https://github.com/TheNeikos/rustbreak/tree/master/examples //! [ron]: https://github.com/ron-rs/ron //! [failure]: https://boats.gitlab.io/failure/intro.html @@ -131,7 +175,6 @@ extern crate tempfile; /// The rustbreak errors that can be returned pub mod error; -/// Different storage backends pub mod backend; /// Different serialization and deserialization methods one can use pub mod deser; @@ -141,6 +184,7 @@ use std::fmt::Debug; use serde::Serialize; use serde::de::DeserializeOwned; +use failure::ResultExt; use deser::DeSerializer; use backend::{Backend, MemoryBackend, FileBackend}; @@ -153,12 +197,14 @@ use backend::{Backend, MemoryBackend, FileBackend}; /// - Back: The storage backend. /// - DeSer: The Serializer/Deserializer or short DeSer. Check the `deser` module for other /// strategies. +/// +/// # Panics +/// +/// If the backend or the de/serialization panics, the database is poisoned. This means that any +/// subsequent writes/reads will fail with an `error::RustbreakErrorKind::PoisonError`. +/// You can only recover from this by re-creating the Database Object. #[derive(Debug)] pub struct Database - where - Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend + Debug, - DeSer: DeSerializer + Debug + Send + Sync + Clone { data: RwLock, backend: Mutex, @@ -184,10 +230,44 @@ impl Database /// /// If you do not have full control over the code being written, and cannot incur the cost of /// having a single operation panicking then use `Database::write_safe`. + /// + /// # Examples + /// + /// ```rust + /// # #[macro_use] extern crate serde_derive; + /// # extern crate rustbreak; + /// # extern crate serde; + /// # extern crate tempfile; + /// # extern crate failure; + /// use rustbreak::{FileDatabase, deser::Ron}; + /// + /// #[derive(Debug, Serialize, Deserialize, Clone)] + /// struct Data { + /// level: u32, + /// } + /// + /// # fn main() { + /// # let func = || -> Result<(), failure::Error> { + /// # let file = tempfile::tempfile()?; + /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; + /// + /// db.write(|db| { + /// db.level = 42; + /// })?; + /// + /// // You can also return from a `.read()`. But don't forget that you cannot return references + /// // into the structure + /// let value = db.read(|db| db.level)?; + /// assert_eq!(42, value); + /// # return Ok(()); + /// # }; + /// # func().unwrap(); + /// # } + /// ``` pub fn write(&self, task: T) -> error::Result<()> where T: FnOnce(&mut Data) { - let mut lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut lock = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; task(&mut lock); Ok(()) } @@ -204,19 +284,78 @@ impl Database /// Depending on the size of the database this can be very costly. This is a tradeoff to make /// for panic safety. /// + /// You should read the documentation about this: + /// [UnwindSafe](https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html) + /// /// # Panics /// - /// When the closure panics, it is caught and a `error::RustbreakErrorkind::WritePanic` will be + /// When the closure panics, it is caught and a `error::RustbreakErrorKind::WritePanic` will be /// returned. + /// + /// # Examples + /// + /// ```rust + /// # #[macro_use] extern crate serde_derive; + /// # extern crate rustbreak; + /// # extern crate serde; + /// # extern crate tempfile; + /// # extern crate failure; + /// use rustbreak::{ + /// FileDatabase, + /// deser::Ron, + /// error::{ + /// RustbreakError, + /// RustbreakErrorKind, + /// } + /// }; + /// + /// #[derive(Debug, Serialize, Deserialize, Clone)] + /// struct Data { + /// level: u32, + /// } + /// + /// # fn main() { + /// # let func = || -> Result<(), failure::Error> { + /// # let file = tempfile::tempfile()?; + /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; + /// + /// let mut level = 0; + /// + /// let result = db.write_safe(|db| { + /// db.level = 42; + /// panic!("We panic inside the write code."); + /// }).expect_err("This should have been caught"); + /// + /// match result.kind() { + /// RustbreakErrorKind::WritePanic => { + /// // We can now handle this, in this example we will just ignore it + /// } + /// e => { + /// println!("{:#?}", e); + /// // You should always have generic error catching here. + /// // This future-proofs your code, and makes your code more robust. + /// // In this example this is unreachable though, and to assert that we have this + /// // macro here + /// unreachable!(); + /// } + /// } + /// + /// // We read it back out again, it has not changed + /// let value = db.read(|db| db.level)?; + /// assert_eq!(0, value); + /// # return Ok(()); + /// # }; + /// # func().unwrap(); + /// # } + /// ``` pub fn write_safe(&self, task: T) -> error::Result<()> where T: FnOnce(&mut Data) + std::panic::UnwindSafe, - for<'r> &'r mut Data: std::panic::UnwindSafe { - let mut lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut data : Data = lock.clone(); - ::std::panic::catch_unwind(|| { + let mut lock = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut data = lock.clone(); + ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { task(&mut data); - }).map_err(|_| error::RustbreakErrorKind::WritePanicError)?; + })).map_err(|_| error::RustbreakErrorKind::WritePanic)?; *lock = data; Ok(()) } @@ -226,47 +365,50 @@ impl Database /// This gives you a read-only lock on the database. You can have as many readers in parallel /// as you wish. /// + /// # Errors + /// + /// May return: + /// + /// - `error::RustbreakErrorKind::Backend` + /// /// # Panics /// /// If you panic in the closure, the database is poisoned. This means that any - /// subsequent writes/reads will fail with an `std::sync::PoisonError`. + /// subsequent writes/reads will fail with an `error::RustbreakErrorKind::Poison`. /// You can only recover from this by re-creating the Database Object. pub fn read(&self, task: T) -> error::Result where T: FnOnce(&Data) -> R { - let mut lock = self.data.read().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut lock = self.data.read().map_err(|_| error::RustbreakErrorKind::Poison)?; Ok(task(&mut lock)) } fn load(backend: &mut Back, deser: &DeSer) -> error::Result { - use failure::ResultExt; - let new_data = deser.deserialize(&backend.get_data()?[..]) - .context(error::RustbreakErrorKind::DeserializationError)?; + let new_data = deser.deserialize( + &backend.get_data().context(error::RustbreakErrorKind::Backend)?[..] + ).context(error::RustbreakErrorKind::Deserialization)?; Ok(new_data) } /// Reload the Data from the backend pub fn reload(&self) -> error::Result<()> { + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; - let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - - *data = Self::load(&mut backend, &self.deser)?; + *data = Self::load(&mut backend, &self.deser).context(error::RustbreakErrorKind::Backend)?; Ok(()) } /// Flush the data structure to the backend pub fn sync(&self) -> error::Result<()> { - use failure::ResultExt; - - let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; + let data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; let ser = self.deser.serialize(&*data) - .context(error::RustbreakErrorKind::SerializationError)?; + .context(error::RustbreakErrorKind::Serialization)?; - backend.put_data(ser.as_bytes())?; + backend.put_data(&ser).context(error::RustbreakErrorKind::Backend)?; Ok(()) } @@ -274,10 +416,10 @@ impl Database /// /// To make sure you have the latest data, call this method with `reload` true pub fn get_data(&self, reload: bool) -> error::Result { - let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; if reload { - *data = Self::load(&mut backend, &self.deser)?; + *data = Self::load(&mut backend, &self.deser).context(error::RustbreakErrorKind::Backend)?; drop(backend); } Ok(data.clone()) @@ -287,16 +429,14 @@ impl Database /// /// To sync the data afterwards, call with `sync` true. pub fn put_data(&self, new_data: Data, sync: bool) -> error::Result<()> { - let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; if sync { // TODO: Spin this into its own method - use failure::ResultExt; - let ser = self.deser.serialize(&*data) - .context(error::RustbreakErrorKind::SerializationError)?; + .context(error::RustbreakErrorKind::Serialization)?; - backend.put_data(ser.as_bytes())?; + backend.put_data(&ser).context(error::RustbreakErrorKind::Backend)?; drop(backend); } *data = new_data; @@ -314,8 +454,8 @@ impl Database /// Break a database into its individual parts pub fn into_inner(self) -> error::Result<(Data, Back, DeSer)> { - Ok((self.data.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?, - self.backend.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?, + Ok((self.data.into_inner().map_err(|_| error::RustbreakErrorKind::Poison)?, + self.backend.into_inner().map_err(|_| error::RustbreakErrorKind::Poison)?, self.deser)) } @@ -333,7 +473,6 @@ impl Database /// # extern crate serde; /// # extern crate tempfile; /// # extern crate failure; - /// /// use rustbreak::{FileDatabase, deser::Ron}; /// /// #[derive(Debug, Serialize, Deserialize, Clone)] @@ -354,6 +493,8 @@ impl Database /// /// let other_db = db.try_clone()?; /// + /// // You can also return from a `.read()`. But don't forget that you cannot return references + /// // into the structure /// let value = other_db.read(|db| db.level)?; /// assert_eq!(42, value); /// # return Ok(()); @@ -362,7 +503,7 @@ impl Database /// # } /// ``` pub fn try_clone(&self) -> error::Result> { - let lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let lock = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; Ok(Database { data: RwLock::new(lock.clone()), @@ -385,7 +526,7 @@ impl Database -> error::Result> where S: AsRef { - let backend = FileBackend::open(path)?; + let backend = FileBackend::open(path).context(error::RustbreakErrorKind::Backend)?; Ok(Database { data: RwLock::new(data), @@ -427,15 +568,9 @@ impl Database } } -impl Database - where - Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend + Debug, - DeSer: DeSerializer + Debug + Send + Sync + Clone -{ - /// Exchanges the DeSerialization strategy with the given one +impl Database { + /// Exchanges the DeSerialization strategy with the new one pub fn with_deser(self, deser: T) -> Database - where T: DeSerializer + Debug + Send + Sync { Database { backend: self.backend, @@ -445,15 +580,12 @@ impl Database } } -impl Database - where - Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend + Debug, - DeSer: DeSerializer + Debug + Send + Sync + Clone -{ - /// Exchanges the Backend with the given one +impl Database { + /// Exchanges the Backend with the new one + /// + /// The new backend does not necessarily have the latest data saved to it, so a `.sync` should + /// be called to make sure that it is saved. pub fn with_backend(self, backend: T) -> Database - where T: Backend + Debug { Database { backend: Mutex::new(backend), From f0283edfc719bb81a0ffb8f45f6bfc3f1cb2edf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Tue, 8 May 2018 09:53:46 +0200 Subject: [PATCH 37/40] Prepare release candidate 2.0-rc1 --- .travis.yml | 6 +++--- Cargo.toml | 6 +++--- README.md | 38 ++++++++++++++++++++++++++++++++++++++ examples/full.rs | 3 ++- examples/switching.rs | 3 ++- src/backend.rs | 1 + 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc24a8f..2c5185c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,9 @@ rust: - stable script: - | - cargo build --features yaml,bin && - cargo test --features yaml,bin && - cargo doc --features yaml,bin + cargo build --features yaml_enc,bin_enc,ron_enc && + cargo test --features yaml_enc,bin_enc,ron_enc && + cargo doc --features yaml_enc,bin_enc,ron_enc env: global: - secure: RFX1Ke3uk8ZdrAGwKPP7FXqQfC6bkGMDzV5ut9s5da6ClvoafosJF4RBvXi5j8KhQP9Kyow7Z8IwtTExNHMIjlE0MguHsYdFBdkj9HdjrqBQHThx5vMOq3VY3bNq9ah83Q6idoTTpQvoZ380BSHznHz8E3rmKGbEA0Z1NigWlT8DFfgqI5nTa64/bvD8IzMTjkLax4pq1SjsG8v+UgwNxkuYE6Yl/mpQJ/xTCGYiFHLKk4EW8z2I5DcK/XqY+w42uFDP2BRvXdzuS937A0lGZtAiK3sKJCBDq/2Ulkz4+onkAo9QT0k9Eea9dSoxXDk3RJHzDb5wLANtubxAWyTKiyTlO3I81ogPmHevSv3xDXXwLBjPxwwIRI1qt/01Nod4yIT4fWfIxcW+rY/W5P4v5QbvAJ079v8be1lnwCEFP55WXfTFEypvKUmNpCTeY9nAKiLywpQuibi/TuHmVH+R9xWlJaXrJ8+fM+dkl57V7AnIKs3nnYmE/c5guUakJ46QVZZtSQRcFfD0QeZPM5WaqAjiZ52eP4y/Mi5Rjg4/noLjXoR3YWYxWZsT5b7U2hv/wEMTzERgHev3aPZttO7gr3ug/FDv0TP6PKPCLd/i+g0w4RKRGArcAXIBWHdeEhfOYuC8Q93gl3d/qutvgGdWF4sdBpaPRcwmaWD+FoEWjWQ= diff --git a/Cargo.toml b/Cargo.toml index f5a886f..8093370 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,12 +3,12 @@ authors = ["Marcel Müller "] description = "A modular and configurable database" documentation = "https://docs.rs/rustbreak" homepage = "https://github.com/TheNeikos/rustbreak" -keywords = ["database", "simple", "fast", "daybreak", "rustbreak"] +keywords = ["database", "simple", "fast", "rustbreak"] license = "MPL-2.0" name = "rustbreak" readme = "README.md" repository = "https://github.com/TheNeikos/rustbreak" -version = "2.0.0" +version = "2.0.0-rc1" [dependencies] failure = "0.1.1" @@ -36,7 +36,7 @@ serde_derive = "1" tempfile = "3" [features] -default = ["ron_enc"] +default = [] ron_enc = ["ron"] bin_enc = ["bincode", "base64"] yaml_enc = ["serde_yaml"] diff --git a/README.md b/README.md index 39061c4..b938a24 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,44 @@ Features - Threadsafe - ron, bincode, or yaml storage +Quickstart +---------- + +Add this to your `Cargo.toml`: + +```toml +[dependencies.rustbreak] +version = "2" +features = ["ron_enc"] # You can also use "yaml_enc" or "bin_enc" + # Check the documentation to add your own! +``` + +```rust +# extern crate failure; +# extern crate rustbreak; +# use std::collections::HashMap; +use rustbreak::{MemoryDatabase, deser::Ron}; + +# fn main() { +# let func = || -> Result<(), failure::Error> { +let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; + +println!("Writing to Database"); +db.write(|db| { + db.insert(0, String::from("world")); + db.insert(1, String::from("bar")); +}); + +db.read(|db| { + // db.insert("foo".into(), String::from("bar")); + // The above line will not compile since we are only reading + println!("Hello: {:?}", db.get(&0)); +})?; +# return Ok(()); }; +# func().unwrap(); +# } +``` + Usage ----- diff --git a/examples/full.rs b/examples/full.rs index 7404e44..d7b400d 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -51,6 +51,7 @@ fn do_main() -> Result<(), failure::Error> { fn main() { if let Err(e) = do_main() { - println!("An error has occurred at: \n{}", e.backtrace()); + eprintln!("An error has occurred at: \n{}", e.backtrace()); + ::std::process::exit(1); } } diff --git a/examples/switching.rs b/examples/switching.rs index 08da877..5e5b7e7 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -47,7 +47,8 @@ fn do_main() -> Result<(), failure::Error> { fn main() { if let Err(e) = do_main() { - println!("An error has occurred at: \n{}", e.backtrace()); + eprintln!("An error has occurred at: \n{}", e.backtrace()); + ::std::process::exit(1); } } diff --git a/src/backend.rs b/src/backend.rs index 1212c08..b475536 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -45,6 +45,7 @@ impl Backend for FileBackend { self.0.seek(SeekFrom::Start(0)).context(error::RustbreakErrorKind::Backend)?; self.0.set_len(0).context(error::RustbreakErrorKind::Backend)?; self.0.write_all(data).context(error::RustbreakErrorKind::Backend)?; + self.0.sync_all().context(error::RustbreakErrorKind::Backend)?; Ok(()) } } From a16e3ce84c503990a9ce3c0a2bb5b04df763375b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 9 May 2018 12:30:49 +0200 Subject: [PATCH 38/40] DeSer now uses failure:Error --- Cargo.toml | 4 ++ README.md | 39 ++++++++++++------- examples/server/Cargo.toml | 1 + examples/server/server.ron | 4 ++ src/deser.rs | 77 +++++++++++++++++++++++++++++++------- src/lib.rs | 6 ++- 6 files changed, 104 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8093370..fac8056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ readme = "README.md" repository = "https://github.com/TheNeikos/rustbreak" version = "2.0.0-rc1" +[package.metadata.docs.rs] +all-features = true + [dependencies] failure = "0.1.1" serde = "1" @@ -40,3 +43,4 @@ default = [] ron_enc = ["ron"] bin_enc = ["bincode", "base64"] yaml_enc = ["serde_yaml"] + diff --git a/README.md b/README.md index b938a24..e1b6b74 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Features - Simple To Use, Fast, Secure - Threadsafe -- ron, bincode, or yaml storage +- Serde compatible storage (ron, bincode, or yaml included) Quickstart ---------- @@ -97,35 +97,48 @@ db.read(|db| { }); ``` +## Encodings + +The following parts explain how to enable the respective features. You can also +enable several at the same time. + ### Yaml -If you would like to use yaml instead of bincode to perhaps read or modify the -database in an editor you can use it like this: - -- Disable default features -- Specify yaml as a feature +If you would like to use yaml you need to specify `yaml_enc` as a feature: ```toml [dependencies.rustbreak] version = "1" -default-features = false -features = ["yaml"] +features = ["yaml_enc"] ``` +You can now use `rustbreak::deser::Yaml` as deserialization struct. + ### Ron -If you would like to use [`ron`](https://github.com/ron-rs/ron) instead of bincode: - -- Disable default features -- Specify ron_enc as a feature +If you would like to use [`ron`](https://github.com/ron-rs/ron) you need to +specify `ron_enc` as a feature: ```toml [dependencies.rustbreak] version = "1" -default-features = false features = ["ron_enc"] ``` +You can now use `rustbreak::deser::Ron` as deserialization struct. + +### Bincode + +If you would like to use bincode you need to specify `bin_enc` as a feature: + +```toml +[dependencies.rustbreak] +version = "1" +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/ diff --git a/examples/server/Cargo.toml b/examples/server/Cargo.toml index 19336f7..68b9f0a 100644 --- a/examples/server/Cargo.toml +++ b/examples/server/Cargo.toml @@ -16,3 +16,4 @@ version = "0.3.3" [dependencies.rustbreak] path = "../.." +features = ["ron_enc"] diff --git a/examples/server/server.ron b/examples/server/server.ron index f1925c0..27f0fd0 100644 --- a/examples/server/server.ron +++ b/examples/server/server.ron @@ -12,6 +12,10 @@ user: "neikos@neikos.email", body: "Hehe", ), + ( + user: "neikos@neikos.email", + body: "Hello World :)", + ), ], users: { "neikos@neikos.email": ( diff --git a/src/deser.rs b/src/deser.rs index 78b27f5..fcdd319 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -3,11 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::io::Read; +use failure; use serde::Serialize; use serde::de::DeserializeOwned; -use error; - #[cfg(feature = "ron_enc")] pub use self::ron::Ron; @@ -17,12 +16,62 @@ pub use self::yaml::Yaml; #[cfg(feature = "bin_enc")] pub use self::bincode::Bincode; -/// A trait to bundle serializer and deserializer +/// A trait to bundle serializer and deserializer in a simple struct +/// +/// It should preferably be an struct: one that does not have any members. +/// +/// # Example +/// +/// For an imaginary serde compatible encoding scheme 'Frobnar', an example implementation can look +/// like this: +/// +/// ```rust +/// extern crate rustbreak; +/// extern crate serde; +/// #[macro_use] extern crate failure; +/// +/// use std::io::Read; +/// use serde::Serialize; +/// use serde::de::Deserialize; +/// use failure::{Context, Fail, Backtrace}; +/// +/// use rustbreak::error; +/// use rustbreak::deser::DeSerializer; +/// +/// #[derive(Fail, Debug)] +/// #[fail(display = "A FrobnarError ocurred")] +/// struct FrobnarError; +/// +/// fn to_frobnar(input: &T) -> Vec { +/// unimplemented!(); // implementation not specified +/// } +/// +/// fn from_frobnar<'r, T: Deserialize<'r> + 'r, R: Read>(input: &R) -> Result { +/// unimplemented!(); // implementation not specified +/// } +/// +/// #[derive(Debug, Default, Clone)] +/// struct Frobnar; +/// +/// impl DeSerializer for Frobnar +/// where for<'de> T: Deserialize<'de> +/// { +/// fn serialize(&self, val: &T) -> Result, failure::Error> { +/// Ok(to_frobnar(val)) +/// } +/// +/// fn deserialize(&self, s: R) -> Result { +/// Ok(from_frobnar(&s)?) +/// } +/// } +/// +/// fn main() {} +/// ``` pub trait DeSerializer : ::std::default::Default + Send + Sync + Clone { /// Serializes a given value to a String - fn serialize(&self, val: &T) -> error::Result>; + fn serialize(&self, val: &T) -> Result, failure::Error>; /// Deserializes a String to a value - fn deserialize(&self, s: R) -> error::Result; + fn deserialize(&self, s: R) -> Result; } @@ -30,6 +79,7 @@ pub trait DeSerializer : ::std::default::Defaul mod ron { use std::io::Read; + use failure; use serde::Serialize; use serde::de::DeserializeOwned; use failure::ResultExt; @@ -46,11 +96,11 @@ mod ron { pub struct Ron; impl DeSerializer for Ron { - fn serialize(&self, val: &T) -> error::Result> { + fn serialize(&self, val: &T) -> Result, failure::Error> { Ok(to_ron_string(val, PrettyConfig::default()).map(String::into_bytes) .context(error::RustbreakErrorKind::Serialization)?) } - fn deserialize(&self, s: R) -> error::Result { + fn deserialize(&self, s: R) -> Result { Ok(from_ron_string(s).context(error::RustbreakErrorKind::Deserialization)?) } } @@ -60,6 +110,7 @@ mod ron { mod yaml { use std::io::Read; + use failure; use serde_yaml::{to_string as to_yaml_string, from_reader as from_yaml_string}; use serde::Serialize; use serde::de::DeserializeOwned; @@ -73,12 +124,12 @@ mod yaml { pub struct Yaml; impl DeSerializer for Yaml { - fn serialize(&self, val: &T) -> error::Result> { + fn serialize(&self, val: &T) -> Result, failure::Error> { Ok(to_yaml_string(val) .map(String::into_bytes) .context(error::RustbreakErrorKind::Serialization)?) } - fn deserialize(&self, s: R) -> error::Result { + fn deserialize(&self, s: R) -> Result { Ok(from_yaml_string(s).context(error::RustbreakErrorKind::Deserialization)?) } } @@ -88,6 +139,7 @@ mod yaml { mod bincode { use std::io::Read; + use failure; use bincode::{deserialize_from, serialize}; use serde::Serialize; use serde::de::DeserializeOwned; @@ -101,11 +153,10 @@ mod bincode { pub struct Bincode; impl DeSerializer for Bincode { - fn serialize(&self, val: &T) -> error::Result> { - let res = serialize(val).context(error::RustbreakErrorKind::Serialization)?; - Ok(res) + fn serialize(&self, val: &T) -> Result, failure::Error> { + Ok(serialize(val).context(error::RustbreakErrorKind::Serialization)?) } - fn deserialize(&self, s: R) -> error::Result { + fn deserialize(&self, s: R) -> Result { Ok(deserialize_from(s).context(error::RustbreakErrorKind::Deserialization)?) } } diff --git a/src/lib.rs b/src/lib.rs index 9db44da..184a7fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,6 +179,11 @@ pub mod backend; /// Different serialization and deserialization methods one can use pub mod deser; +/// The `DeSerializer` trait used by serialization structs +pub use deser::DeSerializer; +/// The general error used by the Rustbreak Module +pub use error::RustbreakError; + use std::sync::{Mutex, RwLock}; use std::fmt::Debug; @@ -186,7 +191,6 @@ use serde::Serialize; use serde::de::DeserializeOwned; use failure::ResultExt; -use deser::DeSerializer; use backend::{Backend, MemoryBackend, FileBackend}; /// The Central Database to RustBreak From 24442834984afbe5c56961838d346863acbc7b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 9 May 2018 12:39:03 +0200 Subject: [PATCH 39/40] Rename reload/sync to load/save --- README.md | 4 +++- examples/config.rs | 2 +- examples/full.rs | 6 +++--- examples/migration.rs.incomplete | 2 +- examples/server/src/main.rs | 6 +++--- examples/switching.rs | 4 ++-- src/lib.rs | 28 ++++++++++++++-------------- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e1b6b74..85cc2d4 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,9 @@ Usage is quite simple: - Create a `MemoryDatabase` with `MemoryDatabase::memory` - Create a `Database` with `Database::from_parts` - `Write`/`Read` data from the Database -- Don't forget to run `sync` periodically +- 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. ```rust # use std::collections::HashMap; diff --git a/examples/config.rs b/examples/config.rs index f715824..10a3e43 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -24,7 +24,7 @@ lazy_static! { static ref CONFIG: DB = { let db = FileDatabase::from_path("/tmp/config.ron", Config::default()) .expect("Create database from path"); - db.reload().expect("Config to load"); + db.load().expect("Config to load"); db }; } diff --git a/examples/full.rs b/examples/full.rs index d7b400d..930124f 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -35,10 +35,10 @@ fn do_main() -> Result<(), failure::Error> { })?; println!("Syncing Database"); - db.sync()?; + db.save()?; - println!("Reloading Database"); - db.reload()?; + println!("Loading Database"); + db.load()?; println!("Reading from Database"); db.read(|db| { diff --git a/examples/migration.rs.incomplete b/examples/migration.rs.incomplete index 1d73b9f..d6b0f92 100644 --- a/examples/migration.rs.incomplete +++ b/examples/migration.rs.incomplete @@ -165,7 +165,7 @@ migration_rules! { fn get_version() -> data::Version { let db = FileDatabase::::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap(); - db.reload().unwrap(); + db.load().unwrap(); db.read(|ver| ver.version).unwrap() } diff --git a/examples/server/src/main.rs b/examples/server/src/main.rs index e4d32d0..519861a 100644 --- a/examples/server/src/main.rs +++ b/examples/server/src/main.rs @@ -99,7 +99,7 @@ fn post_register(db: State, req_user: Form, mut cookies: Cookies) -> R Cookie::build("user_id", user.username.clone()).http_only(true).finish() ); }); - let _ = db.sync(); + let _ = db.save(); Redirect::to("/") } @@ -133,7 +133,7 @@ fn post_paste(db: State, user: User, paste: Form) -> Redirect { }; db.pastes.push(paste); }); - let _ = db.sync(); + let _ = db.save(); Redirect::to("/") } @@ -148,7 +148,7 @@ fn main() { pastes: vec![], users: HashMap::new(), }).unwrap(); - let _ = db.reload(); + let _ = db.load(); rocket::ignite() diff --git a/examples/switching.rs b/examples/switching.rs index 5e5b7e7..0d78275 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -35,12 +35,12 @@ fn do_main() -> Result<(), failure::Error> { })?; println!("Syncing Database"); - db.sync()?; + db.save()?; // Now lets switch it let db = db.with_deser(Yaml).with_backend(FileBackend::open("test.yml")?); - db.sync()?; + db.save()?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 184a7fb..e1a3129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -387,7 +387,7 @@ impl Database Ok(task(&mut lock)) } - fn load(backend: &mut Back, deser: &DeSer) -> error::Result { + fn inner_load(backend: &mut Back, deser: &DeSer) -> error::Result { let new_data = deser.deserialize( &backend.get_data().context(error::RustbreakErrorKind::Backend)?[..] ).context(error::RustbreakErrorKind::Deserialization)?; @@ -395,17 +395,17 @@ impl Database Ok(new_data) } - /// Reload the Data from the backend - pub fn reload(&self) -> error::Result<()> { + /// Load the Data from the backend + pub fn load(&self) -> error::Result<()> { let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; - *data = Self::load(&mut backend, &self.deser).context(error::RustbreakErrorKind::Backend)?; + *data = Self::inner_load(&mut backend, &self.deser).context(error::RustbreakErrorKind::Backend)?; Ok(()) } /// Flush the data structure to the backend - pub fn sync(&self) -> error::Result<()> { + pub fn save(&self) -> error::Result<()> { let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; let data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; @@ -418,12 +418,12 @@ impl Database /// 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 `reload` true - pub fn get_data(&self, reload: bool) -> error::Result { + /// To make sure you have the latest data, call this method with `load` true + pub fn get_data(&self, load: bool) -> error::Result { let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; - if reload { - *data = Self::load(&mut backend, &self.deser).context(error::RustbreakErrorKind::Backend)?; + if load { + *data = Self::inner_load(&mut backend, &self.deser).context(error::RustbreakErrorKind::Backend)?; drop(backend); } Ok(data.clone()) @@ -431,11 +431,11 @@ impl Database /// Puts the data as is into memory /// - /// To sync the data afterwards, call with `sync` true. - pub fn put_data(&self, new_data: Data, sync: bool) -> error::Result<()> { + /// To save the data afterwards, call with `save` true. + pub fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> { let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::Poison)?; let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::Poison)?; - if sync { + if save { // TODO: Spin this into its own method let ser = self.deser.serialize(&*data) .context(error::RustbreakErrorKind::Serialization)?; @@ -493,7 +493,7 @@ impl Database /// db.level = 42; /// })?; /// - /// db.sync()?; + /// db.save()?; /// /// let other_db = db.try_clone()?; /// @@ -587,7 +587,7 @@ impl Database { impl Database { /// Exchanges the Backend with the new one /// - /// The new backend does not necessarily have the latest data saved to it, so a `.sync` should + /// The new backend does not necessarily have the latest data saved to it, so a `.save` should /// be called to make sure that it is saved. pub fn with_backend(self, backend: T) -> Database { From a43c3a08b6669fb17b728e89fead1d4f92cb22b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 10 May 2018 17:26:55 +0200 Subject: [PATCH 40/40] Release 2.0-rc2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fac8056..1d4c7e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MPL-2.0" name = "rustbreak" readme = "README.md" repository = "https://github.com/TheNeikos/rustbreak" -version = "2.0.0-rc1" +version = "2.0.0-rc2" [package.metadata.docs.rs] all-features = true