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