Add extensive example

This commit is contained in:
Marcel Müller
2017-12-06 20:02:15 +01:00
parent 1368d8c299
commit 2c9cd5888c
5 changed files with 306 additions and 155 deletions
+7
View File
@@ -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"]
+2 -4
View File
@@ -17,8 +17,7 @@ struct Person {
fn main() {
use std::collections::HashMap;
use rustbreak::Container;
let db = Database::<Person, String>::from_path("test.yaml").unwrap();
let db = Database::<HashMap<String, Person>>::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");
+155
View File
@@ -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<ServerData, Ron, File>;
#[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.sync();
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.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();
}
+37 -151
View File
@@ -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<String>, 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::<String, String>::memory();
//! let db = Database::<HashMap<String, String>>::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<K: Hash + Eq + Debug, V> : Debug {
/// Insert the value at the given key
///
/// Returns optionally an existing value that was replaced
fn insert<T: Into<K>>(&mut self, key: T, value: V) -> Option<V>;
/// Removes the given value from the Container
fn remove<T: AsRef<K>>(&mut self, key: T) -> Option<V>;
/// Borrows the given value from the Container
fn get<T: AsRef<K>>(&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<V: Debug, K: Hash + Eq + Debug> Container<K, V> for HashMap<K, V> {
fn insert<T: Into<K>>(&mut self, key: T, value: V) -> Option<V> {
self.insert(key.into(), value)
}
fn remove<T: AsRef<K>>(&mut self, key: T) -> Option<V> {
self.remove(key.as_ref())
}
fn get<T: AsRef<K>>(&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<String, D>
/// - 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<V, K = String, C = HashMap<K, V>, S = Ron, F = RWVec>
pub struct Database<V, S = Ron, F = RWVec>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
C: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<C> + Sync + Send + Debug,
S: DeSerializer<V> + Sync + Send + Debug,
F: Write + Resizable + Debug,
for<'r> &'r mut F: Read
{
backing: Mutex<F>,
data: RwLock<C>,
data: RwLock<V>,
deser: S,
data_type: PhantomData<V>,
key_type: PhantomData<K>,
}
impl<V, K> Database<V, K, HashMap<K, V>>
impl<V> Database<V>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
{
/// Constructs a `Database` with in-memory Storage
pub fn memory() -> Database<V, K, HashMap<K, V>, Ron, RWVec>
pub fn memory(initial: V) -> Database<V, Ron, RWVec>
{
Database {
backing: Mutex::new(RWVec::new()),
data: RwLock::new(HashMap::new()),
data: RwLock::new(initial),
deser: Ron,
data_type: PhantomData,
key_type: PhantomData,
}
}
}
impl<V, K> Database<V, K, HashMap<K, V>>
impl<V> Database<V>
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<V, K, HashMap<K, V>, Ron, File> {
pub fn from_file(initial: V, file: File) -> Database<V, Ron, File> {
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<P: AsRef<Path>>(path: P)
-> BreakResult<Database<V, K, HashMap<K, V>, Ron, File>,
<Ron as DeSerializer<K>>::SerError, <Ron as DeSerializer<K>>::DeError> {
pub fn from_path<P: AsRef<Path>>(initial: V, path: P) -> BreakResult<Database<V, Ron, File>,
<Ron as DeSerializer<V>>::SerError, <Ron as DeSerializer<V>>::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<V, K, C, S, F> Database<V, K, C, S, F>
impl<V, S, F> Database<V, S, F>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
C: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<C> + Sync + Send + Debug,
S: DeSerializer<V> + 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<T>(&self, mut task: T)
-> BreakResult<(), <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::DeError>
where T: FnMut(&mut C)
pub fn write<T>(&self, task: T)
-> BreakResult<(), <S as DeSerializer<V>>::SerError, <S as DeSerializer<V>>::DeError>
where T: FnOnce(&mut V)
{
let mut lock = self.data.write()?;
task(&mut lock);
@@ -234,7 +180,7 @@ impl<V, K, C, S, F> Database<V, K, C, S, F>
/// 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<(), <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::DeError>
-> BreakResult<(), <S as DeSerializer<V>>::SerError, <S as DeSerializer<V>>::DeError>
{
let mut backing = self.backing.lock()?;
let data = self.data.read()?;
@@ -253,7 +199,7 @@ impl<V, K, C, S, F> Database<V, K, C, S, F>
///
/// This is useful to call at startup, or your Database might be empty!
pub fn reload(&self)
-> BreakResult<(), <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::DeError>
-> BreakResult<(), <S as DeSerializer<V>>::SerError, <S as DeSerializer<V>>::DeError>
{
let mut backing = self.backing.lock()?;
let mut data = self.data.write()?;
@@ -269,86 +215,53 @@ impl<V, K, C, S, F> Database<V, K, C, S, F>
}
impl<V, K, C, S, F> Database<V, K, C, S, F>
impl<V, S, F> Database<V, S, F>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
C: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<C> + Sync + Send + Debug,
S: DeSerializer<V> + 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<T>(&self, mut task: T)
-> BreakResult<(), <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::DeError>
where T: FnMut(&C)
pub fn read<T>(&self, task: T)
-> BreakResult<(), <S as DeSerializer<V>>::SerError, <S as DeSerializer<V>>::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<T: Into<K>>(&self, key: T, data: V)
-> BreakResult<(), <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::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<KK: AsRef<K>>(&self, key: KK)
-> BreakResult<Option<V>, <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::DeError>
{
let mut lock = self.data.write()?;
Ok(lock.remove(key))
}
/// Directly removes a given piece of data from the Database
pub fn get<KK: AsRef<K>>(&self, key: KK)
-> BreakResult<Option<V>, <S as DeSerializer<C>>::SerError, <S as DeSerializer<C>>::DeError>
{
let lock = self.data.read()?;
Ok(lock.get(key).cloned())
}
}
impl<V, K, C, S, F> Database<V, K, C, S, F>
impl<V, S, F> Database<V, S, F>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
C: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<C> + Sync + Send + Debug,
S: DeSerializer<V> + Sync + Send + Debug,
F: Write + Resizable + Debug,
for<'r> &'r mut F: Read
{
/// Exchanges a given deserialization method with another
pub fn with_deser<T>(self, deser: T) -> Database<V, K, C, T, F>
pub fn with_deser<T>(self, deser: T) -> Database<V, T, F>
where
T: DeSerializer<C> + Sync + Send + Debug,
T: DeSerializer<V> + Sync + Send + Debug,
{
Database {
backing: self.backing,
data: self.data,
deser: deser,
data_type: PhantomData,
key_type: PhantomData,
}
}
}
impl<V, K, C, S, F> Database<V, K, C, S, F>
impl<V, S, F> Database<V, S, F>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
C: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<C> + Sync + Send + Debug,
S: DeSerializer<V> + Sync + Send + Debug,
F: Write + Resizable + Debug,
for<'r> &'r mut F: Read
{
/// Exchanges a given backing method with another
pub fn with_backing<T>(self, backing: T) -> Database<V, K, C, S, T>
pub fn with_backing<T>(self, backing: T) -> Database<V, S, T>
where
T: Write + Resizable + Debug,
for<'r> &'r mut T: Read
@@ -357,33 +270,6 @@ impl<V, K, C, S, F> Database<V, K, C, S, F>
backing: Mutex::new(backing),
data: self.data,
deser: self.deser,
data_type: PhantomData,
key_type: PhantomData,
}
}
}
impl<V, K, C, S, F> Database<V, K, C, S, F>
where
V: Serialize + DeserializeOwned + Debug + Clone,
K: Serialize + DeserializeOwned + Debug + Hash + Eq + Clone,
C: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<C> + Sync + Send + Debug,
F: Write + Resizable + Debug,
for<'r> &'r mut F: Read
{
/// Exchanges a given container method with another
pub fn with_container<T>(self, data: T) -> Database<V, K, T, S, F>
where
T: Serialize + DeserializeOwned + Container<K, V> + Debug,
S: DeSerializer<T> + DeSerializer<C> + Sync + Send + Debug,
{
Database {
backing: self.backing,
data: RwLock::new(data),
deser: self.deser,
data_type: PhantomData,
key_type: PhantomData,
}
}
}
+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>