mockforge_core/workspace/
registry.rs

1//! Workspace registry and management
2//!
3//! This module provides the WorkspaceRegistry for managing multiple workspaces,
4//! including loading, saving, and organizing workspaces.
5
6use crate::routing::RouteRegistry;
7use crate::workspace::core::{EntityId, Environment, Folder, MockRequest, Workspace};
8use crate::workspace::request::RequestProcessor;
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::sync::{Arc, RwLock};
13
14/// Workspace registry for managing multiple workspaces
15#[derive(Debug, Clone)]
16pub struct WorkspaceRegistry {
17    /// All workspaces indexed by ID
18    workspaces: HashMap<EntityId, Workspace>,
19    /// Active workspace ID
20    active_workspace_id: Option<EntityId>,
21    /// Route registry for all workspace requests
22    route_registry: Arc<RwLock<RouteRegistry>>,
23    /// Environment registry
24    environments: HashMap<EntityId, Environment>,
25    /// Request processor for converting requests to routes
26    request_processor: RequestProcessor,
27}
28
29/// Configuration for workspace registry
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct WorkspaceRegistryConfig {
32    /// Maximum number of workspaces allowed
33    pub max_workspaces: Option<usize>,
34    /// Default workspace name
35    pub default_workspace_name: String,
36    /// Auto-save interval in seconds
37    pub auto_save_interval_seconds: u64,
38}
39
40impl Default for WorkspaceRegistryConfig {
41    fn default() -> Self {
42        Self {
43            max_workspaces: None,
44            default_workspace_name: "Default Workspace".to_string(),
45            auto_save_interval_seconds: 300, // 5 minutes
46        }
47    }
48}
49
50/// Workspace statistics
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct WorkspaceStats {
53    /// Total number of workspaces
54    pub total_workspaces: usize,
55    /// Total number of folders across all workspaces
56    pub total_folders: usize,
57    /// Total number of requests across all workspaces
58    pub total_requests: usize,
59    /// Total number of responses across all workspaces
60    pub total_responses: usize,
61    /// Total number of environments
62    pub total_environments: usize,
63    /// Last modification timestamp
64    pub last_modified: DateTime<Utc>,
65}
66
67impl WorkspaceRegistry {
68    /// Create a new empty workspace registry
69    pub fn new() -> Self {
70        Self {
71            workspaces: HashMap::new(),
72            active_workspace_id: None,
73            route_registry: Arc::new(RwLock::new(RouteRegistry::new())),
74            environments: HashMap::new(),
75            request_processor: RequestProcessor::new(),
76        }
77    }
78
79    /// Create a new workspace registry with configuration
80    pub fn with_config(config: WorkspaceRegistryConfig) -> Self {
81        let mut registry = Self::new();
82
83        // Create default workspace
84        let default_workspace = Workspace::new(config.default_workspace_name);
85        let _ = registry.add_workspace(default_workspace);
86
87        registry
88    }
89
90    /// Add a workspace to the registry
91    pub fn add_workspace(&mut self, workspace: Workspace) -> Result<EntityId, String> {
92        // Check max workspaces limit
93        if let Some(max) = self.get_config().max_workspaces {
94            if self.workspaces.len() >= max {
95                return Err(format!("Maximum number of workspaces ({}) exceeded", max));
96            }
97        }
98
99        let id = workspace.id.clone();
100        self.workspaces.insert(id.clone(), workspace);
101
102        // Update route registry
103        self.update_route_registry();
104
105        Ok(id)
106    }
107
108    /// Get a workspace by ID
109    pub fn get_workspace(&self, id: &EntityId) -> Option<&Workspace> {
110        self.workspaces.get(id)
111    }
112
113    /// Get a mutable workspace by ID
114    pub fn get_workspace_mut(&mut self, id: &EntityId) -> Option<&mut Workspace> {
115        self.workspaces.get_mut(id)
116    }
117
118    /// Remove a workspace from the registry
119    pub fn remove_workspace(&mut self, id: &EntityId) -> Result<Workspace, String> {
120        if let Some(workspace) = self.workspaces.remove(id) {
121            // Update active workspace if necessary
122            if self.active_workspace_id.as_ref() == Some(id) {
123                self.active_workspace_id = self.workspaces.keys().next().cloned();
124            }
125
126            // Update route registry
127            self.update_route_registry();
128
129            Ok(workspace)
130        } else {
131            Err(format!("Workspace with ID {} not found", id))
132        }
133    }
134
135    /// Get all workspaces
136    pub fn get_all_workspaces(&self) -> Vec<&Workspace> {
137        self.workspaces.values().collect()
138    }
139
140    /// Get all workspaces mutably
141    pub fn get_all_workspaces_mut(&mut self) -> Vec<&mut Workspace> {
142        self.workspaces.values_mut().collect()
143    }
144
145    /// Set the active workspace
146    pub fn set_active_workspace(&mut self, id: EntityId) -> Result<(), String> {
147        if self.workspaces.contains_key(&id) {
148            self.active_workspace_id = Some(id);
149            Ok(())
150        } else {
151            Err(format!("Workspace with ID {} not found", id))
152        }
153    }
154
155    /// Get the active workspace
156    pub fn get_active_workspace(&self) -> Option<&Workspace> {
157        self.active_workspace_id.as_ref().and_then(|id| self.workspaces.get(id))
158    }
159
160    /// Get the active workspace mutably
161    pub fn get_active_workspace_mut(&mut self) -> Option<&mut Workspace> {
162        self.active_workspace_id.as_ref().and_then(|id| self.workspaces.get_mut(id))
163    }
164
165    /// Add an environment to the registry
166    pub fn add_environment(&mut self, environment: Environment) -> EntityId {
167        let id = environment.id.clone();
168        self.environments.insert(id.clone(), environment);
169        id
170    }
171
172    /// Get an environment by ID
173    pub fn get_environment(&self, id: &EntityId) -> Option<&Environment> {
174        self.environments.get(id)
175    }
176
177    /// Get the active environment
178    pub fn get_active_environment(&self) -> Option<&Environment> {
179        self.environments.values().find(|env| env.active)
180    }
181
182    /// Set the active environment
183    pub fn set_active_environment(&mut self, id: EntityId) -> Result<(), String> {
184        if self.environments.contains_key(&id) {
185            // Deactivate all environments and activate the selected one
186            for (env_id, env) in self.environments.iter_mut() {
187                env.active = *env_id == id;
188            }
189            Ok(())
190        } else {
191            Err(format!("Environment with ID {} not found", id))
192        }
193    }
194
195    /// Get workspace statistics
196    pub fn get_stats(&self) -> WorkspaceStats {
197        let total_folders = self.workspaces.values().map(|w| w.folders.len()).sum::<usize>();
198
199        let total_requests = self.workspaces.values().map(|w| w.requests.len()).sum::<usize>();
200
201        let total_responses = self
202            .workspaces
203            .values()
204            .map(|w| w.requests.iter().map(|r| r.responses.len()).sum::<usize>())
205            .sum::<usize>();
206
207        WorkspaceStats {
208            total_workspaces: self.workspaces.len(),
209            total_folders,
210            total_requests,
211            total_responses,
212            total_environments: self.environments.len(),
213            last_modified: Utc::now(),
214        }
215    }
216
217    /// Update the route registry with all workspace requests
218    fn update_route_registry(&mut self) {
219        if let Ok(mut route_registry) = self.route_registry.write() {
220            route_registry.clear();
221
222            for workspace in self.workspaces.values() {
223                // Add root requests
224                for request in &workspace.requests {
225                    if request.enabled {
226                        if let Some(_response) = request.active_response() {
227                            if let Ok(route) =
228                                self.request_processor.create_route_from_request(request)
229                            {
230                                let _ = route_registry.add_route(route);
231                            }
232                        }
233                    }
234                }
235
236                // Add folder requests recursively
237                self.add_folder_requests_to_registry(&mut route_registry, &workspace.folders);
238            }
239        }
240    }
241
242    /// Recursively add folder requests to the route registry
243    fn add_folder_requests_to_registry(
244        &self,
245        route_registry: &mut RouteRegistry,
246        folders: &[Folder],
247    ) {
248        for folder in folders {
249            // Add folder requests
250            for request in &folder.requests {
251                if request.enabled {
252                    if let Some(_response) = request.active_response() {
253                        if let Ok(route) = self.request_processor.create_route_from_request(request)
254                        {
255                            let _ = route_registry.add_route(route);
256                        }
257                    }
258                }
259            }
260
261            // Add subfolder requests
262            self.add_folder_requests_to_registry(route_registry, &folder.folders);
263        }
264    }
265
266    /// Get the route registry
267    pub fn get_route_registry(&self) -> &Arc<RwLock<RouteRegistry>> {
268        &self.route_registry
269    }
270
271    /// Get the configuration (placeholder implementation)
272    pub fn get_config(&self) -> WorkspaceRegistryConfig {
273        WorkspaceRegistryConfig::default()
274    }
275
276    /// Find a request by ID across all workspaces
277    pub fn find_request(&self, request_id: &EntityId) -> Option<&MockRequest> {
278        for workspace in self.workspaces.values() {
279            // Check root requests
280            if let Some(request) = workspace.requests.iter().find(|r| &r.id == request_id) {
281                return Some(request);
282            }
283
284            // Check folder requests
285            if let Some(request) = self.find_request_in_folder(&workspace.folders, request_id) {
286                return Some(request);
287            }
288        }
289
290        None
291    }
292
293    /// Find a request in a folder hierarchy
294    #[allow(clippy::only_used_in_recursion)]
295    fn find_request_in_folder<'a>(
296        &self,
297        folders: &'a [Folder],
298        request_id: &EntityId,
299    ) -> Option<&'a MockRequest> {
300        for folder in folders {
301            // Check folder requests
302            if let Some(request) = folder.requests.iter().find(|r| &r.id == request_id) {
303                return Some(request);
304            }
305
306            // Check subfolders
307            if let Some(request) = self.find_request_in_folder(&folder.folders, request_id) {
308                return Some(request);
309            }
310        }
311
312        None
313    }
314
315    /// Find a folder by ID across all workspaces
316    pub fn find_folder(&self, folder_id: &EntityId) -> Option<&Folder> {
317        for workspace in self.workspaces.values() {
318            if let Some(folder) = self.find_folder_in_workspace(&workspace.folders, folder_id) {
319                return Some(folder);
320            }
321        }
322
323        None
324    }
325
326    /// Find a folder in a workspace hierarchy
327    #[allow(clippy::only_used_in_recursion)]
328    fn find_folder_in_workspace<'a>(
329        &self,
330        folders: &'a [Folder],
331        folder_id: &EntityId,
332    ) -> Option<&'a Folder> {
333        for folder in folders {
334            if &folder.id == folder_id {
335                return Some(folder);
336            }
337
338            if let Some(found) = self.find_folder_in_workspace(&folder.folders, folder_id) {
339                return Some(found);
340            }
341        }
342
343        None
344    }
345
346    /// Export workspace to JSON
347    pub fn export_workspace(&self, workspace_id: &EntityId) -> Result<String, String> {
348        if let Some(workspace) = self.workspaces.get(workspace_id) {
349            serde_json::to_string_pretty(workspace)
350                .map_err(|e| format!("Failed to serialize workspace: {}", e))
351        } else {
352            Err(format!("Workspace with ID {} not found", workspace_id))
353        }
354    }
355
356    /// Import workspace from JSON
357    pub fn import_workspace(&mut self, json_data: &str) -> Result<EntityId, String> {
358        let workspace: Workspace = serde_json::from_str(json_data)
359            .map_err(|e| format!("Failed to deserialize workspace: {}", e))?;
360
361        self.add_workspace(workspace)
362    }
363
364    /// Search for requests across all workspaces
365    pub fn search_requests(&self, query: &str) -> Vec<&MockRequest> {
366        let query_lower = query.to_lowercase();
367        let mut results = Vec::new();
368
369        for workspace in self.workspaces.values() {
370            // Search root requests
371            for request in &workspace.requests {
372                if request.name.to_lowercase().contains(&query_lower)
373                    || request.url.to_lowercase().contains(&query_lower)
374                    || request
375                        .description
376                        .as_ref()
377                        .map(|d| d.to_lowercase())
378                        .unwrap_or_default()
379                        .contains(&query_lower)
380                {
381                    results.push(request);
382                }
383            }
384
385            // Search folder requests
386            self.search_requests_in_folders(&workspace.folders, &query_lower, &mut results);
387        }
388
389        results
390    }
391
392    /// Search for requests in folder hierarchy
393    #[allow(clippy::only_used_in_recursion)]
394    fn search_requests_in_folders<'a>(
395        &self,
396        folders: &'a [Folder],
397        query: &str,
398        results: &mut Vec<&'a MockRequest>,
399    ) {
400        for folder in folders {
401            // Search folder requests
402            for request in &folder.requests {
403                if request.name.to_lowercase().contains(query)
404                    || request.url.to_lowercase().contains(query)
405                    || request
406                        .description
407                        .as_ref()
408                        .map(|d| d.to_lowercase())
409                        .unwrap_or_default()
410                        .contains(query)
411                {
412                    results.push(request);
413                }
414            }
415
416            // Search subfolders
417            self.search_requests_in_folders(&folder.folders, query, results);
418        }
419    }
420
421    /// Get requests by tag
422    pub fn get_requests_by_tag(&self, tag: &str) -> Vec<&MockRequest> {
423        let mut results = Vec::new();
424
425        for workspace in self.workspaces.values() {
426            // Check root requests
427            for request in &workspace.requests {
428                if request.tags.contains(&tag.to_string()) {
429                    results.push(request);
430                }
431            }
432
433            // Check folder requests
434            self.get_requests_by_tag_in_folders(&workspace.folders, tag, &mut results);
435        }
436
437        results
438    }
439
440    /// Get requests by tag in folder hierarchy
441    #[allow(clippy::only_used_in_recursion)]
442    fn get_requests_by_tag_in_folders<'a>(
443        &self,
444        folders: &'a [Folder],
445        tag: &str,
446        results: &mut Vec<&'a MockRequest>,
447    ) {
448        for folder in folders {
449            // Check folder requests
450            for request in &folder.requests {
451                if request.tags.contains(&tag.to_string()) {
452                    results.push(request);
453                }
454            }
455
456            // Check subfolders
457            self.get_requests_by_tag_in_folders(&folder.folders, tag, results);
458        }
459    }
460}
461
462impl Default for WorkspaceRegistry {
463    fn default() -> Self {
464        Self::new()
465    }
466}