Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updates for async-sessions v4 #26

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[env]
RUST_TEST_THREADS = "1"
7 changes: 0 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,6 @@ jobs:
command: check
args: --all --bins --examples

- name: check avoid-dev-deps
uses: actions-rs/cargo@v1
if: matrix.rust == 'nightly'
with:
command: check
args: --all -Z avoid-dev-deps

- name: tests
uses: actions-rs/cargo@v1
with:
Expand Down
12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ keywords = ["sessions", "tide", "async-session", "redis"]
categories = ["web-programming::http-server", "web-programming", "database"]

[dependencies.redis]
version = "0.21.0"
features = ["aio", "async-std-comp"]
version = "0.22.3"
features = ["aio"]

[dependencies]
async-session = "3.0.0"
async-session = { git = "https://github.com/http-rs/async-session", branch = "overhaul-session-and-session-store", default-features = false }
base64 = "0.21.0"
serde_json = "1.0.93"
thiserror = "1.0.38"

[dev-dependencies]
async-std = { version = "1.9.0", features = ["attributes"] }
redis = { version = "0.22.3", features = ["async-std-comp"] }
async-std = { version = "1.12.0", features = ["attributes"] }
135 changes: 77 additions & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//! # async-redis-session
//! ```rust
//! use async_redis_session::RedisSessionStore;
//! use async_redis_session::{RedisSessionStore, Error};
//! use async_session::{Session, SessionStore};
//!
//! # fn main() -> async_session::Result { async_std::task::block_on(async {
//! # fn main() -> Result<(), Error> { async_std::task::block_on(async {
//! let store = RedisSessionStore::new("redis://127.0.0.1/")?;
//!
//! let mut session = Session::new();
//! session.insert("key", "value")?;
//!
//! let cookie_value = store.store_session(session).await?.unwrap();
//! let session = store.load_session(cookie_value).await?.unwrap();
//! let cookie_value = store.store_session(&mut session).await?.unwrap();
//! let session = store.load_session(&cookie_value).await?.unwrap();
//! assert_eq!(&session.get::<String>("key").unwrap(), "value");
//! # Ok(()) }) }
//! ```
Expand All @@ -25,9 +25,27 @@
unused_qualifications
)]

use async_session::{async_trait, serde_json, Result, Session, SessionStore};
use async_session::{async_trait, Session, SessionStore};
use redis::{aio::Connection, AsyncCommands, Client, IntoConnectionInfo, RedisResult};

/// Errors that can arise in the operation of the session stores
/// included in this crate
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
/// an error that comes from sqlx
#[error(transparent)]
Redis(#[from] redis::RedisError),

/// an error that comes from serde_json
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),

/// an error that comes from base64
#[error(transparent)]
Base64(#[from] base64::DecodeError),
}

