1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11use std::path::PathBuf;
12
13use std::fs;
14use std::path::Path;
15
16pub type Result<T> = std::result::Result<T, WorkspaceError>;
18
19#[derive(Debug, thiserror::Error)]
21pub enum WorkspaceError {
22 #[error("IO error: {0}")]
23 Io(#[from] std::io::Error),
24
25 #[error("Serialization error: {0}")]
26 Serialization(String),
27
28 #[error("Workspace not found: {0}")]
29 NotFound(String),
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct Workspace {
35 pub id: String,
37
38 pub name: String,
40
41 pub root: PathBuf,
43
44 #[serde(default)]
46 pub metadata: HashMap<String, String>,
47
48 #[serde(default)]
50 pub context: HashMap<String, Value>,
51}
52
53impl Workspace {
54 pub fn new(id: impl Into<String>, name: impl Into<String>, root: PathBuf) -> Self {
56 Self {
57 id: id.into(),
58 name: name.into(),
59 root,
60 metadata: HashMap::new(),
61 context: HashMap::new(),
62 }
63 }
64
65 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
67 self.metadata.insert(key.into(), value.into());
68 self
69 }
70
71 pub fn with_context(mut self, key: impl Into<String>, value: Value) -> Self {
73 self.context.insert(key.into(), value);
74 self
75 }
76
77 pub fn get_context(&self, key: &str) -> Option<&Value> {
79 self.context.get(key)
80 }
81
82 pub fn get_metadata(&self, key: &str) -> Option<&String> {
84 self.metadata.get(key)
85 }
86}
87
88impl Workspace {
89 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
91 let json = serde_json::to_string_pretty(self)
92 .map_err(|e| WorkspaceError::Serialization(e.to_string()))?;
93 fs::write(path, json).map_err(WorkspaceError::Io)?;
94 Ok(())
95 }
96
97 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
99 let json = fs::read_to_string(path).map_err(WorkspaceError::Io)?;
100 let workspace = serde_json::from_str(&json)
101 .map_err(|e| WorkspaceError::Serialization(e.to_string()))?;
102 Ok(workspace)
103 }
104}
105
106#[derive(Debug, Default)]
108pub struct WorkspaceManager {
109 workspaces: HashMap<String, Workspace>,
110 active_workspace: Option<String>,
111}
112
113impl WorkspaceManager {
114 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub fn create(&mut self, workspace: Workspace) {
121 self.workspaces.insert(workspace.id.clone(), workspace);
122 }
123
124 pub fn get(&self, id: &str) -> Option<&Workspace> {
126 self.workspaces.get(id)
127 }
128
129 pub fn get_mut(&mut self, id: &str) -> Option<&mut Workspace> {
131 self.workspaces.get_mut(id)
132 }
133
134 pub fn set_active(&mut self, id: &str) -> Result<()> {
136 if !self.workspaces.contains_key(id) {
137 return Err(WorkspaceError::NotFound(id.to_string()));
138 }
139 self.active_workspace = Some(id.to_string());
140 Ok(())
141 }
142
143 pub fn get_active(&self) -> Option<&Workspace> {
145 self.active_workspace
146 .as_ref()
147 .and_then(|id| self.workspaces.get(id))
148 }
149
150 pub fn get_active_mut(&mut self) -> Option<&mut Workspace> {
152 self.active_workspace
153 .as_ref()
154 .and_then(|id| self.workspaces.get_mut(id))
155 }
156
157 pub fn list(&self) -> Vec<String> {
159 self.workspaces.keys().cloned().collect()
160 }
161
162 pub fn remove(&mut self, id: &str) -> Option<Workspace> {
164 if self.active_workspace.as_deref() == Some(id) {
165 self.active_workspace = None;
166 }
167 self.workspaces.remove(id)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_workspace_creation() {
177 let workspace = Workspace::new("test", "Test Workspace", PathBuf::from("/tmp/test"));
178 assert_eq!(workspace.id, "test");
179 assert_eq!(workspace.name, "Test Workspace");
180 }
181
182 #[test]
183 fn test_workspace_builder() {
184 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"))
185 .with_metadata("version", "1.0")
186 .with_context("key", serde_json::json!({"value": 42}));
187
188 assert_eq!(workspace.get_metadata("version"), Some(&"1.0".to_string()));
189 assert!(workspace.get_context("key").is_some());
190 }
191
192 #[test]
193 fn test_workspace_manager() {
194 let mut manager = WorkspaceManager::new();
195
196 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"));
197 manager.create(workspace);
198
199 assert!(manager.get("test").is_some());
200 assert_eq!(manager.list().len(), 1);
201 }
202
203 #[test]
204 fn test_active_workspace() {
205 let mut manager = WorkspaceManager::new();
206
207 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"));
208 manager.create(workspace);
209
210 manager.set_active("test").unwrap();
211 assert!(manager.get_active().is_some());
212 assert_eq!(manager.get_active().unwrap().id, "test");
213 }
214
215 #[test]
216 fn test_remove_workspace() {
217 let mut manager = WorkspaceManager::new();
218
219 let workspace = Workspace::new("test", "Test", PathBuf::from("/tmp"));
220 manager.create(workspace);
221
222 manager.set_active("test").unwrap();
223 assert!(manager.remove("test").is_some());
224 assert!(manager.get_active().is_none());
225 }
226
227 #[test]
228 fn test_workspace_save_load() {
229 let workspace = Workspace::new("test", "Test Workspace", PathBuf::from("/tmp/test"))
230 .with_metadata("version", "1.0")
231 .with_context("key", serde_json::json!({"value": 42}));
232
233 let temp_file = tempfile::NamedTempFile::new().unwrap();
234 let path = temp_file.path();
235
236 workspace.save_to_file(path).unwrap();
238
239 let loaded = Workspace::load_from_file(path).unwrap();
241
242 assert_eq!(workspace.id, loaded.id);
243 assert_eq!(workspace.name, loaded.name);
244 assert_eq!(workspace.metadata, loaded.metadata);
245 assert_eq!(workspace.context, loaded.context);
246 }
247}