Merge pull request #41 from TheNeikos/rewrite-to_v2

Rewrite to v2
This commit is contained in:
Marcel Müller
2018-05-10 17:42:45 +02:00
committed by GitHub
19 changed files with 1659 additions and 678 deletions
+3 -13
View File
@@ -4,21 +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 &&
rm /tmp/*
travis-cargo test -- --no-default-features --features yaml &&
travis-cargo bench &&
travis-cargo --only stable doc
after_success:
- travis-cargo --only stable doc-upload
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:
- 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=
+31 -14
View File
@@ -1,29 +1,46 @@
[package]
authors = ["Marcel Müller <neikos@neikos.email>"]
description = "A single file 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"]
keywords = ["database", "simple", "fast", "rustbreak"]
license = "MPL-2.0"
name = "rustbreak"
readme = "README.md"
repository = "https://github.com/TheNeikos/rustbreak"
version = "1.4.0"
version = "2.0.0-rc2"
[package.metadata.docs.rs]
all-features = true
[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"
failure = "0.1.1"
serde = "1"
[dependencies.ron]
optional = true
version = "0.2.0"
[dependencies.base64]
optional = true
version = "0.9.0"
[dependencies.bincode]
optional = true
version = "1"
[dependencies.serde_yaml]
optional = true
version = "0.7"
[dev-dependencies]
tempfile = "2.1"
lazy_static = "0.2.1"
lazy_static = "1"
serde_derive = "1"
tempfile = "3"
[features]
default = ["bin"]
bin = ["bincode"]
yaml = ["serde_yaml"]
default = []
ron_enc = ["ron"]
bin_enc = ["bincode", "base64"]
yaml_enc = ["serde_yaml"]
+82 -35
View File
@@ -26,73 +26,120 @@ Features
- Simple To Use, Fast, Secure
- Threadsafe
- Key/Value Storage
- bincode or yaml storage
- Serde compatible storage (ron, bincode, or yaml included)
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::<HashMap<u32, String>, 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
-----
Usage is quite simple:
- Create/open a database using `Database::open`
- You can specify the kind of Key you want using this Syntax:
`Database::<Key>::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 `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 rustbreak::Database;
# use std::collections::HashMap;
use rustbreak::{MemoryDatabase, deser::Ron};
fn main() {
let db = Database::open("my_contacts").unwrap();
let db = MemoryDatabase::<HashMap<String, String>, Ron>::memory(HashMap::new(), Ron);
db.insert("Lapfox", "lapfoxtrax.com").unwrap();
db.insert("Rust", "github.com/rust-lang/rust").unwrap();
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"));
});
```
## 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"]
```
How it works
------------
You can now use `rustbreak::deser::Ron` as deserialization struct.
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.
### 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
+56
View File
@@ -0,0 +1,56 @@
// This just reads an example configuration.
// If it doesn't find one, it uses your default configuration
//
// You can create one by writing this file to `/tmp/config.ron`:
// ```
// ---
// user_path: /tmp/nope
// allow_overwrite: true
// ```
//
extern crate rustbreak;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate lazy_static;
use std::path::PathBuf;
use std::default::Default;
use rustbreak::FileDatabase;
use rustbreak::deser::Ron;
type DB = FileDatabase<Config, Ron>;
lazy_static! {
static ref CONFIG: DB = {
let db = FileDatabase::from_path("/tmp/config.ron", Config::default())
.expect("Create database from path");
db.load().expect("Config to load");
db
};
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Config {
user_path: PathBuf,
allow_overwrite: bool,
}
impl Default for Config {
fn default() -> Config {
Config {
user_path: PathBuf::from("/tmp"),
allow_overwrite: false,
}
}
}
fn main() {
let _conf : Config = CONFIG.read(|conf| {
conf.clone()
}).expect("Reading configuration");
let (user_path, allow_overwrite) =
CONFIG.read(|conf| (conf.user_path.clone(), conf.allow_overwrite.clone())).expect("Read config");
println!("The current configuration is: {:?} and {}", user_path, allow_overwrite);
}
+45 -34
View File
@@ -1,46 +1,57 @@
extern crate rustbreak;
#[macro_use] extern crate lazy_static;
#[macro_use] extern crate serde_derive;
extern crate failure;
use rustbreak::{Database, Result as BreakResult};
use rustbreak::FileDatabase;
use rustbreak::deser::Ron;
lazy_static! {
static ref DB: Database<String> = {
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);
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
struct Person {
name: String,
country: Country,
}
fn do_main() -> Result<(), failure::Error> {
use std::collections::HashMap;
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path("test.ron", HashMap::new())?;
println!("Writing to Database");
db.write(|db| {
db.insert("john".into(), Person {
name: String::from("John Andersson"),
country: Country::Italy
});
db.insert("fred".into(), Person {
name: String::from("Fred Johnson"),
country: Country::UnitedKingdom
});
println!("Entries: \n{:#?}", db);
})?;
println!("Syncing Database");
db.save()?;
println!("Loading Database");
db.load()?;
println!("Reading from Database");
db.read(|db| {
println!("Results:");
println!("{:#?}", db);
})?;
fn add_album_to_artist(name: Artist, album: Album) -> BreakResult<()> {
let mut lock = try!(DB.lock());
let mut map: Vec<Album> = lock.retrieve(&name).unwrap_or_else(|_| vec![]);
map.push(album);
try!(lock.insert(&name, map));
Ok(())
}
fn get_albums(name: &str) -> BreakResult<Vec<Album>> {
DB.retrieve(name)
}
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"),
];
for &(artist, album) in albums.iter() {
add_album_to_artist(artist.to_owned(), album.to_owned()).unwrap();
if let Err(e) = do_main() {
eprintln!("An error has occurred at: \n{}", e.backtrace());
::std::process::exit(1);
}
for al in get_albums("The Queenstons").unwrap() {
println!("{}", al);
}
DB.flush().unwrap();
}
+183
View File
@@ -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<i32, String>,
}
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<i32, Player>,
}
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<i32, Player>,
}
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<T: 'static>(input: T) -> Option<$res> {
fn inner(mut input: Box<std::any::Any>) -> 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::<data::Versioning, Ron>::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap();
db.load().unwrap();
db.read(|ver| ver.version).unwrap()
}
fn main() {
use data::v3::Players;
let version = get_version();
// Let's check if there are any updates
let updated_data = update_database(database.get_data(false).unwrap()).unwrap();
let database = FileDatabase::<Players, Ron>::from_path("migration.ron", updated_data).unwrap();
println!("{:#?}", database);
}
+19
View File
@@ -0,0 +1,19 @@
[package]
authors = ["Marcel Müller <neikos@neikos.email>"]
name = "server"
version = "0.1.0"
[dependencies]
rocket = "0.3.3"
rocket_codegen = "0.3.3"
serde = "1.0.23"
serde_derive = "1.0.23"
[dependencies.rocket_contrib]
default-features = false
features = ["handlebars_templates"]
version = "0.3.3"
[dependencies.rustbreak]
path = "../.."
features = ["ron_enc"]
+26
View File
@@ -0,0 +1,26 @@
(
pastes: [
(
user: "neikos@neikos.email",
body: "Hello :)",
),
(
user: "neikos@neikos.email",
body: "This works great!",
),
(
user: "neikos@neikos.email",
body: "Hehe",
),
(
user: "neikos@neikos.email",
body: "Hello World :)",
),
],
users: {
"neikos@neikos.email": (
username: "neikos@neikos.email",
password: "asdf",
),
},
)
+159
View File
@@ -0,0 +1,159 @@
#![feature(plugin, decl_macro, custom_derive)]
#![plugin(rocket_codegen)]
extern crate rustbreak;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;
use std::collections::HashMap;
use rocket::{State, Outcome};
use rocket::http::{Cookies, Cookie};
use rocket::request::{self, Request, FromRequest, Form};
use rocket::response::Redirect;
use rocket_contrib::Template;
use rustbreak::FileDatabase;
use rustbreak::deser::Ron;
// We create a type alias so that we always associate the same types to it
type DB = FileDatabase<ServerData, Ron>;
#[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<User, ()> {
let mut cookies = request.cookies();
let db = request.guard::<State<DB>>()?;
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<Paste>,
users: HashMap<String, User>
}
#[derive(Debug, Serialize)]
struct TemplateData {
pastes: Vec<Paste>,
logged_in: bool,
user: String
}
// Routing
#[get("/")]
fn index(db: State<DB>, user: Option<User>) -> 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 = "<req_user>")]
fn post_register(db: State<DB>, req_user: Form<User>, mut cookies: Cookies) -> Redirect {
let user = req_user.into_inner();
let _ = db.write(|db| {
if db.users.contains_key(&user.username) {
return;
}
db.users.insert(user.username.clone(), user.clone());
cookies.add_private(
Cookie::build("user_id", user.username.clone()).http_only(true).finish()
);
});
let _ = db.save();
Redirect::to("/")
}
#[post("/login", data = "<req_user>")]
fn post_login(db: State<DB>, req_user: Form<User>, 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 = "<paste>")]
fn post_paste(db: State<DB>, user: User, paste: Form<NewPaste>) -> Redirect {
let body : String = paste.into_inner().body.clone();
let _ = db.write(|db| {
let paste = Paste {
body: body,
user: user.username.clone(),
};
db.pastes.push(paste);
});
let _ = db.save();
Redirect::to("/")
}
#[get("/dummy")]
fn get_dummy() -> &'static str {
"Hello World"
}
fn main() {
let db : DB = FileDatabase::from_path("server.ron", ServerData {
pastes: vec![],
users: HashMap::new(),
}).unwrap();
let _ = db.load();
rocket::ignite()
.mount("/", routes![index, post_login, post_paste, post_register, get_dummy])
.attach(Template::fairing())
.manage(db)
.launch();
}
+105
View File
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<title>Rustbreak Pastes</title>
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css">
</head>
<body>
{{#if logged_in }}
<div class="container">
<div>
You are logged in as: <strong>{{user}}</strong>
</div>
<form action="/paste" method="post">
<div class="field">
<label class="label is-small">Name</label>
<div class="control">
<textarea class="textarea" name="body" row="4"></textarea>
</div>
</div>
<div class="field">
<div class="control is-small">
<button class="button is-link is-small">Submit</button>
</div>
</div>
</form>
</div>
{{else}}
<div class="container">
<div class="columns">
<form action="/login" method="post" class="column">
<div class="field">
<label class="label is-small">Name</label>
<div class="control is-small">
<input class="input is-small" type="text" placeholder="Username" name="username">
</div>
</div>
<div class="field">
<label class="label is-small">Password</label>
<div class="control is-small">
<input class="input is-small" type="password" placeholder="Username" name="password">
</div>
</div>
<div class="field">
<div class="control is-small">
<button class="button is-link is-small">Login</button>
</div>
</div>
</form>
<form action="/register" method="post" class="column">
<div class="field">
<label class="label is-small">Name</label>
<div class="control is-small">
<input class="input is-small" type="text" placeholder="Username" name="username">
</div>
</div>
<div class="field">
<label class="label is-small">Password</label>
<div class="control is-small">
<input class="input is-small" type="password" placeholder="Username" name="password">
</div>
</div>
<div class="field">
<div class="control is-small">
<button class="button is-small is-link">Register</button>
</div>
</div>
</form>
</div>
</div>
{{/if}}
<hr>
<div class="container">
<h1 class="title">Pastes</h1>
{{#each pastes as |p| ~}}
<div class="box">
<article class="media">
<div class="media-content">
<p>
<strong>{{p.user}}</strong><br>
{{p.body}}
<p>
</div>
</article>
</div>
{{/each~}}
</div>
<footer class="footer">
<div class="container">
<div class="content has-text-centered">
<p>
Made with love in Germany &mdash; Created using Rocket, Handlebars and Rustbreak
</p>
<a href="https://bulma.io">
<img src="https://bulma.io/images/made-with-bulma.png" alt="Made with Bulma" width="128" height="24">
</a>
</div>
</div>
</footer>
</body>
</html>
-15
View File
@@ -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();
}
+54
View File
@@ -0,0 +1,54 @@
extern crate rustbreak;
#[macro_use] extern crate serde_derive;
extern crate failure;
use rustbreak::{FileDatabase, backend::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 do_main() -> Result<(), failure::Error> {
use std::collections::HashMap;
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path("test.ron", HashMap::new())?;
println!("Writing to Database");
db.write(|db| {
db.insert("john".into(), Person {
name: String::from("John Andersson"),
country: Country::Italy
});
db.insert("fred".into(), Person {
name: String::from("Fred Johnson"),
country: Country::UnitedKingdom
});
println!("Entries: \n{:#?}", db);
})?;
println!("Syncing Database");
db.save()?;
// Now lets switch it
let db = db.with_deser(Yaml).with_backend(FileBackend::open("test.yml")?);
db.save()?;
Ok(())
}
fn main() {
if let Err(e) = do_main() {
eprintln!("An error has occurred at: \n{}", e.backtrace());
::std::process::exit(1);
}
}
+121
View File
@@ -0,0 +1,121 @@
/* 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/. */
//! The persistence backends of the Database
//!
//! A file is a `Backend` through the `FileBackend`, so is a `Vec<u8>` with a `MemoryBackend`.
//!
//! Implementing your own Backend should be straightforward. Check the `Backend` documentation for
//! details.
use failure::ResultExt;
use error;
/// The Backend Trait
///
/// 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 {
/// Read the all data from the backend
fn get_data(&mut self) -> error::Result<Vec<u8>>;
/// Write the whole slice to the backend
fn put_data(&mut self, data: &[u8]) -> error::Result<()>;
}
/// A backend using a file
#[derive(Debug)]
pub struct FileBackend(::std::fs::File);
impl Backend for FileBackend {
fn get_data(&mut self) -> error::Result<Vec<u8>> {
use ::std::io::{Seek, SeekFrom, Read};
let mut buffer = vec![];
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)).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(())
}
}
impl FileBackend {
/// Opens a new FileBackend for a given path, will create it if the file doesn't exist.
pub fn open<P: AsRef<::std::path::Path>>(path: P) -> error::Result<FileBackend> {
use ::std::fs::OpenOptions;
Ok(FileBackend(
OpenOptions::new().read(true).write(true).create(true).open(path).context(error::RustbreakErrorKind::Backend)?,
))
}
/// Use an already open File as the backend
pub fn from_file(file: ::std::fs::File) -> FileBackend {
FileBackend(file)
}
/// Return the inner File
pub fn into_inner(self) -> ::std::fs::File {
self.0
}
}
/// An in memory backend
///
/// It is backed by a `Vec<u8>`
#[derive(Debug)]
pub struct MemoryBackend(Vec<u8>);
impl MemoryBackend {
/// Construct a new Memory Database
pub fn new() -> MemoryBackend {
MemoryBackend(vec![])
}
}
impl Backend for MemoryBackend {
fn get_data(&mut self) -> error::Result<Vec<u8>> {
Ok(self.0.clone())
}
fn put_data(&mut self, data: &[u8]) -> error::Result<()> {
self.0 = data.to_owned();
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);
}
}
-18
View File
@@ -1,18 +0,0 @@
use serde::{Serialize, Deserialize};
use bincode::{self, serialize as bin_serialize, deserialize as bin_deserialize};
pub type Repr = Vec<u8>;
pub type SerializeError = bincode::Error;
pub type DeserializeError = ();
pub fn serialize<T>(value: &T) -> bincode::Result<Vec<u8>>
where T: Serialize
{
bin_serialize(value, bincode::Infinite)
}
pub fn deserialize<'a, T>(bytes: &'a [u8]) -> Result<T, bincode::Error>
where T: Deserialize<'a>
{
bin_deserialize(bytes)
}
+163
View File
@@ -0,0 +1,163 @@
/* 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 failure;
use serde::Serialize;
use serde::de::DeserializeOwned;
#[cfg(feature = "ron_enc")]
pub use self::ron::Ron;
#[cfg(feature = "yaml_enc")]
pub use self::yaml::Yaml;
#[cfg(feature = "bin_enc")]
pub use self::bincode::Bincode;
/// 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<T: Serialize>(input: &T) -> Vec<u8> {
/// unimplemented!(); // implementation not specified
/// }
///
/// fn from_frobnar<'r, T: Deserialize<'r> + 'r, R: Read>(input: &R) -> Result<T, FrobnarError> {
/// unimplemented!(); // implementation not specified
/// }
///
/// #[derive(Debug, Default, Clone)]
/// struct Frobnar;
///
/// impl<T: Serialize> DeSerializer<T> for Frobnar
/// where for<'de> T: Deserialize<'de>
/// {
/// fn serialize(&self, val: &T) -> Result<Vec<u8>, failure::Error> {
/// Ok(to_frobnar(val))
/// }
///
/// fn deserialize<R: Read>(&self, s: R) -> Result<T, failure::Error> {
/// Ok(from_frobnar(&s)?)
/// }
/// }
///
/// fn main() {}
/// ```
pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::std::default::Default + Send + Sync + Clone {
/// Serializes a given value to a String
fn serialize(&self, val: &T) -> Result<Vec<u8>, failure::Error>;
/// Deserializes a String to a value
fn deserialize<R: Read>(&self, s: R) -> Result<T, failure::Error>;
}
#[cfg(feature = "ron_enc")]
mod ron {
use std::io::Read;
use failure;
use serde::Serialize;
use serde::de::DeserializeOwned;
use failure::ResultExt;
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<T: Serialize + DeserializeOwned> DeSerializer<T> for Ron {
fn serialize(&self, val: &T) -> Result<Vec<u8>, failure::Error> {
Ok(to_ron_string(val, PrettyConfig::default()).map(String::into_bytes)
.context(error::RustbreakErrorKind::Serialization)?)
}
fn deserialize<R: Read>(&self, s: R) -> Result<T, failure::Error> {
Ok(from_ron_string(s).context(error::RustbreakErrorKind::Deserialization)?)
}
}
}
#[cfg(feature = "yaml_enc")]
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;
use failure::ResultExt;
use error;
use deser::DeSerializer;
/// The struct that allows you to use yaml
#[derive(Debug, Default, Clone)]
pub struct Yaml;
impl<T: Serialize + DeserializeOwned> DeSerializer<T> for Yaml {
fn serialize(&self, val: &T) -> Result<Vec<u8>, failure::Error> {
Ok(to_yaml_string(val)
.map(String::into_bytes)
.context(error::RustbreakErrorKind::Serialization)?)
}
fn deserialize<R: Read>(&self, s: R) -> Result<T, failure::Error> {
Ok(from_yaml_string(s).context(error::RustbreakErrorKind::Deserialization)?)
}
}
}
#[cfg(feature = "bin_enc")]
mod bincode {
use std::io::Read;
use failure;
use bincode::{deserialize_from, serialize};
use serde::Serialize;
use serde::de::DeserializeOwned;
use failure::ResultExt;
use error;
use deser::DeSerializer;
/// The struct that allows you to use bincode
#[derive(Debug, Default, Clone)]
pub struct Bincode;
impl<T: Serialize + DeserializeOwned> DeSerializer<T> for Bincode {
fn serialize(&self, val: &T) -> Result<Vec<u8>, failure::Error> {
Ok(serialize(val).context(error::RustbreakErrorKind::Serialization)?)
}
fn deserialize<R: Read>(&self, s: R) -> Result<T, failure::Error> {
Ok(deserialize_from(s).context(error::RustbreakErrorKind::Deserialization)?)
}
}
}
+71 -31
View File
@@ -1,37 +1,77 @@
/* 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,
/// 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()
}
/// 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 {}
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")]
Serialization,
/// A context error when a deserialization failed
#[fail(display = "Could not deserialize the value")]
Deserialization,
/// This error is returned if the `Database` is poisoned. See `Database::write` for details
#[fail(display = "The database has been poisoned")]
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")]
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")]
__Nonexhaustive,
}
/// The main error type that gets returned for errors that happen while interacting with a
/// `Database`.
#[derive(Debug)]
pub struct RustbreakError {
inner: Context<RustbreakErrorKind>,
}
impl Fail for RustbreakError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl<T> From<::std::sync::PoisonError<T>> for BreakError {
fn from(_: ::std::sync::PoisonError<T>) -> BreakError {
BreakError::Poison
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<RustbreakErrorKind> for RustbreakError {
fn from(kind: RustbreakErrorKind) -> RustbreakError {
RustbreakError { inner: Context::new(kind) }
}
}
impl From<Context<RustbreakErrorKind>> for RustbreakError {
fn from(inner: Context<RustbreakErrorKind>) -> RustbreakError {
RustbreakError { inner: inner }
}
}
/// A simple type alias for errors
pub type Result<T> = ::std::result::Result<T, RustbreakError>;
+541 -474
View File
File diff suppressed because it is too large Load Diff
-21
View File
@@ -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<T>(value: &T) -> ser::Result<String>
where T: Serialize
{
ser::pretty::to_string(value)
}
pub fn deserialize<T, I>(bytes: &I) -> Result<T, ::error::BreakError>
where T: DeserializeOwned,
I: AsRef<[u8]>
{
let string = try!(String::from_utf8(bytes.as_ref().to_vec()));
Ok(de::from_str(&string)?)
}
-23
View File
@@ -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<T>(value: &T) -> YamlResult<String>
where T: Serialize
{
::serde_yaml::to_string(value)
}
pub fn deserialize<T, I: AsRef<[u8]>>(bytes: &I) -> Result<T, ::error::BreakError>
where T: DeserializeOwned
{
let string = try!(String::from_utf8(bytes.as_ref().to_vec()));
let des = try!(::serde_yaml::from_str(&string));
Ok(des)
}