use std::{
cell::{Ref, RefCell},
mem,
rc::Rc,
};
use actix_utils::future::{ready, Ready};
use actix_web::{
dev::{Extensions, Payload, ServiceRequest, ServiceResponse},
error::Error,
FromRequest, HttpMessage, HttpRequest,
};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::{Map, Value};
use crate::Result;
#[derive(Clone)]
pub struct Session(Rc<RefCell<SessionInner>>);
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum SessionStatus {
Changed,
Purged,
Renewed,
#[default]
Unchanged,
}
#[derive(Default)]
struct SessionInner {
state: Map<String, Value>,
status: SessionStatus,
}
impl Session {
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
if let Some(value) = self.0.borrow().state.get(key) {
Ok(Some(serde_json::from_value::<T>(value.clone())?))
} else {
Ok(None)
}
}
pub fn contains_key(&self, key: &str) -> bool {
self.0.borrow().state.contains_key(key)
}
pub fn entries(&self) -> Ref<'_, Map<String, Value>> {
Ref::map(self.0.borrow(), |inner| &inner.state)
}
pub fn status(&self) -> SessionStatus {
Ref::map(self.0.borrow(), |inner| &inner.status).clone()
}
pub fn insert<T: Serialize>(&self, key: impl Into<String>, value: T) -> Result<()> {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
if inner.status != SessionStatus::Renewed {
inner.status = SessionStatus::Changed;
}
let key = key.into();
let val = serde_json::to_value(&value)?;
inner.state.insert(key, val);
}
Ok(())
}
pub fn update<T: Serialize + DeserializeOwned, F>(
&self,
key: impl Into<String>,
updater: F,
) -> Result<()>
where
F: FnOnce(T) -> T,
{
let mut inner = self.0.borrow_mut();
let key_str = key.into();
if let Some(val) = inner.state.get(&key_str) {
let value = serde_json::from_value(val.clone())?;
let val = serde_json::to_value(updater(value))?;
inner.state.insert(key_str, val);
}
Ok(())
}
pub fn update_or<T: Serialize + DeserializeOwned, F>(
&self,
key: &str,
default_value: T,
updater: F,
) -> Result<()>
where
F: FnOnce(T) -> T,
{
if self.contains_key(key) {
self.update(key, updater)
} else {
self.insert(key, default_value)
}
}
pub fn remove(&self, key: &str) -> Option<Value> {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
if inner.status != SessionStatus::Renewed {
inner.status = SessionStatus::Changed;
}
return inner.state.remove(key);
}
None
}
pub fn remove_as<T: DeserializeOwned>(&self, key: &str) -> Option<Result<T, Value>> {
self.remove(key)
.map(|value| match serde_json::from_value::<T>(value.clone()) {
Ok(val) => Ok(val),
Err(_err) => Err(value),
})
}
pub fn clear(&self) {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
if inner.status != SessionStatus::Renewed {
inner.status = SessionStatus::Changed;
}
inner.state.clear()
}
}
pub fn purge(&self) {
let mut inner = self.0.borrow_mut();
inner.status = SessionStatus::Purged;
inner.state.clear();
}
pub fn renew(&self) {
let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Renewed;
}
}
#[allow(clippy::needless_pass_by_ref_mut)]
pub(crate) fn set_session(
req: &mut ServiceRequest,
data: impl IntoIterator<Item = (String, Value)>,
) {
let session = Session::get_session(&mut req.extensions_mut());
let mut inner = session.0.borrow_mut();
inner.state.extend(data);
}
#[allow(clippy::needless_pass_by_ref_mut)]
pub(crate) fn get_changes<B>(
res: &mut ServiceResponse<B>,
) -> (SessionStatus, Map<String, Value>) {
if let Some(s_impl) = res
.request()
.extensions()
.get::<Rc<RefCell<SessionInner>>>()
{
let state = mem::take(&mut s_impl.borrow_mut().state);
(s_impl.borrow().status.clone(), state)
} else {
(SessionStatus::Unchanged, Map::new())
}
}
pub(crate) fn get_session(extensions: &mut Extensions) -> Session {
if let Some(s_impl) = extensions.get::<Rc<RefCell<SessionInner>>>() {
return Session(Rc::clone(s_impl));
}
let inner = Rc::new(RefCell::new(SessionInner::default()));
extensions.insert(inner.clone());
Session(inner)
}
}
impl FromRequest for Session {
type Error = Error;
type Future = Ready<Result<Session, Error>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(Ok(Session::get_session(&mut req.extensions_mut())))
}
}