1pub mod core;
12pub mod environment;
13pub mod mock_environment;
14pub mod promotion_trait;
15pub mod rbac;
16pub mod registry;
17pub mod request;
18pub mod scenario_promotion;
19pub mod sync;
20pub mod template_application;
21
22pub use environment::*;
24pub use mock_environment::*;
25pub use rbac::*;
26pub use registry::*;
27pub use request::*;
28pub use scenario_promotion::*;
29pub use sync::*;
30pub use template_application::*;
31
32use crate::config::AuthConfig;
34use crate::encryption::AutoEncryptionConfig;
35use crate::fidelity::FidelityScore;
36use crate::reality::RealityLevel;
37use crate::routing::{HttpMethod, Route, RouteRegistry};
38use crate::{Error, Result};
39use chrono::{DateTime, Utc};
40use serde::{Deserialize, Serialize};
41use std::collections::HashMap;
42use uuid::Uuid;
43
44pub type EntityId = String;
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Workspace {
50 pub id: EntityId,
52 pub name: String,
54 pub description: Option<String>,
56 pub created_at: DateTime<Utc>,
58 pub updated_at: DateTime<Utc>,
60 pub tags: Vec<String>,
62 pub config: WorkspaceConfig,
64 pub folders: Vec<Folder>,
66 pub requests: Vec<MockRequest>,
68 #[serde(default)]
70 pub order: i32,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, Default)]
75pub struct FolderInheritanceConfig {
76 #[serde(default)]
78 pub headers: HashMap<String, String>,
79 pub auth: Option<AuthConfig>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Folder {
86 pub id: EntityId,
88 pub name: String,
90 pub description: Option<String>,
92 pub parent_id: Option<EntityId>,
94 pub created_at: DateTime<Utc>,
96 pub updated_at: DateTime<Utc>,
98 pub tags: Vec<String>,
100 #[serde(default)]
102 pub inheritance: FolderInheritanceConfig,
103 pub folders: Vec<Folder>,
105 pub requests: Vec<MockRequest>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct MockRequest {
112 pub id: EntityId,
114 pub name: String,
116 pub description: Option<String>,
118 pub method: HttpMethod,
120 pub path: String,
122 pub headers: HashMap<String, String>,
124 pub query_params: HashMap<String, String>,
126 pub body: Option<String>,
128 pub response: MockResponse,
130 pub response_history: Vec<ResponseHistoryEntry>,
132 pub created_at: DateTime<Utc>,
134 pub updated_at: DateTime<Utc>,
136 pub tags: Vec<String>,
138 pub auth: Option<AuthConfig>,
140 pub priority: i32,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct MockResponse {
147 pub status_code: u16,
149 pub headers: HashMap<String, String>,
151 pub body: Option<String>,
153 pub content_type: Option<String>,
155 pub delay_ms: Option<u64>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ResponseHistoryEntry {
162 pub id: String,
164 pub executed_at: DateTime<Utc>,
166 pub request_method: HttpMethod,
168 pub request_path: String,
170 pub request_headers: HashMap<String, String>,
172 pub request_body: Option<String>,
174 pub response_status_code: u16,
176 pub response_headers: HashMap<String, String>,
178 pub response_body: Option<String>,
180 pub response_time_ms: u64,
182 pub response_size_bytes: u64,
184 pub error_message: Option<String>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct EnvironmentColor {
191 pub hex: String,
193 pub name: Option<String>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct Environment {
200 pub id: EntityId,
202 pub name: String,
204 pub description: Option<String>,
206 pub color: Option<EnvironmentColor>,
208 pub variables: HashMap<String, String>,
210 pub created_at: DateTime<Utc>,
212 pub updated_at: DateTime<Utc>,
214 #[serde(default)]
216 pub order: i32,
217 #[serde(default)]
219 pub sharable: bool,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct SyncConfig {
225 pub enabled: bool,
227 pub target_directory: Option<String>,
229 pub directory_structure: SyncDirectoryStructure,
231 pub sync_direction: SyncDirection,
233 pub include_metadata: bool,
235 pub realtime_monitoring: bool,
237 pub filename_pattern: String,
239 pub exclude_pattern: Option<String>,
241 pub force_overwrite: bool,
243 pub last_sync: Option<DateTime<Utc>>,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub enum SyncDirectoryStructure {
250 Flat,
252 Nested,
254 Grouped,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub enum SyncDirection {
261 Manual,
263 WorkspaceToDirectory,
265 Bidirectional,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct WorkspaceConfig {
272 pub base_url: Option<String>,
274 pub default_headers: HashMap<String, String>,
276 pub auth: Option<AuthConfig>,
278 pub global_environment: Environment,
280 pub environments: Vec<Environment>,
282 pub active_environment_id: Option<EntityId>,
284 #[serde(default)]
287 pub mock_environments: MockEnvironmentManager,
288 pub sync: SyncConfig,
290 #[serde(default)]
292 pub auto_encryption: AutoEncryptionConfig,
293 #[serde(default)]
297 pub reality_level: Option<RealityLevel>,
298 #[serde(default)]
301 pub ai_mode: Option<crate::ai_studio::config::AiMode>,
302 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub fidelity_score: Option<FidelityScore>,
306}
307
308#[derive(Debug, Clone)]
310pub struct WorkspaceRegistry {
311 workspaces: HashMap<EntityId, Workspace>,
312 active_workspace: Option<EntityId>,
313}
314
315impl Workspace {
316 pub fn new(name: String) -> Self {
318 let now = Utc::now();
319 let workspace_id = Uuid::new_v4().to_string();
320 let mut workspace = Self {
321 id: workspace_id.clone(),
322 name,
323 description: None,
324 created_at: now,
325 updated_at: now,
326 tags: Vec::new(),
327 config: WorkspaceConfig::default(),
328 folders: Vec::new(),
329 requests: Vec::new(),
330 order: 0, };
332
333 workspace.initialize_default_mock_environments();
335
336 workspace
337 }
338
339 pub fn initialize_default_mock_environments(&mut self) {
343 if self.config.mock_environments.workspace_id.is_empty()
345 || self.config.mock_environments.environments.is_empty()
346 {
347 if self.config.mock_environments.workspace_id.is_empty() {
349 self.config.mock_environments.workspace_id = self.id.clone();
350 }
351
352 if !self
354 .config
355 .mock_environments
356 .environments
357 .contains_key(&MockEnvironmentName::Dev)
358 {
359 let dev_env = MockEnvironment::new(self.id.clone(), MockEnvironmentName::Dev);
360 self.config.mock_environments.add_environment(dev_env);
361 }
362
363 if !self
365 .config
366 .mock_environments
367 .environments
368 .contains_key(&MockEnvironmentName::Test)
369 {
370 let test_env = MockEnvironment::new(self.id.clone(), MockEnvironmentName::Test);
371 self.config.mock_environments.add_environment(test_env);
372 }
373
374 if !self
376 .config
377 .mock_environments
378 .environments
379 .contains_key(&MockEnvironmentName::Prod)
380 {
381 let prod_env = MockEnvironment::new(self.id.clone(), MockEnvironmentName::Prod);
382 self.config.mock_environments.add_environment(prod_env);
383 }
384
385 if self.config.mock_environments.active_environment.is_none() {
387 let _ =
388 self.config.mock_environments.set_active_environment(MockEnvironmentName::Dev);
389 }
390 }
391 }
392
393 pub fn add_folder(&mut self, name: String) -> Result<EntityId> {
395 let folder = Folder::new(name);
396 let id = folder.id.clone();
397 self.folders.push(folder);
398 self.updated_at = Utc::now();
399 Ok(id)
400 }
401
402 pub fn create_environment(
404 &mut self,
405 name: String,
406 description: Option<String>,
407 ) -> Result<EntityId> {
408 if self.config.environments.iter().any(|env| env.name == name) {
410 return Err(Error::generic(format!("Environment with name '{}' already exists", name)));
411 }
412
413 let mut environment = Environment::new(name);
414 environment.description = description;
415
416 environment.order = self.config.environments.len() as i32;
418
419 let id = environment.id.clone();
420
421 self.config.environments.push(environment);
422 self.updated_at = Utc::now();
423 Ok(id)
424 }
425
426 pub fn get_environments(&self) -> Vec<&Environment> {
428 let mut all_envs = vec![&self.config.global_environment];
429 all_envs.extend(self.config.environments.iter());
430 all_envs
431 }
432
433 pub fn get_environment(&self, id: &str) -> Option<&Environment> {
435 if self.config.global_environment.id == id {
436 Some(&self.config.global_environment)
437 } else {
438 self.config.environments.iter().find(|env| env.id == id)
439 }
440 }
441
442 pub fn get_environment_mut(&mut self, id: &str) -> Option<&mut Environment> {
444 if self.config.global_environment.id == id {
445 Some(&mut self.config.global_environment)
446 } else {
447 self.config.environments.iter_mut().find(|env| env.id == id)
448 }
449 }
450
451 pub fn set_active_environment(&mut self, environment_id: Option<String>) -> Result<()> {
453 if let Some(ref id) = environment_id {
454 if self.get_environment(id).is_none() {
455 return Err(Error::generic(format!("Environment with ID '{}' not found", id)));
456 }
457 }
458 self.config.active_environment_id = environment_id;
459 self.updated_at = Utc::now();
460 Ok(())
461 }
462
463 pub fn get_active_environment(&self) -> &Environment {
465 if let Some(ref active_id) = self.config.active_environment_id {
466 self.get_environment(active_id).unwrap_or(&self.config.global_environment)
467 } else {
468 &self.config.global_environment
469 }
470 }
471
472 pub fn get_active_environment_id(&self) -> Option<&str> {
474 self.config.active_environment_id.as_deref()
475 }
476
477 pub fn get_variable(&self, key: &str) -> Option<&String> {
479 let active_env = self.get_active_environment();
481 active_env.get_variable(key).or_else(|| {
482 if active_env.id != self.config.global_environment.id {
483 self.config.global_environment.get_variable(key)
484 } else {
485 None
486 }
487 })
488 }
489
490 pub fn get_all_variables(&self) -> HashMap<String, String> {
492 let mut variables = HashMap::new();
493
494 variables.extend(self.config.global_environment.variables.clone());
496
497 let active_env = self.get_active_environment();
499 if active_env.id != self.config.global_environment.id {
500 variables.extend(active_env.variables.clone());
501 }
502
503 variables
504 }
505
506 pub fn delete_environment(&mut self, id: &str) -> Result<()> {
508 if id == self.config.global_environment.id {
509 return Err(Error::generic("Cannot delete global environment".to_string()));
510 }
511
512 let position = self.config.environments.iter().position(|env| env.id == id);
513 if let Some(pos) = position {
514 self.config.environments.remove(pos);
515
516 if self.config.active_environment_id.as_deref() == Some(id) {
518 self.config.active_environment_id = None;
519 }
520
521 self.updated_at = Utc::now();
522 Ok(())
523 } else {
524 Err(Error::generic(format!("Environment with ID '{}' not found", id)))
525 }
526 }
527
528 pub fn update_environments_order(&mut self, environment_ids: Vec<String>) -> Result<()> {
530 for env_id in &environment_ids {
532 if !self.config.environments.iter().any(|env| env.id == *env_id) {
533 return Err(Error::generic(format!("Environment with ID '{}' not found", env_id)));
534 }
535 }
536
537 for (index, env_id) in environment_ids.iter().enumerate() {
539 if let Some(env) = self.config.environments.iter_mut().find(|env| env.id == *env_id) {
540 env.order = index as i32;
541 env.updated_at = Utc::now();
542 }
543 }
544
545 self.updated_at = Utc::now();
546 Ok(())
547 }
548
549 pub fn get_environments_ordered(&self) -> Vec<&Environment> {
551 let mut all_envs = vec![&self.config.global_environment];
552 all_envs.extend(self.config.environments.iter());
553 all_envs.sort_by_key(|env| env.order);
554 all_envs
555 }
556
557 pub fn get_mock_environments(&self) -> &MockEnvironmentManager {
559 &self.config.mock_environments
560 }
561
562 pub fn get_mock_environments_mut(&mut self) -> &mut MockEnvironmentManager {
564 &mut self.config.mock_environments
565 }
566
567 pub fn get_mock_environment(&self, name: MockEnvironmentName) -> Option<&MockEnvironment> {
569 self.config.mock_environments.get_environment(name)
570 }
571
572 pub fn get_active_mock_environment(&self) -> Option<&MockEnvironment> {
574 self.config.mock_environments.get_active_environment()
575 }
576
577 pub fn set_active_mock_environment(&mut self, name: MockEnvironmentName) -> Result<()> {
579 self.config.mock_environments.set_active_environment(name)?;
580 self.updated_at = Utc::now();
581 Ok(())
582 }
583
584 pub fn list_mock_environments(&self) -> Vec<&MockEnvironment> {
586 self.config.mock_environments.list_environments()
587 }
588
589 pub fn set_mock_environment_config(
591 &mut self,
592 name: MockEnvironmentName,
593 reality_config: Option<crate::reality::RealityConfig>,
594 chaos_config: Option<crate::chaos_utilities::ChaosConfig>,
595 drift_budget_config: Option<crate::contract_drift::DriftBudgetConfig>,
596 ) -> Result<()> {
597 let env = if let Some(existing) = self.config.mock_environments.get_environment(name) {
599 MockEnvironment::with_configs(
600 existing.workspace_id.clone(),
601 name,
602 reality_config,
603 chaos_config,
604 drift_budget_config,
605 )
606 } else {
607 MockEnvironment::with_configs(
608 self.id.clone(),
609 name,
610 reality_config,
611 chaos_config,
612 drift_budget_config,
613 )
614 };
615
616 self.config.mock_environments.add_environment(env);
617 self.updated_at = Utc::now();
618 Ok(())
619 }
620
621 pub fn configure_sync(&mut self, config: SyncConfig) -> Result<()> {
623 self.config.sync = config;
624 self.updated_at = Utc::now();
625 Ok(())
626 }
627
628 pub fn enable_sync(&mut self, target_directory: String) -> Result<()> {
630 self.config.sync.enabled = true;
631 self.config.sync.target_directory = Some(target_directory);
632 self.config.sync.realtime_monitoring = true; self.updated_at = Utc::now();
634 Ok(())
635 }
636
637 pub fn disable_sync(&mut self) -> Result<()> {
639 self.config.sync.enabled = false;
640 self.updated_at = Utc::now();
641 Ok(())
642 }
643
644 pub fn get_sync_config(&self) -> &SyncConfig {
646 &self.config.sync
647 }
648
649 pub fn is_sync_enabled(&self) -> bool {
651 self.config.sync.enabled
652 }
653
654 pub fn get_sync_directory(&self) -> Option<&str> {
656 self.config.sync.target_directory.as_deref()
657 }
658
659 pub fn set_sync_directory(&mut self, directory: Option<String>) -> Result<()> {
661 self.config.sync.target_directory = directory;
662 self.updated_at = Utc::now();
663 Ok(())
664 }
665
666 pub fn set_sync_direction(&mut self, direction: SyncDirection) -> Result<()> {
668 self.config.sync.sync_direction = direction;
669 self.updated_at = Utc::now();
670 Ok(())
671 }
672
673 pub fn get_sync_direction(&self) -> &SyncDirection {
675 &self.config.sync.sync_direction
676 }
677
678 pub fn set_realtime_monitoring(&mut self, enabled: bool) -> Result<()> {
680 self.config.sync.realtime_monitoring = enabled;
681 self.updated_at = Utc::now();
682 Ok(())
683 }
684
685 pub fn is_realtime_monitoring_enabled(&self) -> bool {
687 self.config.sync.realtime_monitoring && self.config.sync.enabled
688 }
689
690 pub fn to_filtered_for_sync(&self) -> Workspace {
692 let mut filtered = self.clone();
693
694 filtered.config.global_environment.variables =
697 self.filter_sensitive_variables(&self.config.global_environment.variables);
698
699 filtered.config.environments = filtered
701 .config
702 .environments
703 .into_iter()
704 .filter(|env| env.sharable)
705 .map(|mut env| {
706 env.variables = self.filter_sensitive_variables(&env.variables);
707 env
708 })
709 .collect();
710
711 filtered
712 }
713
714 fn filter_sensitive_variables(
716 &self,
717 variables: &HashMap<String, String>,
718 ) -> HashMap<String, String> {
719 let sensitive_keys = [
720 "password",
722 "secret",
723 "key",
724 "token",
725 "credential",
726 "api_key",
727 "apikey",
728 "api_secret",
729 "db_password",
730 "database_password",
731 "aws_secret_key",
732 "aws_session_token",
733 "private_key",
734 "authorization",
735 "auth_token",
736 "access_token",
737 "refresh_token",
738 "cookie",
739 "session",
740 "csrf",
741 "jwt",
742 "bearer",
743 ];
744
745 variables
746 .iter()
747 .filter(|(key, _)| {
748 let key_lower = key.to_lowercase();
749 !sensitive_keys.iter().any(|sensitive| key_lower.contains(sensitive))
750 })
751 .map(|(k, v)| (k.clone(), v.clone()))
752 .collect()
753 }
754
755 pub fn should_sync(&self) -> bool {
757 self.config.sync.enabled && self.config.sync.target_directory.is_some()
758 }
759
760 pub fn get_sync_filename(&self) -> String {
762 let pattern = &self.config.sync.filename_pattern;
764
765 let filename = pattern
767 .replace("{name}", &sanitize_filename(&self.name))
768 .replace("{id}", &self.id);
769
770 if filename.ends_with(".yaml") || filename.ends_with(".yml") {
771 filename
772 } else {
773 format!("{}.yaml", filename)
774 }
775 }
776}
777
778fn sanitize_filename(name: &str) -> String {
780 name.chars()
781 .map(|c| match c {
782 '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
783 c if c.is_control() => '_',
784 c if c.is_whitespace() => '-',
785 c => c,
786 })
787 .collect::<String>()
788 .to_lowercase()
789}
790
791impl Folder {
792 pub fn new(name: String) -> Self {
794 let now = Utc::now();
795 Self {
796 id: Uuid::new_v4().to_string(),
797 name,
798 description: None,
799 parent_id: None,
800 created_at: now,
801 updated_at: now,
802 tags: Vec::new(),
803 inheritance: FolderInheritanceConfig::default(),
804 folders: Vec::new(),
805 requests: Vec::new(),
806 }
807 }
808
809 pub fn add_folder(&mut self, name: String) -> Result<EntityId> {
811 let mut folder = Folder::new(name);
812 folder.parent_id = Some(self.id.clone());
813 let id = folder.id.clone();
814 self.folders.push(folder);
815 self.updated_at = Utc::now();
816 Ok(id)
817 }
818
819 pub fn add_request(&mut self, request: MockRequest) -> Result<EntityId> {
821 let id = request.id.clone();
822 self.requests.push(request);
823 self.updated_at = Utc::now();
824 Ok(id)
825 }
826
827 pub fn find_folder(&self, id: &str) -> Option<&Folder> {
829 for folder in &self.folders {
830 if folder.id == id {
831 return Some(folder);
832 }
833 if let Some(found) = folder.find_folder(id) {
834 return Some(found);
835 }
836 }
837 None
838 }
839
840 pub fn find_folder_mut(&mut self, id: &str) -> Option<&mut Folder> {
842 for folder in &mut self.folders {
843 if folder.id == id {
844 return Some(folder);
845 }
846 if let Some(found) = folder.find_folder_mut(id) {
847 return Some(found);
848 }
849 }
850 None
851 }
852
853 pub fn find_request(&self, id: &str) -> Option<&MockRequest> {
855 for request in &self.requests {
857 if request.id == id {
858 return Some(request);
859 }
860 }
861
862 for folder in &self.folders {
864 if let Some(found) = folder.find_request(id) {
865 return Some(found);
866 }
867 }
868 None
869 }
870
871 pub fn get_routes(&self, workspace_id: &str) -> Vec<Route> {
873 let mut routes = Vec::new();
874
875 for request in &self.requests {
877 routes.push(
878 Route::new(request.method.clone(), request.path.clone())
879 .with_priority(request.priority)
880 .with_metadata("request_id".to_string(), serde_json::json!(request.id))
881 .with_metadata("folder_id".to_string(), serde_json::json!(self.id))
882 .with_metadata("workspace_id".to_string(), serde_json::json!(workspace_id)),
883 );
884 }
885
886 for folder in &self.folders {
888 routes.extend(folder.get_routes(workspace_id));
889 }
890
891 routes
892 }
893}
894
895impl Workspace {
896 pub fn find_folder(&self, id: &str) -> Option<&Folder> {
898 for folder in &self.folders {
899 if folder.id == id {
900 return Some(folder);
901 }
902 if let Some(found) = folder.find_folder(id) {
903 return Some(found);
904 }
905 }
906 None
907 }
908
909 pub fn find_folder_mut(&mut self, id: &str) -> Option<&mut Folder> {
911 for folder in &mut self.folders {
912 if folder.id == id {
913 return Some(folder);
914 }
915 if let Some(found) = folder.find_folder_mut(id) {
916 return Some(found);
917 }
918 }
919 None
920 }
921
922 pub fn add_request(&mut self, request: MockRequest) -> Result<EntityId> {
924 let id = request.id.clone();
925 self.requests.push(request);
926 self.updated_at = Utc::now();
927 Ok(id)
928 }
929
930 pub fn get_routes(&self) -> Vec<Route> {
932 let mut routes = Vec::new();
933
934 for request in &self.requests {
936 routes.push(
937 Route::new(request.method.clone(), request.path.clone())
938 .with_priority(request.priority)
939 .with_metadata("request_id".to_string(), serde_json::json!(request.id))
940 .with_metadata("workspace_id".to_string(), serde_json::json!(self.id)),
941 );
942 }
943
944 for folder in &self.folders {
946 routes.extend(folder.get_routes(&self.id));
947 }
948
949 routes
950 }
951
952 pub fn get_effective_auth<'a>(&'a self, folder_path: &[&'a Folder]) -> Option<&'a AuthConfig> {
954 for folder in folder_path.iter().rev() {
956 if let Some(auth) = &folder.inheritance.auth {
957 return Some(auth);
958 }
959 }
960
961 self.config.auth.as_ref()
963 }
964
965 pub fn get_effective_headers(&self, folder_path: &[&Folder]) -> HashMap<String, String> {
967 let mut effective_headers = HashMap::new();
968
969 for (key, value) in &self.config.default_headers {
971 effective_headers.insert(key.clone(), value.clone());
972 }
973
974 for folder in folder_path {
976 for (key, value) in &folder.inheritance.headers {
977 effective_headers.insert(key.clone(), value.clone());
978 }
979 }
980
981 effective_headers
982 }
983}
984
985impl Folder {
986 pub fn get_inheritance_path<'a>(&'a self, workspace: &'a Workspace) -> Vec<&'a Folder> {
988 let mut path = Vec::new();
989 let mut current = Some(self);
990
991 while let Some(folder) = current {
992 path.push(folder);
993 current =
994 folder.parent_id.as_ref().and_then(|parent_id| workspace.find_folder(parent_id));
995 }
996
997 path.reverse(); path
999 }
1000}
1001
1002impl MockRequest {
1003 pub fn apply_inheritance(
1005 &mut self,
1006 effective_headers: HashMap<String, String>,
1007 effective_auth: Option<&AuthConfig>,
1008 ) {
1009 for (key, value) in effective_headers {
1011 self.headers.entry(key).or_insert(value);
1012 }
1013
1014 if let Some(auth) = effective_auth {
1017 self.auth = Some(auth.clone());
1018 }
1019 }
1020
1021 pub fn create_inherited_request(
1023 mut self,
1024 workspace: &Workspace,
1025 folder_path: &[&Folder],
1026 ) -> Self {
1027 let effective_headers = workspace.get_effective_headers(folder_path);
1028 let effective_auth = workspace.get_effective_auth(folder_path);
1029
1030 self.apply_inheritance(effective_headers, effective_auth);
1031 self
1032 }
1033
1034 pub fn new(method: HttpMethod, path: String, name: String) -> Self {
1036 let now = Utc::now();
1037 Self {
1038 id: Uuid::new_v4().to_string(),
1039 name,
1040 description: None,
1041 method,
1042 path,
1043 headers: HashMap::new(),
1044 query_params: HashMap::new(),
1045 body: None,
1046 response: MockResponse::default(),
1047 response_history: Vec::new(),
1048 created_at: now,
1049 updated_at: now,
1050 tags: Vec::new(),
1051 auth: None,
1052 priority: 0,
1053 }
1054 }
1055
1056 pub fn with_response(mut self, response: MockResponse) -> Self {
1058 self.response = response;
1059 self
1060 }
1061
1062 pub fn with_header(mut self, key: String, value: String) -> Self {
1064 self.headers.insert(key, value);
1065 self
1066 }
1067
1068 pub fn with_query_param(mut self, key: String, value: String) -> Self {
1070 self.query_params.insert(key, value);
1071 self
1072 }
1073
1074 pub fn with_body(mut self, body: String) -> Self {
1076 self.body = Some(body);
1077 self
1078 }
1079
1080 pub fn with_tag(mut self, tag: String) -> Self {
1082 self.tags.push(tag);
1083 self
1084 }
1085
1086 pub fn add_response_history(&mut self, entry: ResponseHistoryEntry) {
1088 self.response_history.push(entry);
1089 if self.response_history.len() > 100 {
1091 self.response_history.remove(0);
1092 }
1093 self.response_history.sort_by(|a, b| b.executed_at.cmp(&a.executed_at));
1095 }
1096
1097 pub fn get_response_history(&self) -> &[ResponseHistoryEntry] {
1099 &self.response_history
1100 }
1101}
1102
1103impl Default for MockResponse {
1104 fn default() -> Self {
1105 Self {
1106 status_code: 200,
1107 headers: HashMap::new(),
1108 body: Some("{}".to_string()),
1109 content_type: Some("application/json".to_string()),
1110 delay_ms: None,
1111 }
1112 }
1113}
1114
1115impl Environment {
1116 pub fn new(name: String) -> Self {
1118 let now = Utc::now();
1119 Self {
1120 id: Uuid::new_v4().to_string(),
1121 name,
1122 description: None,
1123 color: None,
1124 variables: HashMap::new(),
1125 created_at: now,
1126 updated_at: now,
1127 order: 0, sharable: false, }
1130 }
1131
1132 pub fn new_global() -> Self {
1134 let mut env = Self::new("Global".to_string());
1135 env.description =
1136 Some("Global environment variables available in all contexts".to_string());
1137 env
1138 }
1139
1140 pub fn set_variable(&mut self, key: String, value: String) {
1142 self.variables.insert(key, value);
1143 self.updated_at = Utc::now();
1144 }
1145
1146 pub fn remove_variable(&mut self, key: &str) -> bool {
1148 let removed = self.variables.remove(key).is_some();
1149 if removed {
1150 self.updated_at = Utc::now();
1151 }
1152 removed
1153 }
1154
1155 pub fn get_variable(&self, key: &str) -> Option<&String> {
1157 self.variables.get(key)
1158 }
1159
1160 pub fn set_color(&mut self, color: EnvironmentColor) {
1162 self.color = Some(color);
1163 self.updated_at = Utc::now();
1164 }
1165}
1166
1167impl Default for SyncConfig {
1168 fn default() -> Self {
1169 Self {
1170 enabled: false,
1171 target_directory: None,
1172 directory_structure: SyncDirectoryStructure::Nested,
1173 sync_direction: SyncDirection::Manual,
1174 include_metadata: true,
1175 realtime_monitoring: false,
1176 filename_pattern: "{name}".to_string(),
1177 exclude_pattern: None,
1178 force_overwrite: false,
1179 last_sync: None,
1180 }
1181 }
1182}
1183
1184impl Default for WorkspaceConfig {
1185 fn default() -> Self {
1186 Self {
1187 base_url: None,
1188 default_headers: HashMap::new(),
1189 auth: None,
1190 global_environment: Environment::new_global(),
1191 environments: Vec::new(),
1192 active_environment_id: None,
1193 mock_environments: MockEnvironmentManager::default(),
1194 sync: SyncConfig::default(),
1195 auto_encryption: AutoEncryptionConfig::default(),
1196 reality_level: None,
1197 fidelity_score: None,
1198 ai_mode: None,
1199 }
1200 }
1201}
1202
1203impl WorkspaceRegistry {
1204 pub fn new() -> Self {
1206 Self {
1207 workspaces: HashMap::new(),
1208 active_workspace: None,
1209 }
1210 }
1211
1212 pub fn add_workspace(&mut self, mut workspace: Workspace) -> Result<EntityId> {
1214 let id = workspace.id.clone();
1215
1216 if workspace.order == 0 && !self.workspaces.is_empty() {
1218 workspace.order = self.workspaces.len() as i32;
1219 }
1220
1221 self.workspaces.insert(id.clone(), workspace);
1222 Ok(id)
1223 }
1224
1225 pub fn get_workspace(&self, id: &str) -> Option<&Workspace> {
1227 self.workspaces.get(id)
1228 }
1229
1230 pub fn get_workspace_mut(&mut self, id: &str) -> Option<&mut Workspace> {
1232 self.workspaces.get_mut(id)
1233 }
1234
1235 pub fn remove_workspace(&mut self, id: &str) -> Result<()> {
1237 if self.workspaces.remove(id).is_some() {
1238 if self.active_workspace.as_deref() == Some(id) {
1240 self.active_workspace = None;
1241 }
1242 Ok(())
1243 } else {
1244 Err(Error::generic(format!("Workspace with ID '{}' not found", id)))
1245 }
1246 }
1247
1248 pub fn set_active_workspace(&mut self, id: Option<String>) -> Result<()> {
1250 if let Some(ref workspace_id) = id {
1251 if !self.workspaces.contains_key(workspace_id) {
1252 return Err(Error::generic(format!(
1253 "Workspace with ID '{}' not found",
1254 workspace_id
1255 )));
1256 }
1257 }
1258 self.active_workspace = id;
1259 Ok(())
1260 }
1261
1262 pub fn get_active_workspace(&self) -> Option<&Workspace> {
1264 self.active_workspace.as_ref().and_then(|id| self.workspaces.get(id))
1265 }
1266
1267 pub fn get_active_workspace_id(&self) -> Option<&str> {
1269 self.active_workspace.as_deref()
1270 }
1271
1272 pub fn get_workspaces(&self) -> Vec<&Workspace> {
1274 self.workspaces.values().collect()
1275 }
1276
1277 pub fn get_workspaces_ordered(&self) -> Vec<&Workspace> {
1279 let mut workspaces: Vec<&Workspace> = self.workspaces.values().collect();
1280 workspaces.sort_by_key(|w| w.order);
1281 workspaces
1282 }
1283
1284 pub fn update_workspaces_order(&mut self, workspace_ids: Vec<String>) -> Result<()> {
1286 for workspace_id in &workspace_ids {
1288 if !self.workspaces.contains_key(workspace_id) {
1289 return Err(Error::generic(format!(
1290 "Workspace with ID '{}' not found",
1291 workspace_id
1292 )));
1293 }
1294 }
1295
1296 for (index, workspace_id) in workspace_ids.iter().enumerate() {
1298 if let Some(workspace) = self.workspaces.get_mut(workspace_id) {
1299 workspace.order = index as i32;
1300 workspace.updated_at = Utc::now();
1301 }
1302 }
1303
1304 Ok(())
1305 }
1306
1307 pub fn get_all_routes(&self) -> Vec<Route> {
1309 let mut all_routes = Vec::new();
1310 for workspace in self.workspaces.values() {
1311 all_routes.extend(workspace.get_routes());
1312 }
1313 all_routes
1314 }
1315
1316 pub fn create_route_registry(&self) -> Result<RouteRegistry> {
1318 let mut registry = RouteRegistry::new();
1319 let routes = self.get_all_routes();
1320
1321 for route in routes {
1322 registry.add_http_route(route)?;
1323 }
1324
1325 Ok(registry)
1326 }
1327}
1328
1329impl Default for WorkspaceRegistry {
1330 fn default() -> Self {
1331 Self::new()
1332 }
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337 use super::*;
1338
1339 use crate::ApiKeyConfig;
1340
1341 #[test]
1342 fn test_workspace_creation() {
1343 let workspace = Workspace::new("Test Workspace".to_string());
1344 assert_eq!(workspace.name, "Test Workspace");
1345 assert!(!workspace.id.is_empty());
1346 assert!(workspace.folders.is_empty());
1347 assert!(workspace.requests.is_empty());
1348 }
1349
1350 #[test]
1351 fn test_folder_creation() {
1352 let folder = Folder::new("Test Folder".to_string());
1353 assert_eq!(folder.name, "Test Folder");
1354 assert!(!folder.id.is_empty());
1355 assert!(folder.folders.is_empty());
1356 assert!(folder.requests.is_empty());
1357 }
1358
1359 #[test]
1360 fn test_request_creation() {
1361 let request =
1362 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1363 assert_eq!(request.name, "Test Request");
1364 assert_eq!(request.method, HttpMethod::GET);
1365 assert_eq!(request.path, "/test");
1366 assert_eq!(request.response.status_code, 200);
1367 }
1368
1369 #[test]
1370 fn test_workspace_hierarchy() {
1371 let mut workspace = Workspace::new("Test Workspace".to_string());
1372
1373 let folder_id = workspace.add_folder("Test Folder".to_string()).unwrap();
1375 assert_eq!(workspace.folders.len(), 1);
1376
1377 let request =
1379 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1380 workspace.add_request(request).unwrap();
1381 assert_eq!(workspace.requests.len(), 1);
1382
1383 let folder = workspace.find_folder_mut(&folder_id).unwrap();
1385 let folder_request = MockRequest::new(
1386 HttpMethod::POST,
1387 "/folder-test".to_string(),
1388 "Folder Request".to_string(),
1389 );
1390 folder.add_request(folder_request).unwrap();
1391 assert_eq!(folder.requests.len(), 1);
1392 }
1393
1394 #[test]
1395 fn test_workspace_registry() {
1396 let mut registry = WorkspaceRegistry::new();
1397
1398 let workspace = Workspace::new("Test Workspace".to_string());
1399 let workspace_id = registry.add_workspace(workspace).unwrap();
1400
1401 registry.set_active_workspace(Some(workspace_id.clone())).unwrap();
1403 assert!(registry.get_active_workspace().is_some());
1404
1405 let retrieved = registry.get_workspace(&workspace_id).unwrap();
1407 assert_eq!(retrieved.name, "Test Workspace");
1408
1409 registry.remove_workspace(&workspace_id).unwrap();
1411 assert!(registry.get_workspace(&workspace_id).is_none());
1412 }
1413
1414 #[test]
1415 fn test_inheritance_header_priority() {
1416 let mut workspace = Workspace::new("Test Workspace".to_string());
1417 workspace
1418 .config
1419 .default_headers
1420 .insert("X-Common".to_string(), "workspace-value".to_string());
1421 workspace
1422 .config
1423 .default_headers
1424 .insert("X-Workspace-Only".to_string(), "workspace-only-value".to_string());
1425
1426 let mut folder = Folder::new("Test Folder".to_string());
1428 folder
1429 .inheritance
1430 .headers
1431 .insert("X-Common".to_string(), "folder-value".to_string());
1432 folder
1433 .inheritance
1434 .headers
1435 .insert("X-Folder-Only".to_string(), "folder-only-value".to_string());
1436
1437 let folder_path = vec![&folder];
1439 let effective_headers = workspace.get_effective_headers(&folder_path);
1440
1441 assert_eq!(effective_headers.get("X-Common").unwrap(), "folder-value"); assert_eq!(effective_headers.get("X-Workspace-Only").unwrap(), "workspace-only-value"); assert_eq!(effective_headers.get("X-Folder-Only").unwrap(), "folder-only-value");
1444 }
1446
1447 #[test]
1448 fn test_inheritance_request_headers_override() {
1449 let mut workspace = Workspace::new("Test Workspace".to_string());
1450 workspace
1451 .config
1452 .default_headers
1453 .insert("Authorization".to_string(), "Bearer workspace-token".to_string());
1454
1455 let folder_path = vec![];
1456 let effective_headers = workspace.get_effective_headers(&folder_path);
1457 let mut request = MockRequest::new(
1458 crate::routing::HttpMethod::GET,
1459 "/test".to_string(),
1460 "Test Request".to_string(),
1461 );
1462
1463 request
1465 .headers
1466 .insert("Authorization".to_string(), "Bearer request-token".to_string());
1467
1468 request.apply_inheritance(effective_headers, None);
1470
1471 assert_eq!(request.headers.get("Authorization").unwrap(), "Bearer request-token");
1472 }
1473
1474 #[test]
1475 fn test_inheritance_nested_folders() {
1476 let mut workspace = Workspace::new("Test Workspace".to_string());
1477 workspace
1478 .config
1479 .default_headers
1480 .insert("X-Level".to_string(), "workspace".to_string());
1481
1482 let mut parent_folder = Folder::new("Parent Folder".to_string());
1484 parent_folder
1485 .inheritance
1486 .headers
1487 .insert("X-Level".to_string(), "parent".to_string());
1488 parent_folder
1489 .inheritance
1490 .headers
1491 .insert("X-Parent-Only".to_string(), "parent-value".to_string());
1492
1493 let mut child_folder = Folder::new("Child Folder".to_string());
1495 child_folder
1496 .inheritance
1497 .headers
1498 .insert("X-Level".to_string(), "child".to_string());
1499 child_folder
1500 .inheritance
1501 .headers
1502 .insert("X-Child-Only".to_string(), "child-value".to_string());
1503
1504 let folder_path = vec![&parent_folder, &child_folder];
1506 let effective_headers = workspace.get_effective_headers(&folder_path);
1507
1508 assert_eq!(effective_headers.get("X-Level").unwrap(), "child");
1510 assert_eq!(effective_headers.get("X-Parent-Only").unwrap(), "parent-value");
1511 assert_eq!(effective_headers.get("X-Child-Only").unwrap(), "child-value");
1512 }
1513
1514 #[test]
1515 fn test_inheritance_auth_from_folder() {
1516 let workspace = Workspace::new("Test Workspace".to_string());
1518
1519 let mut folder = Folder::new("Test Folder".to_string());
1521 let auth = AuthConfig {
1522 require_auth: true,
1523 api_key: Some(ApiKeyConfig {
1524 header_name: "X-API-Key".to_string(),
1525 query_name: Some("api_key".to_string()),
1526 keys: vec!["folder-key".to_string()],
1527 }),
1528 ..Default::default()
1529 };
1530 folder.inheritance.auth = Some(auth);
1531
1532 let folder_path = vec![&folder];
1533 let effective_auth = workspace.get_effective_auth(&folder_path);
1534
1535 assert!(effective_auth.is_some());
1536 let auth_config = effective_auth.unwrap();
1537 assert!(auth_config.require_auth);
1538 let api_key_config = auth_config.api_key.as_ref().unwrap();
1539 assert_eq!(api_key_config.keys, vec!["folder-key".to_string()]);
1540 }
1541}