mod config;
mod middleware;
mod store;
pub use config::SessionConfig;
pub use middleware::session;
pub use store::{MemoryStore, SessionStore};
use crate::error::Result;
use serde::{de::DeserializeOwned, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
pub type SessionData = HashMap<String, serde_json::Value>;
#[derive(Clone)]
pub struct Session {
inner: Arc<SessionInner>,
}
struct SessionInner {
id: RwLock<String>,
data: RwLock<SessionData>,
dirty: AtomicBool,
destroyed: AtomicBool,
regenerate: AtomicBool,
}
impl Session {
pub(crate) fn new(id: String, data: SessionData) -> Self {
Self {
inner: Arc::new(SessionInner {
id: RwLock::new(id),
data: RwLock::new(data),
dirty: AtomicBool::new(false),
destroyed: AtomicBool::new(false),
regenerate: AtomicBool::new(false),
}),
}
}
pub async fn id(&self) -> String {
self.inner.id.read().await.clone()
}
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
let data = self.inner.data.read().await;
match data.get(key) {
Some(v) => Ok(Some(serde_json::from_value(v.clone())?)),
None => Ok(None),
}
}
pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<()> {
let v = serde_json::to_value(value)?;
self.inner.data.write().await.insert(key.to_string(), v);
self.inner.dirty.store(true, Ordering::SeqCst);
Ok(())
}
pub async fn remove(&self, key: &str) {
self.inner.data.write().await.remove(key);
self.inner.dirty.store(true, Ordering::SeqCst);
}
pub async fn clear(&self) {
self.inner.data.write().await.clear();
self.inner.dirty.store(true, Ordering::SeqCst);
}
pub fn regenerate(&self) {
self.inner.regenerate.store(true, Ordering::SeqCst);
self.inner.dirty.store(true, Ordering::SeqCst);
}
pub fn destroy(&self) {
self.inner.destroyed.store(true, Ordering::SeqCst);
}
pub(crate) fn is_dirty(&self) -> bool {
self.inner.dirty.load(Ordering::SeqCst)
}
pub(crate) fn is_destroyed(&self) -> bool {
self.inner.destroyed.load(Ordering::SeqCst)
}
pub(crate) async fn snapshot(&self) -> SessionData {
self.inner.data.read().await.clone()
}
pub(crate) fn wants_regenerate(&self) -> bool {
self.inner.regenerate.load(Ordering::SeqCst)
}
pub(crate) async fn is_empty(&self) -> bool {
self.inner.data.read().await.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn set_get_and_dirty() {
let s = Session::new("id".into(), SessionData::new());
assert!(!s.is_dirty());
s.set("n", &7u32).await.unwrap();
assert!(s.is_dirty());
assert_eq!(s.get::<u32>("n").await.unwrap(), Some(7));
assert!(!s.is_empty().await);
}
#[tokio::test]
async fn regenerate_sets_flag() {
let s = Session::new("old".into(), SessionData::new());
assert!(!s.wants_regenerate());
s.regenerate();
assert!(s.wants_regenerate());
assert!(s.is_dirty());
}
}