1use std::sync::Mutex;
17use std::sync::atomic::{AtomicUsize, Ordering};
18
19use async_trait::async_trait;
20
21use crate::error::Result;
22use crate::session::Session;
23
24#[async_trait]
27pub trait SessionStore: Send + Sync {
28 async fn load(&self) -> Result<Option<Session>>;
30 async fn save(&self, session: &Session) -> Result<()>;
32}
33
34#[derive(Default)]
36pub struct MemorySessionStore {
37 session: Mutex<Option<Session>>,
38 saves: AtomicUsize,
39}
40
41impl std::fmt::Debug for MemorySessionStore {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("MemorySessionStore")
44 .field("saves", &self.save_count())
45 .finish_non_exhaustive()
46 }
47}
48
49impl MemorySessionStore {
50 pub fn new(session: Option<Session>) -> Self {
51 Self {
52 session: Mutex::new(session),
53 saves: AtomicUsize::new(0),
54 }
55 }
56
57 pub fn get(&self) -> Option<Session> {
59 self.session
60 .lock()
61 .unwrap_or_else(std::sync::PoisonError::into_inner)
62 .clone()
63 }
64
65 pub fn save_count(&self) -> usize {
67 self.saves.load(Ordering::SeqCst)
68 }
69}
70
71#[async_trait]
72impl SessionStore for MemorySessionStore {
73 async fn load(&self) -> Result<Option<Session>> {
74 Ok(self.get())
75 }
76
77 async fn save(&self, session: &Session) -> Result<()> {
78 *self
79 .session
80 .lock()
81 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(session.clone());
82 self.saves.fetch_add(1, Ordering::SeqCst);
83 Ok(())
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::sync::Arc;
91
92 #[tokio::test]
93 async fn memory_store_roundtrip() -> Result<()> {
94 let store: Arc<dyn SessionStore> = Arc::new(MemorySessionStore::default());
95 assert!(store.load().await?.is_none());
96 let s = Session {
97 web_app_version: Some("v1".into()),
98 ..Session::default()
99 };
100 store.save(&s).await?;
101 let back = store
102 .load()
103 .await?
104 .ok_or_else(|| crate::Error::invalid("none"))?;
105 assert_eq!(back.web_app_version.as_deref(), Some("v1"));
106 Ok(())
107 }
108}