mockforge_core/
config.rs

1//! Configuration management for MockForge
2
3use crate::{Config as CoreConfig, Error, RealityLevel, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::Path;
7use tokio::fs;
8
9/// Incident management configuration
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[serde(default)]
13pub struct IncidentConfig {
14    /// Storage configuration
15    pub storage: IncidentStorageConfig,
16    /// External integrations configuration
17    pub external_integrations: crate::incidents::integrations::ExternalIntegrationConfig,
18    /// Webhook configurations
19    pub webhooks: Vec<crate::incidents::integrations::WebhookConfig>,
20}
21
22/// Incident storage configuration
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25pub struct IncidentStorageConfig {
26    /// Use in-memory cache (default: true)
27    pub use_cache: bool,
28    /// Use database persistence (default: true)
29    pub use_database: bool,
30    /// Retention period for resolved incidents (days)
31    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/// Consumer contracts configuration
45#[derive(Debug, Clone, Serialize, Deserialize, Default)]
46#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
47#[serde(default)]
48pub struct ConsumerContractsConfig {
49    /// Whether consumer contracts are enabled
50    pub enabled: bool,
51    /// Auto-register consumers from requests
52    pub auto_register: bool,
53    /// Track field usage
54    pub track_usage: bool,
55}
56
57/// Behavioral cloning configuration
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60#[serde(default)]
61pub struct BehavioralCloningConfig {
62    /// Whether behavioral cloning is enabled
63    pub enabled: bool,
64    /// Path to recorder database (defaults to ./recordings.db)
65    pub database_path: Option<String>,
66    /// Enable middleware to apply learned behavior
67    pub enable_middleware: bool,
68    /// Minimum frequency threshold for sequence learning (0.0 to 1.0)
69    pub min_sequence_frequency: f64,
70    /// Minimum requests per trace for sequence discovery
71    pub min_requests_per_trace: Option<i32>,
72    /// Flow recording configuration
73    #[serde(default)]
74    pub flow_recording: FlowRecordingConfig,
75    /// Scenario replay configuration
76    #[serde(default)]
77    pub scenario_replay: ScenarioReplayConfig,
78}
79
80/// Flow recording configuration
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
83#[serde(default)]
84pub struct FlowRecordingConfig {
85    /// Whether flow recording is enabled
86    pub enabled: bool,
87    /// How to group requests into flows (trace_id, session_id, ip_time_window)
88    pub group_by: String,
89    /// Time window in seconds for IP-based grouping
90    pub time_window_seconds: u64,
91}
92
93impl Default for FlowRecordingConfig {
94    fn default() -> Self {
95        Self {
96            enabled: true,
97            group_by: "trace_id".to_string(),
98            time_window_seconds: 300, // 5 minutes
99        }
100    }
101}
102
103/// Scenario replay configuration
104#[derive(Debug, Clone, Serialize, Deserialize)]
105#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
106#[serde(default)]
107pub struct ScenarioReplayConfig {
108    /// Whether scenario replay is enabled
109    pub enabled: bool,
110    /// Default replay mode (strict or flex)
111    pub default_mode: String,
112    /// List of scenario IDs to activate on startup
113    pub active_scenarios: Vec<String>,
114}
115
116impl Default for ScenarioReplayConfig {
117    fn default() -> Self {
118        Self {
119            enabled: true,
120            default_mode: "strict".to_string(),
121            active_scenarios: Vec::new(),
122        }
123    }
124}
125
126impl Default for BehavioralCloningConfig {
127    fn default() -> Self {
128        Self {
129            enabled: false,
130            database_path: None,
131            enable_middleware: false,
132            min_sequence_frequency: 0.1, // 10% default
133            min_requests_per_trace: None,
134            flow_recording: FlowRecordingConfig::default(),
135            scenario_replay: ScenarioReplayConfig::default(),
136        }
137    }
138}
139
140/// Authentication configuration for HTTP requests
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(default)]
143#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
144pub struct AuthConfig {
145    /// JWT configuration
146    pub jwt: Option<JwtConfig>,
147    /// OAuth2 configuration
148    pub oauth2: Option<OAuth2Config>,
149    /// Basic auth configuration
150    pub basic_auth: Option<BasicAuthConfig>,
151    /// API key configuration
152    pub api_key: Option<ApiKeyConfig>,
153    /// Whether to require authentication for all requests
154    pub require_auth: bool,
155}
156
157/// JWT authentication configuration
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
160pub struct JwtConfig {
161    /// JWT secret key for HMAC algorithms
162    pub secret: Option<String>,
163    /// RSA public key PEM for RSA algorithms
164    pub rsa_public_key: Option<String>,
165    /// ECDSA public key PEM for ECDSA algorithms
166    pub ecdsa_public_key: Option<String>,
167    /// Expected issuer
168    pub issuer: Option<String>,
169    /// Expected audience
170    pub audience: Option<String>,
171    /// Supported algorithms (defaults to HS256, RS256, ES256)
172    pub algorithms: Vec<String>,
173}
174
175/// OAuth2 configuration
176#[derive(Debug, Clone, Serialize, Deserialize)]
177#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
178pub struct OAuth2Config {
179    /// OAuth2 client ID
180    pub client_id: String,
181    /// OAuth2 client secret
182    pub client_secret: String,
183    /// Token introspection URL
184    pub introspection_url: String,
185    /// Authorization server URL
186    pub auth_url: Option<String>,
187    /// Token URL
188    pub token_url: Option<String>,
189    /// Expected token type
190    pub token_type_hint: Option<String>,
191}
192
193/// Basic authentication configuration
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
196pub struct BasicAuthConfig {
197    /// Username/password pairs
198    pub credentials: HashMap<String, String>,
199}
200
201/// API key configuration
202#[derive(Debug, Clone, Serialize, Deserialize)]
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
204pub struct ApiKeyConfig {
205    /// Expected header name (default: X-API-Key)
206    pub header_name: String,
207    /// Expected query parameter name
208    pub query_name: Option<String>,
209    /// Valid API keys
210    pub keys: Vec<String>,
211}
212
213impl Default for AuthConfig {
214    fn default() -> Self {
215        Self {
216            jwt: None,
217            oauth2: None,
218            basic_auth: None,
219            api_key: Some(ApiKeyConfig {
220                header_name: "X-API-Key".to_string(),
221                query_name: None,
222                keys: vec![],
223            }),
224            require_auth: false,
225        }
226    }
227}
228
229/// Route configuration for custom HTTP routes
230#[derive(Debug, Clone, Serialize, Deserialize)]
231#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
232pub struct RouteConfig {
233    /// Route path (supports path parameters like /users/{id})
234    pub path: String,
235    /// HTTP method
236    pub method: String,
237    /// Request configuration
238    pub request: Option<RouteRequestConfig>,
239    /// Response configuration
240    pub response: RouteResponseConfig,
241    /// Per-route fault injection configuration
242    #[serde(default)]
243    pub fault_injection: Option<RouteFaultInjectionConfig>,
244    /// Per-route latency configuration
245    #[serde(default)]
246    pub latency: Option<RouteLatencyConfig>,
247}
248
249/// Request configuration for routes
250#[derive(Debug, Clone, Serialize, Deserialize)]
251#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
252pub struct RouteRequestConfig {
253    /// Request validation configuration
254    pub validation: Option<RouteValidationConfig>,
255}
256
257/// Response configuration for routes
258#[derive(Debug, Clone, Serialize, Deserialize)]
259#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
260pub struct RouteResponseConfig {
261    /// HTTP status code
262    pub status: u16,
263    /// Response headers
264    #[serde(default)]
265    pub headers: HashMap<String, String>,
266    /// Response body
267    pub body: Option<serde_json::Value>,
268}
269
270/// Validation configuration for routes
271#[derive(Debug, Clone, Serialize, Deserialize)]
272#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
273pub struct RouteValidationConfig {
274    /// JSON schema for request validation
275    pub schema: serde_json::Value,
276}
277
278/// Per-route fault injection configuration
279#[derive(Debug, Clone, Serialize, Deserialize)]
280#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
281pub struct RouteFaultInjectionConfig {
282    /// Enable fault injection for this route
283    pub enabled: bool,
284    /// Probability of injecting a fault (0.0-1.0)
285    pub probability: f64,
286    /// Fault types to inject
287    pub fault_types: Vec<RouteFaultType>,
288}
289
290/// Fault types that can be injected per route
291#[derive(Debug, Clone, Serialize, Deserialize)]
292#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
293#[serde(tag = "type", rename_all = "snake_case")]
294pub enum RouteFaultType {
295    /// HTTP error with status code
296    HttpError {
297        /// HTTP status code to return
298        status_code: u16,
299        /// Optional error message
300        message: Option<String>,
301    },
302    /// Connection error
303    ConnectionError {
304        /// Optional error message
305        message: Option<String>,
306    },
307    /// Timeout error
308    Timeout {
309        /// Timeout duration in milliseconds
310        duration_ms: u64,
311        /// Optional error message
312        message: Option<String>,
313    },
314    /// Partial response (truncate at percentage)
315    PartialResponse {
316        /// Percentage of response to truncate (0.0-100.0)
317        truncate_percent: f64,
318    },
319    /// Payload corruption
320    PayloadCorruption {
321        /// Type of corruption to apply
322        corruption_type: String,
323    },
324}
325
326/// Per-route latency configuration
327#[derive(Debug, Clone, Serialize, Deserialize)]
328#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
329pub struct RouteLatencyConfig {
330    /// Enable latency injection for this route
331    pub enabled: bool,
332    /// Probability of applying latency (0.0-1.0)
333    pub probability: f64,
334    /// Fixed delay in milliseconds
335    pub fixed_delay_ms: Option<u64>,
336    /// Random delay range (min_ms, max_ms)
337    pub random_delay_range_ms: Option<(u64, u64)>,
338    /// Jitter percentage (0.0-100.0)
339    pub jitter_percent: f64,
340    /// Latency distribution type
341    #[serde(default)]
342    pub distribution: LatencyDistribution,
343}
344
345/// Latency distribution type
346#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
347#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
348#[serde(rename_all = "snake_case")]
349pub enum LatencyDistribution {
350    /// Fixed delay
351    Fixed,
352    /// Normal distribution (requires mean and std_dev)
353    Normal {
354        /// Mean delay in milliseconds
355        mean_ms: f64,
356        /// Standard deviation in milliseconds
357        std_dev_ms: f64,
358    },
359    /// Exponential distribution (requires lambda)
360    Exponential {
361        /// Lambda parameter for exponential distribution
362        lambda: f64,
363    },
364    /// Uniform distribution (uses random_delay_range_ms)
365    Uniform,
366}
367
368impl Default for LatencyDistribution {
369    fn default() -> Self {
370        Self::Fixed
371    }
372}
373
374impl Default for RouteFaultInjectionConfig {
375    fn default() -> Self {
376        Self {
377            enabled: false,
378            probability: 0.0,
379            fault_types: Vec::new(),
380        }
381    }
382}
383
384impl Default for RouteLatencyConfig {
385    fn default() -> Self {
386        Self {
387            enabled: false,
388            probability: 1.0,
389            fixed_delay_ms: None,
390            random_delay_range_ms: None,
391            jitter_percent: 0.0,
392            distribution: LatencyDistribution::Fixed,
393        }
394    }
395}
396
397/// Deceptive deploy configuration for production-like mock APIs
398#[derive(Debug, Clone, Serialize, Deserialize)]
399#[serde(default)]
400#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
401pub struct DeceptiveDeployConfig {
402    /// Enable deceptive deploy mode
403    pub enabled: bool,
404    /// Production-like CORS configuration
405    pub cors: Option<ProductionCorsConfig>,
406    /// Production-like rate limiting
407    pub rate_limit: Option<ProductionRateLimitConfig>,
408    /// Production-like headers to add to all responses
409    #[serde(default)]
410    pub headers: HashMap<String, String>,
411    /// OAuth configuration for production-like auth flows
412    pub oauth: Option<ProductionOAuthConfig>,
413    /// Custom domain for deployment
414    pub custom_domain: Option<String>,
415    /// Auto-start tunnel when deploying
416    pub auto_tunnel: bool,
417    /// Deceptive canary mode configuration
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub canary: Option<crate::deceptive_canary::DeceptiveCanaryConfig>,
420}
421
422impl Default for DeceptiveDeployConfig {
423    fn default() -> Self {
424        Self {
425            enabled: false,
426            cors: None,
427            rate_limit: None,
428            headers: HashMap::new(),
429            oauth: None,
430            custom_domain: None,
431            auto_tunnel: false,
432            canary: None,
433        }
434    }
435}
436
437impl DeceptiveDeployConfig {
438    /// Generate production-like configuration preset
439    pub fn production_preset() -> Self {
440        let mut headers = HashMap::new();
441        headers.insert("X-API-Version".to_string(), "1.0".to_string());
442        headers.insert("X-Request-ID".to_string(), "{{uuid}}".to_string());
443        headers.insert("X-Powered-By".to_string(), "MockForge".to_string());
444
445        Self {
446            enabled: true,
447            cors: Some(ProductionCorsConfig {
448                allowed_origins: vec!["*".to_string()],
449                allowed_methods: vec![
450                    "GET".to_string(),
451                    "POST".to_string(),
452                    "PUT".to_string(),
453                    "DELETE".to_string(),
454                    "PATCH".to_string(),
455                    "OPTIONS".to_string(),
456                ],
457                allowed_headers: vec!["*".to_string()],
458                allow_credentials: true,
459            }),
460            rate_limit: Some(ProductionRateLimitConfig {
461                requests_per_minute: 1000,
462                burst: 2000,
463                per_ip: true,
464            }),
465            headers,
466            oauth: None, // Configured separately
467            custom_domain: None,
468            auto_tunnel: true,
469            canary: None,
470        }
471    }
472}
473
474/// Production-like CORS configuration
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
477pub struct ProductionCorsConfig {
478    /// Allowed origins (use "*" for all origins)
479    #[serde(default)]
480    pub allowed_origins: Vec<String>,
481    /// Allowed HTTP methods
482    #[serde(default)]
483    pub allowed_methods: Vec<String>,
484    /// Allowed headers (use "*" for all headers)
485    #[serde(default)]
486    pub allowed_headers: Vec<String>,
487    /// Allow credentials (cookies, authorization headers)
488    pub allow_credentials: bool,
489}
490
491/// Production-like rate limiting configuration
492#[derive(Debug, Clone, Serialize, Deserialize)]
493#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
494pub struct ProductionRateLimitConfig {
495    /// Requests per minute allowed
496    pub requests_per_minute: u32,
497    /// Burst capacity (maximum requests in a short burst)
498    pub burst: u32,
499    /// Enable per-IP rate limiting
500    pub per_ip: bool,
501}
502
503/// Production-like OAuth configuration
504#[derive(Debug, Clone, Serialize, Deserialize)]
505#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
506pub struct ProductionOAuthConfig {
507    /// OAuth2 client ID
508    pub client_id: String,
509    /// OAuth2 client secret
510    pub client_secret: String,
511    /// Token introspection URL
512    pub introspection_url: String,
513    /// Authorization server URL
514    pub auth_url: Option<String>,
515    /// Token URL
516    pub token_url: Option<String>,
517    /// Expected token type hint
518    pub token_type_hint: Option<String>,
519}
520
521impl From<ProductionOAuthConfig> for OAuth2Config {
522    /// Convert ProductionOAuthConfig to OAuth2Config for use in auth middleware
523    fn from(prod: ProductionOAuthConfig) -> Self {
524        OAuth2Config {
525            client_id: prod.client_id,
526            client_secret: prod.client_secret,
527            introspection_url: prod.introspection_url,
528            auth_url: prod.auth_url,
529            token_url: prod.token_url,
530            token_type_hint: prod.token_type_hint,
531        }
532    }
533}
534
535/// Protocol enable/disable configuration
536#[derive(Debug, Clone, Serialize, Deserialize)]
537#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
538pub struct ProtocolConfig {
539    /// Enable this protocol
540    pub enabled: bool,
541}
542
543/// Protocols configuration
544#[derive(Debug, Clone, Serialize, Deserialize)]
545#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
546pub struct ProtocolsConfig {
547    /// HTTP protocol configuration
548    pub http: ProtocolConfig,
549    /// GraphQL protocol configuration
550    pub graphql: ProtocolConfig,
551    /// gRPC protocol configuration
552    pub grpc: ProtocolConfig,
553    /// WebSocket protocol configuration
554    pub websocket: ProtocolConfig,
555    /// SMTP protocol configuration
556    pub smtp: ProtocolConfig,
557    /// MQTT protocol configuration
558    pub mqtt: ProtocolConfig,
559    /// FTP protocol configuration
560    pub ftp: ProtocolConfig,
561    /// Kafka protocol configuration
562    pub kafka: ProtocolConfig,
563    /// RabbitMQ protocol configuration
564    pub rabbitmq: ProtocolConfig,
565    /// AMQP protocol configuration
566    pub amqp: ProtocolConfig,
567    /// TCP protocol configuration
568    pub tcp: ProtocolConfig,
569}
570
571impl Default for ProtocolsConfig {
572    fn default() -> Self {
573        Self {
574            http: ProtocolConfig { enabled: true },
575            graphql: ProtocolConfig { enabled: true },
576            grpc: ProtocolConfig { enabled: true },
577            websocket: ProtocolConfig { enabled: true },
578            smtp: ProtocolConfig { enabled: false },
579            mqtt: ProtocolConfig { enabled: true },
580            ftp: ProtocolConfig { enabled: false },
581            kafka: ProtocolConfig { enabled: false },
582            rabbitmq: ProtocolConfig { enabled: false },
583            amqp: ProtocolConfig { enabled: false },
584            tcp: ProtocolConfig { enabled: false },
585        }
586    }
587}
588
589/// Reality slider configuration for YAML config files
590///
591/// This is a simplified configuration that stores just the level.
592/// The full RealityConfig with all subsystem settings is generated
593/// automatically from the level via the RealityEngine.
594#[derive(Debug, Clone, Serialize, Deserialize)]
595#[serde(default)]
596#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
597pub struct RealitySliderConfig {
598    /// Reality level (1-5)
599    pub level: RealityLevel,
600    /// Whether to enable reality slider (if false, uses individual subsystem configs)
601    pub enabled: bool,
602}
603
604impl Default for RealitySliderConfig {
605    fn default() -> Self {
606        Self {
607            level: RealityLevel::ModerateRealism,
608            enabled: true,
609        }
610    }
611}
612
613/// Server configuration
614#[derive(Debug, Clone, Serialize, Deserialize, Default)]
615#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
616#[serde(default)]
617pub struct ServerConfig {
618    /// HTTP server configuration
619    pub http: HttpConfig,
620    /// WebSocket server configuration
621    pub websocket: WebSocketConfig,
622    /// GraphQL server configuration
623    pub graphql: GraphQLConfig,
624    /// gRPC server configuration
625    pub grpc: GrpcConfig,
626    /// MQTT server configuration
627    pub mqtt: MqttConfig,
628    /// SMTP server configuration
629    pub smtp: SmtpConfig,
630    /// FTP server configuration
631    pub ftp: FtpConfig,
632    /// Kafka server configuration
633    pub kafka: KafkaConfig,
634    /// AMQP server configuration
635    pub amqp: AmqpConfig,
636    /// TCP server configuration
637    pub tcp: TcpConfig,
638    /// Admin UI configuration
639    pub admin: AdminConfig,
640    /// Request chaining configuration
641    pub chaining: ChainingConfig,
642    /// Core MockForge configuration
643    pub core: CoreConfig,
644    /// Logging configuration
645    pub logging: LoggingConfig,
646    /// Data generation configuration
647    pub data: DataConfig,
648    /// MockAI (Behavioral Mock Intelligence) configuration
649    #[serde(default)]
650    pub mockai: MockAIConfig,
651    /// Observability configuration (metrics, tracing)
652    pub observability: ObservabilityConfig,
653    /// Multi-tenant workspace configuration
654    pub multi_tenant: crate::multi_tenant::MultiTenantConfig,
655    /// Custom routes configuration
656    #[serde(default)]
657    pub routes: Vec<RouteConfig>,
658    /// Protocol enable/disable configuration
659    #[serde(default)]
660    pub protocols: ProtocolsConfig,
661    /// Named configuration profiles (dev, ci, demo, etc.)
662    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
663    pub profiles: HashMap<String, ProfileConfig>,
664    /// Deceptive deploy configuration for production-like mock APIs
665    #[serde(default)]
666    pub deceptive_deploy: DeceptiveDeployConfig,
667    /// Behavioral cloning configuration
668    #[serde(default, skip_serializing_if = "Option::is_none")]
669    pub behavioral_cloning: Option<BehavioralCloningConfig>,
670    /// Reality slider configuration for unified realism control
671    #[serde(default)]
672    pub reality: RealitySliderConfig,
673    /// Reality Continuum configuration for blending mock and real data sources
674    #[serde(default)]
675    pub reality_continuum: crate::reality_continuum::ContinuumConfig,
676    /// Security monitoring and SIEM configuration
677    #[serde(default)]
678    pub security: SecurityConfig,
679    /// Drift budget and contract monitoring configuration
680    #[serde(default)]
681    pub drift_budget: crate::contract_drift::DriftBudgetConfig,
682    /// Incident management configuration
683    #[serde(default)]
684    pub incidents: IncidentConfig,
685    /// PR generation configuration
686    #[serde(default)]
687    pub pr_generation: crate::pr_generation::PRGenerationConfig,
688    /// Consumer contracts configuration
689    #[serde(default)]
690    pub consumer_contracts: ConsumerContractsConfig,
691}
692
693/// Profile configuration - a partial ServerConfig that overrides base settings
694#[derive(Debug, Clone, Serialize, Deserialize, Default)]
695#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
696#[serde(default)]
697pub struct ProfileConfig {
698    /// HTTP server configuration overrides
699    #[serde(skip_serializing_if = "Option::is_none")]
700    pub http: Option<HttpConfig>,
701    /// WebSocket server configuration overrides
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub websocket: Option<WebSocketConfig>,
704    /// GraphQL server configuration overrides
705    #[serde(skip_serializing_if = "Option::is_none")]
706    pub graphql: Option<GraphQLConfig>,
707    /// gRPC server configuration overrides
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub grpc: Option<GrpcConfig>,
710    /// MQTT server configuration overrides
711    #[serde(skip_serializing_if = "Option::is_none")]
712    pub mqtt: Option<MqttConfig>,
713    /// SMTP server configuration overrides
714    #[serde(skip_serializing_if = "Option::is_none")]
715    pub smtp: Option<SmtpConfig>,
716    /// FTP server configuration overrides
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub ftp: Option<FtpConfig>,
719    /// Kafka server configuration overrides
720    #[serde(skip_serializing_if = "Option::is_none")]
721    pub kafka: Option<KafkaConfig>,
722    /// AMQP server configuration overrides
723    #[serde(skip_serializing_if = "Option::is_none")]
724    pub amqp: Option<AmqpConfig>,
725    /// TCP server configuration overrides
726    #[serde(skip_serializing_if = "Option::is_none")]
727    pub tcp: Option<TcpConfig>,
728    /// Admin UI configuration overrides
729    #[serde(skip_serializing_if = "Option::is_none")]
730    pub admin: Option<AdminConfig>,
731    /// Request chaining configuration overrides
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub chaining: Option<ChainingConfig>,
734    /// Core MockForge configuration overrides
735    #[serde(skip_serializing_if = "Option::is_none")]
736    pub core: Option<CoreConfig>,
737    /// Logging configuration overrides
738    #[serde(skip_serializing_if = "Option::is_none")]
739    pub logging: Option<LoggingConfig>,
740    /// Data generation configuration overrides
741    #[serde(skip_serializing_if = "Option::is_none")]
742    pub data: Option<DataConfig>,
743    /// MockAI configuration overrides
744    #[serde(skip_serializing_if = "Option::is_none")]
745    pub mockai: Option<MockAIConfig>,
746    /// Observability configuration overrides
747    #[serde(skip_serializing_if = "Option::is_none")]
748    pub observability: Option<ObservabilityConfig>,
749    /// Multi-tenant workspace configuration overrides
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub multi_tenant: Option<crate::multi_tenant::MultiTenantConfig>,
752    /// Custom routes configuration overrides
753    #[serde(skip_serializing_if = "Option::is_none")]
754    pub routes: Option<Vec<RouteConfig>>,
755    /// Protocol enable/disable configuration overrides
756    #[serde(skip_serializing_if = "Option::is_none")]
757    pub protocols: Option<ProtocolsConfig>,
758    /// Deceptive deploy configuration overrides
759    #[serde(skip_serializing_if = "Option::is_none")]
760    pub deceptive_deploy: Option<DeceptiveDeployConfig>,
761    /// Reality slider configuration overrides
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub reality: Option<RealitySliderConfig>,
764    /// Reality Continuum configuration overrides
765    #[serde(skip_serializing_if = "Option::is_none")]
766    pub reality_continuum: Option<crate::reality_continuum::ContinuumConfig>,
767    /// Security configuration overrides
768    #[serde(skip_serializing_if = "Option::is_none")]
769    pub security: Option<SecurityConfig>,
770}
771
772// Default is derived for ServerConfig
773
774/// HTTP validation configuration
775#[derive(Debug, Clone, Serialize, Deserialize)]
776#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
777pub struct HttpValidationConfig {
778    /// Request validation mode: off, warn, enforce
779    pub mode: String,
780}
781
782/// HTTP CORS configuration
783#[derive(Debug, Clone, Serialize, Deserialize, Default)]
784#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
785pub struct HttpCorsConfig {
786    /// Enable CORS
787    pub enabled: bool,
788    /// Allowed origins
789    #[serde(default)]
790    pub allowed_origins: Vec<String>,
791    /// Allowed methods
792    #[serde(default)]
793    pub allowed_methods: Vec<String>,
794    /// Allowed headers
795    #[serde(default)]
796    pub allowed_headers: Vec<String>,
797    /// Allow credentials (cookies, authorization headers)
798    /// Note: Cannot be true when using wildcard origin (*)
799    #[serde(default = "default_cors_allow_credentials")]
800    pub allow_credentials: bool,
801}
802
803fn default_cors_allow_credentials() -> bool {
804    false
805}
806
807/// HTTP server configuration
808#[derive(Debug, Clone, Serialize, Deserialize)]
809#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
810#[serde(default)]
811pub struct HttpConfig {
812    /// Enable HTTP server
813    pub enabled: bool,
814    /// Server port
815    pub port: u16,
816    /// Host address
817    pub host: String,
818    /// Path to OpenAPI spec file for HTTP server
819    pub openapi_spec: Option<String>,
820    /// CORS configuration
821    pub cors: Option<HttpCorsConfig>,
822    /// Request timeout in seconds
823    pub request_timeout_secs: u64,
824    /// Request validation configuration
825    pub validation: Option<HttpValidationConfig>,
826    /// Aggregate validation errors into JSON array
827    pub aggregate_validation_errors: bool,
828    /// Validate responses (warn-only logging)
829    pub validate_responses: bool,
830    /// Expand templating tokens in responses/examples
831    pub response_template_expand: bool,
832    /// Validation error HTTP status (e.g., 400 or 422)
833    pub validation_status: Option<u16>,
834    /// Per-route overrides: key "METHOD path" => mode (off/warn/enforce)
835    pub validation_overrides: std::collections::HashMap<String, String>,
836    /// When embedding Admin UI under HTTP, skip validation for the mounted prefix
837    pub skip_admin_validation: bool,
838    /// Authentication configuration
839    pub auth: Option<AuthConfig>,
840    /// TLS/HTTPS configuration
841    #[serde(skip_serializing_if = "Option::is_none")]
842    pub tls: Option<HttpTlsConfig>,
843}
844
845impl Default for HttpConfig {
846    fn default() -> Self {
847        Self {
848            enabled: true,
849            port: 3000,
850            host: "0.0.0.0".to_string(),
851            openapi_spec: None,
852            cors: Some(HttpCorsConfig {
853                enabled: true,
854                allowed_origins: vec!["*".to_string()],
855                allowed_methods: vec![
856                    "GET".to_string(),
857                    "POST".to_string(),
858                    "PUT".to_string(),
859                    "DELETE".to_string(),
860                    "PATCH".to_string(),
861                    "OPTIONS".to_string(),
862                ],
863                allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
864                allow_credentials: false, // Must be false when using wildcard origin
865            }),
866            request_timeout_secs: 30,
867            validation: Some(HttpValidationConfig {
868                mode: "enforce".to_string(),
869            }),
870            aggregate_validation_errors: true,
871            validate_responses: false,
872            response_template_expand: false,
873            validation_status: None,
874            validation_overrides: std::collections::HashMap::new(),
875            skip_admin_validation: true,
876            auth: None,
877            tls: None,
878        }
879    }
880}
881
882/// HTTP TLS/HTTPS configuration
883#[derive(Debug, Clone, Serialize, Deserialize)]
884#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
885pub struct HttpTlsConfig {
886    /// Enable TLS/HTTPS
887    pub enabled: bool,
888    /// Path to TLS certificate file (PEM format)
889    pub cert_file: String,
890    /// Path to TLS private key file (PEM format)
891    pub key_file: String,
892    /// Path to CA certificate file for mutual TLS (optional)
893    #[serde(skip_serializing_if = "Option::is_none")]
894    pub ca_file: Option<String>,
895    /// Minimum TLS version (default: "1.2")
896    #[serde(default = "default_tls_min_version")]
897    pub min_version: String,
898    /// Cipher suites to use (default: safe defaults)
899    #[serde(default, skip_serializing_if = "Vec::is_empty")]
900    pub cipher_suites: Vec<String>,
901    /// Require client certificate (mutual TLS)
902    #[serde(default)]
903    pub require_client_cert: bool,
904}
905
906fn default_tls_min_version() -> String {
907    "1.2".to_string()
908}
909
910impl Default for HttpTlsConfig {
911    fn default() -> Self {
912        Self {
913            enabled: true,
914            cert_file: String::new(),
915            key_file: String::new(),
916            ca_file: None,
917            min_version: "1.2".to_string(),
918            cipher_suites: Vec::new(),
919            require_client_cert: false,
920        }
921    }
922}
923
924/// WebSocket server configuration
925#[derive(Debug, Clone, Serialize, Deserialize)]
926#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
927#[serde(default)]
928pub struct WebSocketConfig {
929    /// Enable WebSocket server
930    pub enabled: bool,
931    /// Server port
932    pub port: u16,
933    /// Host address
934    pub host: String,
935    /// Replay file path
936    pub replay_file: Option<String>,
937    /// Connection timeout in seconds
938    pub connection_timeout_secs: u64,
939}
940
941impl Default for WebSocketConfig {
942    fn default() -> Self {
943        Self {
944            enabled: true,
945            port: 3001,
946            host: "0.0.0.0".to_string(),
947            replay_file: None,
948            connection_timeout_secs: 300,
949        }
950    }
951}
952
953/// gRPC server configuration
954#[derive(Debug, Clone, Serialize, Deserialize)]
955#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
956#[serde(default)]
957pub struct GrpcConfig {
958    /// Enable gRPC server
959    pub enabled: bool,
960    /// Server port
961    pub port: u16,
962    /// Host address
963    pub host: String,
964    /// Proto files directory
965    pub proto_dir: Option<String>,
966    /// TLS configuration
967    pub tls: Option<TlsConfig>,
968}
969
970impl Default for GrpcConfig {
971    fn default() -> Self {
972        Self {
973            enabled: true,
974            port: 50051,
975            host: "0.0.0.0".to_string(),
976            proto_dir: None,
977            tls: None,
978        }
979    }
980}
981
982/// GraphQL server configuration
983#[derive(Debug, Clone, Serialize, Deserialize)]
984#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
985#[serde(default)]
986pub struct GraphQLConfig {
987    /// Enable GraphQL server
988    pub enabled: bool,
989    /// Server port
990    pub port: u16,
991    /// Host address
992    pub host: String,
993    /// GraphQL schema file path (.graphql or .gql)
994    pub schema_path: Option<String>,
995    /// Handlers directory for custom resolvers
996    pub handlers_dir: Option<String>,
997    /// Enable GraphQL Playground UI
998    pub playground_enabled: bool,
999    /// Upstream GraphQL server URL for passthrough
1000    pub upstream_url: Option<String>,
1001    /// Enable introspection queries
1002    pub introspection_enabled: bool,
1003}
1004
1005impl Default for GraphQLConfig {
1006    fn default() -> Self {
1007        Self {
1008            enabled: true,
1009            port: 4000,
1010            host: "0.0.0.0".to_string(),
1011            schema_path: None,
1012            handlers_dir: None,
1013            playground_enabled: true,
1014            upstream_url: None,
1015            introspection_enabled: true,
1016        }
1017    }
1018}
1019
1020/// TLS configuration for gRPC
1021#[derive(Debug, Clone, Serialize, Deserialize)]
1022#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1023pub struct TlsConfig {
1024    /// Certificate file path
1025    pub cert_path: String,
1026    /// Private key file path
1027    pub key_path: String,
1028}
1029
1030/// MQTT server configuration
1031#[derive(Debug, Clone, Serialize, Deserialize)]
1032#[serde(default)]
1033#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1034pub struct MqttConfig {
1035    /// Enable MQTT server
1036    pub enabled: bool,
1037    /// Server port
1038    pub port: u16,
1039    /// Host address
1040    pub host: String,
1041    /// Maximum connections
1042    pub max_connections: usize,
1043    /// Maximum packet size
1044    pub max_packet_size: usize,
1045    /// Keep-alive timeout in seconds
1046    pub keep_alive_secs: u16,
1047    /// Directory containing fixture files
1048    pub fixtures_dir: Option<std::path::PathBuf>,
1049    /// Enable retained messages
1050    pub enable_retained_messages: bool,
1051    /// Maximum retained messages
1052    pub max_retained_messages: usize,
1053}
1054
1055impl Default for MqttConfig {
1056    fn default() -> Self {
1057        Self {
1058            enabled: false,
1059            port: 1883,
1060            host: "0.0.0.0".to_string(),
1061            max_connections: 1000,
1062            max_packet_size: 268435456, // 256 MB
1063            keep_alive_secs: 60,
1064            fixtures_dir: None,
1065            enable_retained_messages: true,
1066            max_retained_messages: 10000,
1067        }
1068    }
1069}
1070
1071/// SMTP server configuration
1072#[derive(Debug, Clone, Serialize, Deserialize)]
1073#[serde(default)]
1074#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1075pub struct SmtpConfig {
1076    /// Enable SMTP server
1077    pub enabled: bool,
1078    /// Server port
1079    pub port: u16,
1080    /// Host address
1081    pub host: String,
1082    /// Server hostname for SMTP greeting
1083    pub hostname: String,
1084    /// Directory containing fixture files
1085    pub fixtures_dir: Option<std::path::PathBuf>,
1086    /// Connection timeout in seconds
1087    pub timeout_secs: u64,
1088    /// Maximum connections
1089    pub max_connections: usize,
1090    /// Enable mailbox storage
1091    pub enable_mailbox: bool,
1092    /// Maximum mailbox size
1093    pub max_mailbox_messages: usize,
1094    /// Enable STARTTLS support
1095    pub enable_starttls: bool,
1096    /// Path to TLS certificate file
1097    pub tls_cert_path: Option<std::path::PathBuf>,
1098    /// Path to TLS private key file
1099    pub tls_key_path: Option<std::path::PathBuf>,
1100}
1101
1102impl Default for SmtpConfig {
1103    fn default() -> Self {
1104        Self {
1105            enabled: false,
1106            port: 1025,
1107            host: "0.0.0.0".to_string(),
1108            hostname: "mockforge-smtp".to_string(),
1109            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
1110            timeout_secs: 300,
1111            max_connections: 10,
1112            enable_mailbox: true,
1113            max_mailbox_messages: 1000,
1114            enable_starttls: false,
1115            tls_cert_path: None,
1116            tls_key_path: None,
1117        }
1118    }
1119}
1120
1121/// FTP server configuration
1122#[derive(Debug, Clone, Serialize, Deserialize)]
1123#[serde(default)]
1124#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1125pub struct FtpConfig {
1126    /// Enable FTP server
1127    pub enabled: bool,
1128    /// Server port
1129    pub port: u16,
1130    /// Host address
1131    pub host: String,
1132    /// Passive mode port range
1133    pub passive_ports: (u16, u16),
1134    /// Maximum connections
1135    pub max_connections: usize,
1136    /// Connection timeout in seconds
1137    pub timeout_secs: u64,
1138    /// Allow anonymous access
1139    pub allow_anonymous: bool,
1140    /// Fixtures directory
1141    pub fixtures_dir: Option<std::path::PathBuf>,
1142    /// Virtual root directory
1143    pub virtual_root: std::path::PathBuf,
1144}
1145
1146impl Default for FtpConfig {
1147    fn default() -> Self {
1148        Self {
1149            enabled: false,
1150            port: 2121,
1151            host: "0.0.0.0".to_string(),
1152            passive_ports: (50000, 51000),
1153            max_connections: 100,
1154            timeout_secs: 300,
1155            allow_anonymous: true,
1156            fixtures_dir: None,
1157            virtual_root: std::path::PathBuf::from("/mockforge"),
1158        }
1159    }
1160}
1161
1162/// Kafka server configuration
1163#[derive(Debug, Clone, Serialize, Deserialize)]
1164#[serde(default)]
1165#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1166pub struct KafkaConfig {
1167    /// Enable Kafka server
1168    pub enabled: bool,
1169    /// Server port
1170    pub port: u16,
1171    /// Host address
1172    pub host: String,
1173    /// Broker ID
1174    pub broker_id: i32,
1175    /// Maximum connections
1176    pub max_connections: usize,
1177    /// Log retention time in milliseconds
1178    pub log_retention_ms: i64,
1179    /// Log segment size in bytes
1180    pub log_segment_bytes: i64,
1181    /// Fixtures directory
1182    pub fixtures_dir: Option<std::path::PathBuf>,
1183    /// Auto-create topics
1184    pub auto_create_topics: bool,
1185    /// Default number of partitions for new topics
1186    pub default_partitions: i32,
1187    /// Default replication factor for new topics
1188    pub default_replication_factor: i16,
1189}
1190
1191impl Default for KafkaConfig {
1192    fn default() -> Self {
1193        Self {
1194            enabled: false,
1195            port: 9092, // Standard Kafka port
1196            host: "0.0.0.0".to_string(),
1197            broker_id: 1,
1198            max_connections: 1000,
1199            log_retention_ms: 604800000,   // 7 days
1200            log_segment_bytes: 1073741824, // 1 GB
1201            fixtures_dir: None,
1202            auto_create_topics: true,
1203            default_partitions: 3,
1204            default_replication_factor: 1,
1205        }
1206    }
1207}
1208
1209/// AMQP server configuration
1210#[derive(Debug, Clone, Serialize, Deserialize)]
1211#[serde(default)]
1212#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1213pub struct AmqpConfig {
1214    /// Enable AMQP server
1215    pub enabled: bool,
1216    /// Server port
1217    pub port: u16,
1218    /// Host address
1219    pub host: String,
1220    /// Maximum connections
1221    pub max_connections: usize,
1222    /// Maximum channels per connection
1223    pub max_channels_per_connection: u16,
1224    /// Frame max size
1225    pub frame_max: u32,
1226    /// Heartbeat interval in seconds
1227    pub heartbeat_interval: u16,
1228    /// Fixtures directory
1229    pub fixtures_dir: Option<std::path::PathBuf>,
1230    /// Virtual hosts
1231    pub virtual_hosts: Vec<String>,
1232}
1233
1234impl Default for AmqpConfig {
1235    fn default() -> Self {
1236        Self {
1237            enabled: false,
1238            port: 5672, // Standard AMQP port
1239            host: "0.0.0.0".to_string(),
1240            max_connections: 1000,
1241            max_channels_per_connection: 100,
1242            frame_max: 131072, // 128 KB
1243            heartbeat_interval: 60,
1244            fixtures_dir: None,
1245            virtual_hosts: vec!["/".to_string()],
1246        }
1247    }
1248}
1249
1250/// TCP server configuration
1251#[derive(Debug, Clone, Serialize, Deserialize)]
1252#[serde(default)]
1253#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1254pub struct TcpConfig {
1255    /// Enable TCP server
1256    pub enabled: bool,
1257    /// Server port
1258    pub port: u16,
1259    /// Host address
1260    pub host: String,
1261    /// Maximum connections
1262    pub max_connections: usize,
1263    /// Connection timeout in seconds
1264    pub timeout_secs: u64,
1265    /// Directory containing fixture files
1266    pub fixtures_dir: Option<std::path::PathBuf>,
1267    /// Enable echo mode (echo received data back)
1268    pub echo_mode: bool,
1269    /// Enable TLS support
1270    pub enable_tls: bool,
1271    /// Path to TLS certificate file
1272    pub tls_cert_path: Option<std::path::PathBuf>,
1273    /// Path to TLS private key file
1274    pub tls_key_path: Option<std::path::PathBuf>,
1275}
1276
1277impl Default for TcpConfig {
1278    fn default() -> Self {
1279        Self {
1280            enabled: false,
1281            port: 9999,
1282            host: "0.0.0.0".to_string(),
1283            max_connections: 1000,
1284            timeout_secs: 300,
1285            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
1286            echo_mode: true,
1287            enable_tls: false,
1288            tls_cert_path: None,
1289            tls_key_path: None,
1290        }
1291    }
1292}
1293
1294/// Admin UI configuration
1295#[derive(Debug, Clone, Serialize, Deserialize)]
1296#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1297#[serde(default)]
1298pub struct AdminConfig {
1299    /// Enable admin UI
1300    pub enabled: bool,
1301    /// Admin UI port
1302    pub port: u16,
1303    /// Host address
1304    pub host: String,
1305    /// Authentication required
1306    pub auth_required: bool,
1307    /// Admin username (if auth required)
1308    pub username: Option<String>,
1309    /// Admin password (if auth required)
1310    pub password: Option<String>,
1311    /// Optional mount path to embed Admin UI under HTTP server (e.g., "/admin")
1312    pub mount_path: Option<String>,
1313    /// Enable Admin API endpoints (under `__mockforge`)
1314    pub api_enabled: bool,
1315    /// Prometheus server URL for analytics queries
1316    pub prometheus_url: String,
1317}
1318
1319impl Default for AdminConfig {
1320    fn default() -> Self {
1321        // Default to 0.0.0.0 if running in Docker (detected via common Docker env vars)
1322        // This makes Admin UI accessible from outside the container by default
1323        let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
1324            || std::env::var("container").is_ok()
1325            || std::path::Path::new("/.dockerenv").exists()
1326        {
1327            "0.0.0.0".to_string()
1328        } else {
1329            "127.0.0.1".to_string()
1330        };
1331
1332        Self {
1333            enabled: false,
1334            port: 9080,
1335            host: default_host,
1336            auth_required: false,
1337            username: None,
1338            password: None,
1339            mount_path: None,
1340            api_enabled: true,
1341            prometheus_url: "http://localhost:9090".to_string(),
1342        }
1343    }
1344}
1345
1346/// Logging configuration
1347#[derive(Debug, Clone, Serialize, Deserialize)]
1348#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1349#[serde(default)]
1350pub struct LoggingConfig {
1351    /// Log level
1352    pub level: String,
1353    /// Enable JSON logging
1354    pub json_format: bool,
1355    /// Log file path (optional)
1356    pub file_path: Option<String>,
1357    /// Maximum log file size in MB
1358    pub max_file_size_mb: u64,
1359    /// Maximum number of log files to keep
1360    pub max_files: u32,
1361}
1362
1363impl Default for LoggingConfig {
1364    fn default() -> Self {
1365        Self {
1366            level: "info".to_string(),
1367            json_format: false,
1368            file_path: None,
1369            max_file_size_mb: 10,
1370            max_files: 5,
1371        }
1372    }
1373}
1374
1375/// Request chaining configuration for multi-step request workflows
1376#[derive(Debug, Clone, Serialize, Deserialize)]
1377#[serde(default, rename_all = "camelCase")]
1378#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1379pub struct ChainingConfig {
1380    /// Enable request chaining
1381    pub enabled: bool,
1382    /// Maximum chain length to prevent infinite loops
1383    pub max_chain_length: usize,
1384    /// Global timeout for chain execution in seconds
1385    pub global_timeout_secs: u64,
1386    /// Enable parallel execution when dependencies allow
1387    pub enable_parallel_execution: bool,
1388}
1389
1390impl Default for ChainingConfig {
1391    fn default() -> Self {
1392        Self {
1393            enabled: false,
1394            max_chain_length: 20,
1395            global_timeout_secs: 300,
1396            enable_parallel_execution: false,
1397        }
1398    }
1399}
1400
1401/// Data generation configuration
1402#[derive(Debug, Clone, Serialize, Deserialize)]
1403#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1404#[serde(default)]
1405pub struct DataConfig {
1406    /// Default number of rows to generate
1407    pub default_rows: usize,
1408    /// Default output format
1409    pub default_format: String,
1410    /// Faker locale
1411    pub locale: String,
1412    /// Custom faker templates
1413    pub templates: HashMap<String, String>,
1414    /// RAG configuration
1415    pub rag: RagConfig,
1416    /// Active persona profile domain (e.g., "finance", "ecommerce", "healthcare")
1417    #[serde(skip_serializing_if = "Option::is_none")]
1418    pub persona_domain: Option<String>,
1419    /// Enable persona-based consistency
1420    #[serde(default = "default_false")]
1421    pub persona_consistency_enabled: bool,
1422    /// Persona registry configuration
1423    #[serde(skip_serializing_if = "Option::is_none")]
1424    pub persona_registry: Option<PersonaRegistryConfig>,
1425}
1426
1427impl Default for DataConfig {
1428    fn default() -> Self {
1429        Self {
1430            default_rows: 100,
1431            default_format: "json".to_string(),
1432            locale: "en".to_string(),
1433            templates: HashMap::new(),
1434            rag: RagConfig::default(),
1435            persona_domain: None,
1436            persona_consistency_enabled: false,
1437            persona_registry: None,
1438        }
1439    }
1440}
1441
1442/// RAG configuration
1443#[derive(Debug, Clone, Serialize, Deserialize)]
1444#[serde(default)]
1445#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1446pub struct RagConfig {
1447    /// Enable RAG by default
1448    pub enabled: bool,
1449    /// LLM provider (openai, anthropic, ollama, openai_compatible)
1450    #[serde(default)]
1451    pub provider: String,
1452    /// API endpoint for LLM
1453    pub api_endpoint: Option<String>,
1454    /// API key for LLM
1455    pub api_key: Option<String>,
1456    /// Model name
1457    pub model: Option<String>,
1458    /// Maximum tokens for generation
1459    #[serde(default = "default_max_tokens")]
1460    pub max_tokens: usize,
1461    /// Temperature for generation (0.0 to 2.0)
1462    #[serde(default = "default_temperature")]
1463    pub temperature: f64,
1464    /// Context window size
1465    pub context_window: usize,
1466    /// Enable caching
1467    #[serde(default = "default_true")]
1468    pub caching: bool,
1469    /// Cache TTL in seconds
1470    #[serde(default = "default_cache_ttl")]
1471    pub cache_ttl_secs: u64,
1472    /// Request timeout in seconds
1473    #[serde(default = "default_timeout")]
1474    pub timeout_secs: u64,
1475    /// Maximum retries for failed requests
1476    #[serde(default = "default_max_retries")]
1477    pub max_retries: usize,
1478}
1479
1480fn default_max_tokens() -> usize {
1481    1024
1482}
1483
1484fn default_temperature() -> f64 {
1485    0.7
1486}
1487
1488fn default_true() -> bool {
1489    true
1490}
1491
1492fn default_cache_ttl() -> u64 {
1493    3600
1494}
1495
1496fn default_timeout() -> u64 {
1497    30
1498}
1499
1500fn default_max_retries() -> usize {
1501    3
1502}
1503
1504fn default_false() -> bool {
1505    false
1506}
1507
1508impl Default for RagConfig {
1509    fn default() -> Self {
1510        Self {
1511            enabled: false,
1512            provider: "openai".to_string(),
1513            api_endpoint: None,
1514            api_key: None,
1515            model: Some("gpt-3.5-turbo".to_string()),
1516            max_tokens: default_max_tokens(),
1517            temperature: default_temperature(),
1518            context_window: 4000,
1519            caching: default_true(),
1520            cache_ttl_secs: default_cache_ttl(),
1521            timeout_secs: default_timeout(),
1522            max_retries: default_max_retries(),
1523        }
1524    }
1525}
1526
1527/// Persona registry configuration
1528#[derive(Debug, Clone, Serialize, Deserialize)]
1529#[serde(default)]
1530#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1531pub struct PersonaRegistryConfig {
1532    /// Enable persistence (save personas to disk)
1533    #[serde(default = "default_false")]
1534    pub persistent: bool,
1535    /// Storage path for persistent personas
1536    #[serde(skip_serializing_if = "Option::is_none")]
1537    pub storage_path: Option<String>,
1538    /// Default traits for new personas
1539    #[serde(default)]
1540    pub default_traits: HashMap<String, String>,
1541}
1542
1543impl Default for PersonaRegistryConfig {
1544    fn default() -> Self {
1545        Self {
1546            persistent: false,
1547            storage_path: None,
1548            default_traits: HashMap::new(),
1549        }
1550    }
1551}
1552
1553/// MockAI (Behavioral Mock Intelligence) configuration
1554#[derive(Debug, Clone, Serialize, Deserialize)]
1555#[serde(default)]
1556#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1557pub struct MockAIConfig {
1558    /// Enable MockAI features
1559    pub enabled: bool,
1560    /// Intelligent behavior configuration
1561    pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
1562    /// Auto-learn from examples
1563    pub auto_learn: bool,
1564    /// Enable mutation detection
1565    pub mutation_detection: bool,
1566    /// Enable AI-driven validation errors
1567    pub ai_validation_errors: bool,
1568    /// Enable context-aware pagination
1569    pub intelligent_pagination: bool,
1570    /// Endpoints to enable MockAI for (empty = all endpoints)
1571    #[serde(default)]
1572    pub enabled_endpoints: Vec<String>,
1573}
1574
1575impl Default for MockAIConfig {
1576    fn default() -> Self {
1577        Self {
1578            enabled: false,
1579            intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
1580            auto_learn: true,
1581            mutation_detection: true,
1582            ai_validation_errors: true,
1583            intelligent_pagination: true,
1584            enabled_endpoints: Vec::new(),
1585        }
1586    }
1587}
1588
1589/// Observability configuration for metrics and distributed tracing
1590#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1591#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1592#[serde(default)]
1593pub struct ObservabilityConfig {
1594    /// Prometheus metrics configuration
1595    pub prometheus: PrometheusConfig,
1596    /// OpenTelemetry distributed tracing configuration
1597    pub opentelemetry: Option<OpenTelemetryConfig>,
1598    /// API Flight Recorder configuration
1599    pub recorder: Option<RecorderConfig>,
1600    /// Chaos engineering configuration
1601    pub chaos: Option<ChaosEngConfig>,
1602}
1603
1604/// Security monitoring and SIEM configuration
1605#[derive(Debug, Clone, Serialize, Deserialize)]
1606#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1607#[serde(default)]
1608pub struct SecurityConfig {
1609    /// Security monitoring configuration
1610    pub monitoring: SecurityMonitoringConfig,
1611}
1612
1613impl Default for SecurityConfig {
1614    fn default() -> Self {
1615        Self {
1616            monitoring: SecurityMonitoringConfig::default(),
1617        }
1618    }
1619}
1620
1621/// Security monitoring configuration
1622#[derive(Debug, Clone, Serialize, Deserialize)]
1623#[serde(default)]
1624#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1625pub struct SecurityMonitoringConfig {
1626    /// SIEM integration configuration
1627    pub siem: crate::security::siem::SiemConfig,
1628    /// Access review configuration
1629    pub access_review: crate::security::access_review::AccessReviewConfig,
1630    /// Privileged access management configuration
1631    pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
1632    /// Change management configuration
1633    pub change_management: crate::security::change_management::ChangeManagementConfig,
1634    /// Compliance dashboard configuration
1635    pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
1636    /// Risk assessment configuration
1637    pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
1638}
1639
1640impl Default for SecurityMonitoringConfig {
1641    fn default() -> Self {
1642        Self {
1643            siem: crate::security::siem::SiemConfig::default(),
1644            access_review: crate::security::access_review::AccessReviewConfig::default(),
1645            privileged_access: crate::security::privileged_access::PrivilegedAccessConfig::default(
1646            ),
1647            change_management: crate::security::change_management::ChangeManagementConfig::default(
1648            ),
1649            compliance_dashboard:
1650                crate::security::compliance_dashboard::ComplianceDashboardConfig::default(),
1651            risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig::default(),
1652        }
1653    }
1654}
1655
1656/// Prometheus metrics configuration
1657#[derive(Debug, Clone, Serialize, Deserialize)]
1658#[serde(default)]
1659#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1660pub struct PrometheusConfig {
1661    /// Enable Prometheus metrics endpoint
1662    pub enabled: bool,
1663    /// Port for metrics endpoint
1664    pub port: u16,
1665    /// Host for metrics endpoint
1666    pub host: String,
1667    /// Path for metrics endpoint
1668    pub path: String,
1669}
1670
1671impl Default for PrometheusConfig {
1672    fn default() -> Self {
1673        Self {
1674            enabled: true,
1675            port: 9090,
1676            host: "0.0.0.0".to_string(),
1677            path: "/metrics".to_string(),
1678        }
1679    }
1680}
1681
1682/// OpenTelemetry distributed tracing configuration
1683#[derive(Debug, Clone, Serialize, Deserialize)]
1684#[serde(default)]
1685#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1686pub struct OpenTelemetryConfig {
1687    /// Enable OpenTelemetry tracing
1688    pub enabled: bool,
1689    /// Service name for traces
1690    pub service_name: String,
1691    /// Deployment environment (development, staging, production)
1692    pub environment: String,
1693    /// Jaeger endpoint for trace export
1694    pub jaeger_endpoint: String,
1695    /// OTLP endpoint (alternative to Jaeger)
1696    pub otlp_endpoint: Option<String>,
1697    /// Protocol: grpc or http
1698    pub protocol: String,
1699    /// Sampling rate (0.0 to 1.0)
1700    pub sampling_rate: f64,
1701}
1702
1703impl Default for OpenTelemetryConfig {
1704    fn default() -> Self {
1705        Self {
1706            enabled: false,
1707            service_name: "mockforge".to_string(),
1708            environment: "development".to_string(),
1709            jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
1710            otlp_endpoint: Some("http://localhost:4317".to_string()),
1711            protocol: "grpc".to_string(),
1712            sampling_rate: 1.0,
1713        }
1714    }
1715}
1716
1717/// API Flight Recorder configuration
1718#[derive(Debug, Clone, Serialize, Deserialize)]
1719#[serde(default)]
1720#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1721pub struct RecorderConfig {
1722    /// Enable recording
1723    pub enabled: bool,
1724    /// Database file path
1725    pub database_path: String,
1726    /// Enable management API
1727    pub api_enabled: bool,
1728    /// Management API port (if different from main port)
1729    pub api_port: Option<u16>,
1730    /// Maximum number of requests to store (0 for unlimited)
1731    pub max_requests: i64,
1732    /// Auto-delete requests older than N days (0 to disable)
1733    pub retention_days: i64,
1734    /// Record HTTP requests
1735    pub record_http: bool,
1736    /// Record gRPC requests
1737    pub record_grpc: bool,
1738    /// Record WebSocket messages
1739    pub record_websocket: bool,
1740    /// Record GraphQL requests
1741    pub record_graphql: bool,
1742    /// Record proxied requests (requests that are forwarded to real backends)
1743    /// When enabled, proxied requests/responses will be recorded with metadata indicating proxy source
1744    #[serde(default = "default_true")]
1745    pub record_proxy: bool,
1746}
1747
1748impl Default for RecorderConfig {
1749    fn default() -> Self {
1750        Self {
1751            enabled: false,
1752            database_path: "./mockforge-recordings.db".to_string(),
1753            api_enabled: true,
1754            api_port: None,
1755            max_requests: 10000,
1756            retention_days: 7,
1757            record_http: true,
1758            record_grpc: true,
1759            record_websocket: true,
1760            record_graphql: true,
1761            record_proxy: true,
1762        }
1763    }
1764}
1765
1766/// Chaos engineering configuration
1767#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1768#[serde(default)]
1769#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1770pub struct ChaosEngConfig {
1771    /// Enable chaos engineering
1772    pub enabled: bool,
1773    /// Latency injection configuration
1774    pub latency: Option<LatencyInjectionConfig>,
1775    /// Fault injection configuration
1776    pub fault_injection: Option<FaultConfig>,
1777    /// Rate limiting configuration
1778    pub rate_limit: Option<RateLimitingConfig>,
1779    /// Traffic shaping configuration
1780    pub traffic_shaping: Option<NetworkShapingConfig>,
1781    /// Predefined scenario to use
1782    pub scenario: Option<String>,
1783}
1784
1785/// Latency injection configuration for chaos engineering
1786#[derive(Debug, Clone, Serialize, Deserialize)]
1787#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1788pub struct LatencyInjectionConfig {
1789    /// Enable latency injection
1790    pub enabled: bool,
1791    /// Fixed delay to inject (in milliseconds)
1792    pub fixed_delay_ms: Option<u64>,
1793    /// Random delay range (min_ms, max_ms) in milliseconds
1794    pub random_delay_range_ms: Option<(u64, u64)>,
1795    /// Jitter percentage to add variance to delays (0.0 to 1.0)
1796    pub jitter_percent: f64,
1797    /// Probability of injecting latency (0.0 to 1.0)
1798    pub probability: f64,
1799}
1800
1801/// Fault injection configuration for chaos engineering
1802#[derive(Debug, Clone, Serialize, Deserialize)]
1803#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1804pub struct FaultConfig {
1805    /// Enable fault injection
1806    pub enabled: bool,
1807    /// HTTP status codes to randomly return (e.g., [500, 502, 503])
1808    pub http_errors: Vec<u16>,
1809    /// Probability of returning HTTP errors (0.0 to 1.0)
1810    pub http_error_probability: f64,
1811    /// Enable connection errors (connection refused, reset, etc.)
1812    pub connection_errors: bool,
1813    /// Probability of connection errors (0.0 to 1.0)
1814    pub connection_error_probability: f64,
1815    /// Enable timeout errors
1816    pub timeout_errors: bool,
1817    /// Timeout duration in milliseconds
1818    pub timeout_ms: u64,
1819    /// Probability of timeout errors (0.0 to 1.0)
1820    pub timeout_probability: f64,
1821}
1822
1823/// Rate limiting configuration for traffic control
1824#[derive(Debug, Clone, Serialize, Deserialize)]
1825#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1826pub struct RateLimitingConfig {
1827    /// Enable rate limiting
1828    pub enabled: bool,
1829    /// Maximum requests per second allowed
1830    pub requests_per_second: u32,
1831    /// Maximum burst size before rate limiting kicks in
1832    pub burst_size: u32,
1833    /// Apply rate limiting per IP address
1834    pub per_ip: bool,
1835    /// Apply rate limiting per endpoint/path
1836    pub per_endpoint: bool,
1837}
1838
1839/// Network shaping configuration for simulating network conditions
1840#[derive(Debug, Clone, Serialize, Deserialize)]
1841#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1842pub struct NetworkShapingConfig {
1843    /// Enable network shaping
1844    pub enabled: bool,
1845    /// Bandwidth limit in bits per second
1846    pub bandwidth_limit_bps: u64,
1847    /// Packet loss percentage (0.0 to 1.0)
1848    pub packet_loss_percent: f64,
1849    /// Maximum concurrent connections allowed
1850    pub max_connections: u32,
1851}
1852
1853/// Load configuration from file
1854pub async fn load_config<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
1855    let content = fs::read_to_string(&path)
1856        .await
1857        .map_err(|e| Error::generic(format!("Failed to read config file: {}", e)))?;
1858
1859    // Parse config with improved error messages
1860    let config: ServerConfig = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
1861        || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
1862    {
1863        serde_yaml::from_str(&content).map_err(|e| {
1864            // Improve error message with field path context
1865            let error_msg = e.to_string();
1866            let mut full_msg = format!("Failed to parse YAML config: {}", error_msg);
1867
1868            // Add helpful context for common errors
1869            if error_msg.contains("missing field") {
1870                full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
1871                full_msg.push_str(
1872                    "\n   Omit fields you don't need - MockForge will use sensible defaults.",
1873                );
1874                full_msg.push_str("\n   See config.template.yaml for all available options.");
1875            } else if error_msg.contains("unknown field") {
1876                full_msg.push_str("\n\n💡 Check for typos in field names.");
1877                full_msg.push_str("\n   See config.template.yaml for valid field names.");
1878            }
1879
1880            Error::generic(full_msg)
1881        })?
1882    } else {
1883        serde_json::from_str(&content).map_err(|e| {
1884            // Improve error message with field path context
1885            let error_msg = e.to_string();
1886            let mut full_msg = format!("Failed to parse JSON config: {}", error_msg);
1887
1888            // Add helpful context for common errors
1889            if error_msg.contains("missing field") {
1890                full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
1891                full_msg.push_str(
1892                    "\n   Omit fields you don't need - MockForge will use sensible defaults.",
1893                );
1894                full_msg.push_str("\n   See config.template.yaml for all available options.");
1895            } else if error_msg.contains("unknown field") {
1896                full_msg.push_str("\n\n💡 Check for typos in field names.");
1897                full_msg.push_str("\n   See config.template.yaml for valid field names.");
1898            }
1899
1900            Error::generic(full_msg)
1901        })?
1902    };
1903
1904    Ok(config)
1905}
1906
1907/// Save configuration to file
1908pub async fn save_config<P: AsRef<Path>>(path: P, config: &ServerConfig) -> Result<()> {
1909    let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
1910        || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
1911    {
1912        serde_yaml::to_string(config)
1913            .map_err(|e| Error::generic(format!("Failed to serialize config to YAML: {}", e)))?
1914    } else {
1915        serde_json::to_string_pretty(config)
1916            .map_err(|e| Error::generic(format!("Failed to serialize config to JSON: {}", e)))?
1917    };
1918
1919    fs::write(path, content)
1920        .await
1921        .map_err(|e| Error::generic(format!("Failed to write config file: {}", e)))?;
1922
1923    Ok(())
1924}
1925
1926/// Load configuration with fallback to default
1927pub async fn load_config_with_fallback<P: AsRef<Path>>(path: P) -> ServerConfig {
1928    match load_config(&path).await {
1929        Ok(config) => {
1930            tracing::info!("Loaded configuration from {:?}", path.as_ref());
1931            config
1932        }
1933        Err(e) => {
1934            tracing::warn!(
1935                "Failed to load config from {:?}: {}. Using defaults.",
1936                path.as_ref(),
1937                e
1938            );
1939            ServerConfig::default()
1940        }
1941    }
1942}
1943
1944/// Create default configuration file
1945pub async fn create_default_config<P: AsRef<Path>>(path: P) -> Result<()> {
1946    let config = ServerConfig::default();
1947    save_config(path, &config).await?;
1948    Ok(())
1949}
1950
1951/// Environment variable overrides for configuration
1952pub fn apply_env_overrides(mut config: ServerConfig) -> ServerConfig {
1953    // HTTP server overrides
1954    if let Ok(port) = std::env::var("MOCKFORGE_HTTP_PORT") {
1955        if let Ok(port_num) = port.parse() {
1956            config.http.port = port_num;
1957        }
1958    }
1959
1960    if let Ok(host) = std::env::var("MOCKFORGE_HTTP_HOST") {
1961        config.http.host = host;
1962    }
1963
1964    // WebSocket server overrides
1965    if let Ok(port) = std::env::var("MOCKFORGE_WS_PORT") {
1966        if let Ok(port_num) = port.parse() {
1967            config.websocket.port = port_num;
1968        }
1969    }
1970
1971    // gRPC server overrides
1972    if let Ok(port) = std::env::var("MOCKFORGE_GRPC_PORT") {
1973        if let Ok(port_num) = port.parse() {
1974            config.grpc.port = port_num;
1975        }
1976    }
1977
1978    // SMTP server overrides
1979    if let Ok(port) = std::env::var("MOCKFORGE_SMTP_PORT") {
1980        if let Ok(port_num) = port.parse() {
1981            config.smtp.port = port_num;
1982        }
1983    }
1984
1985    if let Ok(host) = std::env::var("MOCKFORGE_SMTP_HOST") {
1986        config.smtp.host = host;
1987    }
1988
1989    if let Ok(enabled) = std::env::var("MOCKFORGE_SMTP_ENABLED") {
1990        config.smtp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
1991    }
1992
1993    if let Ok(hostname) = std::env::var("MOCKFORGE_SMTP_HOSTNAME") {
1994        config.smtp.hostname = hostname;
1995    }
1996
1997    // TCP server overrides
1998    if let Ok(port) = std::env::var("MOCKFORGE_TCP_PORT") {
1999        if let Ok(port_num) = port.parse() {
2000            config.tcp.port = port_num;
2001        }
2002    }
2003
2004    if let Ok(host) = std::env::var("MOCKFORGE_TCP_HOST") {
2005        config.tcp.host = host;
2006    }
2007
2008    if let Ok(enabled) = std::env::var("MOCKFORGE_TCP_ENABLED") {
2009        config.tcp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2010    }
2011
2012    // Admin UI overrides
2013    if let Ok(port) = std::env::var("MOCKFORGE_ADMIN_PORT") {
2014        if let Ok(port_num) = port.parse() {
2015            config.admin.port = port_num;
2016        }
2017    }
2018
2019    if std::env::var("MOCKFORGE_ADMIN_ENABLED").unwrap_or_default() == "true" {
2020        config.admin.enabled = true;
2021    }
2022
2023    // Admin UI host override - critical for Docker deployments
2024    if let Ok(host) = std::env::var("MOCKFORGE_ADMIN_HOST") {
2025        config.admin.host = host;
2026    }
2027
2028    if let Ok(mount_path) = std::env::var("MOCKFORGE_ADMIN_MOUNT_PATH") {
2029        if !mount_path.trim().is_empty() {
2030            config.admin.mount_path = Some(mount_path);
2031        }
2032    }
2033
2034    if let Ok(api_enabled) = std::env::var("MOCKFORGE_ADMIN_API_ENABLED") {
2035        let on = api_enabled == "1" || api_enabled.eq_ignore_ascii_case("true");
2036        config.admin.api_enabled = on;
2037    }
2038
2039    if let Ok(prometheus_url) = std::env::var("PROMETHEUS_URL") {
2040        config.admin.prometheus_url = prometheus_url;
2041    }
2042
2043    // Core configuration overrides
2044    if let Ok(latency_enabled) = std::env::var("MOCKFORGE_LATENCY_ENABLED") {
2045        let enabled = latency_enabled == "1" || latency_enabled.eq_ignore_ascii_case("true");
2046        config.core.latency_enabled = enabled;
2047    }
2048
2049    if let Ok(failures_enabled) = std::env::var("MOCKFORGE_FAILURES_ENABLED") {
2050        let enabled = failures_enabled == "1" || failures_enabled.eq_ignore_ascii_case("true");
2051        config.core.failures_enabled = enabled;
2052    }
2053
2054    if let Ok(overrides_enabled) = std::env::var("MOCKFORGE_OVERRIDES_ENABLED") {
2055        let enabled = overrides_enabled == "1" || overrides_enabled.eq_ignore_ascii_case("true");
2056        config.core.overrides_enabled = enabled;
2057    }
2058
2059    if let Ok(traffic_shaping_enabled) = std::env::var("MOCKFORGE_TRAFFIC_SHAPING_ENABLED") {
2060        let enabled =
2061            traffic_shaping_enabled == "1" || traffic_shaping_enabled.eq_ignore_ascii_case("true");
2062        config.core.traffic_shaping_enabled = enabled;
2063    }
2064
2065    // Traffic shaping overrides
2066    if let Ok(bandwidth_enabled) = std::env::var("MOCKFORGE_BANDWIDTH_ENABLED") {
2067        let enabled = bandwidth_enabled == "1" || bandwidth_enabled.eq_ignore_ascii_case("true");
2068        config.core.traffic_shaping.bandwidth.enabled = enabled;
2069    }
2070
2071    if let Ok(max_bytes_per_sec) = std::env::var("MOCKFORGE_BANDWIDTH_MAX_BYTES_PER_SEC") {
2072        if let Ok(bytes) = max_bytes_per_sec.parse() {
2073            config.core.traffic_shaping.bandwidth.max_bytes_per_sec = bytes;
2074            config.core.traffic_shaping.bandwidth.enabled = true;
2075        }
2076    }
2077
2078    if let Ok(burst_capacity) = std::env::var("MOCKFORGE_BANDWIDTH_BURST_CAPACITY_BYTES") {
2079        if let Ok(bytes) = burst_capacity.parse() {
2080            config.core.traffic_shaping.bandwidth.burst_capacity_bytes = bytes;
2081        }
2082    }
2083
2084    if let Ok(burst_loss_enabled) = std::env::var("MOCKFORGE_BURST_LOSS_ENABLED") {
2085        let enabled = burst_loss_enabled == "1" || burst_loss_enabled.eq_ignore_ascii_case("true");
2086        config.core.traffic_shaping.burst_loss.enabled = enabled;
2087    }
2088
2089    if let Ok(burst_probability) = std::env::var("MOCKFORGE_BURST_LOSS_PROBABILITY") {
2090        if let Ok(prob) = burst_probability.parse::<f64>() {
2091            config.core.traffic_shaping.burst_loss.burst_probability = prob.clamp(0.0, 1.0);
2092            config.core.traffic_shaping.burst_loss.enabled = true;
2093        }
2094    }
2095
2096    if let Ok(burst_duration) = std::env::var("MOCKFORGE_BURST_LOSS_DURATION_MS") {
2097        if let Ok(ms) = burst_duration.parse() {
2098            config.core.traffic_shaping.burst_loss.burst_duration_ms = ms;
2099        }
2100    }
2101
2102    if let Ok(loss_rate) = std::env::var("MOCKFORGE_BURST_LOSS_RATE") {
2103        if let Ok(rate) = loss_rate.parse::<f64>() {
2104            config.core.traffic_shaping.burst_loss.loss_rate_during_burst = rate.clamp(0.0, 1.0);
2105        }
2106    }
2107
2108    if let Ok(recovery_time) = std::env::var("MOCKFORGE_BURST_LOSS_RECOVERY_MS") {
2109        if let Ok(ms) = recovery_time.parse() {
2110            config.core.traffic_shaping.burst_loss.recovery_time_ms = ms;
2111        }
2112    }
2113
2114    // Logging overrides
2115    if let Ok(level) = std::env::var("MOCKFORGE_LOG_LEVEL") {
2116        config.logging.level = level;
2117    }
2118
2119    config
2120}
2121
2122/// Validate configuration
2123pub fn validate_config(config: &ServerConfig) -> Result<()> {
2124    // Validate port ranges
2125    if config.http.port == 0 {
2126        return Err(Error::generic("HTTP port cannot be 0"));
2127    }
2128    if config.websocket.port == 0 {
2129        return Err(Error::generic("WebSocket port cannot be 0"));
2130    }
2131    if config.grpc.port == 0 {
2132        return Err(Error::generic("gRPC port cannot be 0"));
2133    }
2134    if config.admin.port == 0 {
2135        return Err(Error::generic("Admin port cannot be 0"));
2136    }
2137
2138    // Check for port conflicts
2139    let ports = [
2140        ("HTTP", config.http.port),
2141        ("WebSocket", config.websocket.port),
2142        ("gRPC", config.grpc.port),
2143        ("Admin", config.admin.port),
2144    ];
2145
2146    for i in 0..ports.len() {
2147        for j in (i + 1)..ports.len() {
2148            if ports[i].1 == ports[j].1 {
2149                return Err(Error::generic(format!(
2150                    "Port conflict: {} and {} both use port {}",
2151                    ports[i].0, ports[j].0, ports[i].1
2152                )));
2153            }
2154        }
2155    }
2156
2157    // Validate log level
2158    let valid_levels = ["trace", "debug", "info", "warn", "error"];
2159    if !valid_levels.contains(&config.logging.level.as_str()) {
2160        return Err(Error::generic(format!(
2161            "Invalid log level: {}. Valid levels: {}",
2162            config.logging.level,
2163            valid_levels.join(", ")
2164        )));
2165    }
2166
2167    Ok(())
2168}
2169
2170/// Apply a profile to a base configuration
2171pub fn apply_profile(mut base: ServerConfig, profile: ProfileConfig) -> ServerConfig {
2172    // Macro to merge optional fields
2173    macro_rules! merge_field {
2174        ($field:ident) => {
2175            if let Some(override_val) = profile.$field {
2176                base.$field = override_val;
2177            }
2178        };
2179    }
2180
2181    merge_field!(http);
2182    merge_field!(websocket);
2183    merge_field!(graphql);
2184    merge_field!(grpc);
2185    merge_field!(mqtt);
2186    merge_field!(smtp);
2187    merge_field!(ftp);
2188    merge_field!(kafka);
2189    merge_field!(amqp);
2190    merge_field!(tcp);
2191    merge_field!(admin);
2192    merge_field!(chaining);
2193    merge_field!(core);
2194    merge_field!(logging);
2195    merge_field!(data);
2196    merge_field!(mockai);
2197    merge_field!(observability);
2198    merge_field!(multi_tenant);
2199    merge_field!(routes);
2200    merge_field!(protocols);
2201
2202    base
2203}
2204
2205/// Load configuration with profile support
2206pub async fn load_config_with_profile<P: AsRef<Path>>(
2207    path: P,
2208    profile_name: Option<&str>,
2209) -> Result<ServerConfig> {
2210    // Use load_config_auto to support all formats
2211    let mut config = load_config_auto(&path).await?;
2212
2213    // Apply profile if specified
2214    if let Some(profile) = profile_name {
2215        if let Some(profile_config) = config.profiles.remove(profile) {
2216            tracing::info!("Applying profile: {}", profile);
2217            config = apply_profile(config, profile_config);
2218        } else {
2219            return Err(Error::generic(format!(
2220                "Profile '{}' not found in configuration. Available profiles: {}",
2221                profile,
2222                config.profiles.keys().map(|k| k.as_str()).collect::<Vec<_>>().join(", ")
2223            )));
2224        }
2225    }
2226
2227    // Clear profiles from final config to save memory
2228    config.profiles.clear();
2229
2230    Ok(config)
2231}
2232
2233/// Load configuration from TypeScript/JavaScript file
2234pub async fn load_config_from_js<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2235    use rquickjs::{Context, Runtime};
2236
2237    let content = fs::read_to_string(&path)
2238        .await
2239        .map_err(|e| Error::generic(format!("Failed to read JS/TS config file: {}", e)))?;
2240
2241    // Create a JavaScript runtime
2242    let runtime = Runtime::new()
2243        .map_err(|e| Error::generic(format!("Failed to create JS runtime: {}", e)))?;
2244    let context = Context::full(&runtime)
2245        .map_err(|e| Error::generic(format!("Failed to create JS context: {}", e)))?;
2246
2247    context.with(|ctx| {
2248        // For TypeScript files, we need to strip type annotations
2249        // This is a simple approach - for production, consider using a proper TS compiler
2250        let js_content = if path
2251            .as_ref()
2252            .extension()
2253            .and_then(|s| s.to_str())
2254            .map(|ext| ext == "ts")
2255            .unwrap_or(false)
2256        {
2257            strip_typescript_types(&content)?
2258        } else {
2259            content
2260        };
2261
2262        // Evaluate the config file
2263        let result: rquickjs::Value = ctx
2264            .eval(js_content.as_bytes())
2265            .map_err(|e| Error::generic(format!("Failed to evaluate JS config: {}", e)))?;
2266
2267        // Convert to JSON string
2268        let json_str: String = ctx
2269            .json_stringify(result)
2270            .map_err(|e| Error::generic(format!("Failed to stringify JS config: {}", e)))?
2271            .ok_or_else(|| Error::generic("JS config returned undefined"))?
2272            .get()
2273            .map_err(|e| Error::generic(format!("Failed to get JSON string: {}", e)))?;
2274
2275        // Parse JSON into ServerConfig
2276        serde_json::from_str(&json_str).map_err(|e| {
2277            Error::generic(format!("Failed to parse JS config as ServerConfig: {}", e))
2278        })
2279    })
2280}
2281
2282/// Simple TypeScript type stripper (removes type annotations)
2283/// Note: This is a basic implementation. For production use, consider using swc or esbuild
2284///
2285/// # Errors
2286/// Returns an error if regex compilation fails. This should never happen with static patterns,
2287/// but we handle it gracefully to prevent panics.
2288fn strip_typescript_types(content: &str) -> Result<String> {
2289    use regex::Regex;
2290
2291    let mut result = content.to_string();
2292
2293    // Compile regex patterns with error handling
2294    // Note: These patterns are statically known and should never fail,
2295    // but we handle errors to prevent panics in edge cases
2296
2297    // Remove interface declarations (handles multi-line)
2298    let interface_re = Regex::new(r"(?ms)interface\s+\w+\s*\{[^}]*\}\s*")
2299        .map_err(|e| Error::generic(format!("Failed to compile interface regex: {}", e)))?;
2300    result = interface_re.replace_all(&result, "").to_string();
2301
2302    // Remove type aliases
2303    let type_alias_re = Regex::new(r"(?m)^type\s+\w+\s*=\s*[^;]+;\s*")
2304        .map_err(|e| Error::generic(format!("Failed to compile type alias regex: {}", e)))?;
2305    result = type_alias_re.replace_all(&result, "").to_string();
2306
2307    // Remove type annotations (: Type)
2308    let type_annotation_re = Regex::new(r":\s*[A-Z]\w*(<[^>]+>)?(\[\])?")
2309        .map_err(|e| Error::generic(format!("Failed to compile type annotation regex: {}", e)))?;
2310    result = type_annotation_re.replace_all(&result, "").to_string();
2311
2312    // Remove type imports and exports
2313    let type_import_re = Regex::new(r"(?m)^(import|export)\s+type\s+.*$")
2314        .map_err(|e| Error::generic(format!("Failed to compile type import regex: {}", e)))?;
2315    result = type_import_re.replace_all(&result, "").to_string();
2316
2317    // Remove as Type
2318    let as_type_re = Regex::new(r"\s+as\s+\w+")
2319        .map_err(|e| Error::generic(format!("Failed to compile 'as type' regex: {}", e)))?;
2320    result = as_type_re.replace_all(&result, "").to_string();
2321
2322    Ok(result)
2323}
2324
2325/// Enhanced load_config that supports multiple formats including JS/TS
2326pub async fn load_config_auto<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2327    let ext = path.as_ref().extension().and_then(|s| s.to_str()).unwrap_or("");
2328
2329    match ext {
2330        "ts" | "js" => load_config_from_js(&path).await,
2331        "yaml" | "yml" | "json" => load_config(&path).await,
2332        _ => Err(Error::generic(format!(
2333            "Unsupported config file format: {}. Supported: .ts, .js, .yaml, .yml, .json",
2334            ext
2335        ))),
2336    }
2337}
2338
2339/// Discover configuration file with support for all formats
2340pub async fn discover_config_file_all_formats() -> Result<std::path::PathBuf> {
2341    let current_dir = std::env::current_dir()
2342        .map_err(|e| Error::generic(format!("Failed to get current directory: {}", e)))?;
2343
2344    let config_names = vec![
2345        "mockforge.config.ts",
2346        "mockforge.config.js",
2347        "mockforge.yaml",
2348        "mockforge.yml",
2349        ".mockforge.yaml",
2350        ".mockforge.yml",
2351    ];
2352
2353    // Check current directory
2354    for name in &config_names {
2355        let path = current_dir.join(name);
2356        if tokio::fs::metadata(&path).await.is_ok() {
2357            return Ok(path);
2358        }
2359    }
2360
2361    // Check parent directories (up to 5 levels)
2362    let mut dir = current_dir.clone();
2363    for _ in 0..5 {
2364        if let Some(parent) = dir.parent() {
2365            for name in &config_names {
2366                let path = parent.join(name);
2367                if tokio::fs::metadata(&path).await.is_ok() {
2368                    return Ok(path);
2369                }
2370            }
2371            dir = parent.to_path_buf();
2372        } else {
2373            break;
2374        }
2375    }
2376
2377    Err(Error::generic(
2378        "No configuration file found. Expected one of: mockforge.config.ts, mockforge.config.js, mockforge.yaml, mockforge.yml",
2379    ))
2380}
2381
2382#[cfg(test)]
2383mod tests {
2384    use super::*;
2385
2386    #[test]
2387    fn test_default_config() {
2388        let config = ServerConfig::default();
2389        assert_eq!(config.http.port, 3000);
2390        assert_eq!(config.websocket.port, 3001);
2391        assert_eq!(config.grpc.port, 50051);
2392        assert_eq!(config.admin.port, 9080);
2393    }
2394
2395    #[test]
2396    fn test_config_validation() {
2397        let mut config = ServerConfig::default();
2398        assert!(validate_config(&config).is_ok());
2399
2400        // Test port conflict
2401        config.websocket.port = config.http.port;
2402        assert!(validate_config(&config).is_err());
2403
2404        // Test invalid log level
2405        config.websocket.port = 3001; // Fix port conflict
2406        config.logging.level = "invalid".to_string();
2407        assert!(validate_config(&config).is_err());
2408    }
2409
2410    #[test]
2411    fn test_apply_profile() {
2412        let mut base = ServerConfig::default();
2413        assert_eq!(base.http.port, 3000);
2414
2415        let mut profile = ProfileConfig::default();
2416        profile.http = Some(HttpConfig {
2417            port: 8080,
2418            ..Default::default()
2419        });
2420        profile.logging = Some(LoggingConfig {
2421            level: "debug".to_string(),
2422            ..Default::default()
2423        });
2424
2425        let merged = apply_profile(base, profile);
2426        assert_eq!(merged.http.port, 8080);
2427        assert_eq!(merged.logging.level, "debug");
2428        assert_eq!(merged.websocket.port, 3001); // Unchanged
2429    }
2430
2431    #[test]
2432    fn test_strip_typescript_types() {
2433        let ts_code = r#"
2434interface Config {
2435    port: number;
2436    host: string;
2437}
2438
2439const config: Config = {
2440    port: 3000,
2441    host: "localhost"
2442} as Config;
2443"#;
2444
2445        let stripped = strip_typescript_types(ts_code).expect("Should strip TypeScript types");
2446        assert!(!stripped.contains("interface"));
2447        assert!(!stripped.contains(": Config"));
2448        assert!(!stripped.contains("as Config"));
2449    }
2450}