Skip to main content

actix_cloud/session/
session.rs

1use std::{
2    cell::{Ref, RefCell},
3    mem,
4    rc::Rc,
5};
6
7use actix_utils::future::{ready, Ready};
8use actix_web::{
9    dev::{Extensions, Payload, ServiceRequest, ServiceResponse},
10    error::Error,
11    FromRequest, HttpMessage, HttpRequest,
12};
13use serde::{de::DeserializeOwned, Serialize};
14use serde_json::{Map, Value};
15
16use crate::Result;
17
18/// The primary interface to access and modify session state.
19///
20/// [`Session`] is an [extractor](#impl-FromRequest)—you can specify it as an input type for your
21/// request handlers and it will be automatically extracted from the incoming request.
22///
23/// You can also retrieve a [`Session`] object from an `HttpRequest` or a `ServiceRequest` using
24/// [`SessionExt`].
25///
26/// [`SessionExt`]: super::SessionExt
27#[derive(Clone)]
28pub struct Session(Rc<RefCell<SessionInner>>);
29
30/// Status of a [`Session`].
31#[derive(Debug, Clone, Default, PartialEq, Eq)]
32pub enum SessionStatus {
33    /// Session state has been updated - the changes will have to be persisted to the backend.
34    Changed,
35
36    /// The session has been flagged for deletion - the session cookie will be removed from
37    /// the client and the session state will be deleted from the session store.
38    ///
39    /// Most operations on the session after it has been marked for deletion will have no effect.
40    Purged,
41
42    /// The session has been flagged for renewal.
43    ///
44    /// The session key will be regenerated and the time-to-live of the session state will be
45    /// extended.
46    Renewed,
47
48    /// The session state has not been modified since its creation/retrieval.
49    #[default]
50    Unchanged,
51}
52
53#[derive(Default)]
54struct SessionInner {
55    state: Map<String, Value>,
56    status: SessionStatus,
57}
58
59impl Session {
60    /// Get a `value` from the session.
61    ///
62    /// It returns an error if it fails to deserialize as `T` the JSON value associated with `key`.
63    pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
64        if let Some(value) = self.0.borrow().state.get(key) {
65            Ok(Some(serde_json::from_value::<T>(value.clone())?))
66        } else {
67            Ok(None)
68        }
69    }
70
71    /// Returns `true` if the session contains a value for the specified `key`.
72    pub fn contains_key(&self, key: &str) -> bool {
73        self.0.borrow().state.contains_key(key)
74    }
75
76    /// Get all raw key-value data from the session.
77    ///
78    /// Note that values are JSON values.
79    pub fn entries(&self) -> Ref<'_, Map<String, Value>> {
80        Ref::map(self.0.borrow(), |inner| &inner.state)
81    }
82
83    /// Returns session status.
84    pub fn status(&self) -> SessionStatus {
85        Ref::map(self.0.borrow(), |inner| &inner.status).clone()
86    }
87
88    /// Inserts a key-value pair into the session.
89    ///
90    /// Any serializable value can be used and will be encoded as JSON in session data, hence why
91    /// only a reference to the value is taken.
92    ///
93    /// # Errors
94    ///
95    /// Returns an error if JSON serialization of `value` fails.
96    pub fn insert<T: Serialize>(&self, key: impl Into<String>, value: T) -> Result<()> {
97        let mut inner = self.0.borrow_mut();
98
99        if inner.status != SessionStatus::Purged {
100            if inner.status != SessionStatus::Renewed {
101                inner.status = SessionStatus::Changed;
102            }
103
104            let key = key.into();
105            let val = serde_json::to_value(&value)?;
106
107            inner.state.insert(key, val);
108        }
109
110        Ok(())
111    }
112
113    /// Updates a key-value pair into the session.
114    ///
115    /// If the key exists then update it to the new value and place it back in. If the key does not
116    /// exist it will not be updated.
117    ///
118    /// Any serializable value can be used and will be encoded as JSON in the session data, hence
119    /// why only a reference to the value is taken.
120    ///
121    /// # Errors
122    ///
123    /// Returns an error if JSON serialization of the value fails.
124    pub fn update<T: Serialize + DeserializeOwned, F>(
125        &self,
126        key: impl Into<String>,
127        updater: F,
128    ) -> Result<()>
129    where
130        F: FnOnce(T) -> T,
131    {
132        let mut inner = self.0.borrow_mut();
133        let key_str = key.into();
134
135        if let Some(val) = inner.state.get(&key_str) {
136            let value = serde_json::from_value(val.clone())?;
137
138            let val = serde_json::to_value(updater(value))?;
139
140            inner.state.insert(key_str, val);
141        }
142
143        Ok(())
144    }
145
146    /// Updates a key-value pair into the session, or inserts a default value.
147    ///
148    /// If the key exists then update it to the new value and place it back in. If the key does not
149    /// exist the default value will be inserted instead.
150    ///
151    /// Any serializable value can be used and will be encoded as JSON in session data, hence why
152    /// only a reference to the value is taken.
153    ///
154    /// # Errors
155    ///
156    /// Returns error if JSON serialization of a value fails.
157    pub fn update_or<T: Serialize + DeserializeOwned, F>(
158        &self,
159        key: &str,
160        default_value: T,
161        updater: F,
162    ) -> Result<()>
163    where
164        F: FnOnce(T) -> T,
165    {
166        if self.contains_key(key) {
167            self.update(key, updater)
168        } else {
169            self.insert(key, default_value)
170        }
171    }
172
173    /// Remove value from the session.
174    ///
175    /// If present, the JSON value is returned.
176    pub fn remove(&self, key: &str) -> Option<Value> {
177        let mut inner = self.0.borrow_mut();
178
179        if inner.status != SessionStatus::Purged {
180            if inner.status != SessionStatus::Renewed {
181                inner.status = SessionStatus::Changed;
182            }
183            return inner.state.remove(key);
184        }
185
186        None
187    }
188
189    /// Remove value from the session and deserialize.
190    ///
191    /// Returns `None` if key was not present in session. Returns `T` if deserialization succeeds,
192    /// otherwise returns the raw JSON value.
193    pub fn remove_as<T: DeserializeOwned>(&self, key: &str) -> Option<Result<T, Value>> {
194        self.remove(key)
195            .map(|value| match serde_json::from_value::<T>(value.clone()) {
196                Ok(val) => Ok(val),
197                Err(_err) => Err(value),
198            })
199    }
200
201    /// Clear the session.
202    pub fn clear(&self) {
203        let mut inner = self.0.borrow_mut();
204
205        if inner.status != SessionStatus::Purged {
206            if inner.status != SessionStatus::Renewed {
207                inner.status = SessionStatus::Changed;
208            }
209            inner.state.clear()
210        }
211    }
212
213    /// Removes session both client and server side.
214    pub fn purge(&self) {
215        let mut inner = self.0.borrow_mut();
216        inner.status = SessionStatus::Purged;
217        inner.state.clear();
218    }
219
220    /// Renews the session key, assigning existing session state to new key.
221    pub fn renew(&self) {
222        let mut inner = self.0.borrow_mut();
223
224        if inner.status != SessionStatus::Purged {
225            inner.status = SessionStatus::Renewed;
226        }
227    }
228
229    /// Adds the given key-value pairs to the session on the request.
230    ///
231    /// Values that match keys already existing on the session will be overwritten. Values should
232    /// already be JSON values.
233    #[allow(clippy::needless_pass_by_ref_mut)]
234    pub(crate) fn set_session(
235        req: &mut ServiceRequest,
236        data: impl IntoIterator<Item = (String, Value)>,
237    ) {
238        let session = Session::get_session(&mut req.extensions_mut());
239        let mut inner = session.0.borrow_mut();
240        inner.state.extend(data);
241    }
242
243    /// Returns session status and iterator of key-value pairs of changes.
244    ///
245    /// This is a destructive operation - the session state is removed from the request extensions
246    /// typemap, leaving behind a new empty map. It should only be used when the session is being
247    /// finalised (i.e. in `SessionMiddleware`).
248    #[allow(clippy::needless_pass_by_ref_mut)]
249    pub(crate) fn get_changes<B>(
250        res: &mut ServiceResponse<B>,
251    ) -> (SessionStatus, Map<String, Value>) {
252        if let Some(s_impl) = res
253            .request()
254            .extensions()
255            .get::<Rc<RefCell<SessionInner>>>()
256        {
257            let state = mem::take(&mut s_impl.borrow_mut().state);
258            (s_impl.borrow().status.clone(), state)
259        } else {
260            (SessionStatus::Unchanged, Map::new())
261        }
262    }
263
264    pub(crate) fn get_session(extensions: &mut Extensions) -> Session {
265        if let Some(s_impl) = extensions.get::<Rc<RefCell<SessionInner>>>() {
266            return Session(Rc::clone(s_impl));
267        }
268
269        let inner = Rc::new(RefCell::new(SessionInner::default()));
270        extensions.insert(inner.clone());
271
272        Session(inner)
273    }
274}
275
276/// Extractor implementation for [`Session`]s.
277impl FromRequest for Session {
278    type Error = Error;
279    type Future = Ready<Result<Session, Error>>;
280
281    #[inline]
282    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
283        ready(Ok(Session::get_session(&mut req.extensions_mut())))
284    }
285}