mockforge_collab/
core_bridge.rs1use crate::error::{CollabError, Result};
8use crate::models::TeamWorkspace;
9use mockforge_core::workspace::Workspace as CoreWorkspace;
10use mockforge_core::workspace_persistence::WorkspacePersistence;
11use serde_json::Value;
12use std::path::Path;
13use uuid::Uuid;
14
15pub struct CoreBridge {
17 persistence: WorkspacePersistence,
18}
19
20impl CoreBridge {
21 pub fn new<P: AsRef<Path>>(workspace_dir: P) -> Self {
23 Self {
24 persistence: WorkspacePersistence::new(workspace_dir),
25 }
26 }
27
28 pub fn team_to_core(&self, team_workspace: &TeamWorkspace) -> Result<CoreWorkspace> {
37 let workspace_json = &team_workspace.config;
39
40 let mut workspace: CoreWorkspace =
42 serde_json::from_value(workspace_json.clone()).map_err(|e| {
43 CollabError::Internal(format!("Failed to deserialize workspace from config: {e}"))
44 })?;
45
46 workspace.id = team_workspace.id.to_string();
49
50 workspace.name.clone_from(&team_workspace.name);
52 workspace.description.clone_from(&team_workspace.description);
53 workspace.updated_at = team_workspace.updated_at;
54
55 workspace.initialize_default_mock_environments();
57
58 Ok(workspace)
59 }
60
61 pub fn core_to_team(
70 &self,
71 core_workspace: &CoreWorkspace,
72 owner_id: Uuid,
73 ) -> Result<TeamWorkspace> {
74 let workspace_json = serde_json::to_value(core_workspace).map_err(|e| {
76 CollabError::Internal(format!("Failed to serialize workspace to JSON: {e}"))
77 })?;
78
79 let mut team_workspace = TeamWorkspace::new(core_workspace.name.clone(), owner_id);
81
82 team_workspace.id = Uuid::parse_str(&core_workspace.id).map_err(|e| {
84 CollabError::Internal(format!(
85 "Invalid workspace ID '{}': {}. Cannot convert to TeamWorkspace with corrupted ID.",
86 core_workspace.id, e
87 ))
88 })?;
89
90 team_workspace.description.clone_from(&core_workspace.description);
91 team_workspace.config = workspace_json;
92 team_workspace.created_at = core_workspace.created_at;
93 team_workspace.updated_at = core_workspace.updated_at;
94
95 Ok(team_workspace)
96 }
97
98 pub fn get_workspace_state(&self, team_workspace: &TeamWorkspace) -> Result<CoreWorkspace> {
106 self.team_to_core(team_workspace)
107 }
108
109 pub fn update_workspace_state(
117 &self,
118 team_workspace: &mut TeamWorkspace,
119 core_workspace: &CoreWorkspace,
120 ) -> Result<()> {
121 let workspace_json = serde_json::to_value(core_workspace)
123 .map_err(|e| CollabError::Internal(format!("Failed to serialize workspace: {e}")))?;
124
125 team_workspace.config = workspace_json;
127 team_workspace.updated_at = chrono::Utc::now();
128
129 Ok(())
130 }
131
132 pub async fn load_workspace_from_disk(
140 &self,
141 workspace_id: &str,
142 owner_id: Uuid,
143 ) -> Result<TeamWorkspace> {
144 let core_workspace = self
146 .persistence
147 .load_workspace(workspace_id)
148 .await
149 .map_err(|e| CollabError::Internal(format!("Failed to load workspace: {e}")))?;
150
151 self.core_to_team(&core_workspace, owner_id)
153 }
154
155 pub async fn save_workspace_to_disk(&self, team_workspace: &TeamWorkspace) -> Result<()> {
163 let core_workspace = self.team_to_core(team_workspace)?;
165
166 self.persistence
168 .save_workspace(&core_workspace)
169 .await
170 .map_err(|e| CollabError::Internal(format!("Failed to save workspace: {e}")))?;
171
172 Ok(())
173 }
174
175 #[allow(clippy::unused_async)]
183 pub async fn export_workspace_for_backup(
184 &self,
185 team_workspace: &TeamWorkspace,
186 ) -> Result<Value> {
187 let core_workspace = self.team_to_core(team_workspace)?;
189
190 serde_json::to_value(&core_workspace)
192 .map_err(|e| CollabError::Internal(format!("Failed to serialize for backup: {e}")))
193 }
194
195 #[allow(clippy::unused_async)]
203 pub async fn import_workspace_from_backup(
204 &self,
205 backup_data: &Value,
206 owner_id: Uuid,
207 new_name: Option<String>,
208 ) -> Result<TeamWorkspace> {
209 let mut core_workspace: CoreWorkspace = serde_json::from_value(backup_data.clone())
211 .map_err(|e| CollabError::Internal(format!("Failed to deserialize backup: {e}")))?;
212
213 if let Some(name) = new_name {
215 core_workspace.name = name;
216 }
217
218 core_workspace.id = Uuid::new_v4().to_string();
220 core_workspace.created_at = chrono::Utc::now();
221 core_workspace.updated_at = chrono::Utc::now();
222
223 self.core_to_team(&core_workspace, owner_id)
225 }
226
227 pub fn get_workspace_state_json(&self, team_workspace: &TeamWorkspace) -> Result<Value> {
235 let core_workspace = self.team_to_core(team_workspace)?;
236 serde_json::to_value(&core_workspace)
237 .map_err(|e| CollabError::Internal(format!("Failed to serialize state: {e}")))
238 }
239
240 pub fn update_workspace_state_from_json(
248 &self,
249 team_workspace: &mut TeamWorkspace,
250 state_json: &Value,
251 ) -> Result<()> {
252 let mut core_workspace: CoreWorkspace = serde_json::from_value(state_json.clone())
254 .map_err(|e| CollabError::Internal(format!("Failed to deserialize state JSON: {e}")))?;
255
256 core_workspace.id = team_workspace.id.to_string();
258 core_workspace.name.clone_from(&team_workspace.name);
259 core_workspace.description.clone_from(&team_workspace.description);
260
261 self.update_workspace_state(team_workspace, &core_workspace)
263 }
264
265 pub fn create_empty_workspace(&self, name: String, owner_id: Uuid) -> Result<TeamWorkspace> {
273 let core_workspace = CoreWorkspace::new(name);
274 self.core_to_team(&core_workspace, owner_id)
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_team_to_core_conversion() {
284 let bridge = CoreBridge::new("/tmp/test");
285 let owner_id = Uuid::new_v4();
286
287 let core_workspace = CoreWorkspace::new("Test Workspace".to_string());
289 let team_workspace = bridge.core_to_team(&core_workspace, owner_id).unwrap();
290
291 let restored = bridge.team_to_core(&team_workspace).unwrap();
293
294 assert_eq!(restored.name, core_workspace.name);
295 assert_eq!(restored.folders.len(), core_workspace.folders.len());
296 assert_eq!(restored.requests.len(), core_workspace.requests.len());
297 }
298
299 #[test]
300 fn test_state_json_roundtrip() {
301 let bridge = CoreBridge::new("/tmp/test");
302 let owner_id = Uuid::new_v4();
303
304 let core_workspace = CoreWorkspace::new("Test".to_string());
306 let mut team_workspace = bridge.core_to_team(&core_workspace, owner_id).unwrap();
307
308 let state_json = bridge.get_workspace_state_json(&team_workspace).unwrap();
310
311 bridge
313 .update_workspace_state_from_json(&mut team_workspace, &state_json)
314 .unwrap();
315
316 let restored = bridge.team_to_core(&team_workspace).unwrap();
318 assert_eq!(restored.name, "Test");
319 }
320
321 #[test]
322 fn test_invalid_uuid_returns_error() {
323 let bridge = CoreBridge::new("/tmp/test");
324 let owner_id = Uuid::new_v4();
325
326 let mut core_workspace = CoreWorkspace::new("Test Invalid UUID".to_string());
328 core_workspace.id = "not-a-valid-uuid".to_string();
329
330 let result = bridge.core_to_team(&core_workspace, owner_id);
332 assert!(result.is_err(), "Expected error for invalid UUID, but conversion succeeded");
333
334 if let Err(e) = result {
336 let error_msg = format!("{e}");
337 assert!(
338 error_msg.contains("not-a-valid-uuid"),
339 "Error message should contain the invalid UUID: {error_msg}",
340 );
341 }
342 }
343
344 #[test]
345 fn test_valid_uuid_conversion() {
346 let bridge = CoreBridge::new("/tmp/test");
347 let owner_id = Uuid::new_v4();
348 let workspace_uuid = Uuid::new_v4();
349
350 let mut core_workspace = CoreWorkspace::new("Test Valid UUID".to_string());
352 core_workspace.id = workspace_uuid.to_string();
353
354 let team_workspace = bridge.core_to_team(&core_workspace, owner_id).unwrap();
356
357 assert_eq!(team_workspace.id, workspace_uuid);
359 }
360}