1use crate::{Config as CoreConfig, Error, RealityLevel, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::Path;
7use tokio::fs;
8
9#[derive(Debug, Clone, Serialize, Deserialize, Default)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[serde(default)]
13pub struct IncidentConfig {
14 pub storage: IncidentStorageConfig,
16 pub external_integrations: crate::incidents::integrations::ExternalIntegrationConfig,
18 pub webhooks: Vec<crate::incidents::integrations::WebhookConfig>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25pub struct IncidentStorageConfig {
26 pub use_cache: bool,
28 pub use_database: bool,
30 pub retention_days: u32,
32}
33
34impl Default for IncidentStorageConfig {
35 fn default() -> Self {
36 Self {
37 use_cache: true,
38 use_database: true,
39 retention_days: 90,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, Default)]
46#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
47#[serde(default)]
48pub struct ConsumerContractsConfig {
49 pub enabled: bool,
51 pub auto_register: bool,
53 pub track_usage: bool,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, Default)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60#[serde(default)]
61pub struct ContractsConfig {
62 pub fitness_rules: Vec<FitnessRuleConfig>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, Default)]
68#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
69#[serde(default)]
70pub struct BehavioralEconomicsConfig {
71 pub enabled: bool,
73 #[serde(default)]
75 pub rules: Vec<crate::behavioral_economics::BehaviorRule>,
76 #[serde(default = "default_behavioral_sensitivity")]
79 pub global_sensitivity: f64,
80 #[serde(default = "default_evaluation_interval_ms")]
82 pub evaluation_interval_ms: u64,
83}
84
85fn default_behavioral_sensitivity() -> f64 {
86 0.5
87}
88
89fn default_evaluation_interval_ms() -> u64 {
90 1000 }
92
93#[derive(Debug, Clone, Serialize, Deserialize, Default)]
95#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
96#[serde(default)]
97pub struct DriftLearningConfig {
98 pub enabled: bool,
100 #[serde(default)]
102 pub mode: DriftLearningMode,
103 #[serde(default = "default_learning_sensitivity")]
105 pub sensitivity: f64,
106 #[serde(default = "default_learning_decay")]
108 pub decay: f64,
109 #[serde(default = "default_min_samples")]
111 pub min_samples: u64,
112 #[serde(default)]
114 pub persona_adaptation: bool,
115 #[serde(default)]
117 pub persona_learning: HashMap<String, bool>, #[serde(default)]
120 pub endpoint_learning: HashMap<String, bool>, }
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
125#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
126#[serde(rename_all = "snake_case")]
127pub enum DriftLearningMode {
128 #[default]
130 Behavioral,
131 Statistical,
133 Hybrid,
135}
136
137fn default_learning_sensitivity() -> f64 {
138 0.2
139}
140
141fn default_learning_decay() -> f64 {
142 0.05
143}
144
145fn default_min_samples() -> u64 {
146 10
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
152pub struct FitnessRuleConfig {
153 pub name: String,
155 pub scope: String,
157 #[serde(rename = "type")]
159 pub rule_type: FitnessRuleType,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub max_percent_increase: Option<f64>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub max_fields: Option<u32>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub max_depth: Option<u32>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
173#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
174#[serde(rename_all = "snake_case")]
175pub enum FitnessRuleType {
176 ResponseSizeDelta,
178 NoNewRequiredFields,
180 FieldCount,
182 SchemaComplexity,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
189#[serde(default)]
190pub struct BehavioralCloningConfig {
191 pub enabled: bool,
193 pub database_path: Option<String>,
195 pub enable_middleware: bool,
197 pub min_sequence_frequency: f64,
199 pub min_requests_per_trace: Option<i32>,
201 #[serde(default)]
203 pub flow_recording: FlowRecordingConfig,
204 #[serde(default)]
206 pub scenario_replay: ScenarioReplayConfig,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
212#[serde(default)]
213pub struct FlowRecordingConfig {
214 pub enabled: bool,
216 pub group_by: String,
218 pub time_window_seconds: u64,
220}
221
222impl Default for FlowRecordingConfig {
223 fn default() -> Self {
224 Self {
225 enabled: true,
226 group_by: "trace_id".to_string(),
227 time_window_seconds: 300, }
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
235#[serde(default)]
236pub struct ScenarioReplayConfig {
237 pub enabled: bool,
239 pub default_mode: String,
241 pub active_scenarios: Vec<String>,
243}
244
245impl Default for ScenarioReplayConfig {
246 fn default() -> Self {
247 Self {
248 enabled: true,
249 default_mode: "strict".to_string(),
250 active_scenarios: Vec::new(),
251 }
252 }
253}
254
255impl Default for BehavioralCloningConfig {
256 fn default() -> Self {
257 Self {
258 enabled: false,
259 database_path: None,
260 enable_middleware: false,
261 min_sequence_frequency: 0.1, min_requests_per_trace: None,
263 flow_recording: FlowRecordingConfig::default(),
264 scenario_replay: ScenarioReplayConfig::default(),
265 }
266 }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271#[serde(default)]
272#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
273pub struct AuthConfig {
274 pub jwt: Option<JwtConfig>,
276 pub oauth2: Option<OAuth2Config>,
278 pub basic_auth: Option<BasicAuthConfig>,
280 pub api_key: Option<ApiKeyConfig>,
282 pub require_auth: bool,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
289pub struct JwtConfig {
290 pub secret: Option<String>,
292 pub rsa_public_key: Option<String>,
294 pub ecdsa_public_key: Option<String>,
296 pub issuer: Option<String>,
298 pub audience: Option<String>,
300 pub algorithms: Vec<String>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
306#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
307pub struct OAuth2Config {
308 pub client_id: String,
310 pub client_secret: String,
312 pub introspection_url: String,
314 pub auth_url: Option<String>,
316 pub token_url: Option<String>,
318 pub token_type_hint: Option<String>,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
324#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
325pub struct BasicAuthConfig {
326 pub credentials: HashMap<String, String>,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
333pub struct ApiKeyConfig {
334 pub header_name: String,
336 pub query_name: Option<String>,
338 pub keys: Vec<String>,
340}
341
342impl Default for AuthConfig {
343 fn default() -> Self {
344 Self {
345 jwt: None,
346 oauth2: None,
347 basic_auth: None,
348 api_key: Some(ApiKeyConfig {
349 header_name: "X-API-Key".to_string(),
350 query_name: None,
351 keys: vec![],
352 }),
353 require_auth: false,
354 }
355 }
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
360#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
361pub struct RouteConfig {
362 pub path: String,
364 pub method: String,
366 pub request: Option<RouteRequestConfig>,
368 pub response: RouteResponseConfig,
370 #[serde(default)]
372 pub fault_injection: Option<RouteFaultInjectionConfig>,
373 #[serde(default)]
375 pub latency: Option<RouteLatencyConfig>,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
380#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
381pub struct RouteRequestConfig {
382 pub validation: Option<RouteValidationConfig>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
389pub struct RouteResponseConfig {
390 pub status: u16,
392 #[serde(default)]
394 pub headers: HashMap<String, String>,
395 pub body: Option<serde_json::Value>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
402pub struct RouteValidationConfig {
403 pub schema: serde_json::Value,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
409#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
410pub struct RouteFaultInjectionConfig {
411 pub enabled: bool,
413 pub probability: f64,
415 pub fault_types: Vec<RouteFaultType>,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
421#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
422#[serde(tag = "type", rename_all = "snake_case")]
423pub enum RouteFaultType {
424 HttpError {
426 status_code: u16,
428 message: Option<String>,
430 },
431 ConnectionError {
433 message: Option<String>,
435 },
436 Timeout {
438 duration_ms: u64,
440 message: Option<String>,
442 },
443 PartialResponse {
445 truncate_percent: f64,
447 },
448 PayloadCorruption {
450 corruption_type: String,
452 },
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
457#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
458pub struct RouteLatencyConfig {
459 pub enabled: bool,
461 pub probability: f64,
463 pub fixed_delay_ms: Option<u64>,
465 pub random_delay_range_ms: Option<(u64, u64)>,
467 pub jitter_percent: f64,
469 #[serde(default)]
471 pub distribution: LatencyDistribution,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
476#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
477#[serde(rename_all = "snake_case")]
478#[derive(Default)]
479pub enum LatencyDistribution {
480 #[default]
482 Fixed,
483 Normal {
485 mean_ms: f64,
487 std_dev_ms: f64,
489 },
490 Exponential {
492 lambda: f64,
494 },
495 Uniform,
497}
498
499impl Default for RouteFaultInjectionConfig {
500 fn default() -> Self {
501 Self {
502 enabled: false,
503 probability: 0.0,
504 fault_types: Vec::new(),
505 }
506 }
507}
508
509impl Default for RouteLatencyConfig {
510 fn default() -> Self {
511 Self {
512 enabled: false,
513 probability: 1.0,
514 fixed_delay_ms: None,
515 random_delay_range_ms: None,
516 jitter_percent: 0.0,
517 distribution: LatencyDistribution::Fixed,
518 }
519 }
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize)]
524#[serde(default)]
525#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
526#[derive(Default)]
527pub struct DeceptiveDeployConfig {
528 pub enabled: bool,
530 pub cors: Option<ProductionCorsConfig>,
532 pub rate_limit: Option<ProductionRateLimitConfig>,
534 #[serde(default)]
536 pub headers: HashMap<String, String>,
537 pub oauth: Option<ProductionOAuthConfig>,
539 pub custom_domain: Option<String>,
541 pub auto_tunnel: bool,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 pub canary: Option<crate::deceptive_canary::DeceptiveCanaryConfig>,
546}
547
548impl DeceptiveDeployConfig {
549 pub fn production_preset() -> Self {
551 let mut headers = HashMap::new();
552 headers.insert("X-API-Version".to_string(), "1.0".to_string());
553 headers.insert("X-Request-ID".to_string(), "{{uuid}}".to_string());
554 headers.insert("X-Powered-By".to_string(), "MockForge".to_string());
555
556 Self {
557 enabled: true,
558 cors: Some(ProductionCorsConfig {
559 allowed_origins: vec!["*".to_string()],
560 allowed_methods: vec![
561 "GET".to_string(),
562 "POST".to_string(),
563 "PUT".to_string(),
564 "DELETE".to_string(),
565 "PATCH".to_string(),
566 "OPTIONS".to_string(),
567 ],
568 allowed_headers: vec!["*".to_string()],
569 allow_credentials: true,
570 }),
571 rate_limit: Some(ProductionRateLimitConfig {
572 requests_per_minute: 1000,
573 burst: 2000,
574 per_ip: true,
575 }),
576 headers,
577 oauth: None, custom_domain: None,
579 auto_tunnel: true,
580 canary: None,
581 }
582 }
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize)]
587#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
588pub struct ProductionCorsConfig {
589 #[serde(default)]
591 pub allowed_origins: Vec<String>,
592 #[serde(default)]
594 pub allowed_methods: Vec<String>,
595 #[serde(default)]
597 pub allowed_headers: Vec<String>,
598 pub allow_credentials: bool,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize)]
604#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
605pub struct ProductionRateLimitConfig {
606 pub requests_per_minute: u32,
608 pub burst: u32,
610 pub per_ip: bool,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
616#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
617pub struct ProductionOAuthConfig {
618 pub client_id: String,
620 pub client_secret: String,
622 pub introspection_url: String,
624 pub auth_url: Option<String>,
626 pub token_url: Option<String>,
628 pub token_type_hint: Option<String>,
630}
631
632impl From<ProductionOAuthConfig> for OAuth2Config {
633 fn from(prod: ProductionOAuthConfig) -> Self {
635 OAuth2Config {
636 client_id: prod.client_id,
637 client_secret: prod.client_secret,
638 introspection_url: prod.introspection_url,
639 auth_url: prod.auth_url,
640 token_url: prod.token_url,
641 token_type_hint: prod.token_type_hint,
642 }
643 }
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
648#[serde(default)]
649#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
650#[derive(Default)]
651pub struct PerformanceConfig {
652 pub compression: CompressionConfig,
654 pub connection_pool: ConnectionPoolConfig,
656 pub request_limits: RequestLimitsConfig,
658 pub workers: WorkerConfig,
660 pub circuit_breaker: CircuitBreakerConfig,
662}
663
664#[derive(Debug, Clone, Serialize, Deserialize)]
666#[serde(default)]
667#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
668pub struct CompressionConfig {
669 pub enabled: bool,
671 pub algorithm: String,
673 pub min_size: usize,
675 pub level: u32,
677 pub content_types: Vec<String>,
679}
680
681impl Default for CompressionConfig {
682 fn default() -> Self {
683 Self {
684 enabled: true,
685 algorithm: "gzip".to_string(),
686 min_size: 1024, level: 6,
688 content_types: vec![
689 "application/json".to_string(),
690 "application/xml".to_string(),
691 "text/plain".to_string(),
692 "text/html".to_string(),
693 "text/css".to_string(),
694 "application/javascript".to_string(),
695 ],
696 }
697 }
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
702#[serde(default)]
703#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
704pub struct ConnectionPoolConfig {
705 pub max_idle_per_host: usize,
707 pub max_connections: usize,
709 pub idle_timeout_secs: u64,
711 pub acquire_timeout_ms: u64,
713 pub enabled: bool,
715}
716
717impl Default for ConnectionPoolConfig {
718 fn default() -> Self {
719 Self {
720 max_idle_per_host: 10,
721 max_connections: 100,
722 idle_timeout_secs: 90,
723 acquire_timeout_ms: 5000,
724 enabled: true,
725 }
726 }
727}
728
729#[derive(Debug, Clone, Serialize, Deserialize)]
731#[serde(default)]
732#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
733pub struct RequestLimitsConfig {
734 pub max_body_size: usize,
736 pub max_header_size: usize,
738 pub max_headers: usize,
740 pub max_uri_length: usize,
742 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
744 pub per_route_limits: HashMap<String, usize>,
745}
746
747impl Default for RequestLimitsConfig {
748 fn default() -> Self {
749 Self {
750 max_body_size: 10 * 1024 * 1024, max_header_size: 16 * 1024, max_headers: 100,
753 max_uri_length: 8192,
754 per_route_limits: HashMap::new(),
755 }
756 }
757}
758
759#[derive(Debug, Clone, Serialize, Deserialize)]
761#[serde(default)]
762#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
763pub struct WorkerConfig {
764 pub threads: usize,
766 pub blocking_threads: usize,
768 pub stack_size: usize,
770 pub name_prefix: String,
772}
773
774impl Default for WorkerConfig {
775 fn default() -> Self {
776 Self {
777 threads: 0, blocking_threads: 512,
779 stack_size: 2 * 1024 * 1024, name_prefix: "mockforge-worker".to_string(),
781 }
782 }
783}
784
785#[derive(Debug, Clone, Serialize, Deserialize)]
787#[serde(default)]
788#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
789pub struct CircuitBreakerConfig {
790 pub enabled: bool,
792 pub failure_threshold: u32,
794 pub success_threshold: u32,
796 pub half_open_timeout_secs: u64,
798 pub window_size: u32,
800 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
802 pub per_endpoint: HashMap<String, EndpointCircuitBreakerConfig>,
803}
804
805impl Default for CircuitBreakerConfig {
806 fn default() -> Self {
807 Self {
808 enabled: false,
809 failure_threshold: 5,
810 success_threshold: 2,
811 half_open_timeout_secs: 30,
812 window_size: 10,
813 per_endpoint: HashMap::new(),
814 }
815 }
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize)]
820#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
821pub struct EndpointCircuitBreakerConfig {
822 pub failure_threshold: u32,
824 pub success_threshold: u32,
826 pub half_open_timeout_secs: u64,
828}
829
830#[derive(Debug, Clone, Serialize, Deserialize)]
832#[serde(default)]
833#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
834pub struct ConfigHotReloadConfig {
835 pub enabled: bool,
837 pub check_interval_secs: u64,
839 pub debounce_delay_ms: u64,
841 #[serde(default, skip_serializing_if = "Vec::is_empty")]
843 pub watch_paths: Vec<String>,
844 pub reload_on_spec_change: bool,
846 pub reload_on_fixture_change: bool,
848 pub reload_on_plugin_change: bool,
850 pub graceful_reload: bool,
852 pub graceful_timeout_secs: u64,
854 pub validate_before_reload: bool,
856 pub rollback_on_failure: bool,
858}
859
860impl Default for ConfigHotReloadConfig {
861 fn default() -> Self {
862 Self {
863 enabled: false,
864 check_interval_secs: 5,
865 debounce_delay_ms: 1000,
866 watch_paths: Vec::new(),
867 reload_on_spec_change: true,
868 reload_on_fixture_change: true,
869 reload_on_plugin_change: true,
870 graceful_reload: true,
871 graceful_timeout_secs: 30,
872 validate_before_reload: true,
873 rollback_on_failure: true,
874 }
875 }
876}
877
878#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
880#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
881#[serde(rename_all = "lowercase")]
882pub enum SecretBackendType {
883 #[default]
885 None,
886 Vault,
888 AwsSecretsManager,
890 AzureKeyVault,
892 GcpSecretManager,
894 Kubernetes,
896 EncryptedFile,
898}
899
900#[derive(Debug, Clone, Serialize, Deserialize)]
902#[serde(default)]
903#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
904pub struct SecretBackendConfig {
905 pub provider: SecretBackendType,
907 #[serde(skip_serializing_if = "Option::is_none")]
909 pub vault: Option<VaultConfig>,
910 #[serde(skip_serializing_if = "Option::is_none")]
912 pub aws: Option<AwsSecretsConfig>,
913 #[serde(skip_serializing_if = "Option::is_none")]
915 pub azure: Option<AzureKeyVaultConfig>,
916 #[serde(skip_serializing_if = "Option::is_none")]
918 pub gcp: Option<GcpSecretManagerConfig>,
919 #[serde(skip_serializing_if = "Option::is_none")]
921 pub kubernetes: Option<KubernetesSecretsConfig>,
922 #[serde(skip_serializing_if = "Option::is_none")]
924 pub encrypted_file: Option<EncryptedFileConfig>,
925 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
927 pub mappings: HashMap<String, String>,
928 pub cache_ttl_secs: u64,
930 pub retry_attempts: u32,
932 pub retry_delay_ms: u64,
934}
935
936impl Default for SecretBackendConfig {
937 fn default() -> Self {
938 Self {
939 provider: SecretBackendType::None,
940 vault: None,
941 aws: None,
942 azure: None,
943 gcp: None,
944 kubernetes: None,
945 encrypted_file: None,
946 mappings: HashMap::new(),
947 cache_ttl_secs: 300, retry_attempts: 3,
949 retry_delay_ms: 1000,
950 }
951 }
952}
953
954#[derive(Debug, Clone, Serialize, Deserialize)]
956#[serde(default)]
957#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
958pub struct VaultConfig {
959 pub address: String,
961 #[serde(skip_serializing_if = "Option::is_none")]
963 pub namespace: Option<String>,
964 pub auth_method: VaultAuthMethod,
966 #[serde(skip_serializing_if = "Option::is_none")]
968 pub token: Option<String>,
969 #[serde(skip_serializing_if = "Option::is_none")]
971 pub role_id: Option<String>,
972 #[serde(skip_serializing_if = "Option::is_none")]
974 pub secret_id: Option<String>,
975 #[serde(skip_serializing_if = "Option::is_none")]
977 pub kubernetes_role: Option<String>,
978 pub mount_path: String,
980 pub path_prefix: String,
982 #[serde(skip_serializing_if = "Option::is_none")]
984 pub ca_cert_path: Option<String>,
985 pub skip_verify: bool,
987 pub timeout_secs: u64,
989}
990
991impl Default for VaultConfig {
992 fn default() -> Self {
993 Self {
994 address: "http://127.0.0.1:8200".to_string(),
995 namespace: None,
996 auth_method: VaultAuthMethod::Token,
997 token: None,
998 role_id: None,
999 secret_id: None,
1000 kubernetes_role: None,
1001 mount_path: "secret".to_string(),
1002 path_prefix: "mockforge".to_string(),
1003 ca_cert_path: None,
1004 skip_verify: false,
1005 timeout_secs: 30,
1006 }
1007 }
1008}
1009
1010#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
1012#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1013#[serde(rename_all = "lowercase")]
1014pub enum VaultAuthMethod {
1015 #[default]
1017 Token,
1018 AppRole,
1020 Kubernetes,
1022 AwsIam,
1024 GitHub,
1026 Ldap,
1028 Userpass,
1030}
1031
1032#[derive(Debug, Clone, Serialize, Deserialize)]
1034#[serde(default)]
1035#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1036pub struct AwsSecretsConfig {
1037 pub region: String,
1039 pub prefix: String,
1041 pub use_iam_role: bool,
1043 #[serde(skip_serializing_if = "Option::is_none")]
1045 pub access_key_id: Option<String>,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1048 pub secret_access_key: Option<String>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1051 pub endpoint_url: Option<String>,
1052}
1053
1054impl Default for AwsSecretsConfig {
1055 fn default() -> Self {
1056 Self {
1057 region: "us-east-1".to_string(),
1058 prefix: "mockforge".to_string(),
1059 use_iam_role: true,
1060 access_key_id: None,
1061 secret_access_key: None,
1062 endpoint_url: None,
1063 }
1064 }
1065}
1066
1067#[derive(Debug, Clone, Serialize, Deserialize)]
1069#[serde(default)]
1070#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1071pub struct AzureKeyVaultConfig {
1072 pub vault_url: String,
1074 #[serde(skip_serializing_if = "Option::is_none")]
1076 pub tenant_id: Option<String>,
1077 #[serde(skip_serializing_if = "Option::is_none")]
1079 pub client_id: Option<String>,
1080 #[serde(skip_serializing_if = "Option::is_none")]
1082 pub client_secret: Option<String>,
1083 pub use_managed_identity: bool,
1085 pub prefix: String,
1087}
1088
1089impl Default for AzureKeyVaultConfig {
1090 fn default() -> Self {
1091 Self {
1092 vault_url: String::new(),
1093 tenant_id: None,
1094 client_id: None,
1095 client_secret: None,
1096 use_managed_identity: true,
1097 prefix: "mockforge".to_string(),
1098 }
1099 }
1100}
1101
1102#[derive(Debug, Clone, Serialize, Deserialize)]
1104#[serde(default)]
1105#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1106pub struct GcpSecretManagerConfig {
1107 pub project_id: String,
1109 pub prefix: String,
1111 #[serde(skip_serializing_if = "Option::is_none")]
1113 pub credentials_file: Option<String>,
1114 pub use_default_credentials: bool,
1116}
1117
1118impl Default for GcpSecretManagerConfig {
1119 fn default() -> Self {
1120 Self {
1121 project_id: String::new(),
1122 prefix: "mockforge".to_string(),
1123 credentials_file: None,
1124 use_default_credentials: true,
1125 }
1126 }
1127}
1128
1129#[derive(Debug, Clone, Serialize, Deserialize)]
1131#[serde(default)]
1132#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1133pub struct KubernetesSecretsConfig {
1134 pub namespace: String,
1136 pub prefix: String,
1138 #[serde(skip_serializing_if = "Option::is_none")]
1140 pub label_selector: Option<String>,
1141 pub in_cluster: bool,
1143 #[serde(skip_serializing_if = "Option::is_none")]
1145 pub kubeconfig_path: Option<String>,
1146}
1147
1148impl Default for KubernetesSecretsConfig {
1149 fn default() -> Self {
1150 Self {
1151 namespace: "default".to_string(),
1152 prefix: "mockforge".to_string(),
1153 label_selector: None,
1154 in_cluster: true,
1155 kubeconfig_path: None,
1156 }
1157 }
1158}
1159
1160#[derive(Debug, Clone, Serialize, Deserialize)]
1162#[serde(default)]
1163#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1164pub struct EncryptedFileConfig {
1165 pub file_path: String,
1167 pub algorithm: String,
1169 pub kdf: String,
1171 #[serde(skip_serializing_if = "Option::is_none")]
1173 pub master_key_env: Option<String>,
1174 #[serde(skip_serializing_if = "Option::is_none")]
1176 pub key_file: Option<String>,
1177}
1178
1179impl Default for EncryptedFileConfig {
1180 fn default() -> Self {
1181 Self {
1182 file_path: "secrets.enc".to_string(),
1183 algorithm: "aes-256-gcm".to_string(),
1184 kdf: "argon2id".to_string(),
1185 master_key_env: Some("MOCKFORGE_MASTER_KEY".to_string()),
1186 key_file: None,
1187 }
1188 }
1189}
1190
1191#[derive(Debug, Clone, Serialize, Deserialize)]
1193#[serde(default)]
1194#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1195pub struct PluginResourceConfig {
1196 pub enabled: bool,
1198 pub max_memory_per_plugin: usize,
1200 pub max_cpu_per_plugin: f64,
1202 pub max_execution_time_ms: u64,
1204 pub allow_network_access: bool,
1206 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1208 pub allowed_fs_paths: Vec<String>,
1209 pub max_concurrent_executions: usize,
1211 #[serde(skip_serializing_if = "Option::is_none")]
1213 pub cache_dir: Option<String>,
1214 pub debug_logging: bool,
1216 pub max_module_size: usize,
1218 pub max_table_elements: usize,
1220 pub max_stack_size: usize,
1222}
1223
1224impl Default for PluginResourceConfig {
1225 fn default() -> Self {
1226 Self {
1227 enabled: true,
1228 max_memory_per_plugin: 10 * 1024 * 1024, max_cpu_per_plugin: 0.5, max_execution_time_ms: 5000, allow_network_access: false,
1232 allowed_fs_paths: Vec::new(),
1233 max_concurrent_executions: 10,
1234 cache_dir: None,
1235 debug_logging: false,
1236 max_module_size: 5 * 1024 * 1024, max_table_elements: 1000,
1238 max_stack_size: 2 * 1024 * 1024, }
1240 }
1241}
1242
1243#[derive(Debug, Clone, Serialize, Deserialize)]
1245#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1246pub struct ProtocolConfig {
1247 pub enabled: bool,
1249}
1250
1251#[derive(Debug, Clone, Serialize, Deserialize)]
1253#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1254pub struct ProtocolsConfig {
1255 pub http: ProtocolConfig,
1257 pub graphql: ProtocolConfig,
1259 pub grpc: ProtocolConfig,
1261 pub websocket: ProtocolConfig,
1263 pub smtp: ProtocolConfig,
1265 pub mqtt: ProtocolConfig,
1267 pub ftp: ProtocolConfig,
1269 pub kafka: ProtocolConfig,
1271 pub rabbitmq: ProtocolConfig,
1273 pub amqp: ProtocolConfig,
1275 pub tcp: ProtocolConfig,
1277}
1278
1279impl Default for ProtocolsConfig {
1280 fn default() -> Self {
1281 Self {
1282 http: ProtocolConfig { enabled: true },
1283 graphql: ProtocolConfig { enabled: true },
1284 grpc: ProtocolConfig { enabled: true },
1285 websocket: ProtocolConfig { enabled: true },
1286 smtp: ProtocolConfig { enabled: false },
1287 mqtt: ProtocolConfig { enabled: true },
1288 ftp: ProtocolConfig { enabled: false },
1289 kafka: ProtocolConfig { enabled: false },
1290 rabbitmq: ProtocolConfig { enabled: false },
1291 amqp: ProtocolConfig { enabled: false },
1292 tcp: ProtocolConfig { enabled: false },
1293 }
1294 }
1295}
1296
1297#[derive(Debug, Clone, Serialize, Deserialize)]
1303#[serde(default)]
1304#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1305pub struct RealitySliderConfig {
1306 pub level: RealityLevel,
1308 pub enabled: bool,
1310}
1311
1312impl Default for RealitySliderConfig {
1313 fn default() -> Self {
1314 Self {
1315 level: RealityLevel::ModerateRealism,
1316 enabled: true,
1317 }
1318 }
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1324#[serde(default)]
1325pub struct ServerConfig {
1326 pub http: HttpConfig,
1328 pub websocket: WebSocketConfig,
1330 pub graphql: GraphQLConfig,
1332 pub grpc: GrpcConfig,
1334 pub mqtt: MqttConfig,
1336 pub smtp: SmtpConfig,
1338 pub ftp: FtpConfig,
1340 pub kafka: KafkaConfig,
1342 pub amqp: AmqpConfig,
1344 pub tcp: TcpConfig,
1346 pub admin: AdminConfig,
1348 pub chaining: ChainingConfig,
1350 pub core: CoreConfig,
1352 pub logging: LoggingConfig,
1354 pub data: DataConfig,
1356 #[serde(default)]
1358 pub mockai: MockAIConfig,
1359 pub observability: ObservabilityConfig,
1361 pub multi_tenant: crate::multi_tenant::MultiTenantConfig,
1363 #[serde(default)]
1365 pub routes: Vec<RouteConfig>,
1366 #[serde(default)]
1368 pub protocols: ProtocolsConfig,
1369 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1371 pub profiles: HashMap<String, ProfileConfig>,
1372 #[serde(default)]
1374 pub deceptive_deploy: DeceptiveDeployConfig,
1375 #[serde(default, skip_serializing_if = "Option::is_none")]
1377 pub behavioral_cloning: Option<BehavioralCloningConfig>,
1378 #[serde(default)]
1380 pub reality: RealitySliderConfig,
1381 #[serde(default)]
1383 pub reality_continuum: crate::reality_continuum::ContinuumConfig,
1384 #[serde(default)]
1386 pub security: SecurityConfig,
1387 #[serde(default)]
1389 pub drift_budget: crate::contract_drift::DriftBudgetConfig,
1390 #[serde(default)]
1392 pub incidents: IncidentConfig,
1393 #[serde(default)]
1395 pub pr_generation: crate::pr_generation::PRGenerationConfig,
1396 #[serde(default)]
1398 pub consumer_contracts: ConsumerContractsConfig,
1399 #[serde(default)]
1401 pub contracts: ContractsConfig,
1402 #[serde(default)]
1404 pub behavioral_economics: BehavioralEconomicsConfig,
1405 #[serde(default)]
1407 pub drift_learning: DriftLearningConfig,
1408 #[serde(default)]
1410 pub org_ai_controls: crate::ai_studio::org_controls::OrgAiControlsConfig,
1411 #[serde(default)]
1413 pub performance: PerformanceConfig,
1414 #[serde(default)]
1416 pub plugins: PluginResourceConfig,
1417 #[serde(default)]
1419 pub hot_reload: ConfigHotReloadConfig,
1420 #[serde(default)]
1422 pub secrets: SecretBackendConfig,
1423}
1424
1425#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1427#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1428#[serde(default)]
1429pub struct ProfileConfig {
1430 #[serde(skip_serializing_if = "Option::is_none")]
1432 pub http: Option<HttpConfig>,
1433 #[serde(skip_serializing_if = "Option::is_none")]
1435 pub websocket: Option<WebSocketConfig>,
1436 #[serde(skip_serializing_if = "Option::is_none")]
1438 pub graphql: Option<GraphQLConfig>,
1439 #[serde(skip_serializing_if = "Option::is_none")]
1441 pub grpc: Option<GrpcConfig>,
1442 #[serde(skip_serializing_if = "Option::is_none")]
1444 pub mqtt: Option<MqttConfig>,
1445 #[serde(skip_serializing_if = "Option::is_none")]
1447 pub smtp: Option<SmtpConfig>,
1448 #[serde(skip_serializing_if = "Option::is_none")]
1450 pub ftp: Option<FtpConfig>,
1451 #[serde(skip_serializing_if = "Option::is_none")]
1453 pub kafka: Option<KafkaConfig>,
1454 #[serde(skip_serializing_if = "Option::is_none")]
1456 pub amqp: Option<AmqpConfig>,
1457 #[serde(skip_serializing_if = "Option::is_none")]
1459 pub tcp: Option<TcpConfig>,
1460 #[serde(skip_serializing_if = "Option::is_none")]
1462 pub admin: Option<AdminConfig>,
1463 #[serde(skip_serializing_if = "Option::is_none")]
1465 pub chaining: Option<ChainingConfig>,
1466 #[serde(skip_serializing_if = "Option::is_none")]
1468 pub core: Option<CoreConfig>,
1469 #[serde(skip_serializing_if = "Option::is_none")]
1471 pub logging: Option<LoggingConfig>,
1472 #[serde(skip_serializing_if = "Option::is_none")]
1474 pub data: Option<DataConfig>,
1475 #[serde(skip_serializing_if = "Option::is_none")]
1477 pub mockai: Option<MockAIConfig>,
1478 #[serde(skip_serializing_if = "Option::is_none")]
1480 pub observability: Option<ObservabilityConfig>,
1481 #[serde(skip_serializing_if = "Option::is_none")]
1483 pub multi_tenant: Option<crate::multi_tenant::MultiTenantConfig>,
1484 #[serde(skip_serializing_if = "Option::is_none")]
1486 pub routes: Option<Vec<RouteConfig>>,
1487 #[serde(skip_serializing_if = "Option::is_none")]
1489 pub protocols: Option<ProtocolsConfig>,
1490 #[serde(skip_serializing_if = "Option::is_none")]
1492 pub deceptive_deploy: Option<DeceptiveDeployConfig>,
1493 #[serde(skip_serializing_if = "Option::is_none")]
1495 pub reality: Option<RealitySliderConfig>,
1496 #[serde(skip_serializing_if = "Option::is_none")]
1498 pub reality_continuum: Option<crate::reality_continuum::ContinuumConfig>,
1499 #[serde(skip_serializing_if = "Option::is_none")]
1501 pub security: Option<SecurityConfig>,
1502}
1503
1504#[derive(Debug, Clone, Serialize, Deserialize)]
1508#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1509pub struct HttpValidationConfig {
1510 pub mode: String,
1512}
1513
1514#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1516#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1517pub struct HttpCorsConfig {
1518 pub enabled: bool,
1520 #[serde(default)]
1522 pub allowed_origins: Vec<String>,
1523 #[serde(default)]
1525 pub allowed_methods: Vec<String>,
1526 #[serde(default)]
1528 pub allowed_headers: Vec<String>,
1529 #[serde(default = "default_cors_allow_credentials")]
1532 pub allow_credentials: bool,
1533}
1534
1535fn default_cors_allow_credentials() -> bool {
1536 false
1537}
1538
1539#[derive(Debug, Clone, Serialize, Deserialize)]
1541#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1542#[serde(default)]
1543pub struct HttpConfig {
1544 pub enabled: bool,
1546 pub port: u16,
1548 pub host: String,
1550 pub openapi_spec: Option<String>,
1552 pub cors: Option<HttpCorsConfig>,
1554 pub request_timeout_secs: u64,
1556 pub validation: Option<HttpValidationConfig>,
1558 pub aggregate_validation_errors: bool,
1560 pub validate_responses: bool,
1562 pub response_template_expand: bool,
1564 pub validation_status: Option<u16>,
1566 pub validation_overrides: HashMap<String, String>,
1568 pub skip_admin_validation: bool,
1570 pub auth: Option<AuthConfig>,
1572 #[serde(skip_serializing_if = "Option::is_none")]
1574 pub tls: Option<HttpTlsConfig>,
1575}
1576
1577impl Default for HttpConfig {
1578 fn default() -> Self {
1579 Self {
1580 enabled: true,
1581 port: 3000,
1582 host: "0.0.0.0".to_string(),
1583 openapi_spec: None,
1584 cors: Some(HttpCorsConfig {
1585 enabled: true,
1586 allowed_origins: vec!["*".to_string()],
1587 allowed_methods: vec![
1588 "GET".to_string(),
1589 "POST".to_string(),
1590 "PUT".to_string(),
1591 "DELETE".to_string(),
1592 "PATCH".to_string(),
1593 "OPTIONS".to_string(),
1594 ],
1595 allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
1596 allow_credentials: false, }),
1598 request_timeout_secs: 30,
1599 validation: Some(HttpValidationConfig {
1600 mode: "enforce".to_string(),
1601 }),
1602 aggregate_validation_errors: true,
1603 validate_responses: false,
1604 response_template_expand: false,
1605 validation_status: None,
1606 validation_overrides: HashMap::new(),
1607 skip_admin_validation: true,
1608 auth: None,
1609 tls: None,
1610 }
1611 }
1612}
1613
1614#[derive(Debug, Clone, Serialize, Deserialize)]
1616#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1617pub struct HttpTlsConfig {
1618 pub enabled: bool,
1620 pub cert_file: String,
1622 pub key_file: String,
1624 #[serde(skip_serializing_if = "Option::is_none")]
1626 pub ca_file: Option<String>,
1627 #[serde(default = "default_tls_min_version")]
1629 pub min_version: String,
1630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1632 pub cipher_suites: Vec<String>,
1633 #[serde(default)]
1635 pub require_client_cert: bool,
1636 #[serde(default = "default_mtls_mode")]
1638 pub mtls_mode: String,
1639}
1640
1641fn default_mtls_mode() -> String {
1642 "off".to_string()
1643}
1644
1645fn default_tls_min_version() -> String {
1646 "1.2".to_string()
1647}
1648
1649impl Default for HttpTlsConfig {
1650 fn default() -> Self {
1651 Self {
1652 enabled: true,
1653 cert_file: String::new(),
1654 key_file: String::new(),
1655 ca_file: None,
1656 min_version: "1.2".to_string(),
1657 cipher_suites: Vec::new(),
1658 require_client_cert: false,
1659 mtls_mode: "off".to_string(),
1660 }
1661 }
1662}
1663
1664#[derive(Debug, Clone, Serialize, Deserialize)]
1666#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1667#[serde(default)]
1668pub struct WebSocketConfig {
1669 pub enabled: bool,
1671 pub port: u16,
1673 pub host: String,
1675 pub replay_file: Option<String>,
1677 pub connection_timeout_secs: u64,
1679}
1680
1681impl Default for WebSocketConfig {
1682 fn default() -> Self {
1683 Self {
1684 enabled: true,
1685 port: 3001,
1686 host: "0.0.0.0".to_string(),
1687 replay_file: None,
1688 connection_timeout_secs: 300,
1689 }
1690 }
1691}
1692
1693#[derive(Debug, Clone, Serialize, Deserialize)]
1695#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1696#[serde(default)]
1697pub struct GrpcConfig {
1698 pub enabled: bool,
1700 pub port: u16,
1702 pub host: String,
1704 pub proto_dir: Option<String>,
1706 pub tls: Option<TlsConfig>,
1708}
1709
1710impl Default for GrpcConfig {
1711 fn default() -> Self {
1712 Self {
1713 enabled: true,
1714 port: 50051,
1715 host: "0.0.0.0".to_string(),
1716 proto_dir: None,
1717 tls: None,
1718 }
1719 }
1720}
1721
1722#[derive(Debug, Clone, Serialize, Deserialize)]
1724#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1725#[serde(default)]
1726pub struct GraphQLConfig {
1727 pub enabled: bool,
1729 pub port: u16,
1731 pub host: String,
1733 pub schema_path: Option<String>,
1735 pub handlers_dir: Option<String>,
1737 pub playground_enabled: bool,
1739 pub upstream_url: Option<String>,
1741 pub introspection_enabled: bool,
1743}
1744
1745impl Default for GraphQLConfig {
1746 fn default() -> Self {
1747 Self {
1748 enabled: true,
1749 port: 4000,
1750 host: "0.0.0.0".to_string(),
1751 schema_path: None,
1752 handlers_dir: None,
1753 playground_enabled: true,
1754 upstream_url: None,
1755 introspection_enabled: true,
1756 }
1757 }
1758}
1759
1760#[derive(Debug, Clone, Serialize, Deserialize)]
1762#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1763pub struct TlsConfig {
1764 pub cert_path: String,
1766 pub key_path: String,
1768}
1769
1770#[derive(Debug, Clone, Serialize, Deserialize)]
1772#[serde(default)]
1773#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1774pub struct MqttConfig {
1775 pub enabled: bool,
1777 pub port: u16,
1779 pub host: String,
1781 pub max_connections: usize,
1783 pub max_packet_size: usize,
1785 pub keep_alive_secs: u16,
1787 pub fixtures_dir: Option<std::path::PathBuf>,
1789 pub enable_retained_messages: bool,
1791 pub max_retained_messages: usize,
1793}
1794
1795impl Default for MqttConfig {
1796 fn default() -> Self {
1797 Self {
1798 enabled: false,
1799 port: 1883,
1800 host: "0.0.0.0".to_string(),
1801 max_connections: 1000,
1802 max_packet_size: 268435456, keep_alive_secs: 60,
1804 fixtures_dir: None,
1805 enable_retained_messages: true,
1806 max_retained_messages: 10000,
1807 }
1808 }
1809}
1810
1811#[derive(Debug, Clone, Serialize, Deserialize)]
1813#[serde(default)]
1814#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1815pub struct SmtpConfig {
1816 pub enabled: bool,
1818 pub port: u16,
1820 pub host: String,
1822 pub hostname: String,
1824 pub fixtures_dir: Option<std::path::PathBuf>,
1826 pub timeout_secs: u64,
1828 pub max_connections: usize,
1830 pub enable_mailbox: bool,
1832 pub max_mailbox_messages: usize,
1834 pub enable_starttls: bool,
1836 pub tls_cert_path: Option<std::path::PathBuf>,
1838 pub tls_key_path: Option<std::path::PathBuf>,
1840}
1841
1842impl Default for SmtpConfig {
1843 fn default() -> Self {
1844 Self {
1845 enabled: false,
1846 port: 1025,
1847 host: "0.0.0.0".to_string(),
1848 hostname: "mockforge-smtp".to_string(),
1849 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
1850 timeout_secs: 300,
1851 max_connections: 10,
1852 enable_mailbox: true,
1853 max_mailbox_messages: 1000,
1854 enable_starttls: false,
1855 tls_cert_path: None,
1856 tls_key_path: None,
1857 }
1858 }
1859}
1860
1861#[derive(Debug, Clone, Serialize, Deserialize)]
1863#[serde(default)]
1864#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1865pub struct FtpConfig {
1866 pub enabled: bool,
1868 pub port: u16,
1870 pub host: String,
1872 pub passive_ports: (u16, u16),
1874 pub max_connections: usize,
1876 pub timeout_secs: u64,
1878 pub allow_anonymous: bool,
1880 pub fixtures_dir: Option<std::path::PathBuf>,
1882 pub virtual_root: std::path::PathBuf,
1884}
1885
1886impl Default for FtpConfig {
1887 fn default() -> Self {
1888 Self {
1889 enabled: false,
1890 port: 2121,
1891 host: "0.0.0.0".to_string(),
1892 passive_ports: (50000, 51000),
1893 max_connections: 100,
1894 timeout_secs: 300,
1895 allow_anonymous: true,
1896 fixtures_dir: None,
1897 virtual_root: std::path::PathBuf::from("/mockforge"),
1898 }
1899 }
1900}
1901
1902#[derive(Debug, Clone, Serialize, Deserialize)]
1904#[serde(default)]
1905#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1906pub struct KafkaConfig {
1907 pub enabled: bool,
1909 pub port: u16,
1911 pub host: String,
1913 pub broker_id: i32,
1915 pub max_connections: usize,
1917 pub log_retention_ms: i64,
1919 pub log_segment_bytes: i64,
1921 pub fixtures_dir: Option<std::path::PathBuf>,
1923 pub auto_create_topics: bool,
1925 pub default_partitions: i32,
1927 pub default_replication_factor: i16,
1929}
1930
1931impl Default for KafkaConfig {
1932 fn default() -> Self {
1933 Self {
1934 enabled: false,
1935 port: 9092, host: "0.0.0.0".to_string(),
1937 broker_id: 1,
1938 max_connections: 1000,
1939 log_retention_ms: 604800000, log_segment_bytes: 1073741824, fixtures_dir: None,
1942 auto_create_topics: true,
1943 default_partitions: 3,
1944 default_replication_factor: 1,
1945 }
1946 }
1947}
1948
1949#[derive(Debug, Clone, Serialize, Deserialize)]
1951#[serde(default)]
1952#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1953pub struct AmqpConfig {
1954 pub enabled: bool,
1956 pub port: u16,
1958 pub host: String,
1960 pub max_connections: usize,
1962 pub max_channels_per_connection: u16,
1964 pub frame_max: u32,
1966 pub heartbeat_interval: u16,
1968 pub fixtures_dir: Option<std::path::PathBuf>,
1970 pub virtual_hosts: Vec<String>,
1972 pub tls_enabled: bool,
1974 pub tls_port: u16,
1976 pub tls_cert_path: Option<std::path::PathBuf>,
1978 pub tls_key_path: Option<std::path::PathBuf>,
1980 pub tls_ca_path: Option<std::path::PathBuf>,
1982 pub tls_client_auth: bool,
1984}
1985
1986impl Default for AmqpConfig {
1987 fn default() -> Self {
1988 Self {
1989 enabled: false,
1990 port: 5672, host: "0.0.0.0".to_string(),
1992 max_connections: 1000,
1993 max_channels_per_connection: 100,
1994 frame_max: 131072, heartbeat_interval: 60,
1996 fixtures_dir: None,
1997 virtual_hosts: vec!["/".to_string()],
1998 tls_enabled: false,
1999 tls_port: 5671, tls_cert_path: None,
2001 tls_key_path: None,
2002 tls_ca_path: None,
2003 tls_client_auth: false,
2004 }
2005 }
2006}
2007
2008#[derive(Debug, Clone, Serialize, Deserialize)]
2010#[serde(default)]
2011#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2012pub struct TcpConfig {
2013 pub enabled: bool,
2015 pub port: u16,
2017 pub host: String,
2019 pub max_connections: usize,
2021 pub timeout_secs: u64,
2023 pub fixtures_dir: Option<std::path::PathBuf>,
2025 pub echo_mode: bool,
2027 pub enable_tls: bool,
2029 pub tls_cert_path: Option<std::path::PathBuf>,
2031 pub tls_key_path: Option<std::path::PathBuf>,
2033}
2034
2035impl Default for TcpConfig {
2036 fn default() -> Self {
2037 Self {
2038 enabled: false,
2039 port: 9999,
2040 host: "0.0.0.0".to_string(),
2041 max_connections: 1000,
2042 timeout_secs: 300,
2043 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
2044 echo_mode: true,
2045 enable_tls: false,
2046 tls_cert_path: None,
2047 tls_key_path: None,
2048 }
2049 }
2050}
2051
2052#[derive(Debug, Clone, Serialize, Deserialize)]
2054#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2055#[serde(default)]
2056pub struct AdminConfig {
2057 pub enabled: bool,
2059 pub port: u16,
2061 pub host: String,
2063 pub auth_required: bool,
2065 pub username: Option<String>,
2067 pub password: Option<String>,
2069 pub mount_path: Option<String>,
2071 pub api_enabled: bool,
2073 pub prometheus_url: String,
2075}
2076
2077impl Default for AdminConfig {
2078 fn default() -> Self {
2079 let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
2082 || std::env::var("container").is_ok()
2083 || Path::new("/.dockerenv").exists()
2084 {
2085 "0.0.0.0".to_string()
2086 } else {
2087 "127.0.0.1".to_string()
2088 };
2089
2090 Self {
2091 enabled: false,
2092 port: 9080,
2093 host: default_host,
2094 auth_required: false,
2095 username: None,
2096 password: None,
2097 mount_path: None,
2098 api_enabled: true,
2099 prometheus_url: "http://localhost:9090".to_string(),
2100 }
2101 }
2102}
2103
2104#[derive(Debug, Clone, Serialize, Deserialize)]
2106#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2107#[serde(default)]
2108pub struct LoggingConfig {
2109 pub level: String,
2111 pub json_format: bool,
2113 pub file_path: Option<String>,
2115 pub max_file_size_mb: u64,
2117 pub max_files: u32,
2119}
2120
2121impl Default for LoggingConfig {
2122 fn default() -> Self {
2123 Self {
2124 level: "info".to_string(),
2125 json_format: false,
2126 file_path: None,
2127 max_file_size_mb: 10,
2128 max_files: 5,
2129 }
2130 }
2131}
2132
2133#[derive(Debug, Clone, Serialize, Deserialize)]
2135#[serde(default, rename_all = "camelCase")]
2136#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2137pub struct ChainingConfig {
2138 pub enabled: bool,
2140 pub max_chain_length: usize,
2142 pub global_timeout_secs: u64,
2144 pub enable_parallel_execution: bool,
2146}
2147
2148impl Default for ChainingConfig {
2149 fn default() -> Self {
2150 Self {
2151 enabled: false,
2152 max_chain_length: 20,
2153 global_timeout_secs: 300,
2154 enable_parallel_execution: false,
2155 }
2156 }
2157}
2158
2159#[derive(Debug, Clone, Serialize, Deserialize)]
2161#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2162#[serde(default)]
2163pub struct DataConfig {
2164 pub default_rows: usize,
2166 pub default_format: String,
2168 pub locale: String,
2170 pub templates: HashMap<String, String>,
2172 pub rag: RagConfig,
2174 #[serde(skip_serializing_if = "Option::is_none")]
2176 pub persona_domain: Option<String>,
2177 #[serde(default = "default_false")]
2179 pub persona_consistency_enabled: bool,
2180 #[serde(skip_serializing_if = "Option::is_none")]
2182 pub persona_registry: Option<PersonaRegistryConfig>,
2183}
2184
2185impl Default for DataConfig {
2186 fn default() -> Self {
2187 Self {
2188 default_rows: 100,
2189 default_format: "json".to_string(),
2190 locale: "en".to_string(),
2191 templates: HashMap::new(),
2192 rag: RagConfig::default(),
2193 persona_domain: None,
2194 persona_consistency_enabled: false,
2195 persona_registry: None,
2196 }
2197 }
2198}
2199
2200#[derive(Debug, Clone, Serialize, Deserialize)]
2202#[serde(default)]
2203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2204pub struct RagConfig {
2205 pub enabled: bool,
2207 #[serde(default)]
2209 pub provider: String,
2210 pub api_endpoint: Option<String>,
2212 pub api_key: Option<String>,
2214 pub model: Option<String>,
2216 #[serde(default = "default_max_tokens")]
2218 pub max_tokens: usize,
2219 #[serde(default = "default_temperature")]
2221 pub temperature: f64,
2222 pub context_window: usize,
2224 #[serde(default = "default_true")]
2226 pub caching: bool,
2227 #[serde(default = "default_cache_ttl")]
2229 pub cache_ttl_secs: u64,
2230 #[serde(default = "default_timeout")]
2232 pub timeout_secs: u64,
2233 #[serde(default = "default_max_retries")]
2235 pub max_retries: usize,
2236}
2237
2238fn default_max_tokens() -> usize {
2239 1024
2240}
2241
2242fn default_temperature() -> f64 {
2243 0.7
2244}
2245
2246fn default_true() -> bool {
2247 true
2248}
2249
2250fn default_cache_ttl() -> u64 {
2251 3600
2252}
2253
2254fn default_timeout() -> u64 {
2255 30
2256}
2257
2258fn default_max_retries() -> usize {
2259 3
2260}
2261
2262fn default_false() -> bool {
2263 false
2264}
2265
2266impl Default for RagConfig {
2267 fn default() -> Self {
2268 Self {
2269 enabled: false,
2270 provider: "openai".to_string(),
2271 api_endpoint: None,
2272 api_key: None,
2273 model: Some("gpt-3.5-turbo".to_string()),
2274 max_tokens: default_max_tokens(),
2275 temperature: default_temperature(),
2276 context_window: 4000,
2277 caching: default_true(),
2278 cache_ttl_secs: default_cache_ttl(),
2279 timeout_secs: default_timeout(),
2280 max_retries: default_max_retries(),
2281 }
2282 }
2283}
2284
2285#[derive(Debug, Clone, Serialize, Deserialize)]
2287#[serde(default)]
2288#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2289#[derive(Default)]
2290pub struct PersonaRegistryConfig {
2291 #[serde(default = "default_false")]
2293 pub persistent: bool,
2294 #[serde(skip_serializing_if = "Option::is_none")]
2296 pub storage_path: Option<String>,
2297 #[serde(default)]
2299 pub default_traits: HashMap<String, String>,
2300}
2301
2302#[derive(Debug, Clone, Serialize, Deserialize)]
2304#[serde(default)]
2305#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2306pub struct MockAIConfig {
2307 pub enabled: bool,
2309 pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
2311 pub auto_learn: bool,
2313 pub mutation_detection: bool,
2315 pub ai_validation_errors: bool,
2317 pub intelligent_pagination: bool,
2319 #[serde(default)]
2321 pub enabled_endpoints: Vec<String>,
2322}
2323
2324impl Default for MockAIConfig {
2325 fn default() -> Self {
2326 Self {
2327 enabled: false,
2328 intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
2329 auto_learn: true,
2330 mutation_detection: true,
2331 ai_validation_errors: true,
2332 intelligent_pagination: true,
2333 enabled_endpoints: Vec::new(),
2334 }
2335 }
2336}
2337
2338#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2340#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2341#[serde(default)]
2342pub struct ObservabilityConfig {
2343 pub prometheus: PrometheusConfig,
2345 pub opentelemetry: Option<OpenTelemetryConfig>,
2347 pub recorder: Option<RecorderConfig>,
2349 pub chaos: Option<ChaosEngConfig>,
2351}
2352
2353#[derive(Debug, Clone, Serialize, Deserialize)]
2355#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2356#[serde(default)]
2357#[derive(Default)]
2358pub struct SecurityConfig {
2359 pub monitoring: SecurityMonitoringConfig,
2361}
2362
2363#[derive(Debug, Clone, Serialize, Deserialize)]
2365#[serde(default)]
2366#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2367#[derive(Default)]
2368pub struct SecurityMonitoringConfig {
2369 pub siem: crate::security::siem::SiemConfig,
2371 pub access_review: crate::security::access_review::AccessReviewConfig,
2373 pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
2375 pub change_management: crate::security::change_management::ChangeManagementConfig,
2377 pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
2379 pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
2381}
2382
2383#[derive(Debug, Clone, Serialize, Deserialize)]
2385#[serde(default)]
2386#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2387pub struct PrometheusConfig {
2388 pub enabled: bool,
2390 pub port: u16,
2392 pub host: String,
2394 pub path: String,
2396}
2397
2398impl Default for PrometheusConfig {
2399 fn default() -> Self {
2400 Self {
2401 enabled: true,
2402 port: 9090,
2403 host: "0.0.0.0".to_string(),
2404 path: "/metrics".to_string(),
2405 }
2406 }
2407}
2408
2409#[derive(Debug, Clone, Serialize, Deserialize)]
2411#[serde(default)]
2412#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2413pub struct OpenTelemetryConfig {
2414 pub enabled: bool,
2416 pub service_name: String,
2418 pub environment: String,
2420 pub jaeger_endpoint: String,
2422 pub otlp_endpoint: Option<String>,
2424 pub protocol: String,
2426 pub sampling_rate: f64,
2428}
2429
2430impl Default for OpenTelemetryConfig {
2431 fn default() -> Self {
2432 Self {
2433 enabled: false,
2434 service_name: "mockforge".to_string(),
2435 environment: "development".to_string(),
2436 jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
2437 otlp_endpoint: Some("http://localhost:4317".to_string()),
2438 protocol: "grpc".to_string(),
2439 sampling_rate: 1.0,
2440 }
2441 }
2442}
2443
2444#[derive(Debug, Clone, Serialize, Deserialize)]
2446#[serde(default)]
2447#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2448pub struct RecorderConfig {
2449 pub enabled: bool,
2451 pub database_path: String,
2453 pub api_enabled: bool,
2455 pub api_port: Option<u16>,
2457 pub max_requests: i64,
2459 pub retention_days: i64,
2461 pub record_http: bool,
2463 pub record_grpc: bool,
2465 pub record_websocket: bool,
2467 pub record_graphql: bool,
2469 #[serde(default = "default_true")]
2472 pub record_proxy: bool,
2473}
2474
2475impl Default for RecorderConfig {
2476 fn default() -> Self {
2477 Self {
2478 enabled: false,
2479 database_path: "./mockforge-recordings.db".to_string(),
2480 api_enabled: true,
2481 api_port: None,
2482 max_requests: 10000,
2483 retention_days: 7,
2484 record_http: true,
2485 record_grpc: true,
2486 record_websocket: true,
2487 record_graphql: true,
2488 record_proxy: true,
2489 }
2490 }
2491}
2492
2493#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2495#[serde(default)]
2496#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2497pub struct ChaosEngConfig {
2498 pub enabled: bool,
2500 pub latency: Option<LatencyInjectionConfig>,
2502 pub fault_injection: Option<FaultConfig>,
2504 pub rate_limit: Option<RateLimitingConfig>,
2506 pub traffic_shaping: Option<NetworkShapingConfig>,
2508 pub scenario: Option<String>,
2510}
2511
2512#[derive(Debug, Clone, Serialize, Deserialize)]
2514#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2515pub struct LatencyInjectionConfig {
2516 pub enabled: bool,
2518 pub fixed_delay_ms: Option<u64>,
2520 pub random_delay_range_ms: Option<(u64, u64)>,
2522 pub jitter_percent: f64,
2524 pub probability: f64,
2526}
2527
2528#[derive(Debug, Clone, Serialize, Deserialize)]
2530#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2531pub struct FaultConfig {
2532 pub enabled: bool,
2534 pub http_errors: Vec<u16>,
2536 pub http_error_probability: f64,
2538 pub connection_errors: bool,
2540 pub connection_error_probability: f64,
2542 pub timeout_errors: bool,
2544 pub timeout_ms: u64,
2546 pub timeout_probability: f64,
2548}
2549
2550#[derive(Debug, Clone, Serialize, Deserialize)]
2552#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2553pub struct RateLimitingConfig {
2554 pub enabled: bool,
2556 pub requests_per_second: u32,
2558 pub burst_size: u32,
2560 pub per_ip: bool,
2562 pub per_endpoint: bool,
2564}
2565
2566#[derive(Debug, Clone, Serialize, Deserialize)]
2568#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2569pub struct NetworkShapingConfig {
2570 pub enabled: bool,
2572 pub bandwidth_limit_bps: u64,
2574 pub packet_loss_percent: f64,
2576 pub max_connections: u32,
2578}
2579
2580pub async fn load_config<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2582 let content = fs::read_to_string(&path)
2583 .await
2584 .map_err(|e| Error::generic(format!("Failed to read config file: {}", e)))?;
2585
2586 let config: ServerConfig = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2588 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2589 {
2590 serde_yaml::from_str(&content).map_err(|e| {
2591 let error_msg = e.to_string();
2593 let mut full_msg = format!("Failed to parse YAML config: {}", error_msg);
2594
2595 if error_msg.contains("missing field") {
2597 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
2598 full_msg.push_str(
2599 "\n Omit fields you don't need - MockForge will use sensible defaults.",
2600 );
2601 full_msg.push_str("\n See config.template.yaml for all available options.");
2602 } else if error_msg.contains("unknown field") {
2603 full_msg.push_str("\n\n💡 Check for typos in field names.");
2604 full_msg.push_str("\n See config.template.yaml for valid field names.");
2605 }
2606
2607 Error::generic(full_msg)
2608 })?
2609 } else {
2610 serde_json::from_str(&content).map_err(|e| {
2611 let error_msg = e.to_string();
2613 let mut full_msg = format!("Failed to parse JSON config: {}", error_msg);
2614
2615 if error_msg.contains("missing field") {
2617 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
2618 full_msg.push_str(
2619 "\n Omit fields you don't need - MockForge will use sensible defaults.",
2620 );
2621 full_msg.push_str("\n See config.template.yaml for all available options.");
2622 } else if error_msg.contains("unknown field") {
2623 full_msg.push_str("\n\n💡 Check for typos in field names.");
2624 full_msg.push_str("\n See config.template.yaml for valid field names.");
2625 }
2626
2627 Error::generic(full_msg)
2628 })?
2629 };
2630
2631 Ok(config)
2632}
2633
2634pub async fn save_config<P: AsRef<Path>>(path: P, config: &ServerConfig) -> Result<()> {
2636 let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2637 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2638 {
2639 serde_yaml::to_string(config)
2640 .map_err(|e| Error::generic(format!("Failed to serialize config to YAML: {}", e)))?
2641 } else {
2642 serde_json::to_string_pretty(config)
2643 .map_err(|e| Error::generic(format!("Failed to serialize config to JSON: {}", e)))?
2644 };
2645
2646 fs::write(path, content)
2647 .await
2648 .map_err(|e| Error::generic(format!("Failed to write config file: {}", e)))?;
2649
2650 Ok(())
2651}
2652
2653pub async fn load_config_with_fallback<P: AsRef<Path>>(path: P) -> ServerConfig {
2655 match load_config(&path).await {
2656 Ok(config) => {
2657 tracing::info!("Loaded configuration from {:?}", path.as_ref());
2658 config
2659 }
2660 Err(e) => {
2661 tracing::warn!(
2662 "Failed to load config from {:?}: {}. Using defaults.",
2663 path.as_ref(),
2664 e
2665 );
2666 ServerConfig::default()
2667 }
2668 }
2669}
2670
2671pub async fn create_default_config<P: AsRef<Path>>(path: P) -> Result<()> {
2673 let config = ServerConfig::default();
2674 save_config(path, &config).await?;
2675 Ok(())
2676}
2677
2678pub fn apply_env_overrides(mut config: ServerConfig) -> ServerConfig {
2680 if let Ok(port) = std::env::var("MOCKFORGE_HTTP_PORT") {
2682 if let Ok(port_num) = port.parse() {
2683 config.http.port = port_num;
2684 }
2685 }
2686
2687 if let Ok(host) = std::env::var("MOCKFORGE_HTTP_HOST") {
2688 config.http.host = host;
2689 }
2690
2691 if let Ok(port) = std::env::var("MOCKFORGE_WS_PORT") {
2693 if let Ok(port_num) = port.parse() {
2694 config.websocket.port = port_num;
2695 }
2696 }
2697
2698 if let Ok(port) = std::env::var("MOCKFORGE_GRPC_PORT") {
2700 if let Ok(port_num) = port.parse() {
2701 config.grpc.port = port_num;
2702 }
2703 }
2704
2705 if let Ok(port) = std::env::var("MOCKFORGE_SMTP_PORT") {
2707 if let Ok(port_num) = port.parse() {
2708 config.smtp.port = port_num;
2709 }
2710 }
2711
2712 if let Ok(host) = std::env::var("MOCKFORGE_SMTP_HOST") {
2713 config.smtp.host = host;
2714 }
2715
2716 if let Ok(enabled) = std::env::var("MOCKFORGE_SMTP_ENABLED") {
2717 config.smtp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2718 }
2719
2720 if let Ok(hostname) = std::env::var("MOCKFORGE_SMTP_HOSTNAME") {
2721 config.smtp.hostname = hostname;
2722 }
2723
2724 if let Ok(port) = std::env::var("MOCKFORGE_TCP_PORT") {
2726 if let Ok(port_num) = port.parse() {
2727 config.tcp.port = port_num;
2728 }
2729 }
2730
2731 if let Ok(host) = std::env::var("MOCKFORGE_TCP_HOST") {
2732 config.tcp.host = host;
2733 }
2734
2735 if let Ok(enabled) = std::env::var("MOCKFORGE_TCP_ENABLED") {
2736 config.tcp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2737 }
2738
2739 if let Ok(port) = std::env::var("MOCKFORGE_ADMIN_PORT") {
2741 if let Ok(port_num) = port.parse() {
2742 config.admin.port = port_num;
2743 }
2744 }
2745
2746 if std::env::var("MOCKFORGE_ADMIN_ENABLED").unwrap_or_default() == "true" {
2747 config.admin.enabled = true;
2748 }
2749
2750 if let Ok(host) = std::env::var("MOCKFORGE_ADMIN_HOST") {
2752 config.admin.host = host;
2753 }
2754
2755 if let Ok(mount_path) = std::env::var("MOCKFORGE_ADMIN_MOUNT_PATH") {
2756 if !mount_path.trim().is_empty() {
2757 config.admin.mount_path = Some(mount_path);
2758 }
2759 }
2760
2761 if let Ok(api_enabled) = std::env::var("MOCKFORGE_ADMIN_API_ENABLED") {
2762 let on = api_enabled == "1" || api_enabled.eq_ignore_ascii_case("true");
2763 config.admin.api_enabled = on;
2764 }
2765
2766 if let Ok(prometheus_url) = std::env::var("PROMETHEUS_URL") {
2767 config.admin.prometheus_url = prometheus_url;
2768 }
2769
2770 if let Ok(latency_enabled) = std::env::var("MOCKFORGE_LATENCY_ENABLED") {
2772 let enabled = latency_enabled == "1" || latency_enabled.eq_ignore_ascii_case("true");
2773 config.core.latency_enabled = enabled;
2774 }
2775
2776 if let Ok(failures_enabled) = std::env::var("MOCKFORGE_FAILURES_ENABLED") {
2777 let enabled = failures_enabled == "1" || failures_enabled.eq_ignore_ascii_case("true");
2778 config.core.failures_enabled = enabled;
2779 }
2780
2781 if let Ok(overrides_enabled) = std::env::var("MOCKFORGE_OVERRIDES_ENABLED") {
2782 let enabled = overrides_enabled == "1" || overrides_enabled.eq_ignore_ascii_case("true");
2783 config.core.overrides_enabled = enabled;
2784 }
2785
2786 if let Ok(traffic_shaping_enabled) = std::env::var("MOCKFORGE_TRAFFIC_SHAPING_ENABLED") {
2787 let enabled =
2788 traffic_shaping_enabled == "1" || traffic_shaping_enabled.eq_ignore_ascii_case("true");
2789 config.core.traffic_shaping_enabled = enabled;
2790 }
2791
2792 if let Ok(bandwidth_enabled) = std::env::var("MOCKFORGE_BANDWIDTH_ENABLED") {
2794 let enabled = bandwidth_enabled == "1" || bandwidth_enabled.eq_ignore_ascii_case("true");
2795 config.core.traffic_shaping.bandwidth.enabled = enabled;
2796 }
2797
2798 if let Ok(max_bytes_per_sec) = std::env::var("MOCKFORGE_BANDWIDTH_MAX_BYTES_PER_SEC") {
2799 if let Ok(bytes) = max_bytes_per_sec.parse() {
2800 config.core.traffic_shaping.bandwidth.max_bytes_per_sec = bytes;
2801 config.core.traffic_shaping.bandwidth.enabled = true;
2802 }
2803 }
2804
2805 if let Ok(burst_capacity) = std::env::var("MOCKFORGE_BANDWIDTH_BURST_CAPACITY_BYTES") {
2806 if let Ok(bytes) = burst_capacity.parse() {
2807 config.core.traffic_shaping.bandwidth.burst_capacity_bytes = bytes;
2808 }
2809 }
2810
2811 if let Ok(burst_loss_enabled) = std::env::var("MOCKFORGE_BURST_LOSS_ENABLED") {
2812 let enabled = burst_loss_enabled == "1" || burst_loss_enabled.eq_ignore_ascii_case("true");
2813 config.core.traffic_shaping.burst_loss.enabled = enabled;
2814 }
2815
2816 if let Ok(burst_probability) = std::env::var("MOCKFORGE_BURST_LOSS_PROBABILITY") {
2817 if let Ok(prob) = burst_probability.parse::<f64>() {
2818 config.core.traffic_shaping.burst_loss.burst_probability = prob.clamp(0.0, 1.0);
2819 config.core.traffic_shaping.burst_loss.enabled = true;
2820 }
2821 }
2822
2823 if let Ok(burst_duration) = std::env::var("MOCKFORGE_BURST_LOSS_DURATION_MS") {
2824 if let Ok(ms) = burst_duration.parse() {
2825 config.core.traffic_shaping.burst_loss.burst_duration_ms = ms;
2826 }
2827 }
2828
2829 if let Ok(loss_rate) = std::env::var("MOCKFORGE_BURST_LOSS_RATE") {
2830 if let Ok(rate) = loss_rate.parse::<f64>() {
2831 config.core.traffic_shaping.burst_loss.loss_rate_during_burst = rate.clamp(0.0, 1.0);
2832 }
2833 }
2834
2835 if let Ok(recovery_time) = std::env::var("MOCKFORGE_BURST_LOSS_RECOVERY_MS") {
2836 if let Ok(ms) = recovery_time.parse() {
2837 config.core.traffic_shaping.burst_loss.recovery_time_ms = ms;
2838 }
2839 }
2840
2841 if let Ok(level) = std::env::var("MOCKFORGE_LOG_LEVEL") {
2843 config.logging.level = level;
2844 }
2845
2846 config
2847}
2848
2849pub fn validate_config(config: &ServerConfig) -> Result<()> {
2851 if config.http.port == 0 {
2853 return Err(Error::generic("HTTP port cannot be 0"));
2854 }
2855 if config.websocket.port == 0 {
2856 return Err(Error::generic("WebSocket port cannot be 0"));
2857 }
2858 if config.grpc.port == 0 {
2859 return Err(Error::generic("gRPC port cannot be 0"));
2860 }
2861 if config.admin.port == 0 {
2862 return Err(Error::generic("Admin port cannot be 0"));
2863 }
2864
2865 let ports = [
2867 ("HTTP", config.http.port),
2868 ("WebSocket", config.websocket.port),
2869 ("gRPC", config.grpc.port),
2870 ("Admin", config.admin.port),
2871 ];
2872
2873 for i in 0..ports.len() {
2874 for j in (i + 1)..ports.len() {
2875 if ports[i].1 == ports[j].1 {
2876 return Err(Error::generic(format!(
2877 "Port conflict: {} and {} both use port {}",
2878 ports[i].0, ports[j].0, ports[i].1
2879 )));
2880 }
2881 }
2882 }
2883
2884 let valid_levels = ["trace", "debug", "info", "warn", "error"];
2886 if !valid_levels.contains(&config.logging.level.as_str()) {
2887 return Err(Error::generic(format!(
2888 "Invalid log level: {}. Valid levels: {}",
2889 config.logging.level,
2890 valid_levels.join(", ")
2891 )));
2892 }
2893
2894 Ok(())
2895}
2896
2897pub fn apply_profile(mut base: ServerConfig, profile: ProfileConfig) -> ServerConfig {
2899 macro_rules! merge_field {
2901 ($field:ident) => {
2902 if let Some(override_val) = profile.$field {
2903 base.$field = override_val;
2904 }
2905 };
2906 }
2907
2908 merge_field!(http);
2909 merge_field!(websocket);
2910 merge_field!(graphql);
2911 merge_field!(grpc);
2912 merge_field!(mqtt);
2913 merge_field!(smtp);
2914 merge_field!(ftp);
2915 merge_field!(kafka);
2916 merge_field!(amqp);
2917 merge_field!(tcp);
2918 merge_field!(admin);
2919 merge_field!(chaining);
2920 merge_field!(core);
2921 merge_field!(logging);
2922 merge_field!(data);
2923 merge_field!(mockai);
2924 merge_field!(observability);
2925 merge_field!(multi_tenant);
2926 merge_field!(routes);
2927 merge_field!(protocols);
2928
2929 base
2930}
2931
2932pub async fn load_config_with_profile<P: AsRef<Path>>(
2934 path: P,
2935 profile_name: Option<&str>,
2936) -> Result<ServerConfig> {
2937 let mut config = load_config_auto(&path).await?;
2939
2940 if let Some(profile) = profile_name {
2942 if let Some(profile_config) = config.profiles.remove(profile) {
2943 tracing::info!("Applying profile: {}", profile);
2944 config = apply_profile(config, profile_config);
2945 } else {
2946 return Err(Error::generic(format!(
2947 "Profile '{}' not found in configuration. Available profiles: {}",
2948 profile,
2949 config.profiles.keys().map(|k| k.as_str()).collect::<Vec<_>>().join(", ")
2950 )));
2951 }
2952 }
2953
2954 config.profiles.clear();
2956
2957 Ok(config)
2958}
2959
2960pub async fn load_config_from_js<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2962 use rquickjs::{Context, Runtime};
2963
2964 let content = fs::read_to_string(&path)
2965 .await
2966 .map_err(|e| Error::generic(format!("Failed to read JS/TS config file: {}", e)))?;
2967
2968 let runtime = Runtime::new()
2970 .map_err(|e| Error::generic(format!("Failed to create JS runtime: {}", e)))?;
2971 let context = Context::full(&runtime)
2972 .map_err(|e| Error::generic(format!("Failed to create JS context: {}", e)))?;
2973
2974 context.with(|ctx| {
2975 let js_content = if path
2978 .as_ref()
2979 .extension()
2980 .and_then(|s| s.to_str())
2981 .map(|ext| ext == "ts")
2982 .unwrap_or(false)
2983 {
2984 strip_typescript_types(&content)?
2985 } else {
2986 content
2987 };
2988
2989 let result: rquickjs::Value = ctx
2991 .eval(js_content.as_bytes())
2992 .map_err(|e| Error::generic(format!("Failed to evaluate JS config: {}", e)))?;
2993
2994 let json_str: String = ctx
2996 .json_stringify(result)
2997 .map_err(|e| Error::generic(format!("Failed to stringify JS config: {}", e)))?
2998 .ok_or_else(|| Error::generic("JS config returned undefined"))?
2999 .get()
3000 .map_err(|e| Error::generic(format!("Failed to get JSON string: {}", e)))?;
3001
3002 serde_json::from_str(&json_str).map_err(|e| {
3004 Error::generic(format!("Failed to parse JS config as ServerConfig: {}", e))
3005 })
3006 })
3007}
3008
3009fn strip_typescript_types(content: &str) -> Result<String> {
3016 use regex::Regex;
3017
3018 let mut result = content.to_string();
3019
3020 let interface_re = Regex::new(r"(?ms)interface\s+\w+\s*\{[^}]*\}\s*")
3026 .map_err(|e| Error::generic(format!("Failed to compile interface regex: {}", e)))?;
3027 result = interface_re.replace_all(&result, "").to_string();
3028
3029 let type_alias_re = Regex::new(r"(?m)^type\s+\w+\s*=\s*[^;]+;\s*")
3031 .map_err(|e| Error::generic(format!("Failed to compile type alias regex: {}", e)))?;
3032 result = type_alias_re.replace_all(&result, "").to_string();
3033
3034 let type_annotation_re = Regex::new(r":\s*[A-Z]\w*(<[^>]+>)?(\[\])?")
3036 .map_err(|e| Error::generic(format!("Failed to compile type annotation regex: {}", e)))?;
3037 result = type_annotation_re.replace_all(&result, "").to_string();
3038
3039 let type_import_re = Regex::new(r"(?m)^(import|export)\s+type\s+.*$")
3041 .map_err(|e| Error::generic(format!("Failed to compile type import regex: {}", e)))?;
3042 result = type_import_re.replace_all(&result, "").to_string();
3043
3044 let as_type_re = Regex::new(r"\s+as\s+\w+")
3046 .map_err(|e| Error::generic(format!("Failed to compile 'as type' regex: {}", e)))?;
3047 result = as_type_re.replace_all(&result, "").to_string();
3048
3049 Ok(result)
3050}
3051
3052pub async fn load_config_auto<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
3054 let ext = path.as_ref().extension().and_then(|s| s.to_str()).unwrap_or("");
3055
3056 match ext {
3057 "ts" | "js" => load_config_from_js(&path).await,
3058 "yaml" | "yml" | "json" => load_config(&path).await,
3059 _ => Err(Error::generic(format!(
3060 "Unsupported config file format: {}. Supported: .ts, .js, .yaml, .yml, .json",
3061 ext
3062 ))),
3063 }
3064}
3065
3066pub async fn discover_config_file_all_formats() -> Result<std::path::PathBuf> {
3068 let current_dir = std::env::current_dir()
3069 .map_err(|e| Error::generic(format!("Failed to get current directory: {}", e)))?;
3070
3071 let config_names = vec![
3072 "mockforge.config.ts",
3073 "mockforge.config.js",
3074 "mockforge.yaml",
3075 "mockforge.yml",
3076 ".mockforge.yaml",
3077 ".mockforge.yml",
3078 ];
3079
3080 for name in &config_names {
3082 let path = current_dir.join(name);
3083 if fs::metadata(&path).await.is_ok() {
3084 return Ok(path);
3085 }
3086 }
3087
3088 let mut dir = current_dir.clone();
3090 for _ in 0..5 {
3091 if let Some(parent) = dir.parent() {
3092 for name in &config_names {
3093 let path = parent.join(name);
3094 if fs::metadata(&path).await.is_ok() {
3095 return Ok(path);
3096 }
3097 }
3098 dir = parent.to_path_buf();
3099 } else {
3100 break;
3101 }
3102 }
3103
3104 Err(Error::generic(
3105 "No configuration file found. Expected one of: mockforge.config.ts, mockforge.config.js, mockforge.yaml, mockforge.yml",
3106 ))
3107}
3108
3109#[cfg(test)]
3110mod tests {
3111 use super::*;
3112
3113 #[test]
3114 fn test_default_config() {
3115 let config = ServerConfig::default();
3116 assert_eq!(config.http.port, 3000);
3117 assert_eq!(config.websocket.port, 3001);
3118 assert_eq!(config.grpc.port, 50051);
3119 assert_eq!(config.admin.port, 9080);
3120 }
3121
3122 #[test]
3123 fn test_config_validation() {
3124 let mut config = ServerConfig::default();
3125 assert!(validate_config(&config).is_ok());
3126
3127 config.websocket.port = config.http.port;
3129 assert!(validate_config(&config).is_err());
3130
3131 config.websocket.port = 3001; config.logging.level = "invalid".to_string();
3134 assert!(validate_config(&config).is_err());
3135 }
3136
3137 #[test]
3138 fn test_apply_profile() {
3139 let base = ServerConfig::default();
3140 assert_eq!(base.http.port, 3000);
3141
3142 let profile = ProfileConfig {
3143 http: Some(HttpConfig {
3144 port: 8080,
3145 ..Default::default()
3146 }),
3147 logging: Some(LoggingConfig {
3148 level: "debug".to_string(),
3149 ..Default::default()
3150 }),
3151 ..Default::default()
3152 };
3153
3154 let merged = apply_profile(base, profile);
3155 assert_eq!(merged.http.port, 8080);
3156 assert_eq!(merged.logging.level, "debug");
3157 assert_eq!(merged.websocket.port, 3001); }
3159
3160 #[test]
3161 fn test_strip_typescript_types() {
3162 let ts_code = r#"
3163interface Config {
3164 port: number;
3165 host: string;
3166}
3167
3168const config: Config = {
3169 port: 3000,
3170 host: "localhost"
3171} as Config;
3172"#;
3173
3174 let stripped = strip_typescript_types(ts_code).expect("Should strip TypeScript types");
3175 assert!(!stripped.contains("interface"));
3176 assert!(!stripped.contains(": Config"));
3177 assert!(!stripped.contains("as Config"));
3178 }
3179}