mockforge_collab/
core_bridge.rs

1//! Bridge between mockforge-collab and mockforge-core workspace types
2//!
3//! This module provides conversion and synchronization between:
4//! - `TeamWorkspace` (collaboration workspace with metadata)
5//! - `Workspace` (full mockforge-core workspace with mocks, folders, etc.)
6
7use 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
15/// Bridge service for integrating collaboration workspaces with core workspaces
16pub struct CoreBridge {
17    persistence: WorkspacePersistence,
18}
19
20impl CoreBridge {
21    /// Create a new core bridge
22    pub fn new<P: AsRef<Path>>(workspace_dir: P) -> Self {
23        Self {
24            persistence: WorkspacePersistence::new(workspace_dir),
25        }
26    }
27
28    /// Convert a TeamWorkspace to a Core Workspace
29    ///
30    /// Extracts the full workspace data from the TeamWorkspace.config field
31    /// and reconstructs a Core Workspace object.
32    pub fn team_to_core(&self, team_workspace: &TeamWorkspace) -> Result<CoreWorkspace> {
33        // The full workspace data is stored in the config field as JSON
34        let workspace_json = &team_workspace.config;
35
36        // Deserialize the workspace from JSON
37        let mut workspace: CoreWorkspace =
38            serde_json::from_value(workspace_json.clone()).map_err(|e| {
39                CollabError::Internal(format!("Failed to deserialize workspace from config: {}", e))
40            })?;
41
42        // Update the workspace ID to match the team workspace ID
43        // (convert UUID to String)
44        workspace.id = team_workspace.id.to_string();
45
46        // Update metadata
47        workspace.name = team_workspace.name.clone();
48        workspace.description = team_workspace.description.clone();
49        workspace.updated_at = team_workspace.updated_at;
50
51        Ok(workspace)
52    }
53
54    /// Convert a Core Workspace to a TeamWorkspace
55    ///
56    /// Serializes the full workspace data into the TeamWorkspace.config field
57    /// and creates a TeamWorkspace with collaboration metadata.
58    pub fn core_to_team(
59        &self,
60        core_workspace: &CoreWorkspace,
61        owner_id: Uuid,
62    ) -> Result<TeamWorkspace> {
63        // Serialize the full workspace to JSON
64        let workspace_json = serde_json::to_value(core_workspace).map_err(|e| {
65            CollabError::Internal(format!("Failed to serialize workspace to JSON: {}", e))
66        })?;
67
68        // Create TeamWorkspace with the serialized workspace in config
69        let mut team_workspace = TeamWorkspace::new(core_workspace.name.clone(), owner_id);
70        team_workspace.id = Uuid::parse_str(&core_workspace.id).unwrap_or_else(|_| Uuid::new_v4()); // Fallback to new UUID if parse fails
71        team_workspace.description = core_workspace.description.clone();
72        team_workspace.config = workspace_json;
73        team_workspace.created_at = core_workspace.created_at;
74        team_workspace.updated_at = core_workspace.updated_at;
75
76        Ok(team_workspace)
77    }
78
79    /// Get the full workspace state from a TeamWorkspace
80    ///
81    /// Returns the complete Core Workspace including all mocks, folders, and configuration.
82    pub fn get_workspace_state(&self, team_workspace: &TeamWorkspace) -> Result<CoreWorkspace> {
83        self.team_to_core(team_workspace)
84    }
85
86    /// Update the workspace state in a TeamWorkspace
87    ///
88    /// Serializes the Core Workspace and stores it in the TeamWorkspace.config field.
89    pub fn update_workspace_state(
90        &self,
91        team_workspace: &mut TeamWorkspace,
92        core_workspace: &CoreWorkspace,
93    ) -> Result<()> {
94        // Serialize the full workspace
95        let workspace_json = serde_json::to_value(core_workspace)
96            .map_err(|e| CollabError::Internal(format!("Failed to serialize workspace: {}", e)))?;
97
98        // Update the config field
99        team_workspace.config = workspace_json;
100        team_workspace.updated_at = chrono::Utc::now();
101
102        Ok(())
103    }
104
105    /// Load workspace from disk using WorkspacePersistence
106    ///
107    /// This loads a workspace from the filesystem and converts it to a TeamWorkspace.
108    pub async fn load_workspace_from_disk(
109        &self,
110        workspace_id: &str,
111        owner_id: Uuid,
112    ) -> Result<TeamWorkspace> {
113        // Load from disk
114        let core_workspace = self
115            .persistence
116            .load_workspace(workspace_id)
117            .await
118            .map_err(|e| CollabError::Internal(format!("Failed to load workspace: {}", e)))?;
119
120        // Convert to TeamWorkspace
121        self.core_to_team(&core_workspace, owner_id)
122    }
123
124    /// Save workspace to disk using WorkspacePersistence
125    ///
126    /// This saves a TeamWorkspace to the filesystem as a Core Workspace.
127    pub async fn save_workspace_to_disk(&self, team_workspace: &TeamWorkspace) -> Result<()> {
128        // Convert to Core Workspace
129        let core_workspace = self.team_to_core(team_workspace)?;
130
131        // Save to disk
132        self.persistence
133            .save_workspace(&core_workspace)
134            .await
135            .map_err(|e| CollabError::Internal(format!("Failed to save workspace: {}", e)))?;
136
137        Ok(())
138    }
139
140    /// Export workspace for backup
141    ///
142    /// Uses WorkspacePersistence to create a backup-compatible export.
143    pub async fn export_workspace_for_backup(
144        &self,
145        team_workspace: &TeamWorkspace,
146    ) -> Result<Value> {
147        // Convert to Core Workspace
148        let core_workspace = self.team_to_core(team_workspace)?;
149
150        // Serialize to JSON for backup
151        serde_json::to_value(&core_workspace)
152            .map_err(|e| CollabError::Internal(format!("Failed to serialize for backup: {}", e)))
153    }
154
155    /// Import workspace from backup
156    ///
157    /// Restores a workspace from a backup JSON value.
158    pub async fn import_workspace_from_backup(
159        &self,
160        backup_data: &Value,
161        owner_id: Uuid,
162        new_name: Option<String>,
163    ) -> Result<TeamWorkspace> {
164        // Deserialize Core Workspace from backup
165        let mut core_workspace: CoreWorkspace = serde_json::from_value(backup_data.clone())
166            .map_err(|e| CollabError::Internal(format!("Failed to deserialize backup: {}", e)))?;
167
168        // Update name if provided
169        if let Some(name) = new_name {
170            core_workspace.name = name;
171        }
172
173        // Generate new ID for restored workspace
174        core_workspace.id = Uuid::new_v4().to_string();
175        core_workspace.created_at = chrono::Utc::now();
176        core_workspace.updated_at = chrono::Utc::now();
177
178        // Convert to TeamWorkspace
179        self.core_to_team(&core_workspace, owner_id)
180    }
181
182    /// Get workspace state as JSON for sync
183    ///
184    /// Returns the full workspace state as a JSON value for real-time synchronization.
185    pub fn get_workspace_state_json(&self, team_workspace: &TeamWorkspace) -> Result<Value> {
186        let core_workspace = self.team_to_core(team_workspace)?;
187        serde_json::to_value(&core_workspace)
188            .map_err(|e| CollabError::Internal(format!("Failed to serialize state: {}", e)))
189    }
190
191    /// Update workspace state from JSON
192    ///
193    /// Updates the TeamWorkspace with state from a JSON value (from sync).
194    pub fn update_workspace_state_from_json(
195        &self,
196        team_workspace: &mut TeamWorkspace,
197        state_json: &Value,
198    ) -> Result<()> {
199        // Deserialize Core Workspace from JSON
200        let mut core_workspace: CoreWorkspace = serde_json::from_value(state_json.clone())
201            .map_err(|e| {
202                CollabError::Internal(format!("Failed to deserialize state JSON: {}", e))
203            })?;
204
205        // Preserve TeamWorkspace metadata
206        core_workspace.id = team_workspace.id.to_string();
207        core_workspace.name = team_workspace.name.clone();
208        core_workspace.description = team_workspace.description.clone();
209
210        // Update the TeamWorkspace
211        self.update_workspace_state(team_workspace, &core_workspace)
212    }
213
214    /// Create a new empty workspace
215    ///
216    /// Creates a new Core Workspace and converts it to a TeamWorkspace.
217    pub fn create_empty_workspace(&self, name: String, owner_id: Uuid) -> Result<TeamWorkspace> {
218        let core_workspace = CoreWorkspace::new(name.clone());
219        self.core_to_team(&core_workspace, owner_id)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_team_to_core_conversion() {
229        let bridge = CoreBridge::new("/tmp/test");
230        let owner_id = Uuid::new_v4();
231
232        // Create a simple core workspace
233        let core_workspace = CoreWorkspace::new("Test Workspace".to_string());
234        let team_workspace = bridge.core_to_team(&core_workspace, owner_id).unwrap();
235
236        // Convert back
237        let restored = bridge.team_to_core(&team_workspace).unwrap();
238
239        assert_eq!(restored.name, core_workspace.name);
240        assert_eq!(restored.folders.len(), core_workspace.folders.len());
241        assert_eq!(restored.requests.len(), core_workspace.requests.len());
242    }
243
244    #[test]
245    fn test_state_json_roundtrip() {
246        let bridge = CoreBridge::new("/tmp/test");
247        let owner_id = Uuid::new_v4();
248
249        // Create workspace
250        let core_workspace = CoreWorkspace::new("Test".to_string());
251        let mut team_workspace = bridge.core_to_team(&core_workspace, owner_id).unwrap();
252
253        // Get state as JSON
254        let state_json = bridge.get_workspace_state_json(&team_workspace).unwrap();
255
256        // Update from JSON
257        bridge
258            .update_workspace_state_from_json(&mut team_workspace, &state_json)
259            .unwrap();
260
261        // Verify it still works
262        let restored = bridge.team_to_core(&team_workspace).unwrap();
263        assert_eq!(restored.name, "Test");
264    }
265}