use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum SessionState {
#[default]
Idle,
Active,
Background,
Paused,
Complete,
Failed,
Cancelled,
}
impl SessionState {
pub fn is_terminal(&self) -> bool {
matches!(
self,
SessionState::Complete | SessionState::Failed | SessionState::Cancelled
)
}
pub fn is_running(&self) -> bool {
matches!(self, SessionState::Active | SessionState::Background)
}
}
#[derive(Error, Debug)]
pub enum StateManagerError {
#[error("Session not found: {0}")]
SessionNotFound(String),
#[error("Invalid state transition from {from:?} to {to:?}")]
InvalidTransition {
from: SessionState,
to: SessionState,
},
#[error("Session is in terminal state {0:?}")]
TerminalState(SessionState),
#[error("Serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: String,
pub state: SessionState,
pub metadata: HashMap<String, serde_json::Value>,
pub created_at: u64,
pub updated_at: u64,
}
impl Session {
pub fn new(id: String) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
id,
state: SessionState::default(),
metadata: HashMap::new(),
created_at: now,
updated_at: now,
}
}
pub fn set_state(&mut self, state: SessionState) {
self.state = state;
self.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
}
pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
self.metadata.get(key)
}
pub fn set_metadata(&mut self, key: String, value: serde_json::Value) {
self.metadata.insert(key, value);
self.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
}
}
#[derive(Debug, Clone)]
pub struct StateManager {
sessions: Arc<RwLock<HashMap<String, Session>>>,
}
impl Default for StateManager {
fn default() -> Self {
Self::new()
}
}
impl StateManager {
pub fn new() -> Self {
Self {
sessions: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn create_session(&self, id: String) -> Session {
let session = Session::new(id.clone());
let mut sessions = self.sessions.write();
sessions.insert(id, session.clone());
session
}
pub fn get_session(&self, id: &str) -> Option<Session> {
let sessions = self.sessions.read();
sessions.get(id).cloned()
}
pub fn get_state(&self, id: &str) -> Result<SessionState, StateManagerError> {
let sessions = self.sessions.read();
sessions
.get(id)
.map(|s| s.state)
.ok_or_else(|| StateManagerError::SessionNotFound(id.to_string()))
}
pub fn set_state(&self, id: &str, state: SessionState) -> Result<(), StateManagerError> {
let mut sessions = self.sessions.write();
let session = sessions
.get_mut(id)
.ok_or_else(|| StateManagerError::SessionNotFound(id.to_string()))?;
if session.state.is_terminal() {
return Err(StateManagerError::TerminalState(session.state));
}
session.set_state(state);
Ok(())
}
pub fn is_active(&self, id: &str) -> bool {
self.get_state(id)
.map(|s| s == SessionState::Active)
.unwrap_or(false)
}
pub fn is_running(&self, id: &str) -> bool {
self.get_state(id).map(|s| s.is_running()).unwrap_or(false)
}
pub fn get_or_create_session(&self, id: &str) -> Session {
let mut sessions = self.sessions.write();
if let Some(session) = sessions.get(id) {
session.clone()
} else {
let session = Session::new(id.to_string());
let result = session.clone();
sessions.insert(id.to_string(), session);
result
}
}
pub fn set_metadata(
&self,
id: &str,
key: String,
value: serde_json::Value,
) -> Result<(), StateManagerError> {
let mut sessions = self.sessions.write();
let session = sessions
.get_mut(id)
.ok_or_else(|| StateManagerError::SessionNotFound(id.to_string()))?;
session.set_metadata(key, value);
Ok(())
}
pub fn get_metadata(
&self,
id: &str,
key: &str,
) -> Result<Option<serde_json::Value>, StateManagerError> {
let sessions = self.sessions.read();
let session = sessions
.get(id)
.ok_or_else(|| StateManagerError::SessionNotFound(id.to_string()))?;
Ok(session.get_metadata(key).cloned())
}
pub fn delete_session(&self, id: &str) -> Option<Session> {
let mut sessions = self.sessions.write();
sessions.remove(id)
}
pub fn list_sessions(&self) -> Vec<String> {
let sessions = self.sessions.read();
sessions.keys().cloned().collect()
}
pub fn active_count(&self) -> usize {
let sessions = self.sessions.read();
sessions
.values()
.filter(|s| s.state == SessionState::Active)
.count()
}
pub fn clear(&self) {
let mut sessions = self.sessions.write();
sessions.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_state_transitions() {
assert!(!SessionState::Active.is_terminal());
assert!(SessionState::Complete.is_terminal());
assert!(SessionState::Active.is_running());
assert!(!SessionState::Idle.is_running());
}
#[test]
fn test_session_creation() {
let session = Session::new("test-1".to_string());
assert_eq!(session.id, "test-1");
assert_eq!(session.state, SessionState::Idle);
assert!(session.metadata.is_empty());
}
#[test]
fn test_state_manager_basic() {
let manager = StateManager::new();
let session = manager.create_session("test-1".to_string());
assert_eq!(session.state, SessionState::Idle);
assert!(manager.is_active("test-1") == false);
}
#[test]
fn test_state_transitions() {
let manager = StateManager::new();
manager.create_session("test-1".to_string());
manager.set_state("test-1", SessionState::Active).unwrap();
assert!(manager.is_active("test-1"));
manager
.set_state("test-1", SessionState::Background)
.unwrap();
assert!(manager.is_running("test-1"));
}
#[test]
fn test_terminal_state_prevention() {
let manager = StateManager::new();
manager.create_session("test-1".to_string());
manager.set_state("test-1", SessionState::Complete).unwrap();
let result = manager.set_state("test-1", SessionState::Active);
assert!(matches!(
result,
Err(StateManagerError::TerminalState(SessionState::Complete))
));
}
#[test]
fn test_metadata() {
let manager = StateManager::new();
manager.create_session("test-1".to_string());
manager
.set_metadata("test-1", "key1".to_string(), serde_json::json!("value1"))
.unwrap();
let value = manager.get_metadata("test-1", "key1").unwrap();
assert_eq!(value, Some(serde_json::json!("value1")));
}
#[test]
fn test_session_not_found() {
let manager = StateManager::new();
let result = manager.get_state("nonexistent");
assert!(matches!(result, Err(StateManagerError::SessionNotFound(_))));
}
#[test]
fn test_list_and_count() {
let manager = StateManager::new();
manager.create_session("test-1".to_string());
manager.create_session("test-2".to_string());
manager.create_session("test-3".to_string());
manager.set_state("test-1", SessionState::Active).unwrap();
manager.set_state("test-2", SessionState::Active).unwrap();
assert_eq!(manager.list_sessions().len(), 3);
assert_eq!(manager.active_count(), 2);
}
#[test]
fn test_clear() {
let manager = StateManager::new();
manager.create_session("test-1".to_string());
manager.create_session("test-2".to_string());
manager.clear();
assert_eq!(manager.list_sessions().len(), 0);
}
}