ricecoder_sessions/
manager.rs

1//! Session manager for lifecycle management and session switching
2
3use crate::error::{SessionError, SessionResult};
4use crate::models::{Session, SessionContext};
5use std::collections::HashMap;
6
7/// Manages session lifecycle and switching
8#[derive(Debug, Clone)]
9pub struct SessionManager {
10    /// All sessions indexed by ID
11    sessions: HashMap<String, Session>,
12    /// Currently active session ID
13    active_session_id: Option<String>,
14    /// Maximum number of concurrent sessions
15    session_limit: usize,
16}
17
18impl SessionManager {
19    /// Create a new session manager with a session limit
20    pub fn new(session_limit: usize) -> Self {
21        Self {
22            sessions: HashMap::new(),
23            active_session_id: None,
24            session_limit,
25        }
26    }
27
28    /// Create a new session
29    pub fn create_session(
30        &mut self,
31        name: String,
32        context: SessionContext,
33    ) -> SessionResult<Session> {
34        // Check session limit
35        if self.sessions.len() >= self.session_limit {
36            return Err(SessionError::LimitReached {
37                max: self.session_limit,
38            });
39        }
40
41        let session = Session::new(name, context);
42        let session_id = session.id.clone();
43
44        self.sessions.insert(session_id.clone(), session.clone());
45
46        // Set as active if it's the first session
47        if self.active_session_id.is_none() {
48            self.active_session_id = Some(session_id);
49        }
50
51        Ok(session)
52    }
53
54    /// Delete a session
55    pub fn delete_session(&mut self, session_id: &str) -> SessionResult<()> {
56        if !self.sessions.contains_key(session_id) {
57            return Err(SessionError::NotFound(session_id.to_string()));
58        }
59
60        self.sessions.remove(session_id);
61
62        // If the deleted session was active, switch to another session
63        if self.active_session_id.as_deref() == Some(session_id) {
64            self.active_session_id = self.sessions.keys().next().cloned();
65        }
66
67        Ok(())
68    }
69
70    /// Get a session by ID
71    pub fn get_session(&self, session_id: &str) -> SessionResult<Session> {
72        self.sessions
73            .get(session_id)
74            .cloned()
75            .ok_or_else(|| SessionError::NotFound(session_id.to_string()))
76    }
77
78    /// Get the active session
79    pub fn get_active_session(&self) -> SessionResult<Session> {
80        let session_id = self
81            .active_session_id
82            .as_ref()
83            .ok_or(SessionError::Invalid("No active session".to_string()))?;
84
85        self.get_session(session_id)
86    }
87
88    /// Switch to a different session
89    pub fn switch_session(&mut self, session_id: &str) -> SessionResult<Session> {
90        // Verify the session exists
91        let session = self.get_session(session_id)?;
92
93        self.active_session_id = Some(session_id.to_string());
94
95        Ok(session)
96    }
97
98    /// List all sessions
99    pub fn list_sessions(&self) -> Vec<Session> {
100        self.sessions.values().cloned().collect()
101    }
102
103    /// Get the ID of the active session
104    pub fn active_session_id(&self) -> Option<&str> {
105        self.active_session_id.as_deref()
106    }
107
108    /// Update a session
109    pub fn update_session(&mut self, session: Session) -> SessionResult<()> {
110        if !self.sessions.contains_key(&session.id) {
111            return Err(SessionError::NotFound(session.id.clone()));
112        }
113
114        self.sessions.insert(session.id.clone(), session);
115        Ok(())
116    }
117
118    /// Get the session limit
119    pub fn session_limit(&self) -> usize {
120        self.session_limit
121    }
122
123    /// Get the number of active sessions
124    pub fn session_count(&self) -> usize {
125        self.sessions.len()
126    }
127
128    /// Check if session limit is reached
129    pub fn is_limit_reached(&self) -> bool {
130        self.sessions.len() >= self.session_limit
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::models::SessionMode;
138
139    fn create_test_context() -> SessionContext {
140        SessionContext::new("openai".to_string(), "gpt-4".to_string(), SessionMode::Chat)
141    }
142
143    #[test]
144    fn test_create_session() {
145        let mut manager = SessionManager::new(5);
146        let context = create_test_context();
147
148        let session = manager
149            .create_session("Test Session".to_string(), context)
150            .unwrap();
151
152        assert_eq!(session.name, "Test Session");
153        assert_eq!(manager.session_count(), 1);
154    }
155
156    #[test]
157    fn test_session_limit_enforcement() {
158        let mut manager = SessionManager::new(2);
159        let context = create_test_context();
160
161        // Create first session
162        manager
163            .create_session("Session 1".to_string(), context.clone())
164            .unwrap();
165
166        // Create second session
167        manager
168            .create_session("Session 2".to_string(), context.clone())
169            .unwrap();
170
171        // Third session should fail
172        let result = manager.create_session("Session 3".to_string(), context);
173        assert!(matches!(result, Err(SessionError::LimitReached { max: 2 })));
174    }
175
176    #[test]
177    fn test_switch_session() {
178        let mut manager = SessionManager::new(5);
179        let context = create_test_context();
180
181        let _session1 = manager
182            .create_session("Session 1".to_string(), context.clone())
183            .unwrap();
184        let session2 = manager
185            .create_session("Session 2".to_string(), context)
186            .unwrap();
187
188        // Switch to session 2
189        manager.switch_session(&session2.id).unwrap();
190
191        let active = manager.get_active_session().unwrap();
192        assert_eq!(active.id, session2.id);
193    }
194
195    #[test]
196    fn test_delete_session() {
197        let mut manager = SessionManager::new(5);
198        let context = create_test_context();
199
200        let session = manager
201            .create_session("Test Session".to_string(), context)
202            .unwrap();
203
204        manager.delete_session(&session.id).unwrap();
205
206        assert_eq!(manager.session_count(), 0);
207        assert!(manager.get_session(&session.id).is_err());
208    }
209
210    #[test]
211    fn test_list_sessions() {
212        let mut manager = SessionManager::new(5);
213        let context = create_test_context();
214
215        manager
216            .create_session("Session 1".to_string(), context.clone())
217            .unwrap();
218        manager
219            .create_session("Session 2".to_string(), context)
220            .unwrap();
221
222        let sessions = manager.list_sessions();
223        assert_eq!(sessions.len(), 2);
224    }
225}