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#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
653pub struct ProtocolConfig {
654 pub enabled: bool,
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize)]
660#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
661pub struct ProtocolsConfig {
662 pub http: ProtocolConfig,
664 pub graphql: ProtocolConfig,
666 pub grpc: ProtocolConfig,
668 pub websocket: ProtocolConfig,
670 pub smtp: ProtocolConfig,
672 pub mqtt: ProtocolConfig,
674 pub ftp: ProtocolConfig,
676 pub kafka: ProtocolConfig,
678 pub rabbitmq: ProtocolConfig,
680 pub amqp: ProtocolConfig,
682 pub tcp: ProtocolConfig,
684}
685
686impl Default for ProtocolsConfig {
687 fn default() -> Self {
688 Self {
689 http: ProtocolConfig { enabled: true },
690 graphql: ProtocolConfig { enabled: true },
691 grpc: ProtocolConfig { enabled: true },
692 websocket: ProtocolConfig { enabled: true },
693 smtp: ProtocolConfig { enabled: false },
694 mqtt: ProtocolConfig { enabled: true },
695 ftp: ProtocolConfig { enabled: false },
696 kafka: ProtocolConfig { enabled: false },
697 rabbitmq: ProtocolConfig { enabled: false },
698 amqp: ProtocolConfig { enabled: false },
699 tcp: ProtocolConfig { enabled: false },
700 }
701 }
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize)]
710#[serde(default)]
711#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
712pub struct RealitySliderConfig {
713 pub level: RealityLevel,
715 pub enabled: bool,
717}
718
719impl Default for RealitySliderConfig {
720 fn default() -> Self {
721 Self {
722 level: RealityLevel::ModerateRealism,
723 enabled: true,
724 }
725 }
726}
727
728#[derive(Debug, Clone, Serialize, Deserialize, Default)]
730#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
731#[serde(default)]
732pub struct ServerConfig {
733 pub http: HttpConfig,
735 pub websocket: WebSocketConfig,
737 pub graphql: GraphQLConfig,
739 pub grpc: GrpcConfig,
741 pub mqtt: MqttConfig,
743 pub smtp: SmtpConfig,
745 pub ftp: FtpConfig,
747 pub kafka: KafkaConfig,
749 pub amqp: AmqpConfig,
751 pub tcp: TcpConfig,
753 pub admin: AdminConfig,
755 pub chaining: ChainingConfig,
757 pub core: CoreConfig,
759 pub logging: LoggingConfig,
761 pub data: DataConfig,
763 #[serde(default)]
765 pub mockai: MockAIConfig,
766 pub observability: ObservabilityConfig,
768 pub multi_tenant: crate::multi_tenant::MultiTenantConfig,
770 #[serde(default)]
772 pub routes: Vec<RouteConfig>,
773 #[serde(default)]
775 pub protocols: ProtocolsConfig,
776 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
778 pub profiles: HashMap<String, ProfileConfig>,
779 #[serde(default)]
781 pub deceptive_deploy: DeceptiveDeployConfig,
782 #[serde(default, skip_serializing_if = "Option::is_none")]
784 pub behavioral_cloning: Option<BehavioralCloningConfig>,
785 #[serde(default)]
787 pub reality: RealitySliderConfig,
788 #[serde(default)]
790 pub reality_continuum: crate::reality_continuum::ContinuumConfig,
791 #[serde(default)]
793 pub security: SecurityConfig,
794 #[serde(default)]
796 pub drift_budget: crate::contract_drift::DriftBudgetConfig,
797 #[serde(default)]
799 pub incidents: IncidentConfig,
800 #[serde(default)]
802 pub pr_generation: crate::pr_generation::PRGenerationConfig,
803 #[serde(default)]
805 pub consumer_contracts: ConsumerContractsConfig,
806 #[serde(default)]
808 pub contracts: ContractsConfig,
809 #[serde(default)]
811 pub behavioral_economics: BehavioralEconomicsConfig,
812 #[serde(default)]
814 pub drift_learning: DriftLearningConfig,
815 #[serde(default)]
817 pub org_ai_controls: crate::ai_studio::org_controls::OrgAiControlsConfig,
818}
819
820#[derive(Debug, Clone, Serialize, Deserialize, Default)]
822#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
823#[serde(default)]
824pub struct ProfileConfig {
825 #[serde(skip_serializing_if = "Option::is_none")]
827 pub http: Option<HttpConfig>,
828 #[serde(skip_serializing_if = "Option::is_none")]
830 pub websocket: Option<WebSocketConfig>,
831 #[serde(skip_serializing_if = "Option::is_none")]
833 pub graphql: Option<GraphQLConfig>,
834 #[serde(skip_serializing_if = "Option::is_none")]
836 pub grpc: Option<GrpcConfig>,
837 #[serde(skip_serializing_if = "Option::is_none")]
839 pub mqtt: Option<MqttConfig>,
840 #[serde(skip_serializing_if = "Option::is_none")]
842 pub smtp: Option<SmtpConfig>,
843 #[serde(skip_serializing_if = "Option::is_none")]
845 pub ftp: Option<FtpConfig>,
846 #[serde(skip_serializing_if = "Option::is_none")]
848 pub kafka: Option<KafkaConfig>,
849 #[serde(skip_serializing_if = "Option::is_none")]
851 pub amqp: Option<AmqpConfig>,
852 #[serde(skip_serializing_if = "Option::is_none")]
854 pub tcp: Option<TcpConfig>,
855 #[serde(skip_serializing_if = "Option::is_none")]
857 pub admin: Option<AdminConfig>,
858 #[serde(skip_serializing_if = "Option::is_none")]
860 pub chaining: Option<ChainingConfig>,
861 #[serde(skip_serializing_if = "Option::is_none")]
863 pub core: Option<CoreConfig>,
864 #[serde(skip_serializing_if = "Option::is_none")]
866 pub logging: Option<LoggingConfig>,
867 #[serde(skip_serializing_if = "Option::is_none")]
869 pub data: Option<DataConfig>,
870 #[serde(skip_serializing_if = "Option::is_none")]
872 pub mockai: Option<MockAIConfig>,
873 #[serde(skip_serializing_if = "Option::is_none")]
875 pub observability: Option<ObservabilityConfig>,
876 #[serde(skip_serializing_if = "Option::is_none")]
878 pub multi_tenant: Option<crate::multi_tenant::MultiTenantConfig>,
879 #[serde(skip_serializing_if = "Option::is_none")]
881 pub routes: Option<Vec<RouteConfig>>,
882 #[serde(skip_serializing_if = "Option::is_none")]
884 pub protocols: Option<ProtocolsConfig>,
885 #[serde(skip_serializing_if = "Option::is_none")]
887 pub deceptive_deploy: Option<DeceptiveDeployConfig>,
888 #[serde(skip_serializing_if = "Option::is_none")]
890 pub reality: Option<RealitySliderConfig>,
891 #[serde(skip_serializing_if = "Option::is_none")]
893 pub reality_continuum: Option<crate::reality_continuum::ContinuumConfig>,
894 #[serde(skip_serializing_if = "Option::is_none")]
896 pub security: Option<SecurityConfig>,
897}
898
899#[derive(Debug, Clone, Serialize, Deserialize)]
903#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
904pub struct HttpValidationConfig {
905 pub mode: String,
907}
908
909#[derive(Debug, Clone, Serialize, Deserialize, Default)]
911#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
912pub struct HttpCorsConfig {
913 pub enabled: bool,
915 #[serde(default)]
917 pub allowed_origins: Vec<String>,
918 #[serde(default)]
920 pub allowed_methods: Vec<String>,
921 #[serde(default)]
923 pub allowed_headers: Vec<String>,
924 #[serde(default = "default_cors_allow_credentials")]
927 pub allow_credentials: bool,
928}
929
930fn default_cors_allow_credentials() -> bool {
931 false
932}
933
934#[derive(Debug, Clone, Serialize, Deserialize)]
936#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
937#[serde(default)]
938pub struct HttpConfig {
939 pub enabled: bool,
941 pub port: u16,
943 pub host: String,
945 pub openapi_spec: Option<String>,
947 pub cors: Option<HttpCorsConfig>,
949 pub request_timeout_secs: u64,
951 pub validation: Option<HttpValidationConfig>,
953 pub aggregate_validation_errors: bool,
955 pub validate_responses: bool,
957 pub response_template_expand: bool,
959 pub validation_status: Option<u16>,
961 pub validation_overrides: std::collections::HashMap<String, String>,
963 pub skip_admin_validation: bool,
965 pub auth: Option<AuthConfig>,
967 #[serde(skip_serializing_if = "Option::is_none")]
969 pub tls: Option<HttpTlsConfig>,
970}
971
972impl Default for HttpConfig {
973 fn default() -> Self {
974 Self {
975 enabled: true,
976 port: 3000,
977 host: "0.0.0.0".to_string(),
978 openapi_spec: None,
979 cors: Some(HttpCorsConfig {
980 enabled: true,
981 allowed_origins: vec!["*".to_string()],
982 allowed_methods: vec![
983 "GET".to_string(),
984 "POST".to_string(),
985 "PUT".to_string(),
986 "DELETE".to_string(),
987 "PATCH".to_string(),
988 "OPTIONS".to_string(),
989 ],
990 allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
991 allow_credentials: false, }),
993 request_timeout_secs: 30,
994 validation: Some(HttpValidationConfig {
995 mode: "enforce".to_string(),
996 }),
997 aggregate_validation_errors: true,
998 validate_responses: false,
999 response_template_expand: false,
1000 validation_status: None,
1001 validation_overrides: std::collections::HashMap::new(),
1002 skip_admin_validation: true,
1003 auth: None,
1004 tls: None,
1005 }
1006 }
1007}
1008
1009#[derive(Debug, Clone, Serialize, Deserialize)]
1011#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1012pub struct HttpTlsConfig {
1013 pub enabled: bool,
1015 pub cert_file: String,
1017 pub key_file: String,
1019 #[serde(skip_serializing_if = "Option::is_none")]
1021 pub ca_file: Option<String>,
1022 #[serde(default = "default_tls_min_version")]
1024 pub min_version: String,
1025 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1027 pub cipher_suites: Vec<String>,
1028 #[serde(default)]
1030 pub require_client_cert: bool,
1031}
1032
1033fn default_tls_min_version() -> String {
1034 "1.2".to_string()
1035}
1036
1037impl Default for HttpTlsConfig {
1038 fn default() -> Self {
1039 Self {
1040 enabled: true,
1041 cert_file: String::new(),
1042 key_file: String::new(),
1043 ca_file: None,
1044 min_version: "1.2".to_string(),
1045 cipher_suites: Vec::new(),
1046 require_client_cert: false,
1047 }
1048 }
1049}
1050
1051#[derive(Debug, Clone, Serialize, Deserialize)]
1053#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1054#[serde(default)]
1055pub struct WebSocketConfig {
1056 pub enabled: bool,
1058 pub port: u16,
1060 pub host: String,
1062 pub replay_file: Option<String>,
1064 pub connection_timeout_secs: u64,
1066}
1067
1068impl Default for WebSocketConfig {
1069 fn default() -> Self {
1070 Self {
1071 enabled: true,
1072 port: 3001,
1073 host: "0.0.0.0".to_string(),
1074 replay_file: None,
1075 connection_timeout_secs: 300,
1076 }
1077 }
1078}
1079
1080#[derive(Debug, Clone, Serialize, Deserialize)]
1082#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1083#[serde(default)]
1084pub struct GrpcConfig {
1085 pub enabled: bool,
1087 pub port: u16,
1089 pub host: String,
1091 pub proto_dir: Option<String>,
1093 pub tls: Option<TlsConfig>,
1095}
1096
1097impl Default for GrpcConfig {
1098 fn default() -> Self {
1099 Self {
1100 enabled: true,
1101 port: 50051,
1102 host: "0.0.0.0".to_string(),
1103 proto_dir: None,
1104 tls: None,
1105 }
1106 }
1107}
1108
1109#[derive(Debug, Clone, Serialize, Deserialize)]
1111#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1112#[serde(default)]
1113pub struct GraphQLConfig {
1114 pub enabled: bool,
1116 pub port: u16,
1118 pub host: String,
1120 pub schema_path: Option<String>,
1122 pub handlers_dir: Option<String>,
1124 pub playground_enabled: bool,
1126 pub upstream_url: Option<String>,
1128 pub introspection_enabled: bool,
1130}
1131
1132impl Default for GraphQLConfig {
1133 fn default() -> Self {
1134 Self {
1135 enabled: true,
1136 port: 4000,
1137 host: "0.0.0.0".to_string(),
1138 schema_path: None,
1139 handlers_dir: None,
1140 playground_enabled: true,
1141 upstream_url: None,
1142 introspection_enabled: true,
1143 }
1144 }
1145}
1146
1147#[derive(Debug, Clone, Serialize, Deserialize)]
1149#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1150pub struct TlsConfig {
1151 pub cert_path: String,
1153 pub key_path: String,
1155}
1156
1157#[derive(Debug, Clone, Serialize, Deserialize)]
1159#[serde(default)]
1160#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1161pub struct MqttConfig {
1162 pub enabled: bool,
1164 pub port: u16,
1166 pub host: String,
1168 pub max_connections: usize,
1170 pub max_packet_size: usize,
1172 pub keep_alive_secs: u16,
1174 pub fixtures_dir: Option<std::path::PathBuf>,
1176 pub enable_retained_messages: bool,
1178 pub max_retained_messages: usize,
1180}
1181
1182impl Default for MqttConfig {
1183 fn default() -> Self {
1184 Self {
1185 enabled: false,
1186 port: 1883,
1187 host: "0.0.0.0".to_string(),
1188 max_connections: 1000,
1189 max_packet_size: 268435456, keep_alive_secs: 60,
1191 fixtures_dir: None,
1192 enable_retained_messages: true,
1193 max_retained_messages: 10000,
1194 }
1195 }
1196}
1197
1198#[derive(Debug, Clone, Serialize, Deserialize)]
1200#[serde(default)]
1201#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1202pub struct SmtpConfig {
1203 pub enabled: bool,
1205 pub port: u16,
1207 pub host: String,
1209 pub hostname: String,
1211 pub fixtures_dir: Option<std::path::PathBuf>,
1213 pub timeout_secs: u64,
1215 pub max_connections: usize,
1217 pub enable_mailbox: bool,
1219 pub max_mailbox_messages: usize,
1221 pub enable_starttls: bool,
1223 pub tls_cert_path: Option<std::path::PathBuf>,
1225 pub tls_key_path: Option<std::path::PathBuf>,
1227}
1228
1229impl Default for SmtpConfig {
1230 fn default() -> Self {
1231 Self {
1232 enabled: false,
1233 port: 1025,
1234 host: "0.0.0.0".to_string(),
1235 hostname: "mockforge-smtp".to_string(),
1236 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
1237 timeout_secs: 300,
1238 max_connections: 10,
1239 enable_mailbox: true,
1240 max_mailbox_messages: 1000,
1241 enable_starttls: false,
1242 tls_cert_path: None,
1243 tls_key_path: None,
1244 }
1245 }
1246}
1247
1248#[derive(Debug, Clone, Serialize, Deserialize)]
1250#[serde(default)]
1251#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1252pub struct FtpConfig {
1253 pub enabled: bool,
1255 pub port: u16,
1257 pub host: String,
1259 pub passive_ports: (u16, u16),
1261 pub max_connections: usize,
1263 pub timeout_secs: u64,
1265 pub allow_anonymous: bool,
1267 pub fixtures_dir: Option<std::path::PathBuf>,
1269 pub virtual_root: std::path::PathBuf,
1271}
1272
1273impl Default for FtpConfig {
1274 fn default() -> Self {
1275 Self {
1276 enabled: false,
1277 port: 2121,
1278 host: "0.0.0.0".to_string(),
1279 passive_ports: (50000, 51000),
1280 max_connections: 100,
1281 timeout_secs: 300,
1282 allow_anonymous: true,
1283 fixtures_dir: None,
1284 virtual_root: std::path::PathBuf::from("/mockforge"),
1285 }
1286 }
1287}
1288
1289#[derive(Debug, Clone, Serialize, Deserialize)]
1291#[serde(default)]
1292#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1293pub struct KafkaConfig {
1294 pub enabled: bool,
1296 pub port: u16,
1298 pub host: String,
1300 pub broker_id: i32,
1302 pub max_connections: usize,
1304 pub log_retention_ms: i64,
1306 pub log_segment_bytes: i64,
1308 pub fixtures_dir: Option<std::path::PathBuf>,
1310 pub auto_create_topics: bool,
1312 pub default_partitions: i32,
1314 pub default_replication_factor: i16,
1316}
1317
1318impl Default for KafkaConfig {
1319 fn default() -> Self {
1320 Self {
1321 enabled: false,
1322 port: 9092, host: "0.0.0.0".to_string(),
1324 broker_id: 1,
1325 max_connections: 1000,
1326 log_retention_ms: 604800000, log_segment_bytes: 1073741824, fixtures_dir: None,
1329 auto_create_topics: true,
1330 default_partitions: 3,
1331 default_replication_factor: 1,
1332 }
1333 }
1334}
1335
1336#[derive(Debug, Clone, Serialize, Deserialize)]
1338#[serde(default)]
1339#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1340pub struct AmqpConfig {
1341 pub enabled: bool,
1343 pub port: u16,
1345 pub host: String,
1347 pub max_connections: usize,
1349 pub max_channels_per_connection: u16,
1351 pub frame_max: u32,
1353 pub heartbeat_interval: u16,
1355 pub fixtures_dir: Option<std::path::PathBuf>,
1357 pub virtual_hosts: Vec<String>,
1359}
1360
1361impl Default for AmqpConfig {
1362 fn default() -> Self {
1363 Self {
1364 enabled: false,
1365 port: 5672, host: "0.0.0.0".to_string(),
1367 max_connections: 1000,
1368 max_channels_per_connection: 100,
1369 frame_max: 131072, heartbeat_interval: 60,
1371 fixtures_dir: None,
1372 virtual_hosts: vec!["/".to_string()],
1373 }
1374 }
1375}
1376
1377#[derive(Debug, Clone, Serialize, Deserialize)]
1379#[serde(default)]
1380#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1381pub struct TcpConfig {
1382 pub enabled: bool,
1384 pub port: u16,
1386 pub host: String,
1388 pub max_connections: usize,
1390 pub timeout_secs: u64,
1392 pub fixtures_dir: Option<std::path::PathBuf>,
1394 pub echo_mode: bool,
1396 pub enable_tls: bool,
1398 pub tls_cert_path: Option<std::path::PathBuf>,
1400 pub tls_key_path: Option<std::path::PathBuf>,
1402}
1403
1404impl Default for TcpConfig {
1405 fn default() -> Self {
1406 Self {
1407 enabled: false,
1408 port: 9999,
1409 host: "0.0.0.0".to_string(),
1410 max_connections: 1000,
1411 timeout_secs: 300,
1412 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
1413 echo_mode: true,
1414 enable_tls: false,
1415 tls_cert_path: None,
1416 tls_key_path: None,
1417 }
1418 }
1419}
1420
1421#[derive(Debug, Clone, Serialize, Deserialize)]
1423#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1424#[serde(default)]
1425pub struct AdminConfig {
1426 pub enabled: bool,
1428 pub port: u16,
1430 pub host: String,
1432 pub auth_required: bool,
1434 pub username: Option<String>,
1436 pub password: Option<String>,
1438 pub mount_path: Option<String>,
1440 pub api_enabled: bool,
1442 pub prometheus_url: String,
1444}
1445
1446impl Default for AdminConfig {
1447 fn default() -> Self {
1448 let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
1451 || std::env::var("container").is_ok()
1452 || std::path::Path::new("/.dockerenv").exists()
1453 {
1454 "0.0.0.0".to_string()
1455 } else {
1456 "127.0.0.1".to_string()
1457 };
1458
1459 Self {
1460 enabled: false,
1461 port: 9080,
1462 host: default_host,
1463 auth_required: false,
1464 username: None,
1465 password: None,
1466 mount_path: None,
1467 api_enabled: true,
1468 prometheus_url: "http://localhost:9090".to_string(),
1469 }
1470 }
1471}
1472
1473#[derive(Debug, Clone, Serialize, Deserialize)]
1475#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1476#[serde(default)]
1477pub struct LoggingConfig {
1478 pub level: String,
1480 pub json_format: bool,
1482 pub file_path: Option<String>,
1484 pub max_file_size_mb: u64,
1486 pub max_files: u32,
1488}
1489
1490impl Default for LoggingConfig {
1491 fn default() -> Self {
1492 Self {
1493 level: "info".to_string(),
1494 json_format: false,
1495 file_path: None,
1496 max_file_size_mb: 10,
1497 max_files: 5,
1498 }
1499 }
1500}
1501
1502#[derive(Debug, Clone, Serialize, Deserialize)]
1504#[serde(default, rename_all = "camelCase")]
1505#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1506pub struct ChainingConfig {
1507 pub enabled: bool,
1509 pub max_chain_length: usize,
1511 pub global_timeout_secs: u64,
1513 pub enable_parallel_execution: bool,
1515}
1516
1517impl Default for ChainingConfig {
1518 fn default() -> Self {
1519 Self {
1520 enabled: false,
1521 max_chain_length: 20,
1522 global_timeout_secs: 300,
1523 enable_parallel_execution: false,
1524 }
1525 }
1526}
1527
1528#[derive(Debug, Clone, Serialize, Deserialize)]
1530#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1531#[serde(default)]
1532pub struct DataConfig {
1533 pub default_rows: usize,
1535 pub default_format: String,
1537 pub locale: String,
1539 pub templates: HashMap<String, String>,
1541 pub rag: RagConfig,
1543 #[serde(skip_serializing_if = "Option::is_none")]
1545 pub persona_domain: Option<String>,
1546 #[serde(default = "default_false")]
1548 pub persona_consistency_enabled: bool,
1549 #[serde(skip_serializing_if = "Option::is_none")]
1551 pub persona_registry: Option<PersonaRegistryConfig>,
1552}
1553
1554impl Default for DataConfig {
1555 fn default() -> Self {
1556 Self {
1557 default_rows: 100,
1558 default_format: "json".to_string(),
1559 locale: "en".to_string(),
1560 templates: HashMap::new(),
1561 rag: RagConfig::default(),
1562 persona_domain: None,
1563 persona_consistency_enabled: false,
1564 persona_registry: None,
1565 }
1566 }
1567}
1568
1569#[derive(Debug, Clone, Serialize, Deserialize)]
1571#[serde(default)]
1572#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1573pub struct RagConfig {
1574 pub enabled: bool,
1576 #[serde(default)]
1578 pub provider: String,
1579 pub api_endpoint: Option<String>,
1581 pub api_key: Option<String>,
1583 pub model: Option<String>,
1585 #[serde(default = "default_max_tokens")]
1587 pub max_tokens: usize,
1588 #[serde(default = "default_temperature")]
1590 pub temperature: f64,
1591 pub context_window: usize,
1593 #[serde(default = "default_true")]
1595 pub caching: bool,
1596 #[serde(default = "default_cache_ttl")]
1598 pub cache_ttl_secs: u64,
1599 #[serde(default = "default_timeout")]
1601 pub timeout_secs: u64,
1602 #[serde(default = "default_max_retries")]
1604 pub max_retries: usize,
1605}
1606
1607fn default_max_tokens() -> usize {
1608 1024
1609}
1610
1611fn default_temperature() -> f64 {
1612 0.7
1613}
1614
1615fn default_true() -> bool {
1616 true
1617}
1618
1619fn default_cache_ttl() -> u64 {
1620 3600
1621}
1622
1623fn default_timeout() -> u64 {
1624 30
1625}
1626
1627fn default_max_retries() -> usize {
1628 3
1629}
1630
1631fn default_false() -> bool {
1632 false
1633}
1634
1635impl Default for RagConfig {
1636 fn default() -> Self {
1637 Self {
1638 enabled: false,
1639 provider: "openai".to_string(),
1640 api_endpoint: None,
1641 api_key: None,
1642 model: Some("gpt-3.5-turbo".to_string()),
1643 max_tokens: default_max_tokens(),
1644 temperature: default_temperature(),
1645 context_window: 4000,
1646 caching: default_true(),
1647 cache_ttl_secs: default_cache_ttl(),
1648 timeout_secs: default_timeout(),
1649 max_retries: default_max_retries(),
1650 }
1651 }
1652}
1653
1654#[derive(Debug, Clone, Serialize, Deserialize)]
1656#[serde(default)]
1657#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1658#[derive(Default)]
1659pub struct PersonaRegistryConfig {
1660 #[serde(default = "default_false")]
1662 pub persistent: bool,
1663 #[serde(skip_serializing_if = "Option::is_none")]
1665 pub storage_path: Option<String>,
1666 #[serde(default)]
1668 pub default_traits: HashMap<String, String>,
1669}
1670
1671#[derive(Debug, Clone, Serialize, Deserialize)]
1673#[serde(default)]
1674#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1675pub struct MockAIConfig {
1676 pub enabled: bool,
1678 pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
1680 pub auto_learn: bool,
1682 pub mutation_detection: bool,
1684 pub ai_validation_errors: bool,
1686 pub intelligent_pagination: bool,
1688 #[serde(default)]
1690 pub enabled_endpoints: Vec<String>,
1691}
1692
1693impl Default for MockAIConfig {
1694 fn default() -> Self {
1695 Self {
1696 enabled: false,
1697 intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
1698 auto_learn: true,
1699 mutation_detection: true,
1700 ai_validation_errors: true,
1701 intelligent_pagination: true,
1702 enabled_endpoints: Vec::new(),
1703 }
1704 }
1705}
1706
1707#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1709#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1710#[serde(default)]
1711pub struct ObservabilityConfig {
1712 pub prometheus: PrometheusConfig,
1714 pub opentelemetry: Option<OpenTelemetryConfig>,
1716 pub recorder: Option<RecorderConfig>,
1718 pub chaos: Option<ChaosEngConfig>,
1720}
1721
1722#[derive(Debug, Clone, Serialize, Deserialize)]
1724#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1725#[serde(default)]
1726#[derive(Default)]
1727pub struct SecurityConfig {
1728 pub monitoring: SecurityMonitoringConfig,
1730}
1731
1732#[derive(Debug, Clone, Serialize, Deserialize)]
1734#[serde(default)]
1735#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1736#[derive(Default)]
1737pub struct SecurityMonitoringConfig {
1738 pub siem: crate::security::siem::SiemConfig,
1740 pub access_review: crate::security::access_review::AccessReviewConfig,
1742 pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
1744 pub change_management: crate::security::change_management::ChangeManagementConfig,
1746 pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
1748 pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
1750}
1751
1752#[derive(Debug, Clone, Serialize, Deserialize)]
1754#[serde(default)]
1755#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1756pub struct PrometheusConfig {
1757 pub enabled: bool,
1759 pub port: u16,
1761 pub host: String,
1763 pub path: String,
1765}
1766
1767impl Default for PrometheusConfig {
1768 fn default() -> Self {
1769 Self {
1770 enabled: true,
1771 port: 9090,
1772 host: "0.0.0.0".to_string(),
1773 path: "/metrics".to_string(),
1774 }
1775 }
1776}
1777
1778#[derive(Debug, Clone, Serialize, Deserialize)]
1780#[serde(default)]
1781#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1782pub struct OpenTelemetryConfig {
1783 pub enabled: bool,
1785 pub service_name: String,
1787 pub environment: String,
1789 pub jaeger_endpoint: String,
1791 pub otlp_endpoint: Option<String>,
1793 pub protocol: String,
1795 pub sampling_rate: f64,
1797}
1798
1799impl Default for OpenTelemetryConfig {
1800 fn default() -> Self {
1801 Self {
1802 enabled: false,
1803 service_name: "mockforge".to_string(),
1804 environment: "development".to_string(),
1805 jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
1806 otlp_endpoint: Some("http://localhost:4317".to_string()),
1807 protocol: "grpc".to_string(),
1808 sampling_rate: 1.0,
1809 }
1810 }
1811}
1812
1813#[derive(Debug, Clone, Serialize, Deserialize)]
1815#[serde(default)]
1816#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1817pub struct RecorderConfig {
1818 pub enabled: bool,
1820 pub database_path: String,
1822 pub api_enabled: bool,
1824 pub api_port: Option<u16>,
1826 pub max_requests: i64,
1828 pub retention_days: i64,
1830 pub record_http: bool,
1832 pub record_grpc: bool,
1834 pub record_websocket: bool,
1836 pub record_graphql: bool,
1838 #[serde(default = "default_true")]
1841 pub record_proxy: bool,
1842}
1843
1844impl Default for RecorderConfig {
1845 fn default() -> Self {
1846 Self {
1847 enabled: false,
1848 database_path: "./mockforge-recordings.db".to_string(),
1849 api_enabled: true,
1850 api_port: None,
1851 max_requests: 10000,
1852 retention_days: 7,
1853 record_http: true,
1854 record_grpc: true,
1855 record_websocket: true,
1856 record_graphql: true,
1857 record_proxy: true,
1858 }
1859 }
1860}
1861
1862#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1864#[serde(default)]
1865#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1866pub struct ChaosEngConfig {
1867 pub enabled: bool,
1869 pub latency: Option<LatencyInjectionConfig>,
1871 pub fault_injection: Option<FaultConfig>,
1873 pub rate_limit: Option<RateLimitingConfig>,
1875 pub traffic_shaping: Option<NetworkShapingConfig>,
1877 pub scenario: Option<String>,
1879}
1880
1881#[derive(Debug, Clone, Serialize, Deserialize)]
1883#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1884pub struct LatencyInjectionConfig {
1885 pub enabled: bool,
1887 pub fixed_delay_ms: Option<u64>,
1889 pub random_delay_range_ms: Option<(u64, u64)>,
1891 pub jitter_percent: f64,
1893 pub probability: f64,
1895}
1896
1897#[derive(Debug, Clone, Serialize, Deserialize)]
1899#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1900pub struct FaultConfig {
1901 pub enabled: bool,
1903 pub http_errors: Vec<u16>,
1905 pub http_error_probability: f64,
1907 pub connection_errors: bool,
1909 pub connection_error_probability: f64,
1911 pub timeout_errors: bool,
1913 pub timeout_ms: u64,
1915 pub timeout_probability: f64,
1917}
1918
1919#[derive(Debug, Clone, Serialize, Deserialize)]
1921#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1922pub struct RateLimitingConfig {
1923 pub enabled: bool,
1925 pub requests_per_second: u32,
1927 pub burst_size: u32,
1929 pub per_ip: bool,
1931 pub per_endpoint: bool,
1933}
1934
1935#[derive(Debug, Clone, Serialize, Deserialize)]
1937#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1938pub struct NetworkShapingConfig {
1939 pub enabled: bool,
1941 pub bandwidth_limit_bps: u64,
1943 pub packet_loss_percent: f64,
1945 pub max_connections: u32,
1947}
1948
1949pub async fn load_config<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
1951 let content = fs::read_to_string(&path)
1952 .await
1953 .map_err(|e| Error::generic(format!("Failed to read config file: {}", e)))?;
1954
1955 let config: ServerConfig = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
1957 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
1958 {
1959 serde_yaml::from_str(&content).map_err(|e| {
1960 let error_msg = e.to_string();
1962 let mut full_msg = format!("Failed to parse YAML config: {}", error_msg);
1963
1964 if error_msg.contains("missing field") {
1966 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
1967 full_msg.push_str(
1968 "\n Omit fields you don't need - MockForge will use sensible defaults.",
1969 );
1970 full_msg.push_str("\n See config.template.yaml for all available options.");
1971 } else if error_msg.contains("unknown field") {
1972 full_msg.push_str("\n\n💡 Check for typos in field names.");
1973 full_msg.push_str("\n See config.template.yaml for valid field names.");
1974 }
1975
1976 Error::generic(full_msg)
1977 })?
1978 } else {
1979 serde_json::from_str(&content).map_err(|e| {
1980 let error_msg = e.to_string();
1982 let mut full_msg = format!("Failed to parse JSON config: {}", error_msg);
1983
1984 if error_msg.contains("missing field") {
1986 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
1987 full_msg.push_str(
1988 "\n Omit fields you don't need - MockForge will use sensible defaults.",
1989 );
1990 full_msg.push_str("\n See config.template.yaml for all available options.");
1991 } else if error_msg.contains("unknown field") {
1992 full_msg.push_str("\n\n💡 Check for typos in field names.");
1993 full_msg.push_str("\n See config.template.yaml for valid field names.");
1994 }
1995
1996 Error::generic(full_msg)
1997 })?
1998 };
1999
2000 Ok(config)
2001}
2002
2003pub async fn save_config<P: AsRef<Path>>(path: P, config: &ServerConfig) -> Result<()> {
2005 let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2006 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2007 {
2008 serde_yaml::to_string(config)
2009 .map_err(|e| Error::generic(format!("Failed to serialize config to YAML: {}", e)))?
2010 } else {
2011 serde_json::to_string_pretty(config)
2012 .map_err(|e| Error::generic(format!("Failed to serialize config to JSON: {}", e)))?
2013 };
2014
2015 fs::write(path, content)
2016 .await
2017 .map_err(|e| Error::generic(format!("Failed to write config file: {}", e)))?;
2018
2019 Ok(())
2020}
2021
2022pub async fn load_config_with_fallback<P: AsRef<Path>>(path: P) -> ServerConfig {
2024 match load_config(&path).await {
2025 Ok(config) => {
2026 tracing::info!("Loaded configuration from {:?}", path.as_ref());
2027 config
2028 }
2029 Err(e) => {
2030 tracing::warn!(
2031 "Failed to load config from {:?}: {}. Using defaults.",
2032 path.as_ref(),
2033 e
2034 );
2035 ServerConfig::default()
2036 }
2037 }
2038}
2039
2040pub async fn create_default_config<P: AsRef<Path>>(path: P) -> Result<()> {
2042 let config = ServerConfig::default();
2043 save_config(path, &config).await?;
2044 Ok(())
2045}
2046
2047pub fn apply_env_overrides(mut config: ServerConfig) -> ServerConfig {
2049 if let Ok(port) = std::env::var("MOCKFORGE_HTTP_PORT") {
2051 if let Ok(port_num) = port.parse() {
2052 config.http.port = port_num;
2053 }
2054 }
2055
2056 if let Ok(host) = std::env::var("MOCKFORGE_HTTP_HOST") {
2057 config.http.host = host;
2058 }
2059
2060 if let Ok(port) = std::env::var("MOCKFORGE_WS_PORT") {
2062 if let Ok(port_num) = port.parse() {
2063 config.websocket.port = port_num;
2064 }
2065 }
2066
2067 if let Ok(port) = std::env::var("MOCKFORGE_GRPC_PORT") {
2069 if let Ok(port_num) = port.parse() {
2070 config.grpc.port = port_num;
2071 }
2072 }
2073
2074 if let Ok(port) = std::env::var("MOCKFORGE_SMTP_PORT") {
2076 if let Ok(port_num) = port.parse() {
2077 config.smtp.port = port_num;
2078 }
2079 }
2080
2081 if let Ok(host) = std::env::var("MOCKFORGE_SMTP_HOST") {
2082 config.smtp.host = host;
2083 }
2084
2085 if let Ok(enabled) = std::env::var("MOCKFORGE_SMTP_ENABLED") {
2086 config.smtp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2087 }
2088
2089 if let Ok(hostname) = std::env::var("MOCKFORGE_SMTP_HOSTNAME") {
2090 config.smtp.hostname = hostname;
2091 }
2092
2093 if let Ok(port) = std::env::var("MOCKFORGE_TCP_PORT") {
2095 if let Ok(port_num) = port.parse() {
2096 config.tcp.port = port_num;
2097 }
2098 }
2099
2100 if let Ok(host) = std::env::var("MOCKFORGE_TCP_HOST") {
2101 config.tcp.host = host;
2102 }
2103
2104 if let Ok(enabled) = std::env::var("MOCKFORGE_TCP_ENABLED") {
2105 config.tcp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2106 }
2107
2108 if let Ok(port) = std::env::var("MOCKFORGE_ADMIN_PORT") {
2110 if let Ok(port_num) = port.parse() {
2111 config.admin.port = port_num;
2112 }
2113 }
2114
2115 if std::env::var("MOCKFORGE_ADMIN_ENABLED").unwrap_or_default() == "true" {
2116 config.admin.enabled = true;
2117 }
2118
2119 if let Ok(host) = std::env::var("MOCKFORGE_ADMIN_HOST") {
2121 config.admin.host = host;
2122 }
2123
2124 if let Ok(mount_path) = std::env::var("MOCKFORGE_ADMIN_MOUNT_PATH") {
2125 if !mount_path.trim().is_empty() {
2126 config.admin.mount_path = Some(mount_path);
2127 }
2128 }
2129
2130 if let Ok(api_enabled) = std::env::var("MOCKFORGE_ADMIN_API_ENABLED") {
2131 let on = api_enabled == "1" || api_enabled.eq_ignore_ascii_case("true");
2132 config.admin.api_enabled = on;
2133 }
2134
2135 if let Ok(prometheus_url) = std::env::var("PROMETHEUS_URL") {
2136 config.admin.prometheus_url = prometheus_url;
2137 }
2138
2139 if let Ok(latency_enabled) = std::env::var("MOCKFORGE_LATENCY_ENABLED") {
2141 let enabled = latency_enabled == "1" || latency_enabled.eq_ignore_ascii_case("true");
2142 config.core.latency_enabled = enabled;
2143 }
2144
2145 if let Ok(failures_enabled) = std::env::var("MOCKFORGE_FAILURES_ENABLED") {
2146 let enabled = failures_enabled == "1" || failures_enabled.eq_ignore_ascii_case("true");
2147 config.core.failures_enabled = enabled;
2148 }
2149
2150 if let Ok(overrides_enabled) = std::env::var("MOCKFORGE_OVERRIDES_ENABLED") {
2151 let enabled = overrides_enabled == "1" || overrides_enabled.eq_ignore_ascii_case("true");
2152 config.core.overrides_enabled = enabled;
2153 }
2154
2155 if let Ok(traffic_shaping_enabled) = std::env::var("MOCKFORGE_TRAFFIC_SHAPING_ENABLED") {
2156 let enabled =
2157 traffic_shaping_enabled == "1" || traffic_shaping_enabled.eq_ignore_ascii_case("true");
2158 config.core.traffic_shaping_enabled = enabled;
2159 }
2160
2161 if let Ok(bandwidth_enabled) = std::env::var("MOCKFORGE_BANDWIDTH_ENABLED") {
2163 let enabled = bandwidth_enabled == "1" || bandwidth_enabled.eq_ignore_ascii_case("true");
2164 config.core.traffic_shaping.bandwidth.enabled = enabled;
2165 }
2166
2167 if let Ok(max_bytes_per_sec) = std::env::var("MOCKFORGE_BANDWIDTH_MAX_BYTES_PER_SEC") {
2168 if let Ok(bytes) = max_bytes_per_sec.parse() {
2169 config.core.traffic_shaping.bandwidth.max_bytes_per_sec = bytes;
2170 config.core.traffic_shaping.bandwidth.enabled = true;
2171 }
2172 }
2173
2174 if let Ok(burst_capacity) = std::env::var("MOCKFORGE_BANDWIDTH_BURST_CAPACITY_BYTES") {
2175 if let Ok(bytes) = burst_capacity.parse() {
2176 config.core.traffic_shaping.bandwidth.burst_capacity_bytes = bytes;
2177 }
2178 }
2179
2180 if let Ok(burst_loss_enabled) = std::env::var("MOCKFORGE_BURST_LOSS_ENABLED") {
2181 let enabled = burst_loss_enabled == "1" || burst_loss_enabled.eq_ignore_ascii_case("true");
2182 config.core.traffic_shaping.burst_loss.enabled = enabled;
2183 }
2184
2185 if let Ok(burst_probability) = std::env::var("MOCKFORGE_BURST_LOSS_PROBABILITY") {
2186 if let Ok(prob) = burst_probability.parse::<f64>() {
2187 config.core.traffic_shaping.burst_loss.burst_probability = prob.clamp(0.0, 1.0);
2188 config.core.traffic_shaping.burst_loss.enabled = true;
2189 }
2190 }
2191
2192 if let Ok(burst_duration) = std::env::var("MOCKFORGE_BURST_LOSS_DURATION_MS") {
2193 if let Ok(ms) = burst_duration.parse() {
2194 config.core.traffic_shaping.burst_loss.burst_duration_ms = ms;
2195 }
2196 }
2197
2198 if let Ok(loss_rate) = std::env::var("MOCKFORGE_BURST_LOSS_RATE") {
2199 if let Ok(rate) = loss_rate.parse::<f64>() {
2200 config.core.traffic_shaping.burst_loss.loss_rate_during_burst = rate.clamp(0.0, 1.0);
2201 }
2202 }
2203
2204 if let Ok(recovery_time) = std::env::var("MOCKFORGE_BURST_LOSS_RECOVERY_MS") {
2205 if let Ok(ms) = recovery_time.parse() {
2206 config.core.traffic_shaping.burst_loss.recovery_time_ms = ms;
2207 }
2208 }
2209
2210 if let Ok(level) = std::env::var("MOCKFORGE_LOG_LEVEL") {
2212 config.logging.level = level;
2213 }
2214
2215 config
2216}
2217
2218pub fn validate_config(config: &ServerConfig) -> Result<()> {
2220 if config.http.port == 0 {
2222 return Err(Error::generic("HTTP port cannot be 0"));
2223 }
2224 if config.websocket.port == 0 {
2225 return Err(Error::generic("WebSocket port cannot be 0"));
2226 }
2227 if config.grpc.port == 0 {
2228 return Err(Error::generic("gRPC port cannot be 0"));
2229 }
2230 if config.admin.port == 0 {
2231 return Err(Error::generic("Admin port cannot be 0"));
2232 }
2233
2234 let ports = [
2236 ("HTTP", config.http.port),
2237 ("WebSocket", config.websocket.port),
2238 ("gRPC", config.grpc.port),
2239 ("Admin", config.admin.port),
2240 ];
2241
2242 for i in 0..ports.len() {
2243 for j in (i + 1)..ports.len() {
2244 if ports[i].1 == ports[j].1 {
2245 return Err(Error::generic(format!(
2246 "Port conflict: {} and {} both use port {}",
2247 ports[i].0, ports[j].0, ports[i].1
2248 )));
2249 }
2250 }
2251 }
2252
2253 let valid_levels = ["trace", "debug", "info", "warn", "error"];
2255 if !valid_levels.contains(&config.logging.level.as_str()) {
2256 return Err(Error::generic(format!(
2257 "Invalid log level: {}. Valid levels: {}",
2258 config.logging.level,
2259 valid_levels.join(", ")
2260 )));
2261 }
2262
2263 Ok(())
2264}
2265
2266pub fn apply_profile(mut base: ServerConfig, profile: ProfileConfig) -> ServerConfig {
2268 macro_rules! merge_field {
2270 ($field:ident) => {
2271 if let Some(override_val) = profile.$field {
2272 base.$field = override_val;
2273 }
2274 };
2275 }
2276
2277 merge_field!(http);
2278 merge_field!(websocket);
2279 merge_field!(graphql);
2280 merge_field!(grpc);
2281 merge_field!(mqtt);
2282 merge_field!(smtp);
2283 merge_field!(ftp);
2284 merge_field!(kafka);
2285 merge_field!(amqp);
2286 merge_field!(tcp);
2287 merge_field!(admin);
2288 merge_field!(chaining);
2289 merge_field!(core);
2290 merge_field!(logging);
2291 merge_field!(data);
2292 merge_field!(mockai);
2293 merge_field!(observability);
2294 merge_field!(multi_tenant);
2295 merge_field!(routes);
2296 merge_field!(protocols);
2297
2298 base
2299}
2300
2301pub async fn load_config_with_profile<P: AsRef<Path>>(
2303 path: P,
2304 profile_name: Option<&str>,
2305) -> Result<ServerConfig> {
2306 let mut config = load_config_auto(&path).await?;
2308
2309 if let Some(profile) = profile_name {
2311 if let Some(profile_config) = config.profiles.remove(profile) {
2312 tracing::info!("Applying profile: {}", profile);
2313 config = apply_profile(config, profile_config);
2314 } else {
2315 return Err(Error::generic(format!(
2316 "Profile '{}' not found in configuration. Available profiles: {}",
2317 profile,
2318 config.profiles.keys().map(|k| k.as_str()).collect::<Vec<_>>().join(", ")
2319 )));
2320 }
2321 }
2322
2323 config.profiles.clear();
2325
2326 Ok(config)
2327}
2328
2329pub async fn load_config_from_js<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2331 use rquickjs::{Context, Runtime};
2332
2333 let content = fs::read_to_string(&path)
2334 .await
2335 .map_err(|e| Error::generic(format!("Failed to read JS/TS config file: {}", e)))?;
2336
2337 let runtime = Runtime::new()
2339 .map_err(|e| Error::generic(format!("Failed to create JS runtime: {}", e)))?;
2340 let context = Context::full(&runtime)
2341 .map_err(|e| Error::generic(format!("Failed to create JS context: {}", e)))?;
2342
2343 context.with(|ctx| {
2344 let js_content = if path
2347 .as_ref()
2348 .extension()
2349 .and_then(|s| s.to_str())
2350 .map(|ext| ext == "ts")
2351 .unwrap_or(false)
2352 {
2353 strip_typescript_types(&content)?
2354 } else {
2355 content
2356 };
2357
2358 let result: rquickjs::Value = ctx
2360 .eval(js_content.as_bytes())
2361 .map_err(|e| Error::generic(format!("Failed to evaluate JS config: {}", e)))?;
2362
2363 let json_str: String = ctx
2365 .json_stringify(result)
2366 .map_err(|e| Error::generic(format!("Failed to stringify JS config: {}", e)))?
2367 .ok_or_else(|| Error::generic("JS config returned undefined"))?
2368 .get()
2369 .map_err(|e| Error::generic(format!("Failed to get JSON string: {}", e)))?;
2370
2371 serde_json::from_str(&json_str).map_err(|e| {
2373 Error::generic(format!("Failed to parse JS config as ServerConfig: {}", e))
2374 })
2375 })
2376}
2377
2378fn strip_typescript_types(content: &str) -> Result<String> {
2385 use regex::Regex;
2386
2387 let mut result = content.to_string();
2388
2389 let interface_re = Regex::new(r"(?ms)interface\s+\w+\s*\{[^}]*\}\s*")
2395 .map_err(|e| Error::generic(format!("Failed to compile interface regex: {}", e)))?;
2396 result = interface_re.replace_all(&result, "").to_string();
2397
2398 let type_alias_re = Regex::new(r"(?m)^type\s+\w+\s*=\s*[^;]+;\s*")
2400 .map_err(|e| Error::generic(format!("Failed to compile type alias regex: {}", e)))?;
2401 result = type_alias_re.replace_all(&result, "").to_string();
2402
2403 let type_annotation_re = Regex::new(r":\s*[A-Z]\w*(<[^>]+>)?(\[\])?")
2405 .map_err(|e| Error::generic(format!("Failed to compile type annotation regex: {}", e)))?;
2406 result = type_annotation_re.replace_all(&result, "").to_string();
2407
2408 let type_import_re = Regex::new(r"(?m)^(import|export)\s+type\s+.*$")
2410 .map_err(|e| Error::generic(format!("Failed to compile type import regex: {}", e)))?;
2411 result = type_import_re.replace_all(&result, "").to_string();
2412
2413 let as_type_re = Regex::new(r"\s+as\s+\w+")
2415 .map_err(|e| Error::generic(format!("Failed to compile 'as type' regex: {}", e)))?;
2416 result = as_type_re.replace_all(&result, "").to_string();
2417
2418 Ok(result)
2419}
2420
2421pub async fn load_config_auto<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2423 let ext = path.as_ref().extension().and_then(|s| s.to_str()).unwrap_or("");
2424
2425 match ext {
2426 "ts" | "js" => load_config_from_js(&path).await,
2427 "yaml" | "yml" | "json" => load_config(&path).await,
2428 _ => Err(Error::generic(format!(
2429 "Unsupported config file format: {}. Supported: .ts, .js, .yaml, .yml, .json",
2430 ext
2431 ))),
2432 }
2433}
2434
2435pub async fn discover_config_file_all_formats() -> Result<std::path::PathBuf> {
2437 let current_dir = std::env::current_dir()
2438 .map_err(|e| Error::generic(format!("Failed to get current directory: {}", e)))?;
2439
2440 let config_names = vec![
2441 "mockforge.config.ts",
2442 "mockforge.config.js",
2443 "mockforge.yaml",
2444 "mockforge.yml",
2445 ".mockforge.yaml",
2446 ".mockforge.yml",
2447 ];
2448
2449 for name in &config_names {
2451 let path = current_dir.join(name);
2452 if tokio::fs::metadata(&path).await.is_ok() {
2453 return Ok(path);
2454 }
2455 }
2456
2457 let mut dir = current_dir.clone();
2459 for _ in 0..5 {
2460 if let Some(parent) = dir.parent() {
2461 for name in &config_names {
2462 let path = parent.join(name);
2463 if tokio::fs::metadata(&path).await.is_ok() {
2464 return Ok(path);
2465 }
2466 }
2467 dir = parent.to_path_buf();
2468 } else {
2469 break;
2470 }
2471 }
2472
2473 Err(Error::generic(
2474 "No configuration file found. Expected one of: mockforge.config.ts, mockforge.config.js, mockforge.yaml, mockforge.yml",
2475 ))
2476}
2477
2478#[cfg(test)]
2479mod tests {
2480 use super::*;
2481
2482 #[test]
2483 fn test_default_config() {
2484 let config = ServerConfig::default();
2485 assert_eq!(config.http.port, 3000);
2486 assert_eq!(config.websocket.port, 3001);
2487 assert_eq!(config.grpc.port, 50051);
2488 assert_eq!(config.admin.port, 9080);
2489 }
2490
2491 #[test]
2492 fn test_config_validation() {
2493 let mut config = ServerConfig::default();
2494 assert!(validate_config(&config).is_ok());
2495
2496 config.websocket.port = config.http.port;
2498 assert!(validate_config(&config).is_err());
2499
2500 config.websocket.port = 3001; config.logging.level = "invalid".to_string();
2503 assert!(validate_config(&config).is_err());
2504 }
2505
2506 #[test]
2507 fn test_apply_profile() {
2508 let mut base = ServerConfig::default();
2509 assert_eq!(base.http.port, 3000);
2510
2511 let mut profile = ProfileConfig::default();
2512 profile.http = Some(HttpConfig {
2513 port: 8080,
2514 ..Default::default()
2515 });
2516 profile.logging = Some(LoggingConfig {
2517 level: "debug".to_string(),
2518 ..Default::default()
2519 });
2520
2521 let merged = apply_profile(base, profile);
2522 assert_eq!(merged.http.port, 8080);
2523 assert_eq!(merged.logging.level, "debug");
2524 assert_eq!(merged.websocket.port, 3001); }
2526
2527 #[test]
2528 fn test_strip_typescript_types() {
2529 let ts_code = r#"
2530interface Config {
2531 port: number;
2532 host: string;
2533}
2534
2535const config: Config = {
2536 port: 3000,
2537 host: "localhost"
2538} as Config;
2539"#;
2540
2541 let stripped = strip_typescript_types(ts_code).expect("Should strip TypeScript types");
2542 assert!(!stripped.contains("interface"));
2543 assert!(!stripped.contains(": Config"));
2544 assert!(!stripped.contains("as Config"));
2545 }
2546}