actix_session_ext/
lib.rs

1//! # actix-session-ext
2//!
3//! The `actix-session-ext` crate provides a safer `actix_session::Session` interface thanks to typed key.
4//!
5//! ## Examples
6//! ```rust,no_run
7//! use actix_web::{Error, Responder, HttpResponse};
8//! use actix_session::Session;
9//! use actix_session_ext::{SessionKey, SessionExt};
10//!
11//! // create an actix application and attach the session middleware to it
12//!
13//! const USER_KEY: SessionKey<String> = SessionKey::new("user");
14//! const TIMESTAMP_KEY: SessionKey<u64> = SessionKey::new("timestamp");
15//!
16//! #[actix_web::post("/login")]
17//! async fn login(session: Session) -> Result<String, Error> {
18//!     session.insert_by_key(USER_KEY, "Dupont".to_owned())?;
19//!     session.insert_by_key(TIMESTAMP_KEY, 1234567890)?;
20//!
21//!    Ok("logged in".to_owned())
22//! }
23//!
24//! #[actix_web::get("/logged_at")]
25//! async fn logged_at(session: Session) -> Result<String, Error> {
26//!    let timestamp = session.get_by_key(TIMESTAMP_KEY)?.unwrap_or_default();
27//!
28//!    Ok(format!("logged at {}", timestamp))
29//! }
30//! ```
31
32#![deny(clippy::pedantic)]
33
34use std::marker::PhantomData;
35
36use actix_session::{Session, SessionGetError, SessionInsertError};
37use serde::{de::DeserializeOwned, Serialize};
38
39/// `SessionKey<T>`, a struct binding a key to its respective type.
40///
41/// This type is useful to avoid common pitfalls when using raw key:
42/// - Mistyped key on both insertion/retrieval
43/// - Wrong type casting on retrieval
44pub struct SessionKey<T> {
45  value: &'static str,
46  _marker: PhantomData<T>,
47}
48
49impl<T: Serialize + DeserializeOwned> SessionKey<T> {
50  /// Constructs a typed session key.
51  #[must_use]
52  pub const fn new(value: &'static str) -> Self {
53    Self {
54      value,
55      _marker: PhantomData,
56    }
57  }
58
59  /// Returns the raw key as a string.
60  #[must_use]
61  pub const fn as_str(&self) -> &'static str {
62    self.value
63  }
64}
65
66/// An extension trait for `actix_session::session` that provides a safer alternative to
67/// `session::get`, `session::insert`, `session::remove` methods.
68/// This trait is implemented for `actix_session::session` and provides methods that take
69/// `SessionKey<T>` instead of raw keys.
70///
71pub trait SessionExt {
72  /// Get a `value` from the session by `key`.
73  ///
74  /// # Errors
75  /// It returns an error if the value is not found or if the value is not of the expected type.
76  fn get_by_key<T: DeserializeOwned>(
77    &self,
78    key: SessionKey<T>,
79  ) -> Result<Option<T>, SessionGetError>;
80
81  /// Insert a `value` into the session by `key`.
82  ///
83  /// # Errors
84  /// It returns an error if the value cannot be serialized.
85  fn insert_by_key<T: Serialize>(
86    &self,
87    key: SessionKey<T>,
88    value: T,
89  ) -> Result<(), SessionInsertError>;
90
91  /// Remove a `value` from the session by `key`.
92  ///
93  /// # Errors
94  /// It returns an error if the value cannot be deserialized.
95  fn remove_by_key<T: DeserializeOwned>(&self, key: SessionKey<T>) -> Result<Option<T>, String>;
96}
97
98impl SessionExt for Session {
99  fn get_by_key<T: DeserializeOwned>(
100    &self,
101    key: SessionKey<T>,
102  ) -> Result<Option<T>, SessionGetError> {
103    self.get::<T>(key.value)
104  }
105
106  fn insert_by_key<T: Serialize>(
107    &self,
108    key: SessionKey<T>,
109    value: T,
110  ) -> Result<(), SessionInsertError> {
111    self.insert(key.value, value)
112  }
113
114  fn remove_by_key<T: DeserializeOwned>(&self, key: SessionKey<T>) -> Result<Option<T>, String> {
115    self.remove_as::<T>(key.value).transpose()
116  }
117}