├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── Cargo.toml ├── README.md └── src └── lib.rs /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions include code, documentation, answering user questions, running the 3 | project's infrastructure, and advocating for all types of users. 4 | 5 | The project welcomes all contributions from anyone willing to work in good faith 6 | with other contributors and the community. No contribution is too small and all 7 | contributions are valued. 8 | 9 | This guide explains the process for contributing to the project's GitHub 10 | Repository. 11 | 12 | - [Code of Conduct](#code-of-conduct) 13 | - [Bad Actors](#bad-actors) 14 | 15 | ## Code of Conduct 16 | The project has a [Code of Conduct](./CODE_OF_CONDUCT.md) that *all* 17 | contributors are expected to follow. This code describes the *minimum* behavior 18 | expectations for all contributors. 19 | 20 | As a contributor, how you choose to act and interact towards your 21 | fellow contributors, as well as to the community, will reflect back not only 22 | on yourself but on the project as a whole. The Code of Conduct is designed and 23 | intended, above all else, to help establish a culture within the project that 24 | allows anyone and everyone who wants to contribute to feel safe doing so. 25 | 26 | Should any individual act in any way that is considered in violation of the 27 | [Code of Conduct](./CODE_OF_CONDUCT.md), corrective actions will be taken. It is 28 | possible, however, for any individual to *act* in such a manner that is not in 29 | violation of the strict letter of the Code of Conduct guidelines while still 30 | going completely against the spirit of what that Code is intended to accomplish. 31 | 32 | Open, diverse, and inclusive communities live and die on the basis of trust. 33 | Contributors can disagree with one another so long as they trust that those 34 | disagreements are in good faith and everyone is working towards a common 35 | goal. 36 | 37 | ## Bad Actors 38 | All contributors to tacitly agree to abide by both the letter and 39 | spirit of the [Code of Conduct](./CODE_OF_CONDUCT.md). Failure, or 40 | unwillingness, to do so will result in contributions being respectfully 41 | declined. 42 | 43 | A *bad actor* is someone who repeatedly violates the *spirit* of the Code of 44 | Conduct through consistent failure to self-regulate the way in which they 45 | interact with other contributors in the project. In doing so, bad actors 46 | alienate other contributors, discourage collaboration, and generally reflect 47 | poorly on the project as a whole. 48 | 49 | Being a bad actor may be intentional or unintentional. Typically, unintentional 50 | bad behavior can be easily corrected by being quick to apologize and correct 51 | course *even if you are not entirely convinced you need to*. Giving other 52 | contributors the benefit of the doubt and having a sincere willingness to admit 53 | that you *might* be wrong is critical for any successful open collaboration. 54 | 55 | Don't be a bad actor. 56 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | env: 10 | RUSTFLAGS: -Dwarnings 11 | 12 | jobs: 13 | build_and_test: 14 | name: Build and test 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | rust: [stable, nightly] 19 | 20 | services: 21 | redis: 22 | image: redis 23 | options: >- 24 | --health-cmd "redis-cli ping" 25 | --health-interval 10s 26 | --health-timeout 5s 27 | --health-retries 5 28 | ports: 29 | - 6379:6379 30 | 31 | steps: 32 | - uses: actions/checkout@master 33 | 34 | - name: Install ${{ matrix.rust }} 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | override: true 39 | 40 | - name: check 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: check 44 | args: --all --bins --examples 45 | 46 | - name: check avoid-dev-deps 47 | uses: actions-rs/cargo@v1 48 | if: matrix.rust == 'nightly' 49 | with: 50 | command: check 51 | args: --all -Z avoid-dev-deps 52 | 53 | - name: tests 54 | uses: actions-rs/cargo@v1 55 | with: 56 | command: test 57 | args: --all -- --test-threads=1 58 | 59 | check_fmt_and_docs: 60 | name: Checking fmt, clippy, and docs 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@master 64 | 65 | - uses: actions-rs/toolchain@v1 66 | with: 67 | toolchain: stable 68 | override: true 69 | 70 | - name: setup 71 | run: | 72 | rustup component add clippy rustfmt 73 | rustc --version 74 | 75 | - name: clippy 76 | run: cargo clippy -- -D warnings 77 | 78 | - name: fmt 79 | run: cargo fmt --all -- --check 80 | 81 | - name: Docs 82 | run: cargo doc --no-deps 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-redis-session" 3 | version = "0.2.2" 4 | authors = ["Jacob Rothstein "] 5 | edition = "2018" 6 | description = "redis session store for async-session" 7 | readme = "README.md" 8 | repository = "https://github.com/jbr/async-redis-session" 9 | documentation = "https://docs.rs/async-redis-session" 10 | license = "MIT OR Apache-2.0" 11 | keywords = ["sessions", "tide", "async-session", "redis"] 12 | categories = ["web-programming::http-server", "web-programming", "database"] 13 | 14 | [dependencies.redis] 15 | version = "0.21.0" 16 | features = ["aio", "async-std-comp"] 17 | 18 | [dependencies] 19 | async-session = "3.0.0" 20 | 21 | [dev-dependencies] 22 | async-std = { version = "1.9.0", features = ["attributes"] } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-redis-session 2 | ## redis-backed session store for [async-session](https://github.com/http-rs/async-session) 3 | 4 | * [CI ![CI][ci-badge]][ci] 5 | * [API Docs][docs] [![docs.rs docs][docs-badge]][docs] 6 | * [Releases][releases] [![crates.io version][version-badge]][lib-rs] 7 | * [Contributing][contributing] 8 | 9 | [ci]: https://github.com/jbr/async-redis-session/actions?query=workflow%3ACI 10 | [ci-badge]: https://github.com/jbr/async-redis-session/workflows/CI/badge.svg 11 | [releases]: https://github.com/jbr/async-redis-session/releases 12 | [docs]: https://docs.rs/async-redis-session 13 | [contributing]: https://github.com/jbr/async-redis-session/blob/master/.github/CONTRIBUTING.md 14 | [lib-rs]: https://lib.rs/async-redis-session 15 | [docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square 16 | [version-badge]: https://img.shields.io/crates/v/async-redis-session.svg?style=flat-square 17 | 18 | ## Installation 19 | ```sh 20 | $ cargo add async-redis-session 21 | ``` 22 | 23 | ## Safety 24 | This crate uses ``#![deny(unsafe_code)]`` to ensure everything is implemented in 25 | 100% Safe Rust. 26 | 27 | ## License 28 | 29 | 30 | Licensed under either of Apache License, Version 31 | 2.0 or MIT license at your option. 32 | 33 | 34 |
35 | 36 | 37 | Unless you explicitly state otherwise, any contribution intentionally submitted 38 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 39 | be dual licensed as above, without any additional terms or conditions. 40 | 41 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # async-redis-session 2 | //! ```rust 3 | //! use async_redis_session::RedisSessionStore; 4 | //! use async_session::{Session, SessionStore}; 5 | //! 6 | //! # fn main() -> async_session::Result { async_std::task::block_on(async { 7 | //! let store = RedisSessionStore::new("redis://127.0.0.1/")?; 8 | //! 9 | //! let mut session = Session::new(); 10 | //! session.insert("key", "value")?; 11 | //! 12 | //! let cookie_value = store.store_session(session).await?.unwrap(); 13 | //! let session = store.load_session(cookie_value).await?.unwrap(); 14 | //! assert_eq!(&session.get::("key").unwrap(), "value"); 15 | //! # Ok(()) }) } 16 | //! ``` 17 | 18 | #![forbid(unsafe_code, future_incompatible)] 19 | #![deny( 20 | missing_debug_implementations, 21 | nonstandard_style, 22 | missing_docs, 23 | unreachable_pub, 24 | missing_copy_implementations, 25 | unused_qualifications 26 | )] 27 | 28 | use async_session::{async_trait, serde_json, Result, Session, SessionStore}; 29 | use redis::{aio::Connection, AsyncCommands, Client, IntoConnectionInfo, RedisResult}; 30 | 31 | /// # RedisSessionStore 32 | #[derive(Clone, Debug)] 33 | pub struct RedisSessionStore { 34 | client: Client, 35 | prefix: Option, 36 | } 37 | 38 | impl RedisSessionStore { 39 | /// creates a redis store from an existing [`redis::Client`] 40 | /// ```rust 41 | /// # use async_redis_session::RedisSessionStore; 42 | /// let client = redis::Client::open("redis://127.0.0.1").unwrap(); 43 | /// let store = RedisSessionStore::from_client(client); 44 | /// ``` 45 | pub fn from_client(client: Client) -> Self { 46 | Self { 47 | client, 48 | prefix: None, 49 | } 50 | } 51 | 52 | /// creates a redis store from a [`redis::IntoConnectionInfo`] 53 | /// such as a [`String`], [`&str`](str), or [`Url`](../url/struct.Url.html) 54 | /// ```rust 55 | /// # use async_redis_session::RedisSessionStore; 56 | /// let store = RedisSessionStore::new("redis://127.0.0.1").unwrap(); 57 | /// ``` 58 | pub fn new(connection_info: impl IntoConnectionInfo) -> RedisResult { 59 | Ok(Self::from_client(Client::open(connection_info)?)) 60 | } 61 | 62 | /// sets a key prefix for this session store 63 | /// 64 | /// ```rust 65 | /// # use async_redis_session::RedisSessionStore; 66 | /// let store = RedisSessionStore::new("redis://127.0.0.1").unwrap() 67 | /// .with_prefix("async-sessions/"); 68 | /// ``` 69 | /// ```rust 70 | /// # use async_redis_session::RedisSessionStore; 71 | /// let client = redis::Client::open("redis://127.0.0.1").unwrap(); 72 | /// let store = RedisSessionStore::from_client(client) 73 | /// .with_prefix("async-sessions/"); 74 | /// ``` 75 | pub fn with_prefix(mut self, prefix: impl AsRef) -> Self { 76 | self.prefix = Some(prefix.as_ref().to_owned()); 77 | self 78 | } 79 | 80 | async fn ids(&self) -> Result> { 81 | Ok(self.connection().await?.keys(self.prefix_key("*")).await?) 82 | } 83 | 84 | /// returns the number of sessions in this store 85 | pub async fn count(&self) -> Result { 86 | if self.prefix.is_none() { 87 | let mut connection = self.connection().await?; 88 | Ok(redis::cmd("DBSIZE").query_async(&mut connection).await?) 89 | } else { 90 | Ok(self.ids().await?.len()) 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | async fn ttl_for_session(&self, session: &Session) -> Result { 96 | Ok(self 97 | .connection() 98 | .await? 99 | .ttl(self.prefix_key(session.id())) 100 | .await?) 101 | } 102 | 103 | fn prefix_key(&self, key: impl AsRef) -> String { 104 | if let Some(ref prefix) = self.prefix { 105 | format!("{}{}", prefix, key.as_ref()) 106 | } else { 107 | key.as_ref().into() 108 | } 109 | } 110 | 111 | async fn connection(&self) -> RedisResult { 112 | self.client.get_async_std_connection().await 113 | } 114 | } 115 | 116 | #[async_trait] 117 | impl SessionStore for RedisSessionStore { 118 | async fn load_session(&self, cookie_value: String) -> Result> { 119 | let id = Session::id_from_cookie_value(&cookie_value)?; 120 | let mut connection = self.connection().await?; 121 | let record: Option = connection.get(self.prefix_key(id)).await?; 122 | match record { 123 | Some(value) => Ok(serde_json::from_str(&value)?), 124 | None => Ok(None), 125 | } 126 | } 127 | 128 | async fn store_session(&self, session: Session) -> Result> { 129 | let id = self.prefix_key(session.id()); 130 | let string = serde_json::to_string(&session)?; 131 | 132 | let mut connection = self.connection().await?; 133 | 134 | match session.expires_in() { 135 | None => connection.set(id, string).await?, 136 | 137 | Some(expiry) => { 138 | connection 139 | .set_ex(id, string, expiry.as_secs() as usize) 140 | .await? 141 | } 142 | }; 143 | 144 | Ok(session.into_cookie_value()) 145 | } 146 | 147 | async fn destroy_session(&self, session: Session) -> Result { 148 | let mut connection = self.connection().await?; 149 | let key = self.prefix_key(session.id().to_string()); 150 | connection.del(key).await?; 151 | Ok(()) 152 | } 153 | 154 | async fn clear_store(&self) -> Result { 155 | let mut connection = self.connection().await?; 156 | 157 | if self.prefix.is_none() { 158 | let _: () = redis::cmd("FLUSHDB").query_async(&mut connection).await?; 159 | } else { 160 | let ids = self.ids().await?; 161 | if !ids.is_empty() { 162 | connection.del(ids).await?; 163 | } 164 | } 165 | Ok(()) 166 | } 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use super::*; 172 | use async_std::task; 173 | use std::time::Duration; 174 | 175 | async fn test_store() -> RedisSessionStore { 176 | let store = RedisSessionStore::new("redis://127.0.0.1").unwrap(); 177 | store.clear_store().await.unwrap(); 178 | store 179 | } 180 | 181 | #[async_std::test] 182 | async fn creating_a_new_session_with_no_expiry() -> Result { 183 | let store = test_store().await; 184 | let mut session = Session::new(); 185 | session.insert("key", "value")?; 186 | let cloned = session.clone(); 187 | let cookie_value = store.store_session(session).await?.unwrap(); 188 | 189 | let loaded_session = store.load_session(cookie_value).await?.unwrap(); 190 | assert_eq!(cloned.id(), loaded_session.id()); 191 | assert_eq!("value", &loaded_session.get::("key").unwrap()); 192 | 193 | assert!(!loaded_session.is_expired()); 194 | Ok(()) 195 | } 196 | 197 | #[async_std::test] 198 | async fn updating_a_session() -> Result { 199 | let store = test_store().await; 200 | let mut session = Session::new(); 201 | 202 | session.insert("key", "value")?; 203 | let cookie_value = store.store_session(session).await?.unwrap(); 204 | 205 | let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); 206 | session.insert("key", "other value")?; 207 | assert_eq!(None, store.store_session(session).await?); 208 | 209 | let session = store.load_session(cookie_value.clone()).await?.unwrap(); 210 | assert_eq!(&session.get::("key").unwrap(), "other value"); 211 | 212 | assert_eq!(1, store.count().await.unwrap()); 213 | Ok(()) 214 | } 215 | 216 | #[async_std::test] 217 | async fn updating_a_session_extending_expiry() -> Result { 218 | let store = test_store().await; 219 | let mut session = Session::new(); 220 | session.expire_in(Duration::from_secs(5)); 221 | let original_expires = session.expiry().unwrap().clone(); 222 | let cookie_value = store.store_session(session).await?.unwrap(); 223 | 224 | let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); 225 | let ttl = store.ttl_for_session(&session).await?; 226 | assert!(ttl > 3 && ttl < 5); 227 | 228 | assert_eq!(session.expiry().unwrap(), &original_expires); 229 | session.expire_in(Duration::from_secs(10)); 230 | let new_expires = session.expiry().unwrap().clone(); 231 | store.store_session(session).await?; 232 | 233 | let session = store.load_session(cookie_value.clone()).await?.unwrap(); 234 | let ttl = store.ttl_for_session(&session).await?; 235 | assert!(ttl > 8 && ttl < 10); 236 | assert_eq!(session.expiry().unwrap(), &new_expires); 237 | 238 | assert_eq!(1, store.count().await.unwrap()); 239 | 240 | task::sleep(Duration::from_secs(10)).await; 241 | assert_eq!(0, store.count().await.unwrap()); 242 | 243 | Ok(()) 244 | } 245 | 246 | #[async_std::test] 247 | async fn creating_a_new_session_with_expiry() -> Result { 248 | let store = test_store().await; 249 | let mut session = Session::new(); 250 | session.expire_in(Duration::from_secs(3)); 251 | session.insert("key", "value")?; 252 | let cloned = session.clone(); 253 | 254 | let cookie_value = store.store_session(session).await?.unwrap(); 255 | 256 | assert!(store.ttl_for_session(&cloned).await? > 1); 257 | 258 | let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap(); 259 | assert_eq!(cloned.id(), loaded_session.id()); 260 | assert_eq!("value", &loaded_session.get::("key").unwrap()); 261 | 262 | assert!(!loaded_session.is_expired()); 263 | 264 | task::sleep(Duration::from_secs(2)).await; 265 | assert_eq!(None, store.load_session(cookie_value).await?); 266 | 267 | Ok(()) 268 | } 269 | 270 | #[async_std::test] 271 | async fn destroying_a_single_session() -> Result { 272 | let store = test_store().await; 273 | for _ in 0..3i8 { 274 | store.store_session(Session::new()).await?; 275 | } 276 | 277 | let cookie = store.store_session(Session::new()).await?.unwrap(); 278 | assert_eq!(4, store.count().await?); 279 | let session = store.load_session(cookie.clone()).await?.unwrap(); 280 | store.destroy_session(session.clone()).await.unwrap(); 281 | assert_eq!(None, store.load_session(cookie).await?); 282 | assert_eq!(3, store.count().await?); 283 | 284 | // attempting to destroy the session again is not an error 285 | assert!(store.destroy_session(session).await.is_ok()); 286 | Ok(()) 287 | } 288 | 289 | #[async_std::test] 290 | async fn clearing_the_whole_store() -> Result { 291 | let store = test_store().await; 292 | for _ in 0..3i8 { 293 | store.store_session(Session::new()).await?; 294 | } 295 | 296 | assert_eq!(3, store.count().await?); 297 | store.clear_store().await.unwrap(); 298 | assert_eq!(0, store.count().await?); 299 | 300 | Ok(()) 301 | } 302 | 303 | #[async_std::test] 304 | async fn prefixes() -> Result { 305 | test_store().await; // clear the db 306 | 307 | let store = RedisSessionStore::new("redis://127.0.0.1")?.with_prefix("sessions/"); 308 | store.clear_store().await?; 309 | 310 | for _ in 0..3i8 { 311 | store.store_session(Session::new()).await?; 312 | } 313 | 314 | let mut session = Session::new(); 315 | 316 | session.insert("key", "value")?; 317 | let cookie_value = store.store_session(session).await?.unwrap(); 318 | 319 | let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); 320 | session.insert("key", "other value")?; 321 | assert_eq!(None, store.store_session(session).await?); 322 | 323 | let session = store.load_session(cookie_value.clone()).await?.unwrap(); 324 | assert_eq!(&session.get::("key").unwrap(), "other value"); 325 | 326 | assert_eq!(4, store.count().await.unwrap()); 327 | 328 | let other_store = 329 | RedisSessionStore::new("redis://127.0.0.1")?.with_prefix("other-namespace/"); 330 | 331 | assert_eq!(0, other_store.count().await.unwrap()); 332 | for _ in 0..3i8 { 333 | other_store.store_session(Session::new()).await?; 334 | } 335 | 336 | other_store.clear_store().await?; 337 | 338 | assert_eq!(0, other_store.count().await?); 339 | assert_eq!(4, store.count().await?); 340 | 341 | Ok(()) 342 | } 343 | } 344 | --------------------------------------------------------------------------------