use std::{collections::HashMap, sync::RwLock};
use chrono::Utc;
use uuid::Uuid;
use crate::{
error::{AuthError, Result},
session_state::store::SessionStateEntry,
};
type Key = (Uuid, String, String);
#[derive(Default)]
pub struct InMemorySessionStateStore {
entries: RwLock<HashMap<Key, SessionStateEntry>>,
}
impl InMemorySessionStateStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn lock_err() -> AuthError {
AuthError::Internal {
message: "session-state lock poisoned".to_string(),
}
}
pub fn get(
&self,
session_id: Uuid,
thread_id: &str,
key: &str,
) -> Result<Option<SessionStateEntry>> {
let map = self.entries.read().map_err(|_| Self::lock_err())?;
Ok(map
.get(&(session_id, thread_id.to_string(), key.to_string()))
.filter(|e| e.expires_at > Utc::now())
.cloned())
}
pub fn set(&self, entry: SessionStateEntry) -> Result<()> {
let mut map = self.entries.write().map_err(|_| Self::lock_err())?;
map.insert((entry.session_id, entry.thread_id.clone(), entry.key.clone()), entry);
Ok(())
}
pub fn delete(&self, session_id: Uuid, thread_id: &str, key: &str) -> Result<()> {
let mut map = self.entries.write().map_err(|_| Self::lock_err())?;
map.remove(&(session_id, thread_id.to_string(), key.to_string()));
Ok(())
}
pub fn list_thread(&self, session_id: Uuid, thread_id: &str) -> Result<Vec<SessionStateEntry>> {
let map = self.entries.read().map_err(|_| Self::lock_err())?;
let now = Utc::now();
let mut entries: Vec<SessionStateEntry> = map
.values()
.filter(|e| {
e.session_id == session_id && e.thread_id == thread_id && e.expires_at > now
})
.cloned()
.collect();
entries.sort_by(|a, b| a.updated_at.cmp(&b.updated_at).then_with(|| a.key.cmp(&b.key)));
Ok(entries)
}
pub fn expire_thread(&self, session_id: Uuid, thread_id: &str) -> Result<()> {
let mut map = self.entries.write().map_err(|_| Self::lock_err())?;
map.retain(|(sid, tid, _), _| !(*sid == session_id && tid == thread_id));
Ok(())
}
pub fn thread_count(
&self,
session_id: Uuid,
thread_id: &str,
exclude_key: &str,
) -> Result<usize> {
let map = self.entries.read().map_err(|_| Self::lock_err())?;
let now = Utc::now();
Ok(map
.values()
.filter(|e| {
e.session_id == session_id
&& e.thread_id == thread_id
&& e.expires_at > now
&& e.key != exclude_key
})
.count())
}
pub fn replace_thread(&self, summary: SessionStateEntry) -> Result<()> {
let mut map = self.entries.write().map_err(|_| Self::lock_err())?;
map.retain(|(sid, tid, _), _| !(*sid == summary.session_id && *tid == summary.thread_id));
map.insert((summary.session_id, summary.thread_id.clone(), summary.key.clone()), summary);
Ok(())
}
pub fn evict_expired(&self) -> Result<u64> {
let mut map = self.entries.write().map_err(|_| Self::lock_err())?;
let now = Utc::now();
let before = map.len();
map.retain(|_, e| e.expires_at > now);
Ok((before - map.len()) as u64)
}
}