feat(session): integrate actix-web framework with enhanced session management
- Added actix-web and actix-multipart dependencies to Cargo.toml - Integrated actix-web ResponseError trait for AppError handling - Migrated session module to use actix-web request lifecycle management - Enhanced Session struct with request-local state handling capabilities - Implemented proper HTTP status code mapping for various error types - Added comprehensive session middleware integration points - Updated session state persistence and modification tracking logic - Integrated proper JSON response formatting for error messages - Added support for session renewal, purge, and unchanged state management
This commit is contained in:
+94
-38
@@ -1,6 +1,10 @@
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::future::{Ready, ready};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse};
|
||||
use actix_web::{FromRequest, HttpMessage, HttpRequest};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Map, Value};
|
||||
@@ -10,21 +14,30 @@ use crate::error::AppError;
|
||||
|
||||
const SESSION_USER_KEY: &str = "session:user_uid";
|
||||
|
||||
/// Request-local session handle.
|
||||
///
|
||||
/// Clones share the same interior state through request extensions, so changes
|
||||
/// made by extractors/handlers can be collected by `SessionMiddleware` at the
|
||||
/// end of the request lifecycle.
|
||||
#[derive(Clone)]
|
||||
pub struct Session(Rc<RefCell<SessionInner>>);
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum SessionStatus {
|
||||
/// Session state has changed and must be persisted.
|
||||
Changed,
|
||||
/// Session and cookie must be deleted. Further mutations are ignored.
|
||||
Purged,
|
||||
/// Session key must be regenerated while preserving current state.
|
||||
Renewed,
|
||||
/// Session has not been modified during this request.
|
||||
#[default]
|
||||
Unchanged,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SessionInner {
|
||||
state: Map<String, Value>,
|
||||
state: SessionState,
|
||||
status: SessionStatus,
|
||||
}
|
||||
|
||||
@@ -33,7 +46,7 @@ impl Session {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn from_state(state: Map<String, Value>) -> Self {
|
||||
pub fn from_state(state: SessionState) -> Self {
|
||||
Self(Rc::new(RefCell::new(SessionInner {
|
||||
state,
|
||||
status: SessionStatus::Unchanged,
|
||||
@@ -41,34 +54,36 @@ impl Session {
|
||||
}
|
||||
|
||||
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, AppError> {
|
||||
if let Some(value) = self.0.borrow().state.get(key) {
|
||||
Ok(Some(serde_json::from_value(value.clone())?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
let value = self.0.borrow().state.get(key).cloned();
|
||||
value
|
||||
.map(serde_json::from_value)
|
||||
.transpose()
|
||||
.map_err(AppError::Json)
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.0.borrow().state.contains_key(key)
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> Ref<'_, Map<String, Value>> {
|
||||
pub fn entries(&self) -> Ref<'_, SessionState> {
|
||||
Ref::map(self.0.borrow(), |inner| &inner.state)
|
||||
}
|
||||
|
||||
pub fn status(&self) -> SessionStatus {
|
||||
Ref::map(self.0.borrow(), |inner| &inner.status).clone()
|
||||
self.0.borrow().status.clone()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.borrow().state.is_empty()
|
||||
}
|
||||
|
||||
pub fn insert<T: Serialize>(&self, key: impl Into<String>, value: T) -> Result<(), AppError> {
|
||||
let value = serde_json::to_value(value)?;
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
if inner.status != SessionStatus::Purged {
|
||||
if inner.status != SessionStatus::Renewed {
|
||||
inner.status = SessionStatus::Changed;
|
||||
}
|
||||
let val = serde_json::to_value(&value)?;
|
||||
inner.state.insert(key.into(), val);
|
||||
mark_changed(&mut inner.status);
|
||||
inner.state.insert(key.into(), value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -83,22 +98,20 @@ impl Session {
|
||||
F: FnOnce(T) -> T,
|
||||
{
|
||||
let mut inner = self.0.borrow_mut();
|
||||
let key_str = key.into();
|
||||
let key = key.into();
|
||||
|
||||
if let Some(val) = inner.state.get(&key_str) {
|
||||
if inner.status == SessionStatus::Purged {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let value: T = serde_json::from_value(val.clone())?;
|
||||
let updated = serde_json::to_value(updater(value))?;
|
||||
|
||||
if inner.status != SessionStatus::Renewed {
|
||||
inner.status = SessionStatus::Changed;
|
||||
}
|
||||
inner.state.insert(key_str, updated);
|
||||
if inner.status == SessionStatus::Purged {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(value) = inner.state.get(&key).cloned() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let value = serde_json::from_value(value)?;
|
||||
let updated = serde_json::to_value(updater(value))?;
|
||||
mark_changed(&mut inner.status);
|
||||
inner.state.insert(key, updated);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -121,14 +134,12 @@ impl Session {
|
||||
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);
|
||||
if inner.status == SessionStatus::Purged {
|
||||
return None;
|
||||
}
|
||||
|
||||
None
|
||||
mark_changed(&mut inner.status);
|
||||
inner.state.remove(key)
|
||||
}
|
||||
|
||||
pub fn remove_as<T: DeserializeOwned>(&self, key: &str) -> Option<Result<T, AppError>> {
|
||||
@@ -140,9 +151,7 @@ impl Session {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
if inner.status != SessionStatus::Purged {
|
||||
if inner.status != SessionStatus::Renewed {
|
||||
inner.status = SessionStatus::Changed;
|
||||
}
|
||||
mark_changed(&mut inner.status);
|
||||
inner.state.clear();
|
||||
}
|
||||
}
|
||||
@@ -174,13 +183,45 @@ impl Session {
|
||||
}
|
||||
|
||||
pub fn take_state(&self) -> SessionState {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
std::mem::take(&mut inner.state)
|
||||
mem::take(&mut self.0.borrow_mut().state)
|
||||
}
|
||||
|
||||
pub fn mark_unchanged(&self) {
|
||||
self.0.borrow_mut().status = SessionStatus::Unchanged;
|
||||
}
|
||||
|
||||
pub(crate) fn set_session(req: &mut ServiceRequest, state: SessionState) {
|
||||
let session = Self::get_session(&mut req.extensions_mut());
|
||||
let mut inner = session.0.borrow_mut();
|
||||
inner.state = state;
|
||||
inner.status = SessionStatus::Unchanged;
|
||||
}
|
||||
|
||||
pub(crate) fn get_changes<B>(res: &mut ServiceResponse<B>) -> (SessionStatus, SessionState) {
|
||||
let Some(inner) = res
|
||||
.request()
|
||||
.extensions()
|
||||
.get::<Rc<RefCell<SessionInner>>>()
|
||||
.cloned()
|
||||
else {
|
||||
return (SessionStatus::Unchanged, SessionState::new());
|
||||
};
|
||||
|
||||
let mut inner = inner.borrow_mut();
|
||||
let status = inner.status.clone();
|
||||
let state = mem::take(&mut inner.state);
|
||||
(status, state)
|
||||
}
|
||||
|
||||
pub(crate) fn get_session(extensions: &mut Extensions) -> Session {
|
||||
if let Some(inner) = extensions.get::<Rc<RefCell<SessionInner>>>() {
|
||||
return Session(Rc::clone(inner));
|
||||
}
|
||||
|
||||
let inner = Rc::new(RefCell::new(SessionInner::default()));
|
||||
extensions.insert(Rc::clone(&inner));
|
||||
Session(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Session {
|
||||
@@ -193,3 +234,18 @@ pub type SessionState = Map<String, Value>;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SessionUser(pub Uuid);
|
||||
|
||||
impl FromRequest for Session {
|
||||
type Error = AppError;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||
ready(Ok(Self::get_session(&mut req.extensions_mut())))
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_changed(status: &mut SessionStatus) {
|
||||
if *status != SessionStatus::Renewed {
|
||||
*status = SessionStatus::Changed;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user