tower_sessions_ext_memory_store/
lib.rs

1use std::{collections::HashMap, sync::Arc};
2
3use async_trait::async_trait;
4use time::OffsetDateTime;
5use tokio::sync::Mutex;
6use tower_sessions_ext_core::{
7    SessionStore,
8    session::{Id, Record},
9    session_store,
10};
11
12/// A session store that lives only in memory.
13///
14/// This is useful for testing but not recommended for real applications.
15///
16/// # Examples
17///
18/// ```rust
19/// use tower_sessions_ext::MemoryStore;
20/// MemoryStore::default();
21/// ```
22#[derive(Clone, Debug, Default)]
23pub struct MemoryStore(Arc<Mutex<HashMap<Id, Record>>>);
24
25#[async_trait]
26impl SessionStore for MemoryStore {
27    async fn create(&self, record: &mut Record) -> session_store::Result<()> {
28        let mut store_guard = self.0.lock().await;
29        while store_guard.contains_key(&record.id) {
30            // Session ID collision mitigation.
31            record.id = Id::default();
32        }
33        store_guard.insert(record.id, record.clone());
34        Ok(())
35    }
36
37    async fn save(&self, record: &Record) -> session_store::Result<()> {
38        self.0.lock().await.insert(record.id, record.clone());
39        Ok(())
40    }
41
42    async fn load(&self, session_id: &Id) -> session_store::Result<Option<Record>> {
43        Ok(self
44            .0
45            .lock()
46            .await
47            .get(session_id)
48            .filter(|Record { expiry_date, .. }| is_active(*expiry_date))
49            .cloned())
50    }
51
52    async fn delete(&self, session_id: &Id) -> session_store::Result<()> {
53        self.0.lock().await.remove(session_id);
54        Ok(())
55    }
56}
57
58fn is_active(expiry_date: OffsetDateTime) -> bool {
59    expiry_date > OffsetDateTime::now_utc()
60}
61
62#[cfg(test)]
63mod tests {
64    use time::Duration;
65
66    use super::*;
67
68    #[tokio::test]
69    async fn test_create() {
70        let store = MemoryStore::default();
71        let mut record = Record {
72            id: Default::default(),
73            data: Default::default(),
74            expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30),
75        };
76        assert!(store.create(&mut record).await.is_ok());
77    }
78
79    #[tokio::test]
80    async fn test_save() {
81        let store = MemoryStore::default();
82        let record = Record {
83            id: Default::default(),
84            data: Default::default(),
85            expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30),
86        };
87        assert!(store.save(&record).await.is_ok());
88    }
89
90    #[tokio::test]
91    async fn test_load() {
92        let store = MemoryStore::default();
93        let mut record = Record {
94            id: Default::default(),
95            data: Default::default(),
96            expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30),
97        };
98        store.create(&mut record).await.unwrap();
99        let loaded_record = store.load(&record.id).await.unwrap();
100        assert_eq!(Some(record), loaded_record);
101    }
102
103    #[tokio::test]
104    async fn test_delete() {
105        let store = MemoryStore::default();
106        let mut record = Record {
107            id: Default::default(),
108            data: Default::default(),
109            expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30),
110        };
111        store.create(&mut record).await.unwrap();
112        assert!(store.delete(&record.id).await.is_ok());
113        assert_eq!(None, store.load(&record.id).await.unwrap());
114    }
115
116    #[tokio::test]
117    async fn test_create_id_collision() {
118        let store = MemoryStore::default();
119        let expiry_date = OffsetDateTime::now_utc() + Duration::minutes(30);
120        let mut record1 = Record {
121            id: Default::default(),
122            data: Default::default(),
123            expiry_date,
124        };
125        let mut record2 = Record {
126            id: Default::default(),
127            data: Default::default(),
128            expiry_date,
129        };
130        store.create(&mut record1).await.unwrap();
131        record2.id = record1.id; // Set the same ID for record2
132        store.create(&mut record2).await.unwrap();
133        assert_ne!(record1.id, record2.id); // IDs should be different
134    }
135}