1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-03-25 22:19:45 +01:00

fix double serialization in session state

This commit is contained in:
zignis 2023-11-19 11:09:15 +05:30
parent 3c5478966f
commit c74b8451c6
21 changed files with 154 additions and 100 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

31
.idea/actix-extras.iml generated Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/actix-cors/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-cors/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-cors/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/actix-identity/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-identity/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-identity/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/actix-limitation/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-limitation/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/actix-protobuf/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-redis/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-redis/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/actix-session/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-session/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-session/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/actix-settings/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-settings/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-web-httpauth/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-web-httpauth/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-ws/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-ws/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/actix-extras.iml" filepath="$PROJECT_DIR$/.idea/actix-extras.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -2,6 +2,10 @@
## Unreleased ## Unreleased
## 0.7.0
- Updated the usage of HashMap with serde Map to be compatible with `actix-session@0.9.0`.
## 0.6.0 ## 0.6.0
- Add `error` module. - Add `error` module.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-identity" name = "actix-identity"
version = "0.6.0" version = "0.7.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>", "Luca Palmieri <rust@lpalmieri.com>",
@ -19,19 +19,20 @@ all-features = true
[dependencies] [dependencies]
actix-service = "2" actix-service = "2"
actix-session = "0.8" actix-session = "0.9"
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] } actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
derive_more = "0.99.7" derive_more = "0.99.7"
futures-core = "0.3.7" futures-core = "0.3.7"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0.108"
tracing = { version = "0.1.30", default-features = false, features = ["log"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] }
[dev-dependencies] [dev-dependencies]
actix-http = "3" actix-http = "3"
actix-web = { version = "4", default-features = false, features = ["macros", "cookies", "secure-cookies"] } actix-web = { version = "4", default-features = false, features = ["macros", "cookies", "secure-cookies"] }
actix-session = { version = "0.8", features = ["redis-rs-session", "cookie-session"] } actix-session = { version = "0.9", features = ["redis-rs-session", "cookie-session"] }
env_logger = "0.10" env_logger = "0.10"
reqwest = { version = "0.11", default-features = false, features = ["cookies", "json"] } reqwest = { version = "0.11", default-features = false, features = ["cookies", "json"] }

View File

