1pub mod filter;
50pub mod session;
51pub mod session_manager;
52
53pub use filter::SessionFilter;
54pub use session::{
55 EntryType, LimitAction, LimitCheck, LimitExceeded, Session, SessionConfig, SessionEntry,
56 SessionId, SessionMetadata, SessionStatus, SessionType, Timestamp,
57};
58pub use session_manager::SessionManager;
59
60use serde::{Deserialize, Serialize};
61use serde_json::Value;
62use std::collections::HashMap;
63use std::path::PathBuf;
64
65use std::fs;
66use std::path::Path;
67
68pub type Result<T> = std::result::Result<T, WorkspaceError>;
70
71#[derive(Debug, thiserror::Error)]
73pub enum WorkspaceError {
74 #[error("IO error: {0}")]
75 Io(#[from] std::io::Error),
76
77 #[error("Serialization error: {0}")]
78 Serialization(String),
79
80 #[error("Workspace not found: {0}")]
81 NotFound(String),
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct Workspace {
87 pub id: String,
89
90 pub name: String,
92
93 pub root: PathBuf,
95
96 #[serde(default)]
98 pub metadata: HashMap<String, String>,
99
100 #[serde(default)]
102 pub context: HashMap<String, Value>,
103}
104
105impl Workspace {
106 pub fn new(id: impl Into<String>, name: impl Into<String>, root: PathBuf) -> Self {
108 Self {
109 id: id.into(),
110 name: name.into(),
111 root,
112 metadata: HashMap::new(),
113 context: HashMap::new(),
114 }
115 }
116
117 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
119 self.metadata.insert(key.into(), value.into());
120 self
121 }
122
123 pub fn with_context(mut self, key: impl Into<String>, value: Value) -> Self {
125 self.context.insert(key.into(), value);
126 self
127 }
128
129 pub fn get_context(&self, key: &str) -> Option<&Value> {
131 self.context.get(key)
132 }
133
134 pub fn get_metadata(&self, key: &str) -> Option<&String> {
136 self.metadata.get(key)
137 }
138}
139
140impl Workspace {
141 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
143 let json = serde_json::to_string_pretty(self)
144 .map_err(|e| WorkspaceError::Serialization(e.to_string()))?;
145 fs::write(path, json).map_err(WorkspaceError::Io)?;
146 Ok(())
147 }
148
149 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
151 let json = fs::read_to_string(path).map_err(WorkspaceError::Io)?;
152 let workspace = serde_json::from_str(&json)
153 .map_err(|e| WorkspaceError::Serialization(e.to_string()))?;
154 Ok(workspace)
155 }
156}
157
158#[derive(Debug, Default)]
160pub struct WorkspaceManager {
161 workspaces: HashMap<String, Workspace>,
162 active_workspace: Option<String>,
163}
164
165impl WorkspaceManager {
166 pub fn new() -> Self {
168 Self::default()
169 }
170
171 pub fn create(&mut self, workspace: Workspace) {
173 self.workspaces.insert(workspace.id.clone(), workspace);
174 }
175
176 pub fn get(&self, id: &str) -> Option<&Workspace> {
178 self.workspaces.get(id)
179 }
180
181 pub fn get_mut(&mut self, id: &str) -> Option<&mut Workspace> {
183 self.workspaces.get_mut(id)
184 }
185
186 pub fn set_active(&mut self, id: &str) -> Result<()> {
188 if !self.workspaces.contains_key(id) {
189 return Err(WorkspaceError::NotFound(id.to_string()));
190 }
191 self.active_workspace = Some(id.to_string());
192 Ok(())
193 }
194
195 pub fn get_active(&self) -> Option<&Workspace> {
197 self.active_workspace
198 .as_ref()
199 .and_then(|id| self.workspaces.get(id))
200 }
201
202 pub fn get_active_mut(&mut self) -> Option<&mut Workspace> {
204 self.active_workspace
205 .as_ref()
206 .and_then(|id| self.workspaces.get_mut(id))
207 }
208
209 pub fn list(&self) -> Vec<String> {
211 self.workspaces.keys().cloned().collect()
212 }
213
214 pub fn remove(&mut self, id: &str) -> Option<Workspace> {
216 if self.active_workspace.as_deref() == Some(id) {
217 self.active_workspace = None;
218 }
219 self.workspaces.remove(id)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_workspace_creation() {
229 let workspace = Workspace::new("test", "Test Workspace", PathBuf::from("/tmp/test"));
230 assert_eq!(workspace.id, "test");
231 assert_eq!(workspace.name, "Test Workspace");
232 }
233
234 #[test]
235 fn test_workspace_builder() {
236 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"))
237 .with_metadata("version", "1.0")
238 .with_context("key", serde_json::json!({"value": 42}));
239
240 assert_eq!(workspace.get_metadata("version"), Some(&"1.0".to_string()));
241 assert!(workspace.get_context("key").is_some());
242 }
243
244 #[test]
245 fn test_workspace_manager() {
246 let mut manager = WorkspaceManager::new();
247
248 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"));
249 manager.create(workspace);
250
251 assert!(manager.get("test").is_some());
252 assert_eq!(manager.list().len(), 1);
253 }
254
255 #[test]
256 fn test_active_workspace() {
257 let mut manager = WorkspaceManager::new();
258
259 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"));
260 manager.create(workspace);
261
262 manager.set_active("test").unwrap();
263 assert!(manager.get_active().is_some());
264 assert_eq!(manager.get_active().unwrap().id, "test");
265 }
266
267 #[test]
268 fn test_remove_workspace() {
269 let mut manager = WorkspaceManager::new();
270
271 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"));
272 manager.create(workspace);
273
274 manager.set_active("test").unwrap();
275 assert!(manager.remove("test").is_some());
276 assert!(manager.get_active().is_none());
277 }
278
279 #[test]
280 fn test_workspace_save_load() {
281 let workspace = Workspace::new("test", "Test Workspace", PathBuf::from("/tmp/test"))
282 .with_metadata("version", "1.0")
283 .with_context("key", serde_json::json!({"value": 42}));
284
285 let temp_file = tempfile::NamedTempFile::new().unwrap();
286 let path = temp_file.path();
287
288 workspace.save_to_file(path).unwrap();
290
291 let loaded = Workspace::load_from_file(path).unwrap();
293
294 assert_eq!(workspace.id, loaded.id);
295 assert_eq!(workspace.name, loaded.name);
296 assert_eq!(workspace.metadata, loaded.metadata);
297 assert_eq!(workspace.context, loaded.context);
298 }
299}