/// # RedisSessionStore
#[derive(Clone, Debug)]
pub struct RedisSessionStore {
Expand Down Expand Up @@ -77,12 +95,12 @@ impl RedisSessionStore {
self
}

async fn ids(&self) -> Result<Vec<String>> {
async fn ids(&self) -> Result<Vec<String>, Error> {
Ok(self.connection().await?.keys(self.prefix_key("*")).await?)
}

/// returns the number of sessions in this store
pub async fn count(&self) -> Result<usize> {
pub async fn count(&self) -> Result<usize, Error> {
if self.prefix.is_none() {
let mut connection = self.connection().await?;
Ok(redis::cmd("DBSIZE").query_async(&mut connection).await?)
Expand All @@ -92,7 +110,7 @@ impl RedisSessionStore {
}

#[cfg(test)]
async fn ttl_for_session(&self, session: &Session) -> Result<usize> {
async fn ttl_for_session(&self, session: &Session) -> Result<usize, Error> {
Ok(self
.connection()
.await?
Expand All @@ -101,22 +119,25 @@ impl RedisSessionStore {
}

fn prefix_key(&self, key: impl AsRef<str>) -> String {
let key = key.as_ref();
if let Some(ref prefix) = self.prefix {
format!("{}{}", prefix, key.as_ref())
format!("{prefix}{key}")
} else {
key.as_ref().into()
key.to_string()
}
}

async fn connection(&self) -> RedisResult<Connection> {
self.client.get_async_std_connection().await
self.client.get_async_connection().await
}
}

#[async_trait]
impl SessionStore for RedisSessionStore {
async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
let id = Session::id_from_cookie_value(&cookie_value)?;
type Error = Error;

async fn load_session(&self, cookie_value: &str) -> Result<Option<Session>, Self::Error> {
let id = Session::id_from_cookie_value(cookie_value)?;
let mut connection = self.connection().await?;
let record: Option<String> = connection.get(self.prefix_key(id)).await?;
match record {
Expand All @@ -125,7 +146,7 @@ impl SessionStore for RedisSessionStore {
}
}

async fn store_session(&self, session: Session) -> Result<Option<String>> {
async fn store_session(&self, session: &mut Session) -> Result<Option<String>, Self::Error> {
let id = self.prefix_key(session.id());
let string = serde_json::to_string(&session)?;

Expand All @@ -141,17 +162,17 @@ impl SessionStore for RedisSessionStore {
}
};

Ok(session.into_cookie_value())
Ok(session.take_cookie_value())
}

async fn destroy_session(&self, session: Session) -> Result {
async fn destroy_session(&self, session: &mut Session) -> Result<(), Self::Error> {
let mut connection = self.connection().await?;
let key = self.prefix_key(session.id().to_string());
let key = self.prefix_key(session.id());
connection.del(key).await?;
Ok(())
}

async fn clear_store(&self) -> Result {
async fn clear_store(&self) -> Result<(), Self::Error> {
let mut connection = self.connection().await?;

if self.prefix.is_none() {
Expand Down Expand Up @@ -179,58 +200,57 @@ mod tests {
}

#[async_std::test]
async fn creating_a_new_session_with_no_expiry() -> Result {
async fn creating_a_new_session_with_no_expiry() -> Result<(), Error> {
let store = test_store().await;
let mut session = Session::new();
session.insert("key", "value")?;
let cloned = session.clone();
let cookie_value = store.store_session(session).await?.unwrap();
let cookie_value = store.store_session(&mut session).await?.unwrap();

let loaded_session = store.load_session(cookie_value).await?.unwrap();
assert_eq!(cloned.id(), loaded_session.id());
let loaded_session = store.load_session(&cookie_value).await?.unwrap();
assert_eq!(session.id(), loaded_session.id());
assert_eq!("value", &loaded_session.get::<String>("key").unwrap());

assert!(!loaded_session.is_expired());
Ok(())
}

#[async_std::test]
async fn updating_a_session() -> Result {
async fn updating_a_session() -> Result<(), Error> {
let store = test_store().await;
let mut session = Session::new();

session.insert("key", "value")?;
let cookie_value = store.store_session(session).await?.unwrap();
let cookie_value = store.store_session(&mut session).await?.unwrap();

let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
let mut session = store.load_session(&cookie_value).await?.unwrap();
session.insert("key", "other value")?;
assert_eq!(None, store.store_session(session).await?);
assert_eq!(None, store.store_session(&mut session).await?);

let session = store.load_session(cookie_value.clone()).await?.unwrap();
let session = store.load_session(&cookie_value).await?.unwrap();
assert_eq!(&session.get::<String>("key").unwrap(), "other value");

assert_eq!(1, store.count().await.unwrap());
Ok(())
}

#[async_std::test]
async fn updating_a_session_extending_expiry() -> Result {
async fn updating_a_session_extending_expiry() -> Result<(), Error> {
let store = test_store().await;
let mut session = Session::new();
session.expire_in(Duration::from_secs(5));
let original_expires = session.expiry().unwrap().clone();
let cookie_value = store.store_session(session).await?.unwrap();
let original_expires = *session.expiry().unwrap();
let cookie_value = store.store_session(&mut session).await?.unwrap();

let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
let mut session = store.load_session(&cookie_value).await?.unwrap();
let ttl = store.ttl_for_session(&session).await?;
assert!(ttl > 3 && ttl < 5);

assert_eq!(session.expiry().unwrap(), &original_expires);
session.expire_in(Duration::from_secs(10));
let new_expires = session.expiry().unwrap().clone();
store.store_session(session).await?;
let new_expires = *session.expiry().unwrap();
store.store_session(&mut session).await?;

let session = store.load_session(cookie_value.clone()).await?.unwrap();
let session = store.load_session(&cookie_value).await?.unwrap();
let ttl = store.ttl_for_session(&session).await?;
assert!(ttl > 8 && ttl < 10);
assert_eq!(session.expiry().unwrap(), &new_expires);
Expand All @@ -244,53 +264,52 @@ mod tests {
}

#[async_std::test]
async fn creating_a_new_session_with_expiry() -> Result {
async fn creating_a_new_session_with_expiry() -> Result<(), Error> {
let store = test_store().await;
let mut session = Session::new();
session.expire_in(Duration::from_secs(3));
session.insert("key", "value")?;
let cloned = session.clone();

let cookie_value = store.store_session(session).await?.unwrap();
let cookie_value = store.store_session(&mut session).await?.unwrap();

assert!(store.ttl_for_session(&cloned).await? > 1);
assert!(store.ttl_for_session(&session).await? > 1);

let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap();
assert_eq!(cloned.id(), loaded_session.id());
let loaded_session = store.load_session(&cookie_value).await?.unwrap();
assert_eq!(session.id(), loaded_session.id());
assert_eq!("value", &loaded_session.get::<String>("key").unwrap());

assert!(!loaded_session.is_expired());

task::sleep(Duration::from_secs(2)).await;
assert_eq!(None, store.load_session(cookie_value).await?);
assert_eq!(None, store.load_session(&cookie_value).await?);

Ok(())
}

#[async_std::test]
async fn destroying_a_single_session() -> Result {
async fn destroying_a_single_session() -> Result<(), Error> {
let store = test_store().await;
for _ in 0..3i8 {
store.store_session(Session::new()).await?;
store.store_session(&mut Session::new()).await?;
}

let cookie = store.store_session(Session::new()).await?.unwrap();
let cookie = store.store_session(&mut Session::new()).await?.unwrap();
assert_eq!(4, store.count().await?);
let session = store.load_session(cookie.clone()).await?.unwrap();
store.destroy_session(session.clone()).await.unwrap();
assert_eq!(None, store.load_session(cookie).await?);
let mut session = store.load_session(&cookie).await?.unwrap();
store.destroy_session(&mut session).await.unwrap();
assert_eq!(None, store.load_session(&cookie).await?);
assert_eq!(3, store.count().await?);

// attempting to destroy the session again is not an error
assert!(store.destroy_session(session).await.is_ok());
assert!(store.destroy_session(&mut session).await.is_ok());
Ok(())
}

#[async_std::test]
async fn clearing_the_whole_store() -> Result {
async fn clearing_the_whole_store() -> Result<(), Error> {
let store = test_store().await;
for _ in 0..3i8 {
store.store_session(Session::new()).await?;
store.store_session(&mut Session::new()).await?;
}

assert_eq!(3, store.count().await?);
Expand All @@ -301,26 +320,26 @@ mod tests {
}

#[async_std::test]
async fn prefixes() -> Result {
async fn prefixes() -> Result<(), Error> {
test_store().await; // clear the db

let store = RedisSessionStore::new("redis://127.0.0.1")?.with_prefix("sessions/");
store.clear_store().await?;

for _ in 0..3i8 {
store.store_session(Session::new()).await?;
store.store_session(&mut Session::new()).await?;
}

let mut session = Session::new();

session.insert("key", "value")?;
let cookie_value = store.store_session(session).await?.unwrap();
let cookie_value = store.store_session(&mut session).await?.unwrap();

let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
let mut session = store.load_session(&cookie_value).await?.unwrap();
session.insert("key", "other value")?;
assert_eq!(None, store.store_session(session).await?);
assert_eq!(None, store.store_session(&mut session).await?);

let session = store.load_session(cookie_value.clone()).await?.unwrap();
let session = store.load_session(&cookie_value).await?.unwrap();
assert_eq!(&session.get::<String>("key").unwrap(), "other value");

assert_eq!(4, store.count().await.unwrap());
Expand All @@ -330,7 +349,7 @@ mod tests {

assert_eq!(0, other_store.count().await.unwrap());
for _ in 0..3i8 {
other_store.store_session(Session::new()).await?;
other_store.store_session(&mut Session::new()).await?;
}

other_store.clear_store().await?;
Expand Down