mockforge_core/workspace/
registry.rs1use 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#[derive(Debug, Clone)]
16pub struct WorkspaceRegistry {
17 workspaces: HashMap<EntityId, Workspace>,
19 active_workspace_id: Option<EntityId>,
21 route_registry: Arc<RwLock<RouteRegistry>>,
23 environments: HashMap<EntityId, Environment>,
25 request_processor: RequestProcessor,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct WorkspaceRegistryConfig {
32 pub max_workspaces: Option<usize>,
34 pub default_workspace_name: String,
36 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, }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct WorkspaceStats {
53 pub total_workspaces: usize,
55 pub total_folders: usize,
57 pub total_requests: usize,
59 pub total_responses: usize,
61 pub total_environments: usize,
63 pub last_modified: DateTime<Utc>,
65}
66
67impl WorkspaceRegistry {
68 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 pub fn with_config(config: WorkspaceRegistryConfig) -> Self {
81 let mut registry = Self::new();
82
83 let default_workspace = Workspace::new(config.default_workspace_name);
85 let _ = registry.add_workspace(default_workspace);
86
87 registry
88 }
89
90 pub fn add_workspace(&mut self, workspace: Workspace) -> Result<EntityId, String> {
92 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 self.update_route_registry();
104
105 Ok(id)
106 }
107
108 pub fn get_workspace(&self, id: &EntityId) -> Option<&Workspace> {
110 self.workspaces.get(id)
111 }
112
113 pub fn get_workspace_mut(&mut self, id: &EntityId) -> Option<&mut Workspace> {
115 self.workspaces.get_mut(id)
116 }
117
118 pub fn remove_workspace(&mut self, id: &EntityId) -> Result<Workspace, String> {
120 if let Some(workspace) = self.workspaces.remove(id) {
121 if self.active_workspace_id.as_ref() == Some(id) {
123 self.active_workspace_id = self.workspaces.keys().next().cloned();
124 }
125
126 self.update_route_registry();
128
129 Ok(workspace)
130 } else {
131 Err(format!("Workspace with ID {} not found", id))
132 }
133 }
134
135 pub fn get_all_workspaces(&self) -> Vec<&Workspace> {
137 self.workspaces.values().collect()
138 }
139
140 pub fn get_all_workspaces_mut(&mut self) -> Vec<&mut Workspace> {
142 self.workspaces.values_mut().collect()
143 }
144
145 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 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 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 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 pub fn get_environment(&self, id: &EntityId) -> Option<&Environment> {
174 self.environments.get(id)
175 }
176
177 pub fn get_active_environment(&self) -> Option<&Environment> {
179 self.environments.values().find(|env| env.active)
180 }
181
182 pub fn set_active_environment(&mut self, id: EntityId) -> Result<(), String> {
184 if self.environments.contains_key(&id) {
185 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 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 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 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 self.add_folder_requests_to_registry(&mut route_registry, &workspace.folders);
238 }
239 }
240 }
241
242 fn add_folder_requests_to_registry(
244 &self,
245 route_registry: &mut RouteRegistry,
246 folders: &[Folder],
247 ) {
248 for folder in folders {
249 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 self.add_folder_requests_to_registry(route_registry, &folder.folders);
263 }
264 }
265
266 pub fn get_route_registry(&self) -> &Arc<RwLock<RouteRegistry>> {
268 &self.route_registry
269 }
270
271 pub fn get_config(&self) -> WorkspaceRegistryConfig {
273 WorkspaceRegistryConfig::default()
274 }
275
276 pub fn find_request(&self, request_id: &EntityId) -> Option<&MockRequest> {
278 for workspace in self.workspaces.values() {
279 if let Some(request) = workspace.requests.iter().find(|r| &r.id == request_id) {
281 return Some(request);
282 }
283
284 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 #[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 if let Some(request) = folder.requests.iter().find(|r| &r.id == request_id) {
303 return Some(request);
304 }
305
306 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 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 #[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 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 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 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 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 self.search_requests_in_folders(&workspace.folders, &query_lower, &mut results);
387 }
388
389 results
390 }
391
392 #[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 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 self.search_requests_in_folders(&folder.folders, query, results);
418 }
419 }
420
421 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 for request in &workspace.requests {
428 if request.tags.contains(&tag.to_string()) {
429 results.push(request);
430 }
431 }
432
433 self.get_requests_by_tag_in_folders(&workspace.folders, tag, &mut results);
435 }
436
437 results
438 }
439
440 #[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 for request in &folder.requests {
451 if request.tags.contains(&tag.to_string()) {
452 results.push(request);
453 }
454 }
455
456 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}