@ -6,6 +6,7 @@ use actix_web::{
http::StatusCode, http::StatusCode,
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, Error, FromRequest, HttpMessage, HttpRequest, HttpResponse,
}; };
use serde_json::Value;
use crate::{ use crate::{
config::LogoutBehaviour, config::LogoutBehaviour,
@ -153,13 +154,17 @@ impl Identity {
/// ``` /// ```
pub fn login(ext: &Extensions, id: String) -> Result<Self, LoginError> { pub fn login(ext: &Extensions, id: String) -> Result<Self, LoginError> {
let inner = IdentityInner::extract(ext); let inner = IdentityInner::extract(ext);
inner.session.insert(ID_KEY, id)?; inner.session.insert(ID_KEY, Value::from(id));
let now = OffsetDateTime::now_utc().unix_timestamp(); let now = OffsetDateTime::now_utc().unix_timestamp();
if inner.is_login_deadline_enabled { if inner.is_login_deadline_enabled {
inner.session.insert(LOGIN_UNIX_TIMESTAMP_KEY, now)?; inner
.session
.insert(LOGIN_UNIX_TIMESTAMP_KEY, Value::from(now));
} }
if inner.is_visit_deadline_enabled { if inner.is_visit_deadline_enabled {
inner.session.insert(LAST_VISIT_UNIX_TIMESTAMP_KEY, now)?; inner
.session
.insert(LAST_VISIT_UNIX_TIMESTAMP_KEY, Value::from(now));
} }
inner.session.renew(); inner.session.renew();
Ok(Self(inner)) Ok(Self(inner))
@ -230,7 +235,9 @@ impl Identity {
pub(crate) fn set_last_visited_at(&self) -> Result<(), LoginError> { pub(crate) fn set_last_visited_at(&self) -> Result<(), LoginError> {
let now = OffsetDateTime::now_utc().unix_timestamp(); let now = OffsetDateTime::now_utc().unix_timestamp();
self.0.session.insert(LAST_VISIT_UNIX_TIMESTAMP_KEY, now)?; self.0
.session
.insert(LAST_VISIT_UNIX_TIMESTAMP_KEY, Value::from(now));
Ok(()) Ok(())
} }
} }

View File

@ -4,6 +4,7 @@ use actix_identity::{config::IdentityMiddlewareBuilder, Identity, IdentityMiddle
use actix_session::{Session, SessionStatus}; use actix_session::{Session, SessionStatus};
use actix_web::{web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer}; use actix_web::{web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::fixtures::session_middleware; use crate::fixtures::session_middleware;
@ -136,7 +137,7 @@ async fn increment(session: Session, user: Option<Identity>) -> HttpResponse {
.get::<i32>("counter") .get::<i32>("counter")
.unwrap_or(Some(0)) .unwrap_or(Some(0))
.map_or(1, |inner| inner + 1); .map_or(1, |inner| inner + 1);
session.insert("counter", counter).unwrap(); session.insert("counter", Value::from(counter));
HttpResponse::Ok().json(&EndpointResponse { HttpResponse::Ok().json(&EndpointResponse {
user_id, user_id,

View File

@ -2,6 +2,11 @@
## Unreleased ## Unreleased
## 0.9.0
- Replace the usages of `HashMap` with serde `Map`.
- Fix double serialization when inserting values into the session.
## 0.8.0 ## 0.8.0
- Set secure attribute when adding a session removal cookie. - Set secure attribute when adding a session removal cookie.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-session" name = "actix-session"
version = "0.8.0" version = "0.9.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>", "Luca Palmieri <rust@lpalmieri.com>",

View File

@ -5,6 +5,7 @@ use actix_web::{
middleware, web, App, Error, HttpResponse, HttpServer, Responder, middleware, web, App, Error, HttpResponse, HttpServer, Responder,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Deserialize)] #[derive(Deserialize)]
struct Credentials { struct Credentials {
@ -54,7 +55,7 @@ async fn login(
let credentials = credentials.into_inner(); let credentials = credentials.into_inner();
match User::authenticate(credentials) { match User::authenticate(credentials) {
Ok(user) => session.insert("user_id", user.id).unwrap(), Ok(user) => session.insert("user_id", Value::from(user.id)),
Err(err) => return Err(InternalError::from_response("", err).into()), Err(err) => return Err(InternalError::from_response("", err).into()),
}; };

View File

@ -1,5 +1,6 @@
use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware}; use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware};
use actix_web::{cookie::Key, middleware, web, App, Error, HttpRequest, HttpServer, Responder}; use actix_web::{cookie::Key, middleware, web, App, Error, HttpRequest, HttpServer, Responder};
use serde_json::Value;
/// simple handler /// simple handler
async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Error> { async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Error> {
@ -8,9 +9,9 @@ async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Err
// session // session
if let Some(count) = session.get::<i32>("counter")? { if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {count}"); println!("SESSION value: {count}");
session.insert("counter", count + 1)?; session.insert("counter", Value::from(count + 1));
} else { } else {
session.insert("counter", 1)?; session.insert("counter", Value::from(1));
} }
Ok("Welcome!") Ok("Welcome!")

View File

@ -69,6 +69,7 @@
//! //!
//! ```no_run //! ```no_run
//! use actix_web::Error; //! use actix_web::Error;
//! use serde_json::Value;
//! use actix_session::Session; //! use actix_session::Session;
//! //!
//! fn index(session: Session) -> Result<&'static str, Error> { //! fn index(session: Session) -> Result<&'static str, Error> {
@ -76,9 +77,9 @@
//! if let Some(count) = session.get::<i32>("counter")? { //! if let Some(count) = session.get::<i32>("counter")? {
//! println!("SESSION value: {}", count); //! println!("SESSION value: {}", count);
//! // modify the session state //! // modify the session state
//! session.insert("counter", count + 1)?; //! session.insert("counter", Value::from(count + 1));
//! } else { //! } else {
//! session.insert("counter", 1)?; //! session.insert("counter", Value::from(1));
//! } //! }
//! //!
//! Ok("Welcome!") //! Ok("Welcome!")
@ -207,7 +208,7 @@ pub mod test_helpers {
App, HttpResponse, Result, App, HttpResponse, Result,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::{json, Value};
use crate::{ use crate::{
config::{CookieContentSecurity, PersistentSession, TtlExtensionPolicy}, config::{CookieContentSecurity, PersistentSession, TtlExtensionPolicy},
@ -238,7 +239,7 @@ pub mod test_helpers {
.build(), .build(),
) )
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.insert("counter", 100); ses.insert("counter", Value::from(100));
"test" "test"
})) }))
.service(web::resource("/test/").to(|ses: Session| async move { .service(web::resource("/test/").to(|ses: Session| async move {
@ -286,7 +287,7 @@ pub mod test_helpers {
.build(), .build(),
) )
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.insert("counter", 100); ses.insert("counter", Value::from(100));
"test" "test"
})) }))
.service(web::resource("/test/").to(|| async move { "no-changes-in-session" })), .service(web::resource("/test/").to(|| async move { "no-changes-in-session" })),
@ -328,7 +329,7 @@ pub mod test_helpers {
.build(), .build(),
) )
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.insert("counter", 100); ses.insert("counter", Value::from(100));
"test" "test"
})) }))
.service(web::resource("/test/").to(|| async move { "no-changes-in-session" })), .service(web::resource("/test/").to(|| async move { "no-changes-in-session" })),
@ -674,7 +675,7 @@ pub mod test_helpers {
.get::<i32>("counter") .get::<i32>("counter")
.unwrap_or(Some(0)) .unwrap_or(Some(0))
.map_or(1, |inner| inner + 1); .map_or(1, |inner| inner + 1);
session.insert("counter", counter)?; session.insert("counter", Value::from(counter));
Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter })) Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter }))
} }
@ -693,7 +694,7 @@ pub mod test_helpers {
async fn login(user_id: web::Json<Identity>, session: Session) -> Result<HttpResponse> { async fn login(user_id: web::Json<Identity>, session: Session) -> Result<HttpResponse> {
let id = user_id.into_inner().user_id; let id = user_id.into_inner().user_id;
session.insert("user_id", &id)?; session.insert("user_id", Value::from(id.clone()));
session.renew(); session.renew();
let counter: i32 = session let counter: i32 = session

View File

@ -1,5 +1,5 @@
use std::{collections::HashMap, convert::TryInto, fmt, future::Future, pin::Pin, rc::Rc}; use std::{ convert::TryInto, fmt, future::Future, pin::Pin, rc::Rc};
use serde_json::{Map,Value};
use actix_utils::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use actix_web::{ use actix_web::{
body::MessageBody, body::MessageBody,
@ -360,7 +360,7 @@ fn extract_session_key(req: &ServiceRequest, config: &CookieConfiguration) -> Op
async fn load_session_state<Store: SessionStore>( async fn load_session_state<Store: SessionStore>(
session_key: Option<SessionKey>, session_key: Option<SessionKey>,
storage_backend: &Store, storage_backend: &Store,
) -> Result<(Option<SessionKey>, HashMap<String, String>), actix_web::Error> { ) -> Result<(Option<SessionKey>, Map<String, Value>), actix_web::Error> {
if let Some(session_key) = session_key { if let Some(session_key) = session_key {
match storage_backend.load(&session_key).await { match storage_backend.load(&session_key).await {
Ok(state) => { Ok(state) => {
@ -378,7 +378,7 @@ async fn load_session_state<Store: SessionStore>(
empty session." empty session."
); );
Ok((None, HashMap::new())) Ok((None, Map::new()))
} }
} }
@ -390,14 +390,14 @@ async fn load_session_state<Store: SessionStore>(
"Invalid session state, creating a new empty session." "Invalid session state, creating a new empty session."
); );
Ok((Some(session_key), HashMap::new())) Ok((Some(session_key), Map::new()))
} }
LoadError::Other(err) => Err(e500(err)), LoadError::Other(err) => Err(e500(err)),
}, },
} }
} else { } else {
Ok((None, HashMap::new())) Ok((None, Map::new()))
} }
} }

View File

@ -1,11 +1,3 @@
use std::{
cell::{Ref, RefCell},
collections::HashMap,
error::Error as StdError,
mem,
rc::Rc,
};
use actix_utils::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use actix_web::{ use actix_web::{
body::BoxBody, body::BoxBody,
@ -15,7 +7,14 @@ use actix_web::{
}; };
use anyhow::Context; use anyhow::Context;
use derive_more::{Display, From}; use derive_more::{Display, From};
use serde::{de::DeserializeOwned, Serialize}; use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use std::{
cell::{Ref, RefCell},
error::Error as StdError,
mem,
rc::Rc,
};
/// The primary interface to access and modify session state. /// The primary interface to access and modify session state.
/// ///
@ -23,14 +22,15 @@ use serde::{de::DeserializeOwned, Serialize};
/// request handlers and it will be automatically extracted from the incoming request. /// request handlers and it will be automatically extracted from the incoming request.
/// ///
/// ``` /// ```
/// use serde_json::Value;
/// use actix_session::Session; /// use actix_session::Session;
/// ///
/// async fn index(session: Session) -> actix_web::Result<&'static str> { /// async fn index(session: Session) -> actix_web::Result<&'static str> {
/// // access session data /// // access session data
/// if let Some(count) = session.get::<i32>("counter")? { /// if let Some(count) = session.get::<i32>("counter")? {
/// session.insert("counter", count + 1)?; /// session.insert("counter", Value::from(count + 1));
/// } else { /// } else {
/// session.insert("counter", 1)?; /// session.insert("counter", Value::from(1));
/// } /// }
/// ///
/// Ok("Welcome!") /// Ok("Welcome!")
@ -70,7 +70,7 @@ pub enum SessionStatus {
#[derive(Default)] #[derive(Default)]
struct SessionInner { struct SessionInner {
state: HashMap<String, String>, state: Map<String, Value>,
status: SessionStatus, status: SessionStatus,
} }
@ -79,9 +79,9 @@ impl Session {
/// ///
/// It returns an error if it fails to deserialize as `T` the JSON value associated with `key`. /// It returns an error if it fails to deserialize as `T` the JSON value associated with `key`.
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, SessionGetError> { pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, SessionGetError> {
if let Some(val_str) = self.0.borrow().state.get(key) { if let Some(value) = self.0.borrow().state.get(key) {
Ok(Some( Ok(Some(
serde_json::from_str(val_str) serde_json::from_value::<T>(value.clone())
.with_context(|| { .with_context(|| {
format!( format!(
"Failed to deserialize the JSON-encoded session data attached to key \ "Failed to deserialize the JSON-encoded session data attached to key \
@ -100,7 +100,7 @@ impl Session {
/// Get all raw key-value data from the session. /// Get all raw key-value data from the session.
/// ///
/// Note that values are JSON encoded. /// Note that values are JSON encoded.
pub fn entries(&self) -> Ref<'_, HashMap<String, String>> { pub fn entries(&self) -> Ref<'_, Map<String, Value>> {
Ref::map(self.0.borrow(), |inner| &inner.state) Ref::map(self.0.borrow(), |inner| &inner.state)
} }
@ -111,15 +111,8 @@ impl Session {
/// Inserts a key-value pair into the session. /// Inserts a key-value pair into the session.
/// ///
/// Any serializable value can be used and will be encoded as JSON in session data, hence why /// Any serializable value can be used and will be encoded as JSON in session data.
/// only a reference to the value is taken. pub fn insert(&self, key: impl Into<String>, value: Value) {
///
/// It returns an error if it fails to serialize `value` to JSON.
pub fn insert<T: Serialize>(
&self,
key: impl Into<String>,
value: T,
) -> Result<(), SessionInsertError> {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged { if inner.status != SessionStatus::Purged {
@ -127,28 +120,14 @@ impl Session {
inner.status = SessionStatus::Changed; inner.status = SessionStatus::Changed;
} }
let key = key.into(); inner.state.insert(key.into(), value);
let val = serde_json::to_string(&value)
.with_context(|| {
format!(
"Failed to serialize the provided `{}` type instance as JSON in order to \
attach as session data to the `{}` key",
std::any::type_name::<T>(),
&key
)
})
.map_err(SessionInsertError)?;
inner.state.insert(key, val);
} }
Ok(())
} }
/// Remove value from the session. /// Remove value from the session.
/// ///
/// If present, the JSON encoded value is returned. /// If present, the JSON encoded value is returned.
pub fn remove(&self, key: &str) -> Option<String> { pub fn remove(&self, key: &str) -> Option<Value> {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged { if inner.status != SessionStatus::Purged {
@ -165,9 +144,9 @@ impl Session {
/// ///
/// Returns `None` if key was not present in session. Returns `T` if deserialization succeeds, /// Returns `None` if key was not present in session. Returns `T` if deserialization succeeds,
/// otherwise returns un-deserialized JSON string. /// otherwise returns un-deserialized JSON string.
pub fn remove_as<T: DeserializeOwned>(&self, key: &str) -> Option<Result<T, String>> { pub fn remove_as<T: DeserializeOwned>(&self, key: &str) -> Option<Result<T, Value>> {
self.remove(key) self.remove(key)
.map(|val_str| match serde_json::from_str(&val_str) { .map(|value| match serde_json::from_value::<T>(value.clone()) {
Ok(val) => Ok(val), Ok(val) => Ok(val),
Err(_err) => { Err(_err) => {
tracing::debug!( tracing::debug!(
@ -176,7 +155,7 @@ impl Session {
std::any::type_name::<T>() std::any::type_name::<T>()
); );
Err(val_str) Err(value)
} }
}) })
} }
@ -216,7 +195,7 @@ impl Session {
#[allow(clippy::needless_pass_by_ref_mut)] #[allow(clippy::needless_pass_by_ref_mut)]
pub(crate) fn set_session( pub(crate) fn set_session(
req: &mut ServiceRequest, req: &mut ServiceRequest,
data: impl IntoIterator<Item = (String, String)>, data: impl IntoIterator<Item = (String, Value)>,
) { ) {
let session = Session::get_session(&mut req.extensions_mut()); let session = Session::get_session(&mut req.extensions_mut());
let mut inner = session.0.borrow_mut(); let mut inner = session.0.borrow_mut();
@ -231,7 +210,7 @@ impl Session {
#[allow(clippy::needless_pass_by_ref_mut)] #[allow(clippy::needless_pass_by_ref_mut)]
pub(crate) fn get_changes<B>( pub(crate) fn get_changes<B>(
res: &mut ServiceResponse<B>, res: &mut ServiceResponse<B>,
) -> (SessionStatus, HashMap<String, String>) { ) -> (SessionStatus, Map<String, Value>) {
if let Some(s_impl) = res if let Some(s_impl) = res
.request() .request()
.extensions() .extensions()
@ -240,7 +219,7 @@ impl Session {
let state = mem::take(&mut s_impl.borrow_mut().state); let state = mem::take(&mut s_impl.borrow_mut().state);
(s_impl.borrow().status.clone(), state) (s_impl.borrow().status.clone(), state)
} else { } else {
(SessionStatus::Unchanged, HashMap::new()) (SessionStatus::Unchanged, Map::new())
} }
} }
@ -261,15 +240,16 @@ impl Session {
/// # Examples /// # Examples
/// ``` /// ```
/// # use actix_web::*; /// # use actix_web::*;
/// use serde_json::Value;
/// use actix_session::Session; /// use actix_session::Session;
/// ///
/// #[get("/")] /// #[get("/")]
/// async fn index(session: Session) -> Result<impl Responder> { /// async fn index(session: Session) -> Result<impl Responder> {
/// // access session data /// // access session data
/// if let Some(count) = session.get::<i32>("counter")? { /// if let Some(count) = session.get::<i32>("counter")? {
/// session.insert("counter", count + 1)?; /// session.insert("counter", Value::from(count + 1));
/// } else { /// } else {
/// session.insert("counter", 1)?; /// session.insert("counter", Value::from(1));
/// } /// }
/// ///
/// let count = session.get::<i32>("counter")?.unwrap(); /// let count = session.get::<i32>("counter")?.unwrap();

View File

@ -1,11 +1,10 @@
use std::collections::HashMap; use serde_json::{Map,Value};
use actix_web::cookie::time::Duration; use actix_web::cookie::time::Duration;
use derive_more::Display; use derive_more::Display;
use super::SessionKey; use super::SessionKey;
pub(crate) type SessionState = HashMap<String, String>; pub(crate) type SessionState = Map<String, Value>;
/// The interface to retrieve and save the current session data from/to the chosen storage backend. /// The interface to retrieve and save the current session data from/to the chosen storage backend.
/// ///

View File

@ -276,9 +276,8 @@ impl SessionStore for RedisActorSessionStore {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashMap;
use actix_web::cookie::time::Duration; use actix_web::cookie::time::Duration;
use serde_json::Map;
use super::*; use super::*;
use crate::test_helpers::acceptance_test_suite; use crate::test_helpers::acceptance_test_suite;
@ -305,7 +304,7 @@ mod tests {
let session_key = generate_session_key(); let session_key = generate_session_key();
let initial_session_key = session_key.as_ref().to_owned(); let initial_session_key = session_key.as_ref().to_owned();
let updated_session_key = store let updated_session_key = store
.update(session_key, HashMap::new(), &Duration::seconds(1)) .update(session_key, Map::new(), &Duration::seconds(1))
.await .await
.unwrap(); .unwrap();
assert_ne!(initial_session_key, updated_session_key.as_ref()); assert_ne!(initial_session_key, updated_session_key.as_ref());

View File

@ -289,10 +289,9 @@ impl RedisSessionStore {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashMap;
use actix_web::cookie::time; use actix_web::cookie::time;
use redis::AsyncCommands; use redis::AsyncCommands;
use serde_json::Map;
use super::*; use super::*;
use crate::test_helpers::acceptance_test_suite; use crate::test_helpers::acceptance_test_suite;
@ -338,7 +337,7 @@ mod tests {
let session_key = generate_session_key(); let session_key = generate_session_key();
let initial_session_key = session_key.as_ref().to_owned(); let initial_session_key = session_key.as_ref().to_owned();
let updated_session_key = store let updated_session_key = store
.update(session_key, HashMap::new(), &time::Duration::seconds(1)) .update(session_key, Map::new(), &time::Duration::seconds(1))
.await .await
.unwrap(); .unwrap();
assert_ne!(initial_session_key, updated_session_key.as_ref()); assert_ne!(initial_session_key, updated_session_key.as_ref());

View File

@ -3,9 +3,10 @@ use actix_web::{
cookie::{time::Duration, Key}, cookie::{time::Duration, Key},
test, web, App, Responder, test, web, App, Responder,
}; };
use serde_json::Value;
async fn login(session: Session) -> impl Responder { async fn login(session: Session) -> impl Responder {
session.insert("user_id", "id").unwrap(); session.insert("user_id", Value::from("id"));
"Logged in" "Logged in"
} }

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, convert::TryInto}; use std::convert::TryInto;
use actix_session::{ use actix_session::{
storage::{LoadError, SaveError, SessionKey, SessionStore, UpdateError}, storage::{LoadError, SaveError, SessionKey, SessionStore, UpdateError},
@ -12,6 +12,7 @@ use actix_web::{
test, web, App, Responder, test, web, App, Responder,
}; };
use anyhow::Error; use anyhow::Error;
use serde_json::{Map, Value};
#[actix_web::test] #[actix_web::test]
async fn errors_are_opaque() { async fn errors_are_opaque() {
@ -49,7 +50,7 @@ impl SessionStore for MockStore {
async fn load( async fn load(
&self, &self,
_session_key: &SessionKey, _session_key: &SessionKey,
) -> Result<Option<HashMap<String, String>>, LoadError> { ) -> Result<Option<Map<String, Value>>, LoadError> {
Err(LoadError::Other(anyhow::anyhow!( Err(LoadError::Other(anyhow::anyhow!(
"My error full of implementation details" "My error full of implementation details"
))) )))
@ -57,7 +58,7 @@ impl SessionStore for MockStore {
async fn save( async fn save(
&self, &self,
_session_state: HashMap<String, String>, _session_state: Map<String, Value>,
_ttl: &Duration, _ttl: &Duration,
) -> Result<SessionKey, SaveError> { ) -> Result<SessionKey, SaveError> {
Ok("random_value".to_string().try_into().unwrap()) Ok("random_value".to_string().try_into().unwrap())
@ -66,7 +67,7 @@ impl SessionStore for MockStore {
async fn update( async fn update(
&self, &self,
_session_key: SessionKey, _session_key: SessionKey,
_session_state: HashMap<String, String>, _session_state: Map<String, Value>,
_ttl: &Duration, _ttl: &Duration,
) -> Result<SessionKey, UpdateError> { ) -> Result<SessionKey, UpdateError> {
#![allow(clippy::diverging_sub_expression)] #![allow(clippy::diverging_sub_expression)]
@ -85,7 +86,7 @@ impl SessionStore for MockStore {
} }
async fn create_session(session: Session) -> impl Responder { async fn create_session(session: Session) -> impl Responder {
session.insert("user_id", "id").unwrap(); session.insert("user_id", Value::from("id"));
"Created" "Created"
} }

View File

@ -1,22 +1,23 @@
use actix_session::{SessionExt, SessionStatus}; use actix_session::{SessionExt, SessionStatus};
use actix_web::{test, HttpResponse}; use actix_web::{test, HttpResponse};
use serde_json::Value;
#[actix_web::test] #[actix_web::test]
async fn session() { async fn session() {
let req = test::TestRequest::default().to_srv_request(); let req = test::TestRequest::default().to_srv_request();
let session = req.get_session(); let session = req.get_session();
session.insert("key", "value").unwrap(); session.insert("key", Value::from("value"));
let res = session.get::<String>("key").unwrap(); let res = session.get::<String>("key").unwrap();
assert_eq!(res, Some("value".to_string())); assert_eq!(res, Some("value".to_string()));
session.insert("key2", "value2").unwrap(); session.insert("key2", Value::from("value2"));
session.remove("key"); session.remove("key");
let res = req.into_response(HttpResponse::Ok().finish()); let res = req.into_response(HttpResponse::Ok().finish());
let state: Vec<_> = res.get_session().entries().clone().into_iter().collect(); let state: Vec<_> = res.get_session().entries().clone().into_iter().collect();
assert_eq!( assert_eq!(
state.as_slice(), state.as_slice(),
[("key2".to_string(), "\"value2\"".to_string())] [("key2".to_string(), Value::from("value2".to_string()))]
); );
} }
@ -25,7 +26,7 @@ async fn get_session() {
let req = test::TestRequest::default().to_srv_request(); let req = test::TestRequest::default().to_srv_request();
let session = req.get_session(); let session = req.get_session();
session.insert("key", true).unwrap(); session.insert("key", Value::from(true));
let res = session.get("key").unwrap(); let res = session.get("key").unwrap();
assert_eq!(res, Some(true)); assert_eq!(res, Some(true));
} }
@ -35,7 +36,7 @@ async fn get_session_from_request_head() {
let req = test::TestRequest::default().to_srv_request(); let req = test::TestRequest::default().to_srv_request();
let session = req.get_session(); let session = req.get_session();
session.insert("key", 10).unwrap(); session.insert("key", Value::from(10));
let res = session.get::<u32>("key").unwrap(); let res = session.get::<u32>("key").unwrap();
assert_eq!(res, Some(10)); assert_eq!(res, Some(10));
} }
@ -62,8 +63,8 @@ async fn renew_session() {
async fn session_entries() { async fn session_entries() {
let req = test::TestRequest::default().to_srv_request(); let req = test::TestRequest::default().to_srv_request();
let session = req.get_session(); let session = req.get_session();
session.insert("test_str", "val").unwrap(); session.insert("test_str", Value::from("val"));
session.insert("test_str", 1).unwrap(); session.insert("test_str", Value::from(1));
let map = session.entries(); let map = session.entries();
map.contains_key("test_str"); map.contains_key("test_str");
map.contains_key("test_num"); map.contains_key("test_num");
@ -73,13 +74,13 @@ async fn session_entries() {
async fn insert_session_after_renew() { async fn insert_session_after_renew() {
let session = test::TestRequest::default().to_srv_request().get_session(); let session = test::TestRequest::default().to_srv_request().get_session();
session.insert("test_val", "val").unwrap(); session.insert("test_val", Value::from("val"));
assert_eq!(session.status(), SessionStatus::Changed); assert_eq!(session.status(), SessionStatus::Changed);
session.renew(); session.renew();
assert_eq!(session.status(), SessionStatus::Renewed); assert_eq!(session.status(), SessionStatus::Renewed);
session.insert("test_val1", "val1").unwrap(); session.insert("test_val1", Value::from("val1"));
assert_eq!(session.status(), SessionStatus::Renewed); assert_eq!(session.status(), SessionStatus::Renewed);
} }
@ -87,12 +88,12 @@ async fn insert_session_after_renew() {
async fn remove_session_after_renew() { async fn remove_session_after_renew() {
let session = test::TestRequest::default().to_srv_request().get_session(); let session = test::TestRequest::default().to_srv_request().get_session();
session.insert("test_val", "val").unwrap(); session.insert("test_val", Value::from("val"));
session.remove("test_val").unwrap(); session.remove("test_val").unwrap();
assert_eq!(session.status(), SessionStatus::Changed); assert_eq!(session.status(), SessionStatus::Changed);
session.renew(); session.renew();
session.insert("test_val", "val").unwrap(); session.insert("test_val", Value::from("val"));
session.remove("test_val").unwrap(); session.remove("test_val").unwrap();
assert_eq!(session.status(), SessionStatus::Renewed); assert_eq!(session.status(), SessionStatus::Renewed);
} }