1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-27 17:22:57 +01:00

various session api improvements (#170)

This commit is contained in:
Rob Ede 2021-03-23 22:35:27 +00:00 committed by GitHub
parent 23912afd49
commit fc6563a019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 186 additions and 95 deletions

View File

@ -53,7 +53,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.set("user_id", user.id).unwrap(), Ok(user) => session.insert("user_id", user.id).unwrap(),
Err(_) => return Err(HttpResponse::Unauthorized().json("Unauthorized")), Err(_) => return Err(HttpResponse::Unauthorized().json("Unauthorized")),
}; };

View File

@ -9,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.set("counter", count + 1)?; session.insert("counter", count + 1)?;
} else { } else {
session.set("counter", 1)?; session.insert("counter", 1)?;
} }
Ok("Welcome!") Ok("Welcome!")

View File

@ -157,8 +157,9 @@ where
Box::pin(async move { Box::pin(async move {
let state = inner.load(&req).await?; let state = inner.load(&req).await?;
let value = if let Some((state, value)) = state { let value = if let Some((state, value)) = state {
Session::set_session(state, &mut req); Session::set_session(&mut req, state);
Some(value) Some(value)
} else { } else {
None None
@ -167,8 +168,7 @@ where
let mut res = srv.call(req).await?; let mut res = srv.call(req).await?;
match Session::get_changes(&mut res) { match Session::get_changes(&mut res) {
(SessionStatus::Unchanged, None) => Ok(res), (SessionStatus::Unchanged, state) => {
(SessionStatus::Unchanged, Some(state)) => {
if value.is_none() { if value.is_none() {
// implies the session is new // implies the session is new
inner.update(res, state, value).await inner.update(res, state, value).await
@ -176,10 +176,10 @@ where
Ok(res) Ok(res)
} }
} }
(SessionStatus::Changed, Some(state)) => {
inner.update(res, state, value).await (SessionStatus::Changed, state) => inner.update(res, state, value).await,
}
(SessionStatus::Purged, Some(_)) => { (SessionStatus::Purged, _) => {
if let Some(val) = value { if let Some(val) = value {
inner.clear_cache(val).await?; inner.clear_cache(val).await?;
match inner.remove_cookie(&mut res) { match inner.remove_cookie(&mut res) {
@ -190,7 +190,8 @@ where
Err(error::ErrorInternalServerError("unexpected")) Err(error::ErrorInternalServerError("unexpected"))
} }
} }
(SessionStatus::Renewed, Some(state)) => {
(SessionStatus::Renewed, state) => {
if let Some(val) = value { if let Some(val) = value {
inner.clear_cache(val).await?; inner.clear_cache(val).await?;
inner.update(res, state, None).await inner.update(res, state, None).await
@ -198,7 +199,6 @@ where
inner.update(res, state, None).await inner.update(res, state, None).await
} }
} }
(_, None) => unreachable!(),
} }
}) })
} }
@ -343,7 +343,7 @@ impl Inner {
Ok(res) Ok(res)
} }
/// removes cache entry /// Removes cache entry.
async fn clear_cache(&self, key: String) -> Result<(), Error> { async fn clear_cache(&self, key: String) -> Result<(), Error> {
let cache_key = (self.cache_keygen)(&key); let cache_key = (self.cache_keygen)(&key);
@ -362,7 +362,7 @@ impl Inner {
} }
} }
/// invalidates session cookie /// Invalidates session cookie.
fn remove_cookie<B>(&self, res: &mut ServiceResponse<B>) -> Result<(), Error> { fn remove_cookie<B>(&self, res: &mut ServiceResponse<B>) -> Result<(), Error> {
let mut cookie = Cookie::named(self.name.clone()); let mut cookie = Cookie::named(self.name.clone());
cookie.set_value(""); cookie.set_value("");
@ -411,7 +411,7 @@ mod test {
.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.set("counter", counter)?; session.insert("counter", &counter)?;
Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter })) Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter }))
} }
@ -426,7 +426,7 @@ mod test {
session: Session, session: Session,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let id = user_id.into_inner().user_id; let id = user_id.into_inner().user_id;
session.set("user_id", &id)?; session.insert("user_id", &id)?;
session.renew(); session.renew();
let counter: i32 = session let counter: i32 = session

View File

@ -1,9 +1,17 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
* Add `Session::entries`. [#170]
* Rename `Session::{set => insert}` to match standard hash map naming. [#170]
* Return values from `Session::remove`. [#170]
* Add `Session::remove_as` deserializing variation. [#170]
* Simplify `Session::get_changes` now always returning iterator even when empty. [#170]
* Swap order of arguments on `Session::set_session`. [#170]
* Update `actix-web` dependency to 4.0.0 beta. * Update `actix-web` dependency to 4.0.0 beta.
* Minimum supported Rust version (MSRV) is now 1.46.0. * Minimum supported Rust version (MSRV) is now 1.46.0.
[#170]: https://github.com/actix/actix-extras/pull/170
## 0.4.1 - 2020-03-21 ## 0.4.1 - 2020-03-21
* `Session::set_session` takes a `IntoIterator` instead of `Iterator`. [#105] * `Session::set_session` takes a `IntoIterator` instead of `Iterator`. [#105]

View File

@ -22,8 +22,10 @@ cookie-session = ["actix-web/secure-cookies"]
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.4", default_features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.4", default_features = false, features = ["cookies"] }
actix-service = "2.0.0-beta.5" actix-service = "2.0.0-beta.5"
derive_more = "0.99.2"
derive_more = "0.99.5"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
log = "0.4"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
time = "0.2.23" time = "0.2.23"

View File

@ -8,7 +8,7 @@ use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::http::{header::SET_COOKIE, HeaderValue}; use actix_web::http::{header::SET_COOKIE, HeaderValue};
use actix_web::{Error, HttpMessage, ResponseError}; use actix_web::{Error, HttpMessage, ResponseError};
use derive_more::Display; use derive_more::Display;
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use futures_util::future::{ok, LocalBoxFuture, Ready};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
@ -77,6 +77,7 @@ impl CookieSessionInner {
let value = let value =
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
if value.len() > 4064 { if value.len() > 4064 {
return Err(CookieSessionError::Overflow.into()); return Err(CookieSessionError::Overflow.into());
} }
@ -144,6 +145,7 @@ impl CookieSessionInner {
jar.private(&self.key).get(&self.name) jar.private(&self.key).get(&self.name)
} }
}; };
if let Some(cookie) = cookie_opt { if let Some(cookie) = cookie_opt {
if let Ok(val) = serde_json::from_str(cookie.value()) { if let Ok(val) = serde_json::from_str(cookie.value()) {
return (false, val); return (false, val);
@ -152,6 +154,7 @@ impl CookieSessionInner {
} }
} }
} }
(true, HashMap::new()) (true, HashMap::new())
} }
} }
@ -309,7 +312,7 @@ where
} }
} }
/// Cookie session middleware /// Cookie based session middleware.
pub struct CookieSessionMiddleware<S> { pub struct CookieSessionMiddleware<S> {
service: S, service: S,
inner: Rc<CookieSessionInner>, inner: Rc<CookieSessionInner>,
@ -336,42 +339,41 @@ where
let inner = self.inner.clone(); let inner = self.inner.clone();
let (is_new, state) = self.inner.load(&req); let (is_new, state) = self.inner.load(&req);
let prolong_expiration = self.inner.expires_in.is_some(); let prolong_expiration = self.inner.expires_in.is_some();
Session::set_session(state, &mut req); Session::set_session(&mut req, state);
let fut = self.service.call(req); let fut = self.service.call(req);
async move { Box::pin(async move {
fut.await.map(|mut res| { let mut res = fut.await?;
match Session::get_changes(&mut res) {
(SessionStatus::Changed, Some(state)) let res = match Session::get_changes(&mut res) {
| (SessionStatus::Renewed, Some(state)) => { (SessionStatus::Changed, state) | (SessionStatus::Renewed, state) => {
res.checked_expr(|res| inner.set_cookie(res, state)) res.checked_expr(|res| inner.set_cookie(res, state))
} }
(SessionStatus::Unchanged, Some(state)) if prolong_expiration => {
(SessionStatus::Unchanged, state) if prolong_expiration => {
res.checked_expr(|res| inner.set_cookie(res, state)) res.checked_expr(|res| inner.set_cookie(res, state))
} }
(SessionStatus::Unchanged, _) =>
// set a new session cookie upon first request (new client) // set a new session cookie upon first request (new client)
{ (SessionStatus::Unchanged, _) => {
if is_new { if is_new {
let state: HashMap<String, String> = HashMap::new(); let state: HashMap<String, String> = HashMap::new();
res.checked_expr(|res| { res.checked_expr(|res| inner.set_cookie(res, state.into_iter()))
inner.set_cookie(res, state.into_iter())
})
} else { } else {
res res
} }
} }
(SessionStatus::Purged, _) => { (SessionStatus::Purged, _) => {
let _ = inner.remove_cookie(&mut res); let _ = inner.remove_cookie(&mut res);
res res
} }
_ => res, };
}
Ok(res)
}) })
} }
.boxed_local()
}
} }
#[cfg(test)] #[cfg(test)]
@ -386,7 +388,7 @@ mod tests {
App::new() App::new()
.wrap(CookieSession::signed(&[0; 32]).secure(false)) .wrap(CookieSession::signed(&[0; 32]).secure(false))
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.set("counter", 100); let _ = ses.insert("counter", 100);
"test" "test"
})), })),
) )
@ -406,7 +408,7 @@ mod tests {
App::new() App::new()
.wrap(CookieSession::private(&[0; 32]).secure(false)) .wrap(CookieSession::private(&[0; 32]).secure(false))
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.set("counter", 100); let _ = ses.insert("counter", 100);
"test" "test"
})), })),
) )
@ -426,7 +428,7 @@ mod tests {
App::new() App::new()
.wrap(CookieSession::signed(&[0; 32]).secure(false).lazy(true)) .wrap(CookieSession::signed(&[0; 32]).secure(false).lazy(true))
.service(web::resource("/count").to(|ses: Session| async move { .service(web::resource("/count").to(|ses: Session| async move {
let _ = ses.set("counter", 100); let _ = ses.insert("counter", 100);
"counting" "counting"
})) }))
.service(web::resource("/").to(|_ses: Session| async move { "test" })), .service(web::resource("/").to(|_ses: Session| async move { "test" })),
@ -452,7 +454,7 @@ mod tests {
App::new() App::new()
.wrap(CookieSession::signed(&[0; 32]).secure(false)) .wrap(CookieSession::signed(&[0; 32]).secure(false))
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.set("counter", 100); let _ = ses.insert("counter", 100);
"test" "test"
})), })),
) )
@ -480,7 +482,7 @@ mod tests {
.max_age(100), .max_age(100),
) )
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.set("counter", 100); let _ = ses.insert("counter", 100);
"test" "test"
})) }))
.service(web::resource("/test/").to(|ses: Session| async move { .service(web::resource("/test/").to(|ses: Session| async move {
@ -513,7 +515,7 @@ mod tests {
App::new() App::new()
.wrap(CookieSession::signed(&[0; 32]).secure(false).expires_in(60)) .wrap(CookieSession::signed(&[0; 32]).secure(false).expires_in(60))
.service(web::resource("/").to(|ses: Session| async move { .service(web::resource("/").to(|ses: Session| async move {
let _ = ses.set("counter", 100); let _ = ses.insert("counter", 100);
"test" "test"
})) }))
.service( .service(

View File

@ -18,9 +18,9 @@
//! // access session data //! // access session data
//! 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.set("counter", count + 1)?; //! session.insert("counter", count + 1)?;
//! } else { //! } else {
//! session.set("counter", 1)?; //! session.insert("counter", 1)?;
//! } //! }
//! //!
//! Ok("Welcome!") //! Ok("Welcome!")
@ -39,21 +39,27 @@
//! } //! }
//! ``` //! ```
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(missing_docs)]
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{
cell::{Ref, RefCell},
use actix_web::dev::{ collections::HashMap,
Extensions, Payload, RequestHead, ServiceRequest, ServiceResponse, mem,
rc::Rc,
};
use actix_web::{
dev::{Extensions, Payload, RequestHead, ServiceRequest, ServiceResponse},
Error, FromRequest, HttpMessage, HttpRequest,
}; };
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest};
use futures_util::future::{ok, Ready}; use futures_util::future::{ok, Ready};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
#[cfg(feature = "cookie-session")] #[cfg(feature = "cookie-session")]
mod cookie; mod cookie;
#[cfg(feature = "cookie-session")] #[cfg(feature = "cookie-session")]
pub use crate::cookie::CookieSession; pub use self::cookie::CookieSession;
/// The high-level interface you use to modify session data. /// The high-level interface you use to modify session data.
/// ///
@ -67,9 +73,9 @@ pub use crate::cookie::CookieSession;
/// async fn index(session: Session) -> Result<&'static str> { /// async fn index(session: Session) -> 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.set("counter", count + 1)?; /// session.insert("counter", count + 1)?;
/// } else { /// } else {
/// session.set("counter", 1)?; /// session.insert("counter", 1)?;
/// } /// }
/// ///
/// Ok("Welcome!") /// Ok("Welcome!")
@ -79,6 +85,7 @@ pub struct Session(Rc<RefCell<SessionInner>>);
/// Extraction of a [`Session`] object. /// Extraction of a [`Session`] object.
pub trait UserSession { pub trait UserSession {
/// Extract the [`Session`] object
fn get_session(&self) -> Session; fn get_session(&self) -> Session;
} }
@ -100,13 +107,31 @@ impl UserSession for RequestHead {
} }
} }
/// Status of a [`Session`].
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
pub enum SessionStatus { pub enum SessionStatus {
/// Session has been updated and requires a new persist operation.
Changed, Changed,
/// Session is flagged for deletion and should be removed from client and server.
///
/// Most operations on the session after purge flag is set should have no effect.
Purged, Purged,
/// Session is flagged for refresh.
///
/// For example, when using a backend that has a TTL (time-to-live) expiry on the session entry,
/// the session will be refreshed even if no data inside it has changed. The client may also
/// be notified of the refresh.
Renewed, Renewed,
/// Session is unchanged from when last seen (if exists).
///
/// This state also captures new (previously unissued) sessions such as a user's first
/// site visit.
Unchanged, Unchanged,
} }
impl Default for SessionStatus { impl Default for SessionStatus {
fn default() -> SessionStatus { fn default() -> SessionStatus {
SessionStatus::Unchanged SessionStatus::Unchanged
@ -116,7 +141,7 @@ impl Default for SessionStatus {
#[derive(Default)] #[derive(Default)]
struct SessionInner { struct SessionInner {
state: HashMap<String, String>, state: HashMap<String, String>,
pub status: SessionStatus, status: SessionStatus,
} }
impl Session { impl Session {
@ -129,37 +154,80 @@ impl Session {
} }
} }
/// Set a `value` from the session. /// Get all raw key-value data from the session.
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> { ///
/// Note that values are JSON encoded.
pub fn entries(&self) -> Ref<'_, HashMap<String, String>> {
Ref::map(self.0.borrow(), |inner| &inner.state)
}
/// 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
/// only a reference to the value is taken.
pub fn insert(
&self,
key: impl Into<String>,
value: impl Serialize,
) -> Result<(), Error> {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged { if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Changed; inner.status = SessionStatus::Changed;
inner let val = serde_json::to_string(&value)?;
.state inner.state.insert(key.into(), val);
.insert(key.to_owned(), serde_json::to_string(&value)?);
} }
Ok(()) Ok(())
} }
/// Remove value from the session. /// Remove value from the session.
pub fn remove(&self, key: &str) { ///
/// If present, the JSON encoded value is returned.
pub fn remove(&self, key: &str) -> Option<String> {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged { if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Changed; inner.status = SessionStatus::Changed;
inner.state.remove(key); return inner.state.remove(key);
} }
None
}
/// Remove value from the session and deserialize.
///
/// Returns None if key was not present in session. Returns T if deserialization succeeds,
/// otherwise returns un-deserialized JSON string.
pub fn remove_as<T: DeserializeOwned>(
&self,
key: &str,
) -> Option<Result<T, String>> {
self.remove(key)
.map(|val_str| match serde_json::from_str(&val_str) {
Ok(val) => Ok(val),
Err(_err) => {
log::debug!(
"removed value (key: {}) could not be deserialized as {}",
key,
std::any::type_name::<T>()
);
Err(val_str)
}
})
} }
/// Clear the session. /// Clear the session.
pub fn clear(&self) { pub fn clear(&self) {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged { if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Changed; inner.status = SessionStatus::Changed;
inner.state.clear() inner.state.clear()
} }
} }
/// Removes session, both client and server side. /// Removes session both client and server side.
pub fn purge(&self) { pub fn purge(&self) {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
inner.status = SessionStatus::Purged; inner.status = SessionStatus::Purged;
@ -169,6 +237,7 @@ impl Session {
/// Renews the session key, assigning existing session state to new key. /// Renews the session key, assigning existing session state to new key.
pub fn renew(&self) { pub fn renew(&self) {
let mut inner = self.0.borrow_mut(); let mut inner = self.0.borrow_mut();
if inner.status != SessionStatus::Purged { if inner.status != SessionStatus::Purged {
inner.status = SessionStatus::Renewed; inner.status = SessionStatus::Renewed;
} }
@ -186,35 +255,32 @@ impl Session {
/// let mut req = test::TestRequest::default().to_srv_request(); /// let mut req = test::TestRequest::default().to_srv_request();
/// ///
/// Session::set_session( /// Session::set_session(
/// vec![("counter".to_string(), serde_json::to_string(&0).unwrap())],
/// &mut req, /// &mut req,
/// vec![("counter".to_string(), serde_json::to_string(&0).unwrap())],
/// ); /// );
/// ``` /// ```
pub fn set_session( pub fn set_session(
data: impl IntoIterator<Item = (String, String)>,
req: &mut ServiceRequest, req: &mut ServiceRequest,
data: impl IntoIterator<Item = (String, String)>,
) { ) {
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();
inner.state.extend(data); inner.state.extend(data);
} }
/// Returns session status and iterator of key-value pairs of changes.
pub fn get_changes<B>( pub fn get_changes<B>(
res: &mut ServiceResponse<B>, res: &mut ServiceResponse<B>,
) -> ( ) -> (SessionStatus, impl Iterator<Item = (String, String)>) {
SessionStatus,
Option<impl Iterator<Item = (String, String)>>,
) {
if let Some(s_impl) = res if let Some(s_impl) = res
.request() .request()
.extensions() .extensions()
.get::<Rc<RefCell<SessionInner>>>() .get::<Rc<RefCell<SessionInner>>>()
{ {
let state = let state = mem::take(&mut s_impl.borrow_mut().state);
std::mem::replace(&mut s_impl.borrow_mut().state, HashMap::new()); (s_impl.borrow().status.clone(), state.into_iter())
(s_impl.borrow().status.clone(), Some(state.into_iter()))
} else { } else {
(SessionStatus::Unchanged, None) (SessionStatus::Unchanged, HashMap::new().into_iter())
} }
} }
@ -230,21 +296,23 @@ impl Session {
/// Extractor implementation for Session type. /// Extractor implementation for Session type.
/// ///
/// ```rust /// # Examples
/// ```
/// # use actix_web::*; /// # use actix_web::*;
/// use actix_session::Session; /// use actix_session::Session;
/// ///
/// fn index(session: Session) -> Result<&'static str> { /// #[get("/")]
/// 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.set("counter", count + 1)?; /// session.insert("counter", count + 1)?;
/// } else { /// } else {
/// session.set("counter", 1)?; /// session.insert("counter", 1)?;
/// } /// }
/// ///
/// Ok("Welcome!") /// let count = session.get::<i32>("counter")?.unwrap();
/// Ok(format!("Counter: {}", count))
/// } /// }
/// # fn main() {}
/// ``` /// ```
impl FromRequest for Session { impl FromRequest for Session {
type Error = Error; type Error = Error;
@ -268,19 +336,19 @@ mod tests {
let mut req = test::TestRequest::default().to_srv_request(); let mut req = test::TestRequest::default().to_srv_request();
Session::set_session( Session::set_session(
vec![("key".to_string(), serde_json::to_string("value").unwrap())],
&mut req, &mut req,
vec![("key".to_string(), serde_json::to_string("value").unwrap())],
); );
let session = Session::get_session(&mut *req.extensions_mut()); let session = Session::get_session(&mut *req.extensions_mut());
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.set("key2", "value2".to_string()).unwrap(); session.insert("key2", "value2").unwrap();
session.remove("key"); session.remove("key");
let mut res = req.into_response(HttpResponse::Ok().finish()); let mut res = req.into_response(HttpResponse::Ok().finish());
let (_status, state) = Session::get_changes(&mut res); let (_status, state) = Session::get_changes(&mut res);
let changes: Vec<_> = state.unwrap().collect(); let changes: Vec<_> = state.collect();
assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]); assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]);
} }
@ -289,8 +357,8 @@ mod tests {
let mut req = test::TestRequest::default().to_srv_request(); let mut req = test::TestRequest::default().to_srv_request();
Session::set_session( Session::set_session(
vec![("key".to_string(), serde_json::to_string(&true).unwrap())],
&mut req, &mut req,
vec![("key".to_string(), serde_json::to_string(&true).unwrap())],
); );
let session = req.get_session(); let session = req.get_session();
@ -303,8 +371,8 @@ mod tests {
let mut req = test::TestRequest::default().to_srv_request(); let mut req = test::TestRequest::default().to_srv_request();
Session::set_session( Session::set_session(
vec![("key".to_string(), serde_json::to_string(&10).unwrap())],
&mut req, &mut req,
vec![("key".to_string(), serde_json::to_string(&10).unwrap())],
); );
let session = req.head_mut().get_session(); let session = req.head_mut().get_session();
@ -329,4 +397,15 @@ mod tests {
session.renew(); session.renew();
assert_eq!(session.0.borrow().status, SessionStatus::Renewed); assert_eq!(session.0.borrow().status, SessionStatus::Renewed);
} }
#[test]
fn session_entries() {
let session = Session(Rc::new(RefCell::new(SessionInner::default())));
session.insert("test_str", "val").unwrap();
session.insert("test_num", 1).unwrap();
let map = session.entries();
map.contains_key("test_str");
map.contains_key("test_num");
}
} }