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")]
478pub enum LatencyDistribution {
479 Fixed,
481 Normal {
483 mean_ms: f64,
485 std_dev_ms: f64,
487 },
488 Exponential {
490 lambda: f64,
492 },
493 Uniform,
495}
496
497impl Default for LatencyDistribution {
498 fn default() -> Self {
499 Self::Fixed
500 }
501}
502
503impl Default for RouteFaultInjectionConfig {
504 fn default() -> Self {
505 Self {
506 enabled: false,
507 probability: 0.0,
508 fault_types: Vec::new(),
509 }
510 }
511}
512
513impl Default for RouteLatencyConfig {
514 fn default() -> Self {
515 Self {
516 enabled: false,
517 probability: 1.0,
518 fixed_delay_ms: None,
519 random_delay_range_ms: None,
520 jitter_percent: 0.0,
521 distribution: LatencyDistribution::Fixed,
522 }
523 }
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
528#[serde(default)]
529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
530#[derive(Default)]
531pub struct DeceptiveDeployConfig {
532 pub enabled: bool,
534 pub cors: Option<ProductionCorsConfig>,
536 pub rate_limit: Option<ProductionRateLimitConfig>,
538 #[serde(default)]
540 pub headers: HashMap<String, String>,
541 pub oauth: Option<ProductionOAuthConfig>,
543 pub custom_domain: Option<String>,
545 pub auto_tunnel: bool,
547 #[serde(skip_serializing_if = "Option::is_none")]
549 pub canary: Option<crate::deceptive_canary::DeceptiveCanaryConfig>,
550}
551
552impl DeceptiveDeployConfig {
553 pub fn production_preset() -> Self {
555 let mut headers = HashMap::new();
556 headers.insert("X-API-Version".to_string(), "1.0".to_string());
557 headers.insert("X-Request-ID".to_string(), "{{uuid}}".to_string());
558 headers.insert("X-Powered-By".to_string(), "MockForge".to_string());
559
560 Self {
561 enabled: true,
562 cors: Some(ProductionCorsConfig {
563 allowed_origins: vec!["*".to_string()],
564 allowed_methods: vec![
565 "GET".to_string(),
566 "POST".to_string(),
567 "PUT".to_string(),
568 "DELETE".to_string(),
569 "PATCH".to_string(),
570 "OPTIONS".to_string(),
571 ],
572 allowed_headers: vec!["*".to_string()],
573 allow_credentials: true,
574 }),
575 rate_limit: Some(ProductionRateLimitConfig {
576 requests_per_minute: 1000,
577 burst: 2000,
578 per_ip: true,
579 }),
580 headers,
581 oauth: None, custom_domain: None,
583 auto_tunnel: true,
584 canary: None,
585 }
586 }
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize)]
591#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
592pub struct ProductionCorsConfig {
593 #[serde(default)]
595 pub allowed_origins: Vec<String>,
596 #[serde(default)]
598 pub allowed_methods: Vec<String>,
599 #[serde(default)]
601 pub allowed_headers: Vec<String>,
602 pub allow_credentials: bool,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
608#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
609pub struct ProductionRateLimitConfig {
610 pub requests_per_minute: u32,
612 pub burst: u32,
614 pub per_ip: bool,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize)]
620#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
621pub struct ProductionOAuthConfig {
622 pub client_id: String,
624 pub client_secret: String,
626 pub introspection_url: String,
628 pub auth_url: Option<String>,
630 pub token_url: Option<String>,
632 pub token_type_hint: Option<String>,
634}
635
636impl From<ProductionOAuthConfig> for OAuth2Config {
637 fn from(prod: ProductionOAuthConfig) -> Self {
639 OAuth2Config {
640 client_id: prod.client_id,
641 client_secret: prod.client_secret,
642 introspection_url: prod.introspection_url,
643 auth_url: prod.auth_url,
644 token_url: prod.token_url,
645 token_type_hint: prod.token_type_hint,
646 }
647 }
648}
649
650#[derive(Debug, Clone, Serialize, Deserialize)]
652#[serde(default)]
653#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
654pub struct PerformanceConfig {
655 pub compression: CompressionConfig,
657 pub connection_pool: ConnectionPoolConfig,
659 pub request_limits: RequestLimitsConfig,
661 pub workers: WorkerConfig,
663 pub circuit_breaker: CircuitBreakerConfig,
665}
666
667impl Default for PerformanceConfig {
668 fn default() -> Self {
669 Self {
670 compression: CompressionConfig::default(),
671 connection_pool: ConnectionPoolConfig::default(),
672 request_limits: RequestLimitsConfig::default(),
673 workers: WorkerConfig::default(),
674 circuit_breaker: CircuitBreakerConfig::default(),
675 }
676 }
677}
678
679#[derive(Debug, Clone, Serialize, Deserialize)]
681#[serde(default)]
682#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
683pub struct CompressionConfig {
684 pub enabled: bool,
686 pub algorithm: String,
688 pub min_size: usize,
690 pub level: u32,
692 pub content_types: Vec<String>,
694}
695
696impl Default for CompressionConfig {
697 fn default() -> Self {
698 Self {
699 enabled: true,
700 algorithm: "gzip".to_string(),
701 min_size: 1024, level: 6,
703 content_types: vec![
704 "application/json".to_string(),
705 "application/xml".to_string(),
706 "text/plain".to_string(),
707 "text/html".to_string(),
708 "text/css".to_string(),
709 "application/javascript".to_string(),
710 ],
711 }
712 }
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize)]
717#[serde(default)]
718#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
719pub struct ConnectionPoolConfig {
720 pub max_idle_per_host: usize,
722 pub max_connections: usize,
724 pub idle_timeout_secs: u64,
726 pub acquire_timeout_ms: u64,
728 pub enabled: bool,
730}
731
732impl Default for ConnectionPoolConfig {
733 fn default() -> Self {
734 Self {
735 max_idle_per_host: 10,
736 max_connections: 100,
737 idle_timeout_secs: 90,
738 acquire_timeout_ms: 5000,
739 enabled: true,
740 }
741 }
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
746#[serde(default)]
747#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
748pub struct RequestLimitsConfig {
749 pub max_body_size: usize,
751 pub max_header_size: usize,
753 pub max_headers: usize,
755 pub max_uri_length: usize,
757 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
759 pub per_route_limits: HashMap<String, usize>,
760}
761
762impl Default for RequestLimitsConfig {
763 fn default() -> Self {
764 Self {
765 max_body_size: 10 * 1024 * 1024, max_header_size: 16 * 1024, max_headers: 100,
768 max_uri_length: 8192,
769 per_route_limits: HashMap::new(),
770 }
771 }
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
776#[serde(default)]
777#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
778pub struct WorkerConfig {
779 pub threads: usize,
781 pub blocking_threads: usize,
783 pub stack_size: usize,
785 pub name_prefix: String,
787}
788
789impl Default for WorkerConfig {
790 fn default() -> Self {
791 Self {
792 threads: 0, blocking_threads: 512,
794 stack_size: 2 * 1024 * 1024, name_prefix: "mockforge-worker".to_string(),
796 }
797 }
798}
799
800#[derive(Debug, Clone, Serialize, Deserialize)]
802#[serde(default)]
803#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
804pub struct CircuitBreakerConfig {
805 pub enabled: bool,
807 pub failure_threshold: u32,
809 pub success_threshold: u32,
811 pub half_open_timeout_secs: u64,
813 pub window_size: u32,
815 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
817 pub per_endpoint: HashMap<String, EndpointCircuitBreakerConfig>,
818}
819
820impl Default for CircuitBreakerConfig {
821 fn default() -> Self {
822 Self {
823 enabled: false,
824 failure_threshold: 5,
825 success_threshold: 2,
826 half_open_timeout_secs: 30,
827 window_size: 10,
828 per_endpoint: HashMap::new(),
829 }
830 }
831}
832
833#[derive(Debug, Clone, Serialize, Deserialize)]
835#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
836pub struct EndpointCircuitBreakerConfig {
837 pub failure_threshold: u32,
839 pub success_threshold: u32,
841 pub half_open_timeout_secs: u64,
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize)]
847#[serde(default)]
848#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
849pub struct ConfigHotReloadConfig {
850 pub enabled: bool,
852 pub check_interval_secs: u64,
854 pub debounce_delay_ms: u64,
856 #[serde(default, skip_serializing_if = "Vec::is_empty")]
858 pub watch_paths: Vec<String>,
859 pub reload_on_spec_change: bool,
861 pub reload_on_fixture_change: bool,
863 pub reload_on_plugin_change: bool,
865 pub graceful_reload: bool,
867 pub graceful_timeout_secs: u64,
869 pub validate_before_reload: bool,
871 pub rollback_on_failure: bool,
873}
874
875impl Default for ConfigHotReloadConfig {
876 fn default() -> Self {
877 Self {
878 enabled: false,
879 check_interval_secs: 5,
880 debounce_delay_ms: 1000,
881 watch_paths: Vec::new(),
882 reload_on_spec_change: true,
883 reload_on_fixture_change: true,
884 reload_on_plugin_change: true,
885 graceful_reload: true,
886 graceful_timeout_secs: 30,
887 validate_before_reload: true,
888 rollback_on_failure: true,
889 }
890 }
891}
892
893#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
895#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
896#[serde(rename_all = "lowercase")]
897pub enum SecretBackendType {
898 #[default]
900 None,
901 Vault,
903 AwsSecretsManager,
905 AzureKeyVault,
907 GcpSecretManager,
909 Kubernetes,
911 EncryptedFile,
913}
914
915#[derive(Debug, Clone, Serialize, Deserialize)]
917#[serde(default)]
918#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
919pub struct SecretBackendConfig {
920 pub provider: SecretBackendType,
922 #[serde(skip_serializing_if = "Option::is_none")]
924 pub vault: Option<VaultConfig>,
925 #[serde(skip_serializing_if = "Option::is_none")]
927 pub aws: Option<AwsSecretsConfig>,
928 #[serde(skip_serializing_if = "Option::is_none")]
930 pub azure: Option<AzureKeyVaultConfig>,
931 #[serde(skip_serializing_if = "Option::is_none")]
933 pub gcp: Option<GcpSecretManagerConfig>,
934 #[serde(skip_serializing_if = "Option::is_none")]
936 pub kubernetes: Option<KubernetesSecretsConfig>,
937 #[serde(skip_serializing_if = "Option::is_none")]
939 pub encrypted_file: Option<EncryptedFileConfig>,
940 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
942 pub mappings: HashMap<String, String>,
943 pub cache_ttl_secs: u64,
945 pub retry_attempts: u32,
947 pub retry_delay_ms: u64,
949}
950
951impl Default for SecretBackendConfig {
952 fn default() -> Self {
953 Self {
954 provider: SecretBackendType::None,
955 vault: None,
956 aws: None,
957 azure: None,
958 gcp: None,
959 kubernetes: None,
960 encrypted_file: None,
961 mappings: HashMap::new(),
962 cache_ttl_secs: 300, retry_attempts: 3,
964 retry_delay_ms: 1000,
965 }
966 }
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize)]
971#[serde(default)]
972#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
973pub struct VaultConfig {
974 pub address: String,
976 #[serde(skip_serializing_if = "Option::is_none")]
978 pub namespace: Option<String>,
979 pub auth_method: VaultAuthMethod,
981 #[serde(skip_serializing_if = "Option::is_none")]
983 pub token: Option<String>,
984 #[serde(skip_serializing_if = "Option::is_none")]
986 pub role_id: Option<String>,
987 #[serde(skip_serializing_if = "Option::is_none")]
989 pub secret_id: Option<String>,
990 #[serde(skip_serializing_if = "Option::is_none")]
992 pub kubernetes_role: Option<String>,
993 pub mount_path: String,
995 pub path_prefix: String,
997 #[serde(skip_serializing_if = "Option::is_none")]
999 pub ca_cert_path: Option<String>,
1000 pub skip_verify: bool,
1002 pub timeout_secs: u64,
1004}
1005
1006impl Default for VaultConfig {
1007 fn default() -> Self {
1008 Self {
1009 address: "http://127.0.0.1:8200".to_string(),
1010 namespace: None,
1011 auth_method: VaultAuthMethod::Token,
1012 token: None,
1013 role_id: None,
1014 secret_id: None,
1015 kubernetes_role: None,
1016 mount_path: "secret".to_string(),
1017 path_prefix: "mockforge".to_string(),
1018 ca_cert_path: None,
1019 skip_verify: false,
1020 timeout_secs: 30,
1021 }
1022 }
1023}
1024
1025#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
1027#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1028#[serde(rename_all = "lowercase")]
1029pub enum VaultAuthMethod {
1030 #[default]
1032 Token,
1033 AppRole,
1035 Kubernetes,
1037 AwsIam,
1039 GitHub,
1041 Ldap,
1043 Userpass,
1045}
1046
1047#[derive(Debug, Clone, Serialize, Deserialize)]
1049#[serde(default)]
1050#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1051pub struct AwsSecretsConfig {
1052 pub region: String,
1054 pub prefix: String,
1056 pub use_iam_role: bool,
1058 #[serde(skip_serializing_if = "Option::is_none")]
1060 pub access_key_id: Option<String>,
1061 #[serde(skip_serializing_if = "Option::is_none")]
1063 pub secret_access_key: Option<String>,
1064 #[serde(skip_serializing_if = "Option::is_none")]
1066 pub endpoint_url: Option<String>,
1067}
1068
1069impl Default for AwsSecretsConfig {
1070 fn default() -> Self {
1071 Self {
1072 region: "us-east-1".to_string(),
1073 prefix: "mockforge".to_string(),
1074 use_iam_role: true,
1075 access_key_id: None,
1076 secret_access_key: None,
1077 endpoint_url: None,
1078 }
1079 }
1080}
1081
1082#[derive(Debug, Clone, Serialize, Deserialize)]
1084#[serde(default)]
1085#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1086pub struct AzureKeyVaultConfig {
1087 pub vault_url: String,
1089 #[serde(skip_serializing_if = "Option::is_none")]
1091 pub tenant_id: Option<String>,
1092 #[serde(skip_serializing_if = "Option::is_none")]
1094 pub client_id: Option<String>,
1095 #[serde(skip_serializing_if = "Option::is_none")]
1097 pub client_secret: Option<String>,
1098 pub use_managed_identity: bool,
1100 pub prefix: String,
1102}
1103
1104impl Default for AzureKeyVaultConfig {
1105 fn default() -> Self {
1106 Self {
1107 vault_url: String::new(),
1108 tenant_id: None,
1109 client_id: None,
1110 client_secret: None,
1111 use_managed_identity: true,
1112 prefix: "mockforge".to_string(),
1113 }
1114 }
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1119#[serde(default)]
1120#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1121pub struct GcpSecretManagerConfig {
1122 pub project_id: String,
1124 pub prefix: String,
1126 #[serde(skip_serializing_if = "Option::is_none")]
1128 pub credentials_file: Option<String>,
1129 pub use_default_credentials: bool,
1131}
1132
1133impl Default for GcpSecretManagerConfig {
1134 fn default() -> Self {
1135 Self {
1136 project_id: String::new(),
1137 prefix: "mockforge".to_string(),
1138 credentials_file: None,
1139 use_default_credentials: true,
1140 }
1141 }
1142}
1143
1144#[derive(Debug, Clone, Serialize, Deserialize)]
1146#[serde(default)]
1147#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1148pub struct KubernetesSecretsConfig {
1149 pub namespace: String,
1151 pub prefix: String,
1153 #[serde(skip_serializing_if = "Option::is_none")]
1155 pub label_selector: Option<String>,
1156 pub in_cluster: bool,
1158 #[serde(skip_serializing_if = "Option::is_none")]
1160 pub kubeconfig_path: Option<String>,
1161}
1162
1163impl Default for KubernetesSecretsConfig {
1164 fn default() -> Self {
1165 Self {
1166 namespace: "default".to_string(),
1167 prefix: "mockforge".to_string(),
1168 label_selector: None,
1169 in_cluster: true,
1170 kubeconfig_path: None,
1171 }
1172 }
1173}
1174
1175#[derive(Debug, Clone, Serialize, Deserialize)]
1177#[serde(default)]
1178#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1179pub struct EncryptedFileConfig {
1180 pub file_path: String,
1182 pub algorithm: String,
1184 pub kdf: String,
1186 #[serde(skip_serializing_if = "Option::is_none")]
1188 pub master_key_env: Option<String>,
1189 #[serde(skip_serializing_if = "Option::is_none")]
1191 pub key_file: Option<String>,
1192}
1193
1194impl Default for EncryptedFileConfig {
1195 fn default() -> Self {
1196 Self {
1197 file_path: "secrets.enc".to_string(),
1198 algorithm: "aes-256-gcm".to_string(),
1199 kdf: "argon2id".to_string(),
1200 master_key_env: Some("MOCKFORGE_MASTER_KEY".to_string()),
1201 key_file: None,
1202 }
1203 }
1204}
1205
1206#[derive(Debug, Clone, Serialize, Deserialize)]
1208#[serde(default)]
1209#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1210pub struct PluginResourceConfig {
1211 pub enabled: bool,
1213 pub max_memory_per_plugin: usize,
1215 pub max_cpu_per_plugin: f64,
1217 pub max_execution_time_ms: u64,
1219 pub allow_network_access: bool,
1221 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1223 pub allowed_fs_paths: Vec<String>,
1224 pub max_concurrent_executions: usize,
1226 #[serde(skip_serializing_if = "Option::is_none")]
1228 pub cache_dir: Option<String>,
1229 pub debug_logging: bool,
1231 pub max_module_size: usize,
1233 pub max_table_elements: usize,
1235 pub max_stack_size: usize,
1237}
1238
1239impl Default for PluginResourceConfig {
1240 fn default() -> Self {
1241 Self {
1242 enabled: true,
1243 max_memory_per_plugin: 10 * 1024 * 1024, max_cpu_per_plugin: 0.5, max_execution_time_ms: 5000, allow_network_access: false,
1247 allowed_fs_paths: Vec::new(),
1248 max_concurrent_executions: 10,
1249 cache_dir: None,
1250 debug_logging: false,
1251 max_module_size: 5 * 1024 * 1024, max_table_elements: 1000,
1253 max_stack_size: 2 * 1024 * 1024, }
1255 }
1256}
1257
1258#[derive(Debug, Clone, Serialize, Deserialize)]
1260#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1261pub struct ProtocolConfig {
1262 pub enabled: bool,
1264}
1265
1266#[derive(Debug, Clone, Serialize, Deserialize)]
1268#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1269pub struct ProtocolsConfig {
1270 pub http: ProtocolConfig,
1272 pub graphql: ProtocolConfig,
1274 pub grpc: ProtocolConfig,
1276 pub websocket: ProtocolConfig,
1278 pub smtp: ProtocolConfig,
1280 pub mqtt: ProtocolConfig,
1282 pub ftp: ProtocolConfig,
1284 pub kafka: ProtocolConfig,
1286 pub rabbitmq: ProtocolConfig,
1288 pub amqp: ProtocolConfig,
1290 pub tcp: ProtocolConfig,
1292}
1293
1294impl Default for ProtocolsConfig {
1295 fn default() -> Self {
1296 Self {
1297 http: ProtocolConfig { enabled: true },
1298 graphql: ProtocolConfig { enabled: true },
1299 grpc: ProtocolConfig { enabled: true },
1300 websocket: ProtocolConfig { enabled: true },
1301 smtp: ProtocolConfig { enabled: false },
1302 mqtt: ProtocolConfig { enabled: true },
1303 ftp: ProtocolConfig { enabled: false },
1304 kafka: ProtocolConfig { enabled: false },
1305 rabbitmq: ProtocolConfig { enabled: false },
1306 amqp: ProtocolConfig { enabled: false },
1307 tcp: ProtocolConfig { enabled: false },
1308 }
1309 }
1310}
1311
1312#[derive(Debug, Clone, Serialize, Deserialize)]
1318#[serde(default)]
1319#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1320pub struct RealitySliderConfig {
1321 pub level: RealityLevel,
1323 pub enabled: bool,
1325}
1326
1327impl Default for RealitySliderConfig {
1328 fn default() -> Self {
1329 Self {
1330 level: RealityLevel::ModerateRealism,
1331 enabled: true,
1332 }
1333 }
1334}
1335
1336#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1338#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1339#[serde(default)]
1340pub struct ServerConfig {
1341 pub http: HttpConfig,
1343 pub websocket: WebSocketConfig,
1345 pub graphql: GraphQLConfig,
1347 pub grpc: GrpcConfig,
1349 pub mqtt: MqttConfig,
1351 pub smtp: SmtpConfig,
1353 pub ftp: FtpConfig,
1355 pub kafka: KafkaConfig,
1357 pub amqp: AmqpConfig,
1359 pub tcp: TcpConfig,
1361 pub admin: AdminConfig,
1363 pub chaining: ChainingConfig,
1365 pub core: CoreConfig,
1367 pub logging: LoggingConfig,
1369 pub data: DataConfig,
1371 #[serde(default)]
1373 pub mockai: MockAIConfig,
1374 pub observability: ObservabilityConfig,
1376 pub multi_tenant: crate::multi_tenant::MultiTenantConfig,
1378 #[serde(default)]
1380 pub routes: Vec<RouteConfig>,
1381 #[serde(default)]
1383 pub protocols: ProtocolsConfig,
1384 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1386 pub profiles: HashMap<String, ProfileConfig>,
1387 #[serde(default)]
1389 pub deceptive_deploy: DeceptiveDeployConfig,
1390 #[serde(default, skip_serializing_if = "Option::is_none")]
1392 pub behavioral_cloning: Option<BehavioralCloningConfig>,
1393 #[serde(default)]
1395 pub reality: RealitySliderConfig,
1396 #[serde(default)]
1398 pub reality_continuum: crate::reality_continuum::ContinuumConfig,
1399 #[serde(default)]
1401 pub security: SecurityConfig,
1402 #[serde(default)]
1404 pub drift_budget: crate::contract_drift::DriftBudgetConfig,
1405 #[serde(default)]
1407 pub incidents: IncidentConfig,
1408 #[serde(default)]
1410 pub pr_generation: crate::pr_generation::PRGenerationConfig,
1411 #[serde(default)]
1413 pub consumer_contracts: ConsumerContractsConfig,
1414 #[serde(default)]
1416 pub contracts: ContractsConfig,
1417 #[serde(default)]
1419 pub behavioral_economics: BehavioralEconomicsConfig,
1420 #[serde(default)]
1422 pub drift_learning: DriftLearningConfig,
1423 #[serde(default)]
1425 pub org_ai_controls: crate::ai_studio::org_controls::OrgAiControlsConfig,
1426 #[serde(default)]
1428 pub performance: PerformanceConfig,
1429 #[serde(default)]
1431 pub plugins: PluginResourceConfig,
1432 #[serde(default)]
1434 pub hot_reload: ConfigHotReloadConfig,
1435 #[serde(default)]
1437 pub secrets: SecretBackendConfig,
1438}
1439
1440#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1442#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1443#[serde(default)]
1444pub struct ProfileConfig {
1445 #[serde(skip_serializing_if = "Option::is_none")]
1447 pub http: Option<HttpConfig>,
1448 #[serde(skip_serializing_if = "Option::is_none")]
1450 pub websocket: Option<WebSocketConfig>,
1451 #[serde(skip_serializing_if = "Option::is_none")]
1453 pub graphql: Option<GraphQLConfig>,
1454 #[serde(skip_serializing_if = "Option::is_none")]
1456 pub grpc: Option<GrpcConfig>,
1457 #[serde(skip_serializing_if = "Option::is_none")]
1459 pub mqtt: Option<MqttConfig>,
1460 #[serde(skip_serializing_if = "Option::is_none")]
1462 pub smtp: Option<SmtpConfig>,
1463 #[serde(skip_serializing_if = "Option::is_none")]
1465 pub ftp: Option<FtpConfig>,
1466 #[serde(skip_serializing_if = "Option::is_none")]
1468 pub kafka: Option<KafkaConfig>,
1469 #[serde(skip_serializing_if = "Option::is_none")]
1471 pub amqp: Option<AmqpConfig>,
1472 #[serde(skip_serializing_if = "Option::is_none")]
1474 pub tcp: Option<TcpConfig>,
1475 #[serde(skip_serializing_if = "Option::is_none")]
1477 pub admin: Option<AdminConfig>,
1478 #[serde(skip_serializing_if = "Option::is_none")]
1480 pub chaining: Option<ChainingConfig>,
1481 #[serde(skip_serializing_if = "Option::is_none")]
1483 pub core: Option<CoreConfig>,
1484 #[serde(skip_serializing_if = "Option::is_none")]
1486 pub logging: Option<LoggingConfig>,
1487 #[serde(skip_serializing_if = "Option::is_none")]
1489 pub data: Option<DataConfig>,
1490 #[serde(skip_serializing_if = "Option::is_none")]
1492 pub mockai: Option<MockAIConfig>,
1493 #[serde(skip_serializing_if = "Option::is_none")]
1495 pub observability: Option<ObservabilityConfig>,
1496 #[serde(skip_serializing_if = "Option::is_none")]
1498 pub multi_tenant: Option<crate::multi_tenant::MultiTenantConfig>,
1499 #[serde(skip_serializing_if = "Option::is_none")]
1501 pub routes: Option<Vec<RouteConfig>>,
1502 #[serde(skip_serializing_if = "Option::is_none")]
1504 pub protocols: Option<ProtocolsConfig>,
1505 #[serde(skip_serializing_if = "Option::is_none")]
1507 pub deceptive_deploy: Option<DeceptiveDeployConfig>,
1508 #[serde(skip_serializing_if = "Option::is_none")]
1510 pub reality: Option<RealitySliderConfig>,
1511 #[serde(skip_serializing_if = "Option::is_none")]
1513 pub reality_continuum: Option<crate::reality_continuum::ContinuumConfig>,
1514 #[serde(skip_serializing_if = "Option::is_none")]
1516 pub security: Option<SecurityConfig>,
1517}
1518
1519#[derive(Debug, Clone, Serialize, Deserialize)]
1523#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1524pub struct HttpValidationConfig {
1525 pub mode: String,
1527}
1528
1529#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1531#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1532pub struct HttpCorsConfig {
1533 pub enabled: bool,
1535 #[serde(default)]
1537 pub allowed_origins: Vec<String>,
1538 #[serde(default)]
1540 pub allowed_methods: Vec<String>,
1541 #[serde(default)]
1543 pub allowed_headers: Vec<String>,
1544 #[serde(default = "default_cors_allow_credentials")]
1547 pub allow_credentials: bool,
1548}
1549
1550fn default_cors_allow_credentials() -> bool {
1551 false
1552}
1553
1554#[derive(Debug, Clone, Serialize, Deserialize)]
1556#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1557#[serde(default)]
1558pub struct HttpConfig {
1559 pub enabled: bool,
1561 pub port: u16,
1563 pub host: String,
1565 pub openapi_spec: Option<String>,
1567 pub cors: Option<HttpCorsConfig>,
1569 pub request_timeout_secs: u64,
1571 pub validation: Option<HttpValidationConfig>,
1573 pub aggregate_validation_errors: bool,
1575 pub validate_responses: bool,
1577 pub response_template_expand: bool,
1579 pub validation_status: Option<u16>,
1581 pub validation_overrides: std::collections::HashMap<String, String>,
1583 pub skip_admin_validation: bool,
1585 pub auth: Option<AuthConfig>,
1587 #[serde(skip_serializing_if = "Option::is_none")]
1589 pub tls: Option<HttpTlsConfig>,
1590}
1591
1592impl Default for HttpConfig {
1593 fn default() -> Self {
1594 Self {
1595 enabled: true,
1596 port: 3000,
1597 host: "0.0.0.0".to_string(),
1598 openapi_spec: None,
1599 cors: Some(HttpCorsConfig {
1600 enabled: true,
1601 allowed_origins: vec!["*".to_string()],
1602 allowed_methods: vec![
1603 "GET".to_string(),
1604 "POST".to_string(),
1605 "PUT".to_string(),
1606 "DELETE".to_string(),
1607 "PATCH".to_string(),
1608 "OPTIONS".to_string(),
1609 ],
1610 allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
1611 allow_credentials: false, }),
1613 request_timeout_secs: 30,
1614 validation: Some(HttpValidationConfig {
1615 mode: "enforce".to_string(),
1616 }),
1617 aggregate_validation_errors: true,
1618 validate_responses: false,
1619 response_template_expand: false,
1620 validation_status: None,
1621 validation_overrides: std::collections::HashMap::new(),
1622 skip_admin_validation: true,
1623 auth: None,
1624 tls: None,
1625 }
1626 }
1627}
1628
1629#[derive(Debug, Clone, Serialize, Deserialize)]
1631#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1632pub struct HttpTlsConfig {
1633 pub enabled: bool,
1635 pub cert_file: String,
1637 pub key_file: String,
1639 #[serde(skip_serializing_if = "Option::is_none")]
1641 pub ca_file: Option<String>,
1642 #[serde(default = "default_tls_min_version")]
1644 pub min_version: String,
1645 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1647 pub cipher_suites: Vec<String>,
1648 #[serde(default)]
1650 pub require_client_cert: bool,
1651 #[serde(default = "default_mtls_mode")]
1653 pub mtls_mode: String,
1654}
1655
1656fn default_mtls_mode() -> String {
1657 "off".to_string()
1658}
1659
1660fn default_tls_min_version() -> String {
1661 "1.2".to_string()
1662}
1663
1664impl Default for HttpTlsConfig {
1665 fn default() -> Self {
1666 Self {
1667 enabled: true,
1668 cert_file: String::new(),
1669 key_file: String::new(),
1670 ca_file: None,
1671 min_version: "1.2".to_string(),
1672 cipher_suites: Vec::new(),
1673 require_client_cert: false,
1674 mtls_mode: "off".to_string(),
1675 }
1676 }
1677}
1678
1679#[derive(Debug, Clone, Serialize, Deserialize)]
1681#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1682#[serde(default)]
1683pub struct WebSocketConfig {
1684 pub enabled: bool,
1686 pub port: u16,
1688 pub host: String,
1690 pub replay_file: Option<String>,
1692 pub connection_timeout_secs: u64,
1694}
1695
1696impl Default for WebSocketConfig {
1697 fn default() -> Self {
1698 Self {
1699 enabled: true,
1700 port: 3001,
1701 host: "0.0.0.0".to_string(),
1702 replay_file: None,
1703 connection_timeout_secs: 300,
1704 }
1705 }
1706}
1707
1708#[derive(Debug, Clone, Serialize, Deserialize)]
1710#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1711#[serde(default)]
1712pub struct GrpcConfig {
1713 pub enabled: bool,
1715 pub port: u16,
1717 pub host: String,
1719 pub proto_dir: Option<String>,
1721 pub tls: Option<TlsConfig>,
1723}
1724
1725impl Default for GrpcConfig {
1726 fn default() -> Self {
1727 Self {
1728 enabled: true,
1729 port: 50051,
1730 host: "0.0.0.0".to_string(),
1731 proto_dir: None,
1732 tls: None,
1733 }
1734 }
1735}
1736
1737#[derive(Debug, Clone, Serialize, Deserialize)]
1739#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1740#[serde(default)]
1741pub struct GraphQLConfig {
1742 pub enabled: bool,
1744 pub port: u16,
1746 pub host: String,
1748 pub schema_path: Option<String>,
1750 pub handlers_dir: Option<String>,
1752 pub playground_enabled: bool,
1754 pub upstream_url: Option<String>,
1756 pub introspection_enabled: bool,
1758}
1759
1760impl Default for GraphQLConfig {
1761 fn default() -> Self {
1762 Self {
1763 enabled: true,
1764 port: 4000,
1765 host: "0.0.0.0".to_string(),
1766 schema_path: None,
1767 handlers_dir: None,
1768 playground_enabled: true,
1769 upstream_url: None,
1770 introspection_enabled: true,
1771 }
1772 }
1773}
1774
1775#[derive(Debug, Clone, Serialize, Deserialize)]
1777#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1778pub struct TlsConfig {
1779 pub cert_path: String,
1781 pub key_path: String,
1783}
1784
1785#[derive(Debug, Clone, Serialize, Deserialize)]
1787#[serde(default)]
1788#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1789pub struct MqttConfig {
1790 pub enabled: bool,
1792 pub port: u16,
1794 pub host: String,
1796 pub max_connections: usize,
1798 pub max_packet_size: usize,
1800 pub keep_alive_secs: u16,
1802 pub fixtures_dir: Option<std::path::PathBuf>,
1804 pub enable_retained_messages: bool,
1806 pub max_retained_messages: usize,
1808}
1809
1810impl Default for MqttConfig {
1811 fn default() -> Self {
1812 Self {
1813 enabled: false,
1814 port: 1883,
1815 host: "0.0.0.0".to_string(),
1816 max_connections: 1000,
1817 max_packet_size: 268435456, keep_alive_secs: 60,
1819 fixtures_dir: None,
1820 enable_retained_messages: true,
1821 max_retained_messages: 10000,
1822 }
1823 }
1824}
1825
1826#[derive(Debug, Clone, Serialize, Deserialize)]
1828#[serde(default)]
1829#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1830pub struct SmtpConfig {
1831 pub enabled: bool,
1833 pub port: u16,
1835 pub host: String,
1837 pub hostname: String,
1839 pub fixtures_dir: Option<std::path::PathBuf>,
1841 pub timeout_secs: u64,
1843 pub max_connections: usize,
1845 pub enable_mailbox: bool,
1847 pub max_mailbox_messages: usize,
1849 pub enable_starttls: bool,
1851 pub tls_cert_path: Option<std::path::PathBuf>,
1853 pub tls_key_path: Option<std::path::PathBuf>,
1855}
1856
1857impl Default for SmtpConfig {
1858 fn default() -> Self {
1859 Self {
1860 enabled: false,
1861 port: 1025,
1862 host: "0.0.0.0".to_string(),
1863 hostname: "mockforge-smtp".to_string(),
1864 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
1865 timeout_secs: 300,
1866 max_connections: 10,
1867 enable_mailbox: true,
1868 max_mailbox_messages: 1000,
1869 enable_starttls: false,
1870 tls_cert_path: None,
1871 tls_key_path: None,
1872 }
1873 }
1874}
1875
1876#[derive(Debug, Clone, Serialize, Deserialize)]
1878#[serde(default)]
1879#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1880pub struct FtpConfig {
1881 pub enabled: bool,
1883 pub port: u16,
1885 pub host: String,
1887 pub passive_ports: (u16, u16),
1889 pub max_connections: usize,
1891 pub timeout_secs: u64,
1893 pub allow_anonymous: bool,
1895 pub fixtures_dir: Option<std::path::PathBuf>,
1897 pub virtual_root: std::path::PathBuf,
1899}
1900
1901impl Default for FtpConfig {
1902 fn default() -> Self {
1903 Self {
1904 enabled: false,
1905 port: 2121,
1906 host: "0.0.0.0".to_string(),
1907 passive_ports: (50000, 51000),
1908 max_connections: 100,
1909 timeout_secs: 300,
1910 allow_anonymous: true,
1911 fixtures_dir: None,
1912 virtual_root: std::path::PathBuf::from("/mockforge"),
1913 }
1914 }
1915}
1916
1917#[derive(Debug, Clone, Serialize, Deserialize)]
1919#[serde(default)]
1920#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1921pub struct KafkaConfig {
1922 pub enabled: bool,
1924 pub port: u16,
1926 pub host: String,
1928 pub broker_id: i32,
1930 pub max_connections: usize,
1932 pub log_retention_ms: i64,
1934 pub log_segment_bytes: i64,
1936 pub fixtures_dir: Option<std::path::PathBuf>,
1938 pub auto_create_topics: bool,
1940 pub default_partitions: i32,
1942 pub default_replication_factor: i16,
1944}
1945
1946impl Default for KafkaConfig {
1947 fn default() -> Self {
1948 Self {
1949 enabled: false,
1950 port: 9092, host: "0.0.0.0".to_string(),
1952 broker_id: 1,
1953 max_connections: 1000,
1954 log_retention_ms: 604800000, log_segment_bytes: 1073741824, fixtures_dir: None,
1957 auto_create_topics: true,
1958 default_partitions: 3,
1959 default_replication_factor: 1,
1960 }
1961 }
1962}
1963
1964#[derive(Debug, Clone, Serialize, Deserialize)]
1966#[serde(default)]
1967#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1968pub struct AmqpConfig {
1969 pub enabled: bool,
1971 pub port: u16,
1973 pub host: String,
1975 pub max_connections: usize,
1977 pub max_channels_per_connection: u16,
1979 pub frame_max: u32,
1981 pub heartbeat_interval: u16,
1983 pub fixtures_dir: Option<std::path::PathBuf>,
1985 pub virtual_hosts: Vec<String>,
1987 pub tls_enabled: bool,
1989 pub tls_port: u16,
1991 pub tls_cert_path: Option<std::path::PathBuf>,
1993 pub tls_key_path: Option<std::path::PathBuf>,
1995 pub tls_ca_path: Option<std::path::PathBuf>,
1997 pub tls_client_auth: bool,
1999}
2000
2001impl Default for AmqpConfig {
2002 fn default() -> Self {
2003 Self {
2004 enabled: false,
2005 port: 5672, host: "0.0.0.0".to_string(),
2007 max_connections: 1000,
2008 max_channels_per_connection: 100,
2009 frame_max: 131072, heartbeat_interval: 60,
2011 fixtures_dir: None,
2012 virtual_hosts: vec!["/".to_string()],
2013 tls_enabled: false,
2014 tls_port: 5671, tls_cert_path: None,
2016 tls_key_path: None,
2017 tls_ca_path: None,
2018 tls_client_auth: false,
2019 }
2020 }
2021}
2022
2023#[derive(Debug, Clone, Serialize, Deserialize)]
2025#[serde(default)]
2026#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2027pub struct TcpConfig {
2028 pub enabled: bool,
2030 pub port: u16,
2032 pub host: String,
2034 pub max_connections: usize,
2036 pub timeout_secs: u64,
2038 pub fixtures_dir: Option<std::path::PathBuf>,
2040 pub echo_mode: bool,
2042 pub enable_tls: bool,
2044 pub tls_cert_path: Option<std::path::PathBuf>,
2046 pub tls_key_path: Option<std::path::PathBuf>,
2048}
2049
2050impl Default for TcpConfig {
2051 fn default() -> Self {
2052 Self {
2053 enabled: false,
2054 port: 9999,
2055 host: "0.0.0.0".to_string(),
2056 max_connections: 1000,
2057 timeout_secs: 300,
2058 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
2059 echo_mode: true,
2060 enable_tls: false,
2061 tls_cert_path: None,
2062 tls_key_path: None,
2063 }
2064 }
2065}
2066
2067#[derive(Debug, Clone, Serialize, Deserialize)]
2069#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2070#[serde(default)]
2071pub struct AdminConfig {
2072 pub enabled: bool,
2074 pub port: u16,
2076 pub host: String,
2078 pub auth_required: bool,
2080 pub username: Option<String>,
2082 pub password: Option<String>,
2084 pub mount_path: Option<String>,
2086 pub api_enabled: bool,
2088 pub prometheus_url: String,
2090}
2091
2092impl Default for AdminConfig {
2093 fn default() -> Self {
2094 let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
2097 || std::env::var("container").is_ok()
2098 || std::path::Path::new("/.dockerenv").exists()
2099 {
2100 "0.0.0.0".to_string()
2101 } else {
2102 "127.0.0.1".to_string()
2103 };
2104
2105 Self {
2106 enabled: false,
2107 port: 9080,
2108 host: default_host,
2109 auth_required: false,
2110 username: None,
2111 password: None,
2112 mount_path: None,
2113 api_enabled: true,
2114 prometheus_url: "http://localhost:9090".to_string(),
2115 }
2116 }
2117}
2118
2119#[derive(Debug, Clone, Serialize, Deserialize)]
2121#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2122#[serde(default)]
2123pub struct LoggingConfig {
2124 pub level: String,
2126 pub json_format: bool,
2128 pub file_path: Option<String>,
2130 pub max_file_size_mb: u64,
2132 pub max_files: u32,
2134}
2135
2136impl Default for LoggingConfig {
2137 fn default() -> Self {
2138 Self {
2139 level: "info".to_string(),
2140 json_format: false,
2141 file_path: None,
2142 max_file_size_mb: 10,
2143 max_files: 5,
2144 }
2145 }
2146}
2147
2148#[derive(Debug, Clone, Serialize, Deserialize)]
2150#[serde(default, rename_all = "camelCase")]
2151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2152pub struct ChainingConfig {
2153 pub enabled: bool,
2155 pub max_chain_length: usize,
2157 pub global_timeout_secs: u64,
2159 pub enable_parallel_execution: bool,
2161}
2162
2163impl Default for ChainingConfig {
2164 fn default() -> Self {
2165 Self {
2166 enabled: false,
2167 max_chain_length: 20,
2168 global_timeout_secs: 300,
2169 enable_parallel_execution: false,
2170 }
2171 }
2172}
2173
2174#[derive(Debug, Clone, Serialize, Deserialize)]
2176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2177#[serde(default)]
2178pub struct DataConfig {
2179 pub default_rows: usize,
2181 pub default_format: String,
2183 pub locale: String,
2185 pub templates: HashMap<String, String>,
2187 pub rag: RagConfig,
2189 #[serde(skip_serializing_if = "Option::is_none")]
2191 pub persona_domain: Option<String>,
2192 #[serde(default = "default_false")]
2194 pub persona_consistency_enabled: bool,
2195 #[serde(skip_serializing_if = "Option::is_none")]
2197 pub persona_registry: Option<PersonaRegistryConfig>,
2198}
2199
2200impl Default for DataConfig {
2201 fn default() -> Self {
2202 Self {
2203 default_rows: 100,
2204 default_format: "json".to_string(),
2205 locale: "en".to_string(),
2206 templates: HashMap::new(),
2207 rag: RagConfig::default(),
2208 persona_domain: None,
2209 persona_consistency_enabled: false,
2210 persona_registry: None,
2211 }
2212 }
2213}
2214
2215#[derive(Debug, Clone, Serialize, Deserialize)]
2217#[serde(default)]
2218#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2219pub struct RagConfig {
2220 pub enabled: bool,
2222 #[serde(default)]
2224 pub provider: String,
2225 pub api_endpoint: Option<String>,
2227 pub api_key: Option<String>,
2229 pub model: Option<String>,
2231 #[serde(default = "default_max_tokens")]
2233 pub max_tokens: usize,
2234 #[serde(default = "default_temperature")]
2236 pub temperature: f64,
2237 pub context_window: usize,
2239 #[serde(default = "default_true")]
2241 pub caching: bool,
2242 #[serde(default = "default_cache_ttl")]
2244 pub cache_ttl_secs: u64,
2245 #[serde(default = "default_timeout")]
2247 pub timeout_secs: u64,
2248 #[serde(default = "default_max_retries")]
2250 pub max_retries: usize,
2251}
2252
2253fn default_max_tokens() -> usize {
2254 1024
2255}
2256
2257fn default_temperature() -> f64 {
2258 0.7
2259}
2260
2261fn default_true() -> bool {
2262 true
2263}
2264
2265fn default_cache_ttl() -> u64 {
2266 3600
2267}
2268
2269fn default_timeout() -> u64 {
2270 30
2271}
2272
2273fn default_max_retries() -> usize {
2274 3
2275}
2276
2277fn default_false() -> bool {
2278 false
2279}
2280
2281impl Default for RagConfig {
2282 fn default() -> Self {
2283 Self {
2284 enabled: false,
2285 provider: "openai".to_string(),
2286 api_endpoint: None,
2287 api_key: None,
2288 model: Some("gpt-3.5-turbo".to_string()),
2289 max_tokens: default_max_tokens(),
2290 temperature: default_temperature(),
2291 context_window: 4000,
2292 caching: default_true(),
2293 cache_ttl_secs: default_cache_ttl(),
2294 timeout_secs: default_timeout(),
2295 max_retries: default_max_retries(),
2296 }
2297 }
2298}
2299
2300#[derive(Debug, Clone, Serialize, Deserialize)]
2302#[serde(default)]
2303#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2304#[derive(Default)]
2305pub struct PersonaRegistryConfig {
2306 #[serde(default = "default_false")]
2308 pub persistent: bool,
2309 #[serde(skip_serializing_if = "Option::is_none")]
2311 pub storage_path: Option<String>,
2312 #[serde(default)]
2314 pub default_traits: HashMap<String, String>,
2315}
2316
2317#[derive(Debug, Clone, Serialize, Deserialize)]
2319#[serde(default)]
2320#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2321pub struct MockAIConfig {
2322 pub enabled: bool,
2324 pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
2326 pub auto_learn: bool,
2328 pub mutation_detection: bool,
2330 pub ai_validation_errors: bool,
2332 pub intelligent_pagination: bool,
2334 #[serde(default)]
2336 pub enabled_endpoints: Vec<String>,
2337}
2338
2339impl Default for MockAIConfig {
2340 fn default() -> Self {
2341 Self {
2342 enabled: false,
2343 intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
2344 auto_learn: true,
2345 mutation_detection: true,
2346 ai_validation_errors: true,
2347 intelligent_pagination: true,
2348 enabled_endpoints: Vec::new(),
2349 }
2350 }
2351}
2352
2353#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2355#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2356#[serde(default)]
2357pub struct ObservabilityConfig {
2358 pub prometheus: PrometheusConfig,
2360 pub opentelemetry: Option<OpenTelemetryConfig>,
2362 pub recorder: Option<RecorderConfig>,
2364 pub chaos: Option<ChaosEngConfig>,
2366}
2367
2368#[derive(Debug, Clone, Serialize, Deserialize)]
2370#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2371#[serde(default)]
2372#[derive(Default)]
2373pub struct SecurityConfig {
2374 pub monitoring: SecurityMonitoringConfig,
2376}
2377
2378#[derive(Debug, Clone, Serialize, Deserialize)]
2380#[serde(default)]
2381#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2382#[derive(Default)]
2383pub struct SecurityMonitoringConfig {
2384 pub siem: crate::security::siem::SiemConfig,
2386 pub access_review: crate::security::access_review::AccessReviewConfig,
2388 pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
2390 pub change_management: crate::security::change_management::ChangeManagementConfig,
2392 pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
2394 pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
2396}
2397
2398#[derive(Debug, Clone, Serialize, Deserialize)]
2400#[serde(default)]
2401#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2402pub struct PrometheusConfig {
2403 pub enabled: bool,
2405 pub port: u16,
2407 pub host: String,
2409 pub path: String,
2411}
2412
2413impl Default for PrometheusConfig {
2414 fn default() -> Self {
2415 Self {
2416 enabled: true,
2417 port: 9090,
2418 host: "0.0.0.0".to_string(),
2419 path: "/metrics".to_string(),
2420 }
2421 }
2422}
2423
2424#[derive(Debug, Clone, Serialize, Deserialize)]
2426#[serde(default)]
2427#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2428pub struct OpenTelemetryConfig {
2429 pub enabled: bool,
2431 pub service_name: String,
2433 pub environment: String,
2435 pub jaeger_endpoint: String,
2437 pub otlp_endpoint: Option<String>,
2439 pub protocol: String,
2441 pub sampling_rate: f64,
2443}
2444
2445impl Default for OpenTelemetryConfig {
2446 fn default() -> Self {
2447 Self {
2448 enabled: false,
2449 service_name: "mockforge".to_string(),
2450 environment: "development".to_string(),
2451 jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
2452 otlp_endpoint: Some("http://localhost:4317".to_string()),
2453 protocol: "grpc".to_string(),
2454 sampling_rate: 1.0,
2455 }
2456 }
2457}
2458
2459#[derive(Debug, Clone, Serialize, Deserialize)]
2461#[serde(default)]
2462#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2463pub struct RecorderConfig {
2464 pub enabled: bool,
2466 pub database_path: String,
2468 pub api_enabled: bool,
2470 pub api_port: Option<u16>,
2472 pub max_requests: i64,
2474 pub retention_days: i64,
2476 pub record_http: bool,
2478 pub record_grpc: bool,
2480 pub record_websocket: bool,
2482 pub record_graphql: bool,
2484 #[serde(default = "default_true")]
2487 pub record_proxy: bool,
2488}
2489
2490impl Default for RecorderConfig {
2491 fn default() -> Self {
2492 Self {
2493 enabled: false,
2494 database_path: "./mockforge-recordings.db".to_string(),
2495 api_enabled: true,
2496 api_port: None,
2497 max_requests: 10000,
2498 retention_days: 7,
2499 record_http: true,
2500 record_grpc: true,
2501 record_websocket: true,
2502 record_graphql: true,
2503 record_proxy: true,
2504 }
2505 }
2506}
2507
2508#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2510#[serde(default)]
2511#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2512pub struct ChaosEngConfig {
2513 pub enabled: bool,
2515 pub latency: Option<LatencyInjectionConfig>,
2517 pub fault_injection: Option<FaultConfig>,
2519 pub rate_limit: Option<RateLimitingConfig>,
2521 pub traffic_shaping: Option<NetworkShapingConfig>,
2523 pub scenario: Option<String>,
2525}
2526
2527#[derive(Debug, Clone, Serialize, Deserialize)]
2529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2530pub struct LatencyInjectionConfig {
2531 pub enabled: bool,
2533 pub fixed_delay_ms: Option<u64>,
2535 pub random_delay_range_ms: Option<(u64, u64)>,
2537 pub jitter_percent: f64,
2539 pub probability: f64,
2541}
2542
2543#[derive(Debug, Clone, Serialize, Deserialize)]
2545#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2546pub struct FaultConfig {
2547 pub enabled: bool,
2549 pub http_errors: Vec<u16>,
2551 pub http_error_probability: f64,
2553 pub connection_errors: bool,
2555 pub connection_error_probability: f64,
2557 pub timeout_errors: bool,
2559 pub timeout_ms: u64,
2561 pub timeout_probability: f64,
2563}
2564
2565#[derive(Debug, Clone, Serialize, Deserialize)]
2567#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2568pub struct RateLimitingConfig {
2569 pub enabled: bool,
2571 pub requests_per_second: u32,
2573 pub burst_size: u32,
2575 pub per_ip: bool,
2577 pub per_endpoint: bool,
2579}
2580
2581#[derive(Debug, Clone, Serialize, Deserialize)]
2583#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2584pub struct NetworkShapingConfig {
2585 pub enabled: bool,
2587 pub bandwidth_limit_bps: u64,
2589 pub packet_loss_percent: f64,
2591 pub max_connections: u32,
2593}
2594
2595pub async fn load_config<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2597 let content = fs::read_to_string(&path)
2598 .await
2599 .map_err(|e| Error::generic(format!("Failed to read config file: {}", e)))?;
2600
2601 let config: ServerConfig = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2603 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2604 {
2605 serde_yaml::from_str(&content).map_err(|e| {
2606 let error_msg = e.to_string();
2608 let mut full_msg = format!("Failed to parse YAML config: {}", error_msg);
2609
2610 if error_msg.contains("missing field") {
2612 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
2613 full_msg.push_str(
2614 "\n Omit fields you don't need - MockForge will use sensible defaults.",
2615 );
2616 full_msg.push_str("\n See config.template.yaml for all available options.");
2617 } else if error_msg.contains("unknown field") {
2618 full_msg.push_str("\n\n💡 Check for typos in field names.");
2619 full_msg.push_str("\n See config.template.yaml for valid field names.");
2620 }
2621
2622 Error::generic(full_msg)
2623 })?
2624 } else {
2625 serde_json::from_str(&content).map_err(|e| {
2626 let error_msg = e.to_string();
2628 let mut full_msg = format!("Failed to parse JSON config: {}", error_msg);
2629
2630 if error_msg.contains("missing field") {
2632 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
2633 full_msg.push_str(
2634 "\n Omit fields you don't need - MockForge will use sensible defaults.",
2635 );
2636 full_msg.push_str("\n See config.template.yaml for all available options.");
2637 } else if error_msg.contains("unknown field") {
2638 full_msg.push_str("\n\n💡 Check for typos in field names.");
2639 full_msg.push_str("\n See config.template.yaml for valid field names.");
2640 }
2641
2642 Error::generic(full_msg)
2643 })?
2644 };
2645
2646 Ok(config)
2647}
2648
2649pub async fn save_config<P: AsRef<Path>>(path: P, config: &ServerConfig) -> Result<()> {
2651 let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2652 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2653 {
2654 serde_yaml::to_string(config)
2655 .map_err(|e| Error::generic(format!("Failed to serialize config to YAML: {}", e)))?
2656 } else {
2657 serde_json::to_string_pretty(config)
2658 .map_err(|e| Error::generic(format!("Failed to serialize config to JSON: {}", e)))?
2659 };
2660
2661 fs::write(path, content)
2662 .await
2663 .map_err(|e| Error::generic(format!("Failed to write config file: {}", e)))?;
2664
2665 Ok(())
2666}
2667
2668pub async fn load_config_with_fallback<P: AsRef<Path>>(path: P) -> ServerConfig {
2670 match load_config(&path).await {
2671 Ok(config) => {
2672 tracing::info!("Loaded configuration from {:?}", path.as_ref());
2673 config
2674 }
2675 Err(e) => {
2676 tracing::warn!(
2677 "Failed to load config from {:?}: {}. Using defaults.",
2678 path.as_ref(),
2679 e
2680 );
2681 ServerConfig::default()
2682 }
2683 }
2684}
2685
2686pub async fn create_default_config<P: AsRef<Path>>(path: P) -> Result<()> {
2688 let config = ServerConfig::default();
2689 save_config(path, &config).await?;
2690 Ok(())
2691}
2692
2693pub fn apply_env_overrides(mut config: ServerConfig) -> ServerConfig {
2695 if let Ok(port) = std::env::var("MOCKFORGE_HTTP_PORT") {
2697 if let Ok(port_num) = port.parse() {
2698 config.http.port = port_num;
2699 }
2700 }
2701
2702 if let Ok(host) = std::env::var("MOCKFORGE_HTTP_HOST") {
2703 config.http.host = host;
2704 }
2705
2706 if let Ok(port) = std::env::var("MOCKFORGE_WS_PORT") {
2708 if let Ok(port_num) = port.parse() {
2709 config.websocket.port = port_num;
2710 }
2711 }
2712
2713 if let Ok(port) = std::env::var("MOCKFORGE_GRPC_PORT") {
2715 if let Ok(port_num) = port.parse() {
2716 config.grpc.port = port_num;
2717 }
2718 }
2719
2720 if let Ok(port) = std::env::var("MOCKFORGE_SMTP_PORT") {
2722 if let Ok(port_num) = port.parse() {
2723 config.smtp.port = port_num;
2724 }
2725 }
2726
2727 if let Ok(host) = std::env::var("MOCKFORGE_SMTP_HOST") {
2728 config.smtp.host = host;
2729 }
2730
2731 if let Ok(enabled) = std::env::var("MOCKFORGE_SMTP_ENABLED") {
2732 config.smtp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2733 }
2734
2735 if let Ok(hostname) = std::env::var("MOCKFORGE_SMTP_HOSTNAME") {
2736 config.smtp.hostname = hostname;
2737 }
2738
2739 if let Ok(port) = std::env::var("MOCKFORGE_TCP_PORT") {
2741 if let Ok(port_num) = port.parse() {
2742 config.tcp.port = port_num;
2743 }
2744 }
2745
2746 if let Ok(host) = std::env::var("MOCKFORGE_TCP_HOST") {
2747 config.tcp.host = host;
2748 }
2749
2750 if let Ok(enabled) = std::env::var("MOCKFORGE_TCP_ENABLED") {
2751 config.tcp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2752 }
2753
2754 if let Ok(port) = std::env::var("MOCKFORGE_ADMIN_PORT") {
2756 if let Ok(port_num) = port.parse() {
2757 config.admin.port = port_num;
2758 }
2759 }
2760
2761 if std::env::var("MOCKFORGE_ADMIN_ENABLED").unwrap_or_default() == "true" {
2762 config.admin.enabled = true;
2763 }
2764
2765 if let Ok(host) = std::env::var("MOCKFORGE_ADMIN_HOST") {
2767 config.admin.host = host;
2768 }
2769
2770 if let Ok(mount_path) = std::env::var("MOCKFORGE_ADMIN_MOUNT_PATH") {
2771 if !mount_path.trim().is_empty() {
2772 config.admin.mount_path = Some(mount_path);
2773 }
2774 }
2775
2776 if let Ok(api_enabled) = std::env::var("MOCKFORGE_ADMIN_API_ENABLED") {
2777 let on = api_enabled == "1" || api_enabled.eq_ignore_ascii_case("true");
2778 config.admin.api_enabled = on;
2779 }
2780
2781 if let Ok(prometheus_url) = std::env::var("PROMETHEUS_URL") {
2782 config.admin.prometheus_url = prometheus_url;
2783 }
2784
2785 if let Ok(latency_enabled) = std::env::var("MOCKFORGE_LATENCY_ENABLED") {
2787 let enabled = latency_enabled == "1" || latency_enabled.eq_ignore_ascii_case("true");
2788 config.core.latency_enabled = enabled;
2789 }
2790
2791 if let Ok(failures_enabled) = std::env::var("MOCKFORGE_FAILURES_ENABLED") {
2792 let enabled = failures_enabled == "1" || failures_enabled.eq_ignore_ascii_case("true");
2793 config.core.failures_enabled = enabled;
2794 }
2795
2796 if let Ok(overrides_enabled) = std::env::var("MOCKFORGE_OVERRIDES_ENABLED") {
2797 let enabled = overrides_enabled == "1" || overrides_enabled.eq_ignore_ascii_case("true");
2798 config.core.overrides_enabled = enabled;
2799 }
2800
2801 if let Ok(traffic_shaping_enabled) = std::env::var("MOCKFORGE_TRAFFIC_SHAPING_ENABLED") {
2802 let enabled =
2803 traffic_shaping_enabled == "1" || traffic_shaping_enabled.eq_ignore_ascii_case("true");
2804 config.core.traffic_shaping_enabled = enabled;
2805 }
2806
2807 if let Ok(bandwidth_enabled) = std::env::var("MOCKFORGE_BANDWIDTH_ENABLED") {
2809 let enabled = bandwidth_enabled == "1" || bandwidth_enabled.eq_ignore_ascii_case("true");
2810 config.core.traffic_shaping.bandwidth.enabled = enabled;
2811 }
2812
2813 if let Ok(max_bytes_per_sec) = std::env::var("MOCKFORGE_BANDWIDTH_MAX_BYTES_PER_SEC") {
2814 if let Ok(bytes) = max_bytes_per_sec.parse() {
2815 config.core.traffic_shaping.bandwidth.max_bytes_per_sec = bytes;
2816 config.core.traffic_shaping.bandwidth.enabled = true;
2817 }
2818 }
2819
2820 if let Ok(burst_capacity) = std::env::var("MOCKFORGE_BANDWIDTH_BURST_CAPACITY_BYTES") {
2821 if let Ok(bytes) = burst_capacity.parse() {
2822 config.core.traffic_shaping.bandwidth.burst_capacity_bytes = bytes;
2823 }
2824 }
2825
2826 if let Ok(burst_loss_enabled) = std::env::var("MOCKFORGE_BURST_LOSS_ENABLED") {
2827 let enabled = burst_loss_enabled == "1" || burst_loss_enabled.eq_ignore_ascii_case("true");
2828 config.core.traffic_shaping.burst_loss.enabled = enabled;
2829 }
2830
2831 if let Ok(burst_probability) = std::env::var("MOCKFORGE_BURST_LOSS_PROBABILITY") {
2832 if let Ok(prob) = burst_probability.parse::<f64>() {
2833 config.core.traffic_shaping.burst_loss.burst_probability = prob.clamp(0.0, 1.0);
2834 config.core.traffic_shaping.burst_loss.enabled = true;
2835 }
2836 }
2837
2838 if let Ok(burst_duration) = std::env::var("MOCKFORGE_BURST_LOSS_DURATION_MS") {
2839 if let Ok(ms) = burst_duration.parse() {
2840 config.core.traffic_shaping.burst_loss.burst_duration_ms = ms;
2841 }
2842 }
2843
2844 if let Ok(loss_rate) = std::env::var("MOCKFORGE_BURST_LOSS_RATE") {
2845 if let Ok(rate) = loss_rate.parse::<f64>() {
2846 config.core.traffic_shaping.burst_loss.loss_rate_during_burst = rate.clamp(0.0, 1.0);
2847 }
2848 }
2849
2850 if let Ok(recovery_time) = std::env::var("MOCKFORGE_BURST_LOSS_RECOVERY_MS") {
2851 if let Ok(ms) = recovery_time.parse() {
2852 config.core.traffic_shaping.burst_loss.recovery_time_ms = ms;
2853 }
2854 }
2855
2856 if let Ok(level) = std::env::var("MOCKFORGE_LOG_LEVEL") {
2858 config.logging.level = level;
2859 }
2860
2861 config
2862}
2863
2864pub fn validate_config(config: &ServerConfig) -> Result<()> {
2866 if config.http.port == 0 {
2868 return Err(Error::generic("HTTP port cannot be 0"));
2869 }
2870 if config.websocket.port == 0 {
2871 return Err(Error::generic("WebSocket port cannot be 0"));
2872 }
2873 if config.grpc.port == 0 {
2874 return Err(Error::generic("gRPC port cannot be 0"));
2875 }
2876 if config.admin.port == 0 {
2877 return Err(Error::generic("Admin port cannot be 0"));
2878 }
2879
2880 let ports = [
2882 ("HTTP", config.http.port),
2883 ("WebSocket", config.websocket.port),
2884 ("gRPC", config.grpc.port),
2885 ("Admin", config.admin.port),
2886 ];
2887
2888 for i in 0..ports.len() {
2889 for j in (i + 1)..ports.len() {
2890 if ports[i].1 == ports[j].1 {
2891 return Err(Error::generic(format!(
2892 "Port conflict: {} and {} both use port {}",
2893 ports[i].0, ports[j].0, ports[i].1
2894 )));
2895 }
2896 }
2897 }
2898
2899 let valid_levels = ["trace", "debug", "info", "warn", "error"];
2901 if !valid_levels.contains(&config.logging.level.as_str()) {
2902 return Err(Error::generic(format!(
2903 "Invalid log level: {}. Valid levels: {}",
2904 config.logging.level,
2905 valid_levels.join(", ")
2906 )));
2907 }
2908
2909 Ok(())
2910}
2911
2912pub fn apply_profile(mut base: ServerConfig, profile: ProfileConfig) -> ServerConfig {
2914 macro_rules! merge_field {
2916 ($field:ident) => {
2917 if let Some(override_val) = profile.$field {
2918 base.$field = override_val;
2919 }
2920 };
2921 }
2922
2923 merge_field!(http);
2924 merge_field!(websocket);
2925 merge_field!(graphql);
2926 merge_field!(grpc);
2927 merge_field!(mqtt);
2928 merge_field!(smtp);
2929 merge_field!(ftp);
2930 merge_field!(kafka);
2931 merge_field!(amqp);
2932 merge_field!(tcp);
2933 merge_field!(admin);
2934 merge_field!(chaining);
2935 merge_field!(core);
2936 merge_field!(logging);
2937 merge_field!(data);
2938 merge_field!(mockai);
2939 merge_field!(observability);
2940 merge_field!(multi_tenant);
2941 merge_field!(routes);
2942 merge_field!(protocols);
2943
2944 base
2945}
2946
2947pub async fn load_config_with_profile<P: AsRef<Path>>(
2949 path: P,
2950 profile_name: Option<&str>,
2951) -> Result<ServerConfig> {
2952 let mut config = load_config_auto(&path).await?;
2954
2955 if let Some(profile) = profile_name {
2957 if let Some(profile_config) = config.profiles.remove(profile) {
2958 tracing::info!("Applying profile: {}", profile);
2959 config = apply_profile(config, profile_config);
2960 } else {
2961 return Err(Error::generic(format!(
2962 "Profile '{}' not found in configuration. Available profiles: {}",
2963 profile,
2964 config.profiles.keys().map(|k| k.as_str()).collect::<Vec<_>>().join(", ")
2965 )));
2966 }
2967 }
2968
2969 config.profiles.clear();
2971
2972 Ok(config)
2973}
2974
2975pub async fn load_config_from_js<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2977 use rquickjs::{Context, Runtime};
2978
2979 let content = fs::read_to_string(&path)
2980 .await
2981 .map_err(|e| Error::generic(format!("Failed to read JS/TS config file: {}", e)))?;
2982
2983 let runtime = Runtime::new()
2985 .map_err(|e| Error::generic(format!("Failed to create JS runtime: {}", e)))?;
2986 let context = Context::full(&runtime)
2987 .map_err(|e| Error::generic(format!("Failed to create JS context: {}", e)))?;
2988
2989 context.with(|ctx| {
2990 let js_content = if path
2993 .as_ref()
2994 .extension()
2995 .and_then(|s| s.to_str())
2996 .map(|ext| ext == "ts")
2997 .unwrap_or(false)
2998 {
2999 strip_typescript_types(&content)?
3000 } else {
3001 content
3002 };
3003
3004 let result: rquickjs::Value = ctx
3006 .eval(js_content.as_bytes())
3007 .map_err(|e| Error::generic(format!("Failed to evaluate JS config: {}", e)))?;
3008
3009 let json_str: String = ctx
3011 .json_stringify(result)
3012 .map_err(|e| Error::generic(format!("Failed to stringify JS config: {}", e)))?
3013 .ok_or_else(|| Error::generic("JS config returned undefined"))?
3014 .get()
3015 .map_err(|e| Error::generic(format!("Failed to get JSON string: {}", e)))?;
3016
3017 serde_json::from_str(&json_str).map_err(|e| {
3019 Error::generic(format!("Failed to parse JS config as ServerConfig: {}", e))
3020 })
3021 })
3022}
3023
3024fn strip_typescript_types(content: &str) -> Result<String> {
3031 use regex::Regex;
3032
3033 let mut result = content.to_string();
3034
3035 let interface_re = Regex::new(r"(?ms)interface\s+\w+\s*\{[^}]*\}\s*")
3041 .map_err(|e| Error::generic(format!("Failed to compile interface regex: {}", e)))?;
3042 result = interface_re.replace_all(&result, "").to_string();
3043
3044 let type_alias_re = Regex::new(r"(?m)^type\s+\w+\s*=\s*[^;]+;\s*")
3046 .map_err(|e| Error::generic(format!("Failed to compile type alias regex: {}", e)))?;
3047 result = type_alias_re.replace_all(&result, "").to_string();
3048
3049 let type_annotation_re = Regex::new(r":\s*[A-Z]\w*(<[^>]+>)?(\[\])?")
3051 .map_err(|e| Error::generic(format!("Failed to compile type annotation regex: {}", e)))?;
3052 result = type_annotation_re.replace_all(&result, "").to_string();
3053
3054 let type_import_re = Regex::new(r"(?m)^(import|export)\s+type\s+.*$")
3056 .map_err(|e| Error::generic(format!("Failed to compile type import regex: {}", e)))?;
3057 result = type_import_re.replace_all(&result, "").to_string();
3058
3059 let as_type_re = Regex::new(r"\s+as\s+\w+")
3061 .map_err(|e| Error::generic(format!("Failed to compile 'as type' regex: {}", e)))?;
3062 result = as_type_re.replace_all(&result, "").to_string();
3063
3064 Ok(result)
3065}
3066
3067pub async fn load_config_auto<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
3069 let ext = path.as_ref().extension().and_then(|s| s.to_str()).unwrap_or("");
3070
3071 match ext {
3072 "ts" | "js" => load_config_from_js(&path).await,
3073 "yaml" | "yml" | "json" => load_config(&path).await,
3074 _ => Err(Error::generic(format!(
3075 "Unsupported config file format: {}. Supported: .ts, .js, .yaml, .yml, .json",
3076 ext
3077 ))),
3078 }
3079}
3080
3081pub async fn discover_config_file_all_formats() -> Result<std::path::PathBuf> {
3083 let current_dir = std::env::current_dir()
3084 .map_err(|e| Error::generic(format!("Failed to get current directory: {}", e)))?;
3085
3086 let config_names = vec![
3087 "mockforge.config.ts",
3088 "mockforge.config.js",
3089 "mockforge.yaml",
3090 "mockforge.yml",
3091 ".mockforge.yaml",
3092 ".mockforge.yml",
3093 ];
3094
3095 for name in &config_names {
3097 let path = current_dir.join(name);
3098 if tokio::fs::metadata(&path).await.is_ok() {
3099 return Ok(path);
3100 }
3101 }
3102
3103 let mut dir = current_dir.clone();
3105 for _ in 0..5 {
3106 if let Some(parent) = dir.parent() {
3107 for name in &config_names {
3108 let path = parent.join(name);
3109 if tokio::fs::metadata(&path).await.is_ok() {
3110 return Ok(path);
3111 }
3112 }
3113 dir = parent.to_path_buf();
3114 } else {
3115 break;
3116 }
3117 }
3118
3119 Err(Error::generic(
3120 "No configuration file found. Expected one of: mockforge.config.ts, mockforge.config.js, mockforge.yaml, mockforge.yml",
3121 ))
3122}
3123
3124#[cfg(test)]
3125mod tests {
3126 use super::*;
3127
3128 #[test]
3129 fn test_default_config() {
3130 let config = ServerConfig::default();
3131 assert_eq!(config.http.port, 3000);
3132 assert_eq!(config.websocket.port, 3001);
3133 assert_eq!(config.grpc.port, 50051);
3134 assert_eq!(config.admin.port, 9080);
3135 }
3136
3137 #[test]
3138 fn test_config_validation() {
3139 let mut config = ServerConfig::default();
3140 assert!(validate_config(&config).is_ok());
3141
3142 config.websocket.port = config.http.port;
3144 assert!(validate_config(&config).is_err());
3145
3146 config.websocket.port = 3001; config.logging.level = "invalid".to_string();
3149 assert!(validate_config(&config).is_err());
3150 }
3151
3152 #[test]
3153 fn test_apply_profile() {
3154 let mut base = ServerConfig::default();
3155 assert_eq!(base.http.port, 3000);
3156
3157 let mut profile = ProfileConfig::default();
3158 profile.http = Some(HttpConfig {
3159 port: 8080,
3160 ..Default::default()
3161 });
3162 profile.logging = Some(LoggingConfig {
3163 level: "debug".to_string(),
3164 ..Default::default()
3165 });
3166
3167 let merged = apply_profile(base, profile);
3168 assert_eq!(merged.http.port, 8080);
3169 assert_eq!(merged.logging.level, "debug");
3170 assert_eq!(merged.websocket.port, 3001); }
3172
3173 #[test]
3174 fn test_strip_typescript_types() {
3175 let ts_code = r#"
3176interface Config {
3177 port: number;
3178 host: string;
3179}
3180
3181const config: Config = {
3182 port: 3000,
3183 host: "localhost"
3184} as Config;
3185"#;
3186
3187 let stripped = strip_typescript_types(ts_code).expect("Should strip TypeScript types");
3188 assert!(!stripped.contains("interface"));
3189 assert!(!stripped.contains(": Config"));
3190 assert!(!stripped.contains("as Config"));
3191 }
3192}