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}