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] 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~}} +
+ + +