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 #[serde(default = "default_mtls_mode")]
1033 pub mtls_mode: String,
1034}
1035
1036fn default_mtls_mode() -> String {
1037 "off".to_string()
1038}
1039
1040fn default_tls_min_version() -> String {
1041 "1.2".to_string()
1042}
1043
1044impl Default for HttpTlsConfig {
1045 fn default() -> Self {
1046 Self {
1047 enabled: true,
1048 cert_file: String::new(),
1049 key_file: String::new(),
1050 ca_file: None,
1051 min_version: "1.2".to_string(),
1052 cipher_suites: Vec::new(),
1053 require_client_cert: false,
1054 mtls_mode: "off".to_string(),
1055 }
1056 }
1057}
1058
1059#[derive(Debug, Clone, Serialize, Deserialize)]
1061#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1062#[serde(default)]
1063pub struct WebSocketConfig {
1064 pub enabled: bool,
1066 pub port: u16,
1068 pub host: String,
1070 pub replay_file: Option<String>,
1072 pub connection_timeout_secs: u64,
1074}
1075
1076impl Default for WebSocketConfig {
1077 fn default() -> Self {
1078 Self {
1079 enabled: true,
1080 port: 3001,
1081 host: "0.0.0.0".to_string(),
1082 replay_file: None,
1083 connection_timeout_secs: 300,
1084 }
1085 }
1086}
1087
1088#[derive(Debug, Clone, Serialize, Deserialize)]
1090#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1091#[serde(default)]
1092pub struct GrpcConfig {
1093 pub enabled: bool,
1095 pub port: u16,
1097 pub host: String,
1099 pub proto_dir: Option<String>,
1101 pub tls: Option<TlsConfig>,
1103}
1104
1105impl Default for GrpcConfig {
1106 fn default() -> Self {
1107 Self {
1108 enabled: true,
1109 port: 50051,
1110 host: "0.0.0.0".to_string(),
1111 proto_dir: None,
1112 tls: None,
1113 }
1114 }
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1119#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1120#[serde(default)]
1121pub struct GraphQLConfig {
1122 pub enabled: bool,
1124 pub port: u16,
1126 pub host: String,
1128 pub schema_path: Option<String>,
1130 pub handlers_dir: Option<String>,
1132 pub playground_enabled: bool,
1134 pub upstream_url: Option<String>,
1136 pub introspection_enabled: bool,
1138}
1139
1140impl Default for GraphQLConfig {
1141 fn default() -> Self {
1142 Self {
1143 enabled: true,
1144 port: 4000,
1145 host: "0.0.0.0".to_string(),
1146 schema_path: None,
1147 handlers_dir: None,
1148 playground_enabled: true,
1149 upstream_url: None,
1150 introspection_enabled: true,
1151 }
1152 }
1153}
1154
1155#[derive(Debug, Clone, Serialize, Deserialize)]
1157#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1158pub struct TlsConfig {
1159 pub cert_path: String,
1161 pub key_path: String,
1163}
1164
1165#[derive(Debug, Clone, Serialize, Deserialize)]
1167#[serde(default)]
1168#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1169pub struct MqttConfig {
1170 pub enabled: bool,
1172 pub port: u16,
1174 pub host: String,
1176 pub max_connections: usize,
1178 pub max_packet_size: usize,
1180 pub keep_alive_secs: u16,
1182 pub fixtures_dir: Option<std::path::PathBuf>,
1184 pub enable_retained_messages: bool,
1186 pub max_retained_messages: usize,
1188}
1189
1190impl Default for MqttConfig {
1191 fn default() -> Self {
1192 Self {
1193 enabled: false,
1194 port: 1883,
1195 host: "0.0.0.0".to_string(),
1196 max_connections: 1000,
1197 max_packet_size: 268435456, keep_alive_secs: 60,
1199 fixtures_dir: None,
1200 enable_retained_messages: true,
1201 max_retained_messages: 10000,
1202 }
1203 }
1204}
1205
1206#[derive(Debug, Clone, Serialize, Deserialize)]
1208#[serde(default)]
1209#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1210pub struct SmtpConfig {
1211 pub enabled: bool,
1213 pub port: u16,
1215 pub host: String,
1217 pub hostname: String,
1219 pub fixtures_dir: Option<std::path::PathBuf>,
1221 pub timeout_secs: u64,
1223 pub max_connections: usize,
1225 pub enable_mailbox: bool,
1227 pub max_mailbox_messages: usize,
1229 pub enable_starttls: bool,
1231 pub tls_cert_path: Option<std::path::PathBuf>,
1233 pub tls_key_path: Option<std::path::PathBuf>,
1235}
1236
1237impl Default for SmtpConfig {
1238 fn default() -> Self {
1239 Self {
1240 enabled: false,
1241 port: 1025,
1242 host: "0.0.0.0".to_string(),
1243 hostname: "mockforge-smtp".to_string(),
1244 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
1245 timeout_secs: 300,
1246 max_connections: 10,
1247 enable_mailbox: true,
1248 max_mailbox_messages: 1000,
1249 enable_starttls: false,
1250 tls_cert_path: None,
1251 tls_key_path: None,
1252 }
1253 }
1254}
1255
1256#[derive(Debug, Clone, Serialize, Deserialize)]
1258#[serde(default)]
1259#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1260pub struct FtpConfig {
1261 pub enabled: bool,
1263 pub port: u16,
1265 pub host: String,
1267 pub passive_ports: (u16, u16),
1269 pub max_connections: usize,
1271 pub timeout_secs: u64,
1273 pub allow_anonymous: bool,
1275 pub fixtures_dir: Option<std::path::PathBuf>,
1277 pub virtual_root: std::path::PathBuf,
1279}
1280
1281impl Default for FtpConfig {
1282 fn default() -> Self {
1283 Self {
1284 enabled: false,
1285 port: 2121,
1286 host: "0.0.0.0".to_string(),
1287 passive_ports: (50000, 51000),
1288 max_connections: 100,
1289 timeout_secs: 300,
1290 allow_anonymous: true,
1291 fixtures_dir: None,
1292 virtual_root: std::path::PathBuf::from("/mockforge"),
1293 }
1294 }
1295}
1296
1297#[derive(Debug, Clone, Serialize, Deserialize)]
1299#[serde(default)]
1300#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1301pub struct KafkaConfig {
1302 pub enabled: bool,
1304 pub port: u16,
1306 pub host: String,
1308 pub broker_id: i32,
1310 pub max_connections: usize,
1312 pub log_retention_ms: i64,
1314 pub log_segment_bytes: i64,
1316 pub fixtures_dir: Option<std::path::PathBuf>,
1318 pub auto_create_topics: bool,
1320 pub default_partitions: i32,
1322 pub default_replication_factor: i16,
1324}
1325
1326impl Default for KafkaConfig {
1327 fn default() -> Self {
1328 Self {
1329 enabled: false,
1330 port: 9092, host: "0.0.0.0".to_string(),
1332 broker_id: 1,
1333 max_connections: 1000,
1334 log_retention_ms: 604800000, log_segment_bytes: 1073741824, fixtures_dir: None,
1337 auto_create_topics: true,
1338 default_partitions: 3,
1339 default_replication_factor: 1,
1340 }
1341 }
1342}
1343
1344#[derive(Debug, Clone, Serialize, Deserialize)]
1346#[serde(default)]
1347#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1348pub struct AmqpConfig {
1349 pub enabled: bool,
1351 pub port: u16,
1353 pub host: String,
1355 pub max_connections: usize,
1357 pub max_channels_per_connection: u16,
1359 pub frame_max: u32,
1361 pub heartbeat_interval: u16,
1363 pub fixtures_dir: Option<std::path::PathBuf>,
1365 pub virtual_hosts: Vec<String>,
1367}
1368
1369impl Default for AmqpConfig {
1370 fn default() -> Self {
1371 Self {
1372 enabled: false,
1373 port: 5672, host: "0.0.0.0".to_string(),
1375 max_connections: 1000,
1376 max_channels_per_connection: 100,
1377 frame_max: 131072, heartbeat_interval: 60,
1379 fixtures_dir: None,
1380 virtual_hosts: vec!["/".to_string()],
1381 }
1382 }
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize)]
1387#[serde(default)]
1388#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1389pub struct TcpConfig {
1390 pub enabled: bool,
1392 pub port: u16,
1394 pub host: String,
1396 pub max_connections: usize,
1398 pub timeout_secs: u64,
1400 pub fixtures_dir: Option<std::path::PathBuf>,
1402 pub echo_mode: bool,
1404 pub enable_tls: bool,
1406 pub tls_cert_path: Option<std::path::PathBuf>,
1408 pub tls_key_path: Option<std::path::PathBuf>,
1410}
1411
1412impl Default for TcpConfig {
1413 fn default() -> Self {
1414 Self {
1415 enabled: false,
1416 port: 9999,
1417 host: "0.0.0.0".to_string(),
1418 max_connections: 1000,
1419 timeout_secs: 300,
1420 fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
1421 echo_mode: true,
1422 enable_tls: false,
1423 tls_cert_path: None,
1424 tls_key_path: None,
1425 }
1426 }
1427}
1428
1429#[derive(Debug, Clone, Serialize, Deserialize)]
1431#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1432#[serde(default)]
1433pub struct AdminConfig {
1434 pub enabled: bool,
1436 pub port: u16,
1438 pub host: String,
1440 pub auth_required: bool,
1442 pub username: Option<String>,
1444 pub password: Option<String>,
1446 pub mount_path: Option<String>,
1448 pub api_enabled: bool,
1450 pub prometheus_url: String,
1452}
1453
1454impl Default for AdminConfig {
1455 fn default() -> Self {
1456 let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
1459 || std::env::var("container").is_ok()
1460 || std::path::Path::new("/.dockerenv").exists()
1461 {
1462 "0.0.0.0".to_string()
1463 } else {
1464 "127.0.0.1".to_string()
1465 };
1466
1467 Self {
1468 enabled: false,
1469 port: 9080,
1470 host: default_host,
1471 auth_required: false,
1472 username: None,
1473 password: None,
1474 mount_path: None,
1475 api_enabled: true,
1476 prometheus_url: "http://localhost:9090".to_string(),
1477 }
1478 }
1479}
1480
1481#[derive(Debug, Clone, Serialize, Deserialize)]
1483#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1484#[serde(default)]
1485pub struct LoggingConfig {
1486 pub level: String,
1488 pub json_format: bool,
1490 pub file_path: Option<String>,
1492 pub max_file_size_mb: u64,
1494 pub max_files: u32,
1496}
1497
1498impl Default for LoggingConfig {
1499 fn default() -> Self {
1500 Self {
1501 level: "info".to_string(),
1502 json_format: false,
1503 file_path: None,
1504 max_file_size_mb: 10,
1505 max_files: 5,
1506 }
1507 }
1508}
1509
1510#[derive(Debug, Clone, Serialize, Deserialize)]
1512#[serde(default, rename_all = "camelCase")]
1513#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1514pub struct ChainingConfig {
1515 pub enabled: bool,
1517 pub max_chain_length: usize,
1519 pub global_timeout_secs: u64,
1521 pub enable_parallel_execution: bool,
1523}
1524
1525impl Default for ChainingConfig {
1526 fn default() -> Self {
1527 Self {
1528 enabled: false,
1529 max_chain_length: 20,
1530 global_timeout_secs: 300,
1531 enable_parallel_execution: false,
1532 }
1533 }
1534}
1535
1536#[derive(Debug, Clone, Serialize, Deserialize)]
1538#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1539#[serde(default)]
1540pub struct DataConfig {
1541 pub default_rows: usize,
1543 pub default_format: String,
1545 pub locale: String,
1547 pub templates: HashMap<String, String>,
1549 pub rag: RagConfig,
1551 #[serde(skip_serializing_if = "Option::is_none")]
1553 pub persona_domain: Option<String>,
1554 #[serde(default = "default_false")]
1556 pub persona_consistency_enabled: bool,
1557 #[serde(skip_serializing_if = "Option::is_none")]
1559 pub persona_registry: Option<PersonaRegistryConfig>,
1560}
1561
1562impl Default for DataConfig {
1563 fn default() -> Self {
1564 Self {
1565 default_rows: 100,
1566 default_format: "json".to_string(),
1567 locale: "en".to_string(),
1568 templates: HashMap::new(),
1569 rag: RagConfig::default(),
1570 persona_domain: None,
1571 persona_consistency_enabled: false,
1572 persona_registry: None,
1573 }
1574 }
1575}
1576
1577#[derive(Debug, Clone, Serialize, Deserialize)]
1579#[serde(default)]
1580#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1581pub struct RagConfig {
1582 pub enabled: bool,
1584 #[serde(default)]
1586 pub provider: String,
1587 pub api_endpoint: Option<String>,
1589 pub api_key: Option<String>,
1591 pub model: Option<String>,
1593 #[serde(default = "default_max_tokens")]
1595 pub max_tokens: usize,
1596 #[serde(default = "default_temperature")]
1598 pub temperature: f64,
1599 pub context_window: usize,
1601 #[serde(default = "default_true")]
1603 pub caching: bool,
1604 #[serde(default = "default_cache_ttl")]
1606 pub cache_ttl_secs: u64,
1607 #[serde(default = "default_timeout")]
1609 pub timeout_secs: u64,
1610 #[serde(default = "default_max_retries")]
1612 pub max_retries: usize,
1613}
1614
1615fn default_max_tokens() -> usize {
1616 1024
1617}
1618
1619fn default_temperature() -> f64 {
1620 0.7
1621}
1622
1623fn default_true() -> bool {
1624 true
1625}
1626
1627fn default_cache_ttl() -> u64 {
1628 3600
1629}
1630
1631fn default_timeout() -> u64 {
1632 30
1633}
1634
1635fn default_max_retries() -> usize {
1636 3
1637}
1638
1639fn default_false() -> bool {
1640 false
1641}
1642
1643impl Default for RagConfig {
1644 fn default() -> Self {
1645 Self {
1646 enabled: false,
1647 provider: "openai".to_string(),
1648 api_endpoint: None,
1649 api_key: None,
1650 model: Some("gpt-3.5-turbo".to_string()),
1651 max_tokens: default_max_tokens(),
1652 temperature: default_temperature(),
1653 context_window: 4000,
1654 caching: default_true(),
1655 cache_ttl_secs: default_cache_ttl(),
1656 timeout_secs: default_timeout(),
1657 max_retries: default_max_retries(),
1658 }
1659 }
1660}
1661
1662#[derive(Debug, Clone, Serialize, Deserialize)]
1664#[serde(default)]
1665#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1666#[derive(Default)]
1667pub struct PersonaRegistryConfig {
1668 #[serde(default = "default_false")]
1670 pub persistent: bool,
1671 #[serde(skip_serializing_if = "Option::is_none")]
1673 pub storage_path: Option<String>,
1674 #[serde(default)]
1676 pub default_traits: HashMap<String, String>,
1677}
1678
1679#[derive(Debug, Clone, Serialize, Deserialize)]
1681#[serde(default)]
1682#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1683pub struct MockAIConfig {
1684 pub enabled: bool,
1686 pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
1688 pub auto_learn: bool,
1690 pub mutation_detection: bool,
1692 pub ai_validation_errors: bool,
1694 pub intelligent_pagination: bool,
1696 #[serde(default)]
1698 pub enabled_endpoints: Vec<String>,
1699}
1700
1701impl Default for MockAIConfig {
1702 fn default() -> Self {
1703 Self {
1704 enabled: false,
1705 intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
1706 auto_learn: true,
1707 mutation_detection: true,
1708 ai_validation_errors: true,
1709 intelligent_pagination: true,
1710 enabled_endpoints: Vec::new(),
1711 }
1712 }
1713}
1714
1715#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1717#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1718#[serde(default)]
1719pub struct ObservabilityConfig {
1720 pub prometheus: PrometheusConfig,
1722 pub opentelemetry: Option<OpenTelemetryConfig>,
1724 pub recorder: Option<RecorderConfig>,
1726 pub chaos: Option<ChaosEngConfig>,
1728}
1729
1730#[derive(Debug, Clone, Serialize, Deserialize)]
1732#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1733#[serde(default)]
1734#[derive(Default)]
1735pub struct SecurityConfig {
1736 pub monitoring: SecurityMonitoringConfig,
1738}
1739
1740#[derive(Debug, Clone, Serialize, Deserialize)]
1742#[serde(default)]
1743#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1744#[derive(Default)]
1745pub struct SecurityMonitoringConfig {
1746 pub siem: crate::security::siem::SiemConfig,
1748 pub access_review: crate::security::access_review::AccessReviewConfig,
1750 pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
1752 pub change_management: crate::security::change_management::ChangeManagementConfig,
1754 pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
1756 pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
1758}
1759
1760#[derive(Debug, Clone, Serialize, Deserialize)]
1762#[serde(default)]
1763#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1764pub struct PrometheusConfig {
1765 pub enabled: bool,
1767 pub port: u16,
1769 pub host: String,
1771 pub path: String,
1773}
1774
1775impl Default for PrometheusConfig {
1776 fn default() -> Self {
1777 Self {
1778 enabled: true,
1779 port: 9090,
1780 host: "0.0.0.0".to_string(),
1781 path: "/metrics".to_string(),
1782 }
1783 }
1784}
1785
1786#[derive(Debug, Clone, Serialize, Deserialize)]
1788#[serde(default)]
1789#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1790pub struct OpenTelemetryConfig {
1791 pub enabled: bool,
1793 pub service_name: String,
1795 pub environment: String,
1797 pub jaeger_endpoint: String,
1799 pub otlp_endpoint: Option<String>,
1801 pub protocol: String,
1803 pub sampling_rate: f64,
1805}
1806
1807impl Default for OpenTelemetryConfig {
1808 fn default() -> Self {
1809 Self {
1810 enabled: false,
1811 service_name: "mockforge".to_string(),
1812 environment: "development".to_string(),
1813 jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
1814 otlp_endpoint: Some("http://localhost:4317".to_string()),
1815 protocol: "grpc".to_string(),
1816 sampling_rate: 1.0,
1817 }
1818 }
1819}
1820
1821#[derive(Debug, Clone, Serialize, Deserialize)]
1823#[serde(default)]
1824#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1825pub struct RecorderConfig {
1826 pub enabled: bool,
1828 pub database_path: String,
1830 pub api_enabled: bool,
1832 pub api_port: Option<u16>,
1834 pub max_requests: i64,
1836 pub retention_days: i64,
1838 pub record_http: bool,
1840 pub record_grpc: bool,
1842 pub record_websocket: bool,
1844 pub record_graphql: bool,
1846 #[serde(default = "default_true")]
1849 pub record_proxy: bool,
1850}
1851
1852impl Default for RecorderConfig {
1853 fn default() -> Self {
1854 Self {
1855 enabled: false,
1856 database_path: "./mockforge-recordings.db".to_string(),
1857 api_enabled: true,
1858 api_port: None,
1859 max_requests: 10000,
1860 retention_days: 7,
1861 record_http: true,
1862 record_grpc: true,
1863 record_websocket: true,
1864 record_graphql: true,
1865 record_proxy: true,
1866 }
1867 }
1868}
1869
1870#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1872#[serde(default)]
1873#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1874pub struct ChaosEngConfig {
1875 pub enabled: bool,
1877 pub latency: Option<LatencyInjectionConfig>,
1879 pub fault_injection: Option<FaultConfig>,
1881 pub rate_limit: Option<RateLimitingConfig>,
1883 pub traffic_shaping: Option<NetworkShapingConfig>,
1885 pub scenario: Option<String>,
1887}
1888
1889#[derive(Debug, Clone, Serialize, Deserialize)]
1891#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1892pub struct LatencyInjectionConfig {
1893 pub enabled: bool,
1895 pub fixed_delay_ms: Option<u64>,
1897 pub random_delay_range_ms: Option<(u64, u64)>,
1899 pub jitter_percent: f64,
1901 pub probability: f64,
1903}
1904
1905#[derive(Debug, Clone, Serialize, Deserialize)]
1907#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1908pub struct FaultConfig {
1909 pub enabled: bool,
1911 pub http_errors: Vec<u16>,
1913 pub http_error_probability: f64,
1915 pub connection_errors: bool,
1917 pub connection_error_probability: f64,
1919 pub timeout_errors: bool,
1921 pub timeout_ms: u64,
1923 pub timeout_probability: f64,
1925}
1926
1927#[derive(Debug, Clone, Serialize, Deserialize)]
1929#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1930pub struct RateLimitingConfig {
1931 pub enabled: bool,
1933 pub requests_per_second: u32,
1935 pub burst_size: u32,
1937 pub per_ip: bool,
1939 pub per_endpoint: bool,
1941}
1942
1943#[derive(Debug, Clone, Serialize, Deserialize)]
1945#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1946pub struct NetworkShapingConfig {
1947 pub enabled: bool,
1949 pub bandwidth_limit_bps: u64,
1951 pub packet_loss_percent: f64,
1953 pub max_connections: u32,
1955}
1956
1957pub async fn load_config<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
1959 let content = fs::read_to_string(&path)
1960 .await
1961 .map_err(|e| Error::generic(format!("Failed to read config file: {}", e)))?;
1962
1963 let config: ServerConfig = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
1965 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
1966 {
1967 serde_yaml::from_str(&content).map_err(|e| {
1968 let error_msg = e.to_string();
1970 let mut full_msg = format!("Failed to parse YAML config: {}", error_msg);
1971
1972 if error_msg.contains("missing field") {
1974 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
1975 full_msg.push_str(
1976 "\n Omit fields you don't need - MockForge will use sensible defaults.",
1977 );
1978 full_msg.push_str("\n See config.template.yaml for all available options.");
1979 } else if error_msg.contains("unknown field") {
1980 full_msg.push_str("\n\n💡 Check for typos in field names.");
1981 full_msg.push_str("\n See config.template.yaml for valid field names.");
1982 }
1983
1984 Error::generic(full_msg)
1985 })?
1986 } else {
1987 serde_json::from_str(&content).map_err(|e| {
1988 let error_msg = e.to_string();
1990 let mut full_msg = format!("Failed to parse JSON config: {}", error_msg);
1991
1992 if error_msg.contains("missing field") {
1994 full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
1995 full_msg.push_str(
1996 "\n Omit fields you don't need - MockForge will use sensible defaults.",
1997 );
1998 full_msg.push_str("\n See config.template.yaml for all available options.");
1999 } else if error_msg.contains("unknown field") {
2000 full_msg.push_str("\n\n💡 Check for typos in field names.");
2001 full_msg.push_str("\n See config.template.yaml for valid field names.");
2002 }
2003
2004 Error::generic(full_msg)
2005 })?
2006 };
2007
2008 Ok(config)
2009}
2010
2011pub async fn save_config<P: AsRef<Path>>(path: P, config: &ServerConfig) -> Result<()> {
2013 let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2014 || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2015 {
2016 serde_yaml::to_string(config)
2017 .map_err(|e| Error::generic(format!("Failed to serialize config to YAML: {}", e)))?
2018 } else {
2019 serde_json::to_string_pretty(config)
2020 .map_err(|e| Error::generic(format!("Failed to serialize config to JSON: {}", e)))?
2021 };
2022
2023 fs::write(path, content)
2024 .await
2025 .map_err(|e| Error::generic(format!("Failed to write config file: {}", e)))?;
2026
2027 Ok(())
2028}
2029
2030pub async fn load_config_with_fallback<P: AsRef<Path>>(path: P) -> ServerConfig {
2032 match load_config(&path).await {
2033 Ok(config) => {
2034 tracing::info!("Loaded configuration from {:?}", path.as_ref());
2035 config
2036 }
2037 Err(e) => {
2038 tracing::warn!(
2039 "Failed to load config from {:?}: {}. Using defaults.",
2040 path.as_ref(),
2041 e
2042 );
2043 ServerConfig::default()
2044 }
2045 }
2046}
2047
2048pub async fn create_default_config<P: AsRef<Path>>(path: P) -> Result<()> {
2050 let config = ServerConfig::default();
2051 save_config(path, &config).await?;
2052 Ok(())
2053}
2054
2055pub fn apply_env_overrides(mut config: ServerConfig) -> ServerConfig {
2057 if let Ok(port) = std::env::var("MOCKFORGE_HTTP_PORT") {
2059 if let Ok(port_num) = port.parse() {
2060 config.http.port = port_num;
2061 }
2062 }
2063
2064 if let Ok(host) = std::env::var("MOCKFORGE_HTTP_HOST") {
2065 config.http.host = host;
2066 }
2067
2068 if let Ok(port) = std::env::var("MOCKFORGE_WS_PORT") {
2070 if let Ok(port_num) = port.parse() {
2071 config.websocket.port = port_num;
2072 }
2073 }
2074
2075 if let Ok(port) = std::env::var("MOCKFORGE_GRPC_PORT") {
2077 if let Ok(port_num) = port.parse() {
2078 config.grpc.port = port_num;
2079 }
2080 }
2081
2082 if let Ok(port) = std::env::var("MOCKFORGE_SMTP_PORT") {
2084 if let Ok(port_num) = port.parse() {
2085 config.smtp.port = port_num;
2086 }
2087 }
2088
2089 if let Ok(host) = std::env::var("MOCKFORGE_SMTP_HOST") {
2090 config.smtp.host = host;
2091 }
2092
2093 if let Ok(enabled) = std::env::var("MOCKFORGE_SMTP_ENABLED") {
2094 config.smtp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2095 }
2096
2097 if let Ok(hostname) = std::env::var("MOCKFORGE_SMTP_HOSTNAME") {
2098 config.smtp.hostname = hostname;
2099 }
2100
2101 if let Ok(port) = std::env::var("MOCKFORGE_TCP_PORT") {
2103 if let Ok(port_num) = port.parse() {
2104 config.tcp.port = port_num;
2105 }
2106 }
2107
2108 if let Ok(host) = std::env::var("MOCKFORGE_TCP_HOST") {
2109 config.tcp.host = host;
2110 }
2111
2112 if let Ok(enabled) = std::env::var("MOCKFORGE_TCP_ENABLED") {
2113 config.tcp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2114 }
2115
2116 if let Ok(port) = std::env::var("MOCKFORGE_ADMIN_PORT") {
2118 if let Ok(port_num) = port.parse() {
2119 config.admin.port = port_num;
2120 }
2121 }
2122
2123 if std::env::var("MOCKFORGE_ADMIN_ENABLED").unwrap_or_default() == "true" {
2124 config.admin.enabled = true;
2125 }
2126
2127 if let Ok(host) = std::env::var("MOCKFORGE_ADMIN_HOST") {
2129 config.admin.host = host;
2130 }
2131
2132 if let Ok(mount_path) = std::env::var("MOCKFORGE_ADMIN_MOUNT_PATH") {
2133 if !mount_path.trim().is_empty() {
2134 config.admin.mount_path = Some(mount_path);
2135 }
2136 }
2137
2138 if let Ok(api_enabled) = std::env::var("MOCKFORGE_ADMIN_API_ENABLED") {
2139 let on = api_enabled == "1" || api_enabled.eq_ignore_ascii_case("true");
2140 config.admin.api_enabled = on;
2141 }
2142
2143 if let Ok(prometheus_url) = std::env::var("PROMETHEUS_URL") {
2144 config.admin.prometheus_url = prometheus_url;
2145 }
2146
2147 if let Ok(latency_enabled) = std::env::var("MOCKFORGE_LATENCY_ENABLED") {
2149 let enabled = latency_enabled == "1" || latency_enabled.eq_ignore_ascii_case("true");
2150 config.core.latency_enabled = enabled;
2151 }
2152
2153 if let Ok(failures_enabled) = std::env::var("MOCKFORGE_FAILURES_ENABLED") {
2154 let enabled = failures_enabled == "1" || failures_enabled.eq_ignore_ascii_case("true");
2155 config.core.failures_enabled = enabled;
2156 }
2157
2158 if let Ok(overrides_enabled) = std::env::var("MOCKFORGE_OVERRIDES_ENABLED") {
2159 let enabled = overrides_enabled == "1" || overrides_enabled.eq_ignore_ascii_case("true");
2160 config.core.overrides_enabled = enabled;
2161 }
2162
2163 if let Ok(traffic_shaping_enabled) = std::env::var("MOCKFORGE_TRAFFIC_SHAPING_ENABLED") {
2164 let enabled =
2165 traffic_shaping_enabled == "1" || traffic_shaping_enabled.eq_ignore_ascii_case("true");
2166 config.core.traffic_shaping_enabled = enabled;
2167 }
2168
2169 if let Ok(bandwidth_enabled) = std::env::var("MOCKFORGE_BANDWIDTH_ENABLED") {
2171 let enabled = bandwidth_enabled == "1" || bandwidth_enabled.eq_ignore_ascii_case("true");
2172 config.core.traffic_shaping.bandwidth.enabled = enabled;
2173 }
2174
2175 if let Ok(max_bytes_per_sec) = std::env::var("MOCKFORGE_BANDWIDTH_MAX_BYTES_PER_SEC") {
2176 if let Ok(bytes) = max_bytes_per_sec.parse() {
2177 config.core.traffic_shaping.bandwidth.max_bytes_per_sec = bytes;
2178 config.core.traffic_shaping.bandwidth.enabled = true;
2179 }
2180 }
2181
2182 if let Ok(burst_capacity) = std::env::var("MOCKFORGE_BANDWIDTH_BURST_CAPACITY_BYTES") {
2183 if let Ok(bytes) = burst_capacity.parse() {
2184 config.core.traffic_shaping.bandwidth.burst_capacity_bytes = bytes;
2185 }
2186 }
2187
2188 if let Ok(burst_loss_enabled) = std::env::var("MOCKFORGE_BURST_LOSS_ENABLED") {
2189 let enabled = burst_loss_enabled == "1" || burst_loss_enabled.eq_ignore_ascii_case("true");
2190 config.core.traffic_shaping.burst_loss.enabled = enabled;
2191 }
2192
2193 if let Ok(burst_probability) = std::env::var("MOCKFORGE_BURST_LOSS_PROBABILITY") {
2194 if let Ok(prob) = burst_probability.parse::<f64>() {
2195 config.core.traffic_shaping.burst_loss.burst_probability = prob.clamp(0.0, 1.0);
2196 config.core.traffic_shaping.burst_loss.enabled = true;
2197 }
2198 }
2199
2200 if let Ok(burst_duration) = std::env::var("MOCKFORGE_BURST_LOSS_DURATION_MS") {
2201 if let Ok(ms) = burst_duration.parse() {
2202 config.core.traffic_shaping.burst_loss.burst_duration_ms = ms;
2203 }
2204 }
2205
2206 if let Ok(loss_rate) = std::env::var("MOCKFORGE_BURST_LOSS_RATE") {
2207 if let Ok(rate) = loss_rate.parse::<f64>() {
2208 config.core.traffic_shaping.burst_loss.loss_rate_during_burst = rate.clamp(0.0, 1.0);
2209 }
2210 }
2211
2212 if let Ok(recovery_time) = std::env::var("MOCKFORGE_BURST_LOSS_RECOVERY_MS") {
2213 if let Ok(ms) = recovery_time.parse() {
2214 config.core.traffic_shaping.burst_loss.recovery_time_ms = ms;
2215 }
2216 }
2217
2218 if let Ok(level) = std::env::var("MOCKFORGE_LOG_LEVEL") {
2220 config.logging.level = level;
2221 }
2222
2223 config
2224}
2225
2226pub fn validate_config(config: &ServerConfig) -> Result<()> {
2228 if config.http.port == 0 {
2230 return Err(Error::generic("HTTP port cannot be 0"));
2231 }
2232 if config.websocket.port == 0 {
2233 return Err(Error::generic("WebSocket port cannot be 0"));
2234 }
2235 if config.grpc.port == 0 {
2236 return Err(Error::generic("gRPC port cannot be 0"));
2237 }
2238 if config.admin.port == 0 {
2239 return Err(Error::generic("Admin port cannot be 0"));
2240 }
2241
2242 let ports = [
2244 ("HTTP", config.http.port),
2245 ("WebSocket", config.websocket.port),
2246 ("gRPC", config.grpc.port),
2247 ("Admin", config.admin.port),
2248 ];
2249
2250 for i in 0..ports.len() {
2251 for j in (i + 1)..ports.len() {
2252 if ports[i].1 == ports[j].1 {
2253 return Err(Error::generic(format!(
2254 "Port conflict: {} and {} both use port {}",
2255 ports[i].0, ports[j].0, ports[i].1
2256 )));
2257 }
2258 }
2259 }
2260
2261 let valid_levels = ["trace", "debug", "info", "warn", "error"];
2263 if !valid_levels.contains(&config.logging.level.as_str()) {
2264 return Err(Error::generic(format!(
2265 "Invalid log level: {}. Valid levels: {}",
2266 config.logging.level,
2267 valid_levels.join(", ")
2268 )));
2269 }
2270
2271 Ok(())
2272}
2273
2274pub fn apply_profile(mut base: ServerConfig, profile: ProfileConfig) -> ServerConfig {
2276 macro_rules! merge_field {
2278 ($field:ident) => {
2279 if let Some(override_val) = profile.$field {
2280 base.$field = override_val;
2281 }
2282 };
2283 }
2284
2285 merge_field!(http);
2286 merge_field!(websocket);
2287 merge_field!(graphql);
2288 merge_field!(grpc);
2289 merge_field!(mqtt);
2290 merge_field!(smtp);
2291 merge_field!(ftp);
2292 merge_field!(kafka);
2293 merge_field!(amqp);
2294 merge_field!(tcp);
2295 merge_field!(admin);
2296 merge_field!(chaining);
2297 merge_field!(core);
2298 merge_field!(logging);
2299 merge_field!(data);
2300 merge_field!(mockai);
2301 merge_field!(observability);
2302 merge_field!(multi_tenant);
2303 merge_field!(routes);
2304 merge_field!(protocols);
2305
2306 base
2307}
2308
2309pub async fn load_config_with_profile<P: AsRef<Path>>(
2311 path: P,
2312 profile_name: Option<&str>,
2313) -> Result<ServerConfig> {
2314 let mut config = load_config_auto(&path).await?;
2316
2317 if let Some(profile) = profile_name {
2319 if let Some(profile_config) = config.profiles.remove(profile) {
2320 tracing::info!("Applying profile: {}", profile);
2321 config = apply_profile(config, profile_config);
2322 } else {
2323 return Err(Error::generic(format!(
2324 "Profile '{}' not found in configuration. Available profiles: {}",
2325 profile,
2326 config.profiles.keys().map(|k| k.as_str()).collect::<Vec<_>>().join(", ")
2327 )));
2328 }
2329 }
2330
2331 config.profiles.clear();
2333
2334 Ok(config)
2335}
2336
2337pub async fn load_config_from_js<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2339 use rquickjs::{Context, Runtime};
2340
2341 let content = fs::read_to_string(&path)
2342 .await
2343 .map_err(|e| Error::generic(format!("Failed to read JS/TS config file: {}", e)))?;
2344
2345 let runtime = Runtime::new()
2347 .map_err(|e| Error::generic(format!("Failed to create JS runtime: {}", e)))?;
2348 let context = Context::full(&runtime)
2349 .map_err(|e| Error::generic(format!("Failed to create JS context: {}", e)))?;
2350
2351 context.with(|ctx| {
2352 let js_content = if path
2355 .as_ref()
2356 .extension()
2357 .and_then(|s| s.to_str())
2358 .map(|ext| ext == "ts")
2359 .unwrap_or(false)
2360 {
2361 strip_typescript_types(&content)?
2362 } else {
2363 content
2364 };
2365
2366 let result: rquickjs::Value = ctx
2368 .eval(js_content.as_bytes())
2369 .map_err(|e| Error::generic(format!("Failed to evaluate JS config: {}", e)))?;
2370
2371 let json_str: String = ctx
2373 .json_stringify(result)
2374 .map_err(|e| Error::generic(format!("Failed to stringify JS config: {}", e)))?
2375 .ok_or_else(|| Error::generic("JS config returned undefined"))?
2376 .get()
2377 .map_err(|e| Error::generic(format!("Failed to get JSON string: {}", e)))?;
2378
2379 serde_json::from_str(&json_str).map_err(|e| {
2381 Error::generic(format!("Failed to parse JS config as ServerConfig: {}", e))
2382 })
2383 })
2384}
2385
2386fn strip_typescript_types(content: &str) -> Result<String> {
2393 use regex::Regex;
2394
2395 let mut result = content.to_string();
2396
2397 let interface_re = Regex::new(r"(?ms)interface\s+\w+\s*\{[^}]*\}\s*")
2403 .map_err(|e| Error::generic(format!("Failed to compile interface regex: {}", e)))?;
2404 result = interface_re.replace_all(&result, "").to_string();
2405
2406 let type_alias_re = Regex::new(r"(?m)^type\s+\w+\s*=\s*[^;]+;\s*")
2408 .map_err(|e| Error::generic(format!("Failed to compile type alias regex: {}", e)))?;
2409 result = type_alias_re.replace_all(&result, "").to_string();
2410
2411 let type_annotation_re = Regex::new(r":\s*[A-Z]\w*(<[^>]+>)?(\[\])?")
2413 .map_err(|e| Error::generic(format!("Failed to compile type annotation regex: {}", e)))?;
2414 result = type_annotation_re.replace_all(&result, "").to_string();
2415
2416 let type_import_re = Regex::new(r"(?m)^(import|export)\s+type\s+.*$")
2418 .map_err(|e| Error::generic(format!("Failed to compile type import regex: {}", e)))?;
2419 result = type_import_re.replace_all(&result, "").to_string();
2420
2421 let as_type_re = Regex::new(r"\s+as\s+\w+")
2423 .map_err(|e| Error::generic(format!("Failed to compile 'as type' regex: {}", e)))?;
2424 result = as_type_re.replace_all(&result, "").to_string();
2425
2426 Ok(result)
2427}
2428
2429pub async fn load_config_auto<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2431 let ext = path.as_ref().extension().and_then(|s| s.to_str()).unwrap_or("");
2432
2433 match ext {
2434 "ts" | "js" => load_config_from_js(&path).await,
2435 "yaml" | "yml" | "json" => load_config(&path).await,
2436 _ => Err(Error::generic(format!(
2437 "Unsupported config file format: {}. Supported: .ts, .js, .yaml, .yml, .json",
2438 ext
2439 ))),
2440 }
2441}
2442
2443pub async fn discover_config_file_all_formats() -> Result<std::path::PathBuf> {
2445 let current_dir = std::env::current_dir()
2446 .map_err(|e| Error::generic(format!("Failed to get current directory: {}", e)))?;
2447
2448 let config_names = vec![
2449 "mockforge.config.ts",
2450 "mockforge.config.js",
2451 "mockforge.yaml",
2452 "mockforge.yml",
2453 ".mockforge.yaml",
2454 ".mockforge.yml",
2455 ];
2456
2457 for name in &config_names {
2459 let path = current_dir.join(name);
2460 if tokio::fs::metadata(&path).await.is_ok() {
2461 return Ok(path);
2462 }
2463 }
2464
2465 let mut dir = current_dir.clone();
2467 for _ in 0..5 {
2468 if let Some(parent) = dir.parent() {
2469 for name in &config_names {
2470 let path = parent.join(name);
2471 if tokio::fs::metadata(&path).await.is_ok() {
2472 return Ok(path);
2473 }
2474 }
2475 dir = parent.to_path_buf();
2476 } else {
2477 break;
2478 }
2479 }
2480
2481 Err(Error::generic(
2482 "No configuration file found. Expected one of: mockforge.config.ts, mockforge.config.js, mockforge.yaml, mockforge.yml",
2483 ))
2484}
2485
2486#[cfg(test)]
2487mod tests {
2488 use super::*;
2489
2490 #[test]
2491 fn test_default_config() {
2492 let config = ServerConfig::default();
2493 assert_eq!(config.http.port, 3000);
2494 assert_eq!(config.websocket.port, 3001);
2495 assert_eq!(config.grpc.port, 50051);
2496 assert_eq!(config.admin.port, 9080);
2497 }
2498
2499 #[test]
2500 fn test_config_validation() {
2501 let mut config = ServerConfig::default();
2502 assert!(validate_config(&config).is_ok());
2503
2504 config.websocket.port = config.http.port;
2506 assert!(validate_config(&config).is_err());
2507
2508 config.websocket.port = 3001; config.logging.level = "invalid".to_string();
2511 assert!(validate_config(&config).is_err());
2512 }
2513
2514 #[test]
2515 fn test_apply_profile() {
2516 let mut base = ServerConfig::default();
2517 assert_eq!(base.http.port, 3000);
2518
2519 let mut profile = ProfileConfig::default();
2520 profile.http = Some(HttpConfig {
2521 port: 8080,
2522 ..Default::default()
2523 });
2524 profile.logging = Some(LoggingConfig {
2525 level: "debug".to_string(),
2526 ..Default::default()
2527 });
2528
2529 let merged = apply_profile(base, profile);
2530 assert_eq!(merged.http.port, 8080);
2531 assert_eq!(merged.logging.level, "debug");
2532 assert_eq!(merged.websocket.port, 3001); }
2534
2535 #[test]
2536 fn test_strip_typescript_types() {
2537 let ts_code = r#"
2538interface Config {
2539 port: number;
2540 host: string;
2541}
2542
2543const config: Config = {
2544 port: 3000,
2545 host: "localhost"
2546} as Config;
2547"#;
2548
2549 let stripped = strip_typescript_types(ts_code).expect("Should strip TypeScript types");
2550 assert!(!stripped.contains("interface"));
2551 assert!(!stripped.contains(": Config"));
2552 assert!(!stripped.contains("as Config"));
2553 }
2554}