use std::{collections::BTreeMap, sync::RwLock};
use soaprs_auth::{Session, SessionId, SessionStore};
use soaprs_core::{BoxFuture, SoapError, SoapResult};
#[derive(Debug)]
pub struct MemorySessionStore<P> {
sessions: RwLock<BTreeMap<SessionId, Session<P>>>,
}
impl<P> MemorySessionStore<P> {
pub const fn new() -> Self {
Self {
sessions: RwLock::new(BTreeMap::new()),
}
}
}
impl<P> Default for MemorySessionStore<P> {
fn default() -> Self {
Self::new()
}
}
impl<P> SessionStore<P> for MemorySessionStore<P>
where
P: Clone + Send + Sync + 'static,
{
fn load<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<Option<Session<P>>>> {
Box::pin(async move {
let sessions = self
.sessions
.read()
.map_err(|_| SoapError::infrastructure("in-memory session read lock poisoned"))?;
Ok(sessions.get(id).cloned())
})
}
fn save(&self, session: Session<P>) -> BoxFuture<'_, SoapResult<()>> {
Box::pin(async move {
let mut sessions = self
.sessions
.write()
.map_err(|_| SoapError::infrastructure("in-memory session write lock poisoned"))?;
sessions.insert(session.id().clone(), session);
Ok(())
})
}
fn delete<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<()>> {
Box::pin(async move {
let mut sessions = self
.sessions
.write()
.map_err(|_| SoapError::infrastructure("in-memory session write lock poisoned"))?;
sessions.remove(id);
Ok(())
})
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, UNIX_EPOCH};
use soaprs_auth::{Session, SessionId, StandardPrincipal};
use soaprs_contract_tests::{block_on, verify_session_store_contract};
use super::MemorySessionStore;
#[test]
fn session_store_passes_the_shared_contract() {
let Some(id) = SessionId::new("session-contract").ok() else {
panic!("valid session ID");
};
let first = StandardPrincipal::new("user-1").and_then(|principal| {
Session::new(
id.clone(),
principal,
UNIX_EPOCH,
UNIX_EPOCH + Duration::from_secs(60),
)
});
let replacement = StandardPrincipal::new("user-2").and_then(|principal| {
Session::new(
id,
principal,
UNIX_EPOCH,
UNIX_EPOCH + Duration::from_secs(120),
)
});
let (Some(first), Some(replacement)) = (first.ok(), replacement.ok()) else {
panic!("valid session fixtures");
};
let store = MemorySessionStore::new();
assert!(block_on(verify_session_store_contract(&store, first, replacement)).is_ok());
}
}