diff --git a/src/session.rs b/src/session.rs index 71d3198..86892a7 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,4 +1,3 @@ -use json_dotpath::DotPaths; use parking_lot::RwLock; use rand::Rng; use rocket::fairing::{self, Fairing, Info}; @@ -8,30 +7,40 @@ use rocket::{ http::{Cookie, Status}, Outcome, Request, Response, Rocket, State, }; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json::{Map, Value}; +use serde::export::PhantomData; use std::collections::HashMap; use std::ops::Add; use std::time::{Duration, Instant}; -const SESSION_ID: &str = "SESSID"; - -type SessionsMap = HashMap; +const SESSION_COOKIE: &str = "SESSID"; +const SESSION_ID_LEN : usize = 16; +/// Session, as stored in the sessions store #[derive(Debug)] -struct SessionInstance { - data: serde_json::Map, +struct SessionInstance +where + D: 'static + Sync + Send + Default, +{ + /// Data object + data: D, + /// Expiry expires: Instant, } +/// Session store (shared state) #[derive(Default, Debug)] -struct SessionStore { - inner: RwLock, +struct SessionStore +where + D: 'static + Sync + Send + Default, +{ + /// The internaly mutable map of sessions + inner: RwLock>>, + /// Sessions lifespan lifespan: Duration, } +/// Session ID newtype for rocket's "local_cache" #[derive(PartialEq, Hash, Clone, Debug)] struct SessionID(String); @@ -40,7 +49,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a SessionID { fn from_request(request: &'a Request<'r>) -> Outcome { Outcome::Success(request.local_cache(|| { - if let Some(cookie) = request.cookies().get(SESSION_ID) { + if let Some(cookie) = request.cookies().get(SESSION_COOKIE) { SessionID(cookie.value().to_string()) // FIXME avoid cloning (cow?) } else { SessionID( @@ -54,25 +63,34 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a SessionID { } } +/// Session instance #[derive(Debug)] -pub struct Session<'a> { - store: State<'a, SessionStore>, +pub struct Session<'a, D> +where + D: 'static + Sync + Send + Default, +{ + /// The shared state reference + store: State<'a, SessionStore>, + /// Session ID id: &'a SessionID, } -impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> { +impl<'a, 'r, D> FromRequest<'a, 'r> for Session<'a, D> +where + D: 'static + Sync + Send + Default, +{ type Error = (); fn from_request(request: &'a Request<'r>) -> Outcome { Outcome::Success(Session { id: request.local_cache(|| { - if let Some(cookie) = request.cookies().get(SESSION_ID) { + if let Some(cookie) = request.cookies().get(SESSION_COOKIE) { SessionID(cookie.value().to_string()) } else { SessionID( rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) - .take(16) + .take(SESSION_ID_LEN) .collect(), ) } @@ -82,18 +100,27 @@ impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> { } } -impl<'a> Session<'a> { +impl<'a, D> Session<'a, D> +where + D: 'static + Sync + Send + Default, +{ + /// Get the fairing object pub fn fairing(lifespan: Duration) -> impl Fairing { - SessionFairing { lifespan } + SessionFairing:: { + lifespan, + _phantom: PhantomData, + } } - pub fn tap(&self, func: impl FnOnce(&mut serde_json::Map) -> T) -> T { + /// Run a closure with a mutable reference to the session object. + /// The closure's return value is send to the caller. + pub fn tap(&self, func: impl FnOnce(&mut D) -> T) -> T { let mut wg = self.store.inner.write(); if let Some(instance) = wg.get_mut(&self.id.0) { instance.expires = Instant::now().add(self.store.lifespan); func(&mut instance.data) } else { - let mut data = Map::new(); + let mut data = D::default(); let rv = func(&mut data); wg.insert( self.id.0.clone(), @@ -106,53 +133,25 @@ impl<'a> Session<'a> { } } + /// Renew the session pub fn renew(&self) { self.tap(|_| ()) } - - pub fn reset(&self) { - self.tap(|data| data.clear()) - } - - pub fn get(&self, path: &str) -> Option { - self.tap(|data| data.dot_get(path)) - } - - pub fn get_or(&self, path: &str, def: T) -> T { - self.get(path).unwrap_or(def) - } - - pub fn get_or_else T>(&self, path: &str, def: F) -> T { - self.get(path).unwrap_or_else(def) - } - - pub fn get_or_default(&self, path: &str) -> T { - self.get(path).unwrap_or_default() - } - - pub fn take(&self, path: &str) -> Option { - self.tap(|data| data.dot_take(path)) - } - - pub fn replace(&self, path: &str, new: N) -> Option { - self.tap(|data| data.dot_replace(path, new)) - } - - pub fn set(&self, path: &str, value: T) { - self.tap(|data| data.dot_set(path, value)); - } - - pub fn remove(&self, path: &str) -> bool { - self.tap(|data| data.dot_remove(path)) - } } /// Fairing struct -struct SessionFairing { - lifespan: Duration +struct SessionFairing +where + D: 'static + Sync + Send + Default, +{ + lifespan: Duration, + _phantom: PhantomData, } -impl Fairing for SessionFairing { +impl Fairing for SessionFairing +where + D: 'static + Sync + Send + Default, +{ fn info(&self) -> Info { Info { name: "Session", @@ -161,7 +160,7 @@ impl Fairing for SessionFairing { } fn on_attach(&self, rocket: Rocket) -> Result { - Ok(rocket.manage(SessionStore { + Ok(rocket.manage(SessionStore:: { inner: Default::default(), lifespan: self.lifespan, })) @@ -171,7 +170,7 @@ impl Fairing for SessionFairing { let session = request.local_cache(|| SessionID("".to_string())); if !session.0.is_empty() { - response.adjoin_header(Cookie::build(SESSION_ID, session.0.clone()).finish()); + response.adjoin_header(Cookie::build(SESSION_COOKIE, session.0.clone()).finish()); } } }