tower_sessions_redis_store/
lib.rs

1use std::fmt::Debug;
2
3use async_trait::async_trait;
4pub use fred;
5use fred::{
6    prelude::KeysInterface,
7    types::{Expiration, SetOptions},
8};
9use time::OffsetDateTime;
10use tower_sessions_core::{
11    session::{Id, Record},
12    session_store, SessionStore,
13};
14
15#[derive(Debug, thiserror::Error)]
16pub enum RedisStoreError {
17    #[error(transparent)]
18    Redis(#[from] fred::error::Error),
19
20    #[error(transparent)]
21    Decode(#[from] rmp_serde::decode::Error),
22
23    #[error(transparent)]
24    Encode(#[from] rmp_serde::encode::Error),
25}
26
27impl From<RedisStoreError> for session_store::Error {
28    fn from(err: RedisStoreError) -> Self {
29        match err {
30            RedisStoreError::Redis(inner) => session_store::Error::Backend(inner.to_string()),
31            RedisStoreError::Decode(inner) => session_store::Error::Decode(inner.to_string()),
32            RedisStoreError::Encode(inner) => session_store::Error::Encode(inner.to_string()),
33        }
34    }
35}
36
37/// A Redis session store.
38#[derive(Debug, Clone, Default)]
39pub struct RedisStore<C: KeysInterface + Send + Sync> {
40    client: C,
41}
42
43impl<C: KeysInterface + Send + Sync> RedisStore<C> {
44    /// Create a new Redis store with the provided client.
45    ///
46    /// # Examples
47    ///
48    /// ```rust,no_run
49    /// use tower_sessions_redis_store::{fred::prelude::*, RedisStore};
50    ///
51    /// # tokio_test::block_on(async {
52    /// let pool = Pool::new(Config::default(), None, None, None, 6).unwrap();
53    ///
54    /// let _ = pool.connect();
55    /// pool.wait_for_connect().await.unwrap();
56    ///
57    /// let session_store = RedisStore::new(pool);
58    /// })
59    /// ```
60    pub fn new(client: C) -> Self {
61        Self { client }
62    }
63
64    async fn save_with_options(
65        &self,
66        record: &Record,
67        options: Option<SetOptions>,
68    ) -> session_store::Result<bool> {
69        let expire = Some(Expiration::EXAT(OffsetDateTime::unix_timestamp(
70            record.expiry_date,
71        )));
72
73        Ok(self
74            .client
75            .set(
76                record.id.to_string(),
77                rmp_serde::to_vec(&record)
78                    .map_err(RedisStoreError::Encode)?
79                    .as_slice(),
80                expire,
81                options,
82                false,
83            )
84            .await
85            .map_err(RedisStoreError::Redis)?)
86    }
87}
88
89#[async_trait]
90impl<C> SessionStore for RedisStore<C>
91where
92    C: KeysInterface + Send + Sync + Debug + 'static,
93{
94    async fn create(&self, record: &mut Record) -> session_store::Result<()> {
95        loop {
96            if !self.save_with_options(record, Some(SetOptions::NX)).await? {
97                record.id = Id::default();
98                continue;
99            }
100            break;
101        }
102        Ok(())
103    }
104
105    async fn save(&self, record: &Record) -> session_store::Result<()> {
106        self.save_with_options(record, Some(SetOptions::XX)).await?;
107        Ok(())
108    }
109
110    async fn load(&self, session_id: &Id) -> session_store::Result<Option<Record>> {
111        let data = self
112            .client
113            .get::<Option<Vec<u8>>, _>(session_id.to_string())
114            .await
115            .map_err(RedisStoreError::Redis)?;
116
117        if let Some(data) = data {
118            Ok(Some(
119                rmp_serde::from_slice(&data).map_err(RedisStoreError::Decode)?,
120            ))
121        } else {
122            Ok(None)
123        }
124    }
125
126    async fn delete(&self, session_id: &Id) -> session_store::Result<()> {
127        let _: () = self
128            .client
129            .del(session_id.to_string())
130            .await
131            .map_err(RedisStoreError::Redis)?;
132        Ok(())
133    }
134}