1use std::{collections::BTreeMap, sync::RwLock};
4
5use soaprs_auth::{Session, SessionId, SessionStore};
6use soaprs_core::{BoxFuture, SoapError, SoapResult};
7
8#[derive(Debug)]
10pub struct MemorySessionStore<P> {
11 sessions: RwLock<BTreeMap<SessionId, Session<P>>>,
12}
13
14impl<P> MemorySessionStore<P> {
15 pub const fn new() -> Self {
17 Self {
18 sessions: RwLock::new(BTreeMap::new()),
19 }
20 }
21}
22
23impl<P> Default for MemorySessionStore<P> {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl<P> SessionStore<P> for MemorySessionStore<P>
30where
31 P: Clone + Send + Sync + 'static,
32{
33 fn load<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<Option<Session<P>>>> {
34 Box::pin(async move {
35 let sessions = self
36 .sessions
37 .read()
38 .map_err(|_| SoapError::infrastructure("in-memory session read lock poisoned"))?;
39 Ok(sessions.get(id).cloned())
40 })
41 }
42
43 fn save(&self, session: Session<P>) -> BoxFuture<'_, SoapResult<()>> {
44 Box::pin(async move {
45 let mut sessions = self
46 .sessions
47 .write()
48 .map_err(|_| SoapError::infrastructure("in-memory session write lock poisoned"))?;
49 sessions.insert(session.id().clone(), session);
50 Ok(())
51 })
52 }
53
54 fn delete<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<()>> {
55 Box::pin(async move {
56 let mut sessions = self
57 .sessions
58 .write()
59 .map_err(|_| SoapError::infrastructure("in-memory session write lock poisoned"))?;
60 sessions.remove(id);
61 Ok(())
62 })
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use std::time::{Duration, UNIX_EPOCH};
69
70 use soaprs_auth::{Session, SessionId, StandardPrincipal};
71 use soaprs_contract_tests::{block_on, verify_session_store_contract};
72
73 use super::MemorySessionStore;
74
75 #[test]
76 fn session_store_passes_the_shared_contract() {
77 let Some(id) = SessionId::new("session-contract").ok() else {
78 panic!("valid session ID");
79 };
80 let first = StandardPrincipal::new("user-1").and_then(|principal| {
81 Session::new(
82 id.clone(),
83 principal,
84 UNIX_EPOCH,
85 UNIX_EPOCH + Duration::from_secs(60),
86 )
87 });
88 let replacement = StandardPrincipal::new("user-2").and_then(|principal| {
89 Session::new(
90 id,
91 principal,
92 UNIX_EPOCH,
93 UNIX_EPOCH + Duration::from_secs(120),
94 )
95 });
96 let (Some(first), Some(replacement)) = (first.ok(), replacement.ok()) else {
97 panic!("valid session fixtures");
98 };
99 let store = MemorySessionStore::new();
100
101 assert!(block_on(verify_session_store_contract(&store, first, replacement)).is_ok());
102 }
103}