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/// Contracts configuration for fitness rules and contract management
58#[derive(Debug, Clone, Serialize, Deserialize, Default)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60#[serde(default)]
61pub struct ContractsConfig {
62    /// Fitness rules for contract validation
63    pub fitness_rules: Vec<FitnessRuleConfig>,
64}
65
66/// Behavioral Economics Engine configuration
67#[derive(Debug, Clone, Serialize, Deserialize, Default)]
68#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
69#[serde(default)]
70pub struct BehavioralEconomicsConfig {
71    /// Whether the behavioral economics engine is enabled
72    pub enabled: bool,
73    /// List of behavior rules
74    #[serde(default)]
75    pub rules: Vec<crate::behavioral_economics::BehaviorRule>,
76    /// Global sensitivity for behavioral changes (0.0 - 1.0)
77    /// A higher sensitivity means mocks react more strongly to conditions.
78    #[serde(default = "default_behavioral_sensitivity")]
79    pub global_sensitivity: f64,
80    /// Interval in milliseconds for re-evaluating time-based conditions
81    #[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 // 1 second
91}
92
93/// Drift Learning configuration
94#[derive(Debug, Clone, Serialize, Deserialize, Default)]
95#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
96#[serde(default)]
97pub struct DriftLearningConfig {
98    /// Enable or disable drift learning
99    pub enabled: bool,
100    /// Learning mode (behavioral, statistical, hybrid)
101    #[serde(default)]
102    pub mode: DriftLearningMode,
103    /// How quickly mocks adapt to new patterns (0.0 - 1.0)
104    #[serde(default = "default_learning_sensitivity")]
105    pub sensitivity: f64,
106    /// How quickly old patterns are forgotten (0.0 - 1.0)
107    #[serde(default = "default_learning_decay")]
108    pub decay: f64,
109    /// Minimum number of samples required to learn a pattern
110    #[serde(default = "default_min_samples")]
111    pub min_samples: u64,
112    /// Enable persona-specific behavior adaptation
113    #[serde(default)]
114    pub persona_adaptation: bool,
115    /// Opt-in configuration for specific personas to learn
116    #[serde(default)]
117    pub persona_learning: HashMap<String, bool>, // persona_id -> enabled
118    /// Opt-in configuration for specific endpoints to learn
119    #[serde(default)]
120    pub endpoint_learning: HashMap<String, bool>, // endpoint_pattern -> enabled
121}
122
123/// Drift learning mode
124#[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    /// Behavioral learning - adapts to behavior patterns
129    #[default]
130    Behavioral,
131    /// Statistical learning - adapts to statistical patterns
132    Statistical,
133    /// Hybrid - combines behavioral and statistical
134    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/// Configuration for a fitness rule (YAML config format)
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
152pub struct FitnessRuleConfig {
153    /// Human-readable name for the fitness rule
154    pub name: String,
155    /// Scope where this rule applies (endpoint pattern, service name, or "global")
156    pub scope: String,
157    /// Type of fitness rule
158    #[serde(rename = "type")]
159    pub rule_type: FitnessRuleType,
160    /// Maximum percent increase for response size (for response_size_delta type)
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub max_percent_increase: Option<f64>,
163    /// Maximum number of fields (for field_count type)
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub max_fields: Option<u32>,
166    /// Maximum schema depth (for schema_complexity type)
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub max_depth: Option<u32>,
169}
170
171/// Type of fitness rule (YAML config format)
172#[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    /// Response size must not increase by more than max_percent_increase
177    ResponseSizeDelta,
178    /// No new required fields allowed
179    NoNewRequiredFields,
180    /// Field count must not exceed max_fields
181    FieldCount,
182    /// Schema complexity (depth) must not exceed max_depth
183    SchemaComplexity,
184}
185
186/// Behavioral cloning configuration
187#[derive(Debug, Clone, Serialize, Deserialize)]
188#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
189#[serde(default)]
190pub struct BehavioralCloningConfig {
191    /// Whether behavioral cloning is enabled
192    pub enabled: bool,
193    /// Path to recorder database (defaults to ./recordings.db)
194    pub database_path: Option<String>,
195    /// Enable middleware to apply learned behavior
196    pub enable_middleware: bool,
197    /// Minimum frequency threshold for sequence learning (0.0 to 1.0)
198    pub min_sequence_frequency: f64,
199    /// Minimum requests per trace for sequence discovery
200    pub min_requests_per_trace: Option<i32>,
201    /// Flow recording configuration
202    #[serde(default)]
203    pub flow_recording: FlowRecordingConfig,
204    /// Scenario replay configuration
205    #[serde(default)]
206    pub scenario_replay: ScenarioReplayConfig,
207}
208
209/// Flow recording configuration
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
212#[serde(default)]
213pub struct FlowRecordingConfig {
214    /// Whether flow recording is enabled
215    pub enabled: bool,
216    /// How to group requests into flows (trace_id, session_id, ip_time_window)
217    pub group_by: String,
218    /// Time window in seconds for IP-based grouping
219    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, // 5 minutes
228        }
229    }
230}
231
232/// Scenario replay configuration
233#[derive(Debug, Clone, Serialize, Deserialize)]
234#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
235#[serde(default)]
236pub struct ScenarioReplayConfig {
237    /// Whether scenario replay is enabled
238    pub enabled: bool,
239    /// Default replay mode (strict or flex)
240    pub default_mode: String,
241    /// List of scenario IDs to activate on startup
242    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, // 10% default
262            min_requests_per_trace: None,
263            flow_recording: FlowRecordingConfig::default(),
264            scenario_replay: ScenarioReplayConfig::default(),
265        }
266    }
267}
268
269/// Authentication configuration for HTTP requests
270#[derive(Debug, Clone, Serialize, Deserialize)]
271#[serde(default)]
272#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
273pub struct AuthConfig {
274    /// JWT configuration
275    pub jwt: Option<JwtConfig>,
276    /// OAuth2 configuration
277    pub oauth2: Option<OAuth2Config>,
278    /// Basic auth configuration
279    pub basic_auth: Option<BasicAuthConfig>,
280    /// API key configuration
281    pub api_key: Option<ApiKeyConfig>,
282    /// Whether to require authentication for all requests
283    pub require_auth: bool,
284}
285
286/// JWT authentication configuration
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
289pub struct JwtConfig {
290    /// JWT secret key for HMAC algorithms
291    pub secret: Option<String>,
292    /// RSA public key PEM for RSA algorithms
293    pub rsa_public_key: Option<String>,
294    /// ECDSA public key PEM for ECDSA algorithms
295    pub ecdsa_public_key: Option<String>,
296    /// Expected issuer
297    pub issuer: Option<String>,
298    /// Expected audience
299    pub audience: Option<String>,
300    /// Supported algorithms (defaults to HS256, RS256, ES256)
301    pub algorithms: Vec<String>,
302}
303
304/// OAuth2 configuration
305#[derive(Debug, Clone, Serialize, Deserialize)]
306#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
307pub struct OAuth2Config {
308    /// OAuth2 client ID
309    pub client_id: String,
310    /// OAuth2 client secret
311    pub client_secret: String,
312    /// Token introspection URL
313    pub introspection_url: String,
314    /// Authorization server URL
315    pub auth_url: Option<String>,
316    /// Token URL
317    pub token_url: Option<String>,
318    /// Expected token type
319    pub token_type_hint: Option<String>,
320}
321
322/// Basic authentication configuration
323#[derive(Debug, Clone, Serialize, Deserialize)]
324#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
325pub struct BasicAuthConfig {
326    /// Username/password pairs
327    pub credentials: HashMap<String, String>,
328}
329
330/// API key configuration
331#[derive(Debug, Clone, Serialize, Deserialize)]
332#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
333pub struct ApiKeyConfig {
334    /// Expected header name (default: X-API-Key)
335    pub header_name: String,
336    /// Expected query parameter name
337    pub query_name: Option<String>,
338    /// Valid API keys
339    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/// Route configuration for custom HTTP routes
359#[derive(Debug, Clone, Serialize, Deserialize)]
360#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
361pub struct RouteConfig {
362    /// Route path (supports path parameters like /users/{id})
363    pub path: String,
364    /// HTTP method
365    pub method: String,
366    /// Request configuration
367    pub request: Option<RouteRequestConfig>,
368    /// Response configuration
369    pub response: RouteResponseConfig,
370    /// Per-route fault injection configuration
371    #[serde(default)]
372    pub fault_injection: Option<RouteFaultInjectionConfig>,
373    /// Per-route latency configuration
374    #[serde(default)]
375    pub latency: Option<RouteLatencyConfig>,
376}
377
378/// Request configuration for routes
379#[derive(Debug, Clone, Serialize, Deserialize)]
380#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
381pub struct RouteRequestConfig {
382    /// Request validation configuration
383    pub validation: Option<RouteValidationConfig>,
384}
385
386/// Response configuration for routes
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
389pub struct RouteResponseConfig {
390    /// HTTP status code
391    pub status: u16,
392    /// Response headers
393    #[serde(default)]
394    pub headers: HashMap<String, String>,
395    /// Response body
396    pub body: Option<serde_json::Value>,
397}
398
399/// Validation configuration for routes
400#[derive(Debug, Clone, Serialize, Deserialize)]
401#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
402pub struct RouteValidationConfig {
403    /// JSON schema for request validation
404    pub schema: serde_json::Value,
405}
406
407/// Per-route fault injection configuration
408#[derive(Debug, Clone, Serialize, Deserialize)]
409#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
410pub struct RouteFaultInjectionConfig {
411    /// Enable fault injection for this route
412    pub enabled: bool,
413    /// Probability of injecting a fault (0.0-1.0)
414    pub probability: f64,
415    /// Fault types to inject
416    pub fault_types: Vec<RouteFaultType>,
417}
418
419/// Fault types that can be injected per route
420#[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    /// HTTP error with status code
425    HttpError {
426        /// HTTP status code to return
427        status_code: u16,
428        /// Optional error message
429        message: Option<String>,
430    },
431    /// Connection error
432    ConnectionError {
433        /// Optional error message
434        message: Option<String>,
435    },
436    /// Timeout error
437    Timeout {
438        /// Timeout duration in milliseconds
439        duration_ms: u64,
440        /// Optional error message
441        message: Option<String>,
442    },
443    /// Partial response (truncate at percentage)
444    PartialResponse {
445        /// Percentage of response to truncate (0.0-100.0)
446        truncate_percent: f64,
447    },
448    /// Payload corruption
449    PayloadCorruption {
450        /// Type of corruption to apply
451        corruption_type: String,
452    },
453}
454
455/// Per-route latency configuration
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
458pub struct RouteLatencyConfig {
459    /// Enable latency injection for this route
460    pub enabled: bool,
461    /// Probability of applying latency (0.0-1.0)
462    pub probability: f64,
463    /// Fixed delay in milliseconds
464    pub fixed_delay_ms: Option<u64>,
465    /// Random delay range (min_ms, max_ms)
466    pub random_delay_range_ms: Option<(u64, u64)>,
467    /// Jitter percentage (0.0-100.0)
468    pub jitter_percent: f64,
469    /// Latency distribution type
470    #[serde(default)]
471    pub distribution: LatencyDistribution,
472}
473
474/// Latency distribution type
475#[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 delay
480    Fixed,
481    /// Normal distribution (requires mean and std_dev)
482    Normal {
483        /// Mean delay in milliseconds
484        mean_ms: f64,
485        /// Standard deviation in milliseconds
486        std_dev_ms: f64,
487    },
488    /// Exponential distribution (requires lambda)
489    Exponential {
490        /// Lambda parameter for exponential distribution
491        lambda: f64,
492    },
493    /// Uniform distribution (uses random_delay_range_ms)
494    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/// Deceptive deploy configuration for production-like mock APIs
527#[derive(Debug, Clone, Serialize, Deserialize)]
528#[serde(default)]
529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
530#[derive(Default)]
531pub struct DeceptiveDeployConfig {
532    /// Enable deceptive deploy mode
533    pub enabled: bool,
534    /// Production-like CORS configuration
535    pub cors: Option<ProductionCorsConfig>,
536    /// Production-like rate limiting
537    pub rate_limit: Option<ProductionRateLimitConfig>,
538    /// Production-like headers to add to all responses
539    #[serde(default)]
540    pub headers: HashMap<String, String>,
541    /// OAuth configuration for production-like auth flows
542    pub oauth: Option<ProductionOAuthConfig>,
543    /// Custom domain for deployment
544    pub custom_domain: Option<String>,
545    /// Auto-start tunnel when deploying
546    pub auto_tunnel: bool,
547    /// Deceptive canary mode configuration
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub canary: Option<crate::deceptive_canary::DeceptiveCanaryConfig>,
550}
551
552impl DeceptiveDeployConfig {
553    /// Generate production-like configuration preset
554    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, // Configured separately
582            custom_domain: None,
583            auto_tunnel: true,
584            canary: None,
585        }
586    }
587}
588
589/// Production-like CORS configuration
590#[derive(Debug, Clone, Serialize, Deserialize)]
591#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
592pub struct ProductionCorsConfig {
593    /// Allowed origins (use "*" for all origins)
594    #[serde(default)]
595    pub allowed_origins: Vec<String>,
596    /// Allowed HTTP methods
597    #[serde(default)]
598    pub allowed_methods: Vec<String>,
599    /// Allowed headers (use "*" for all headers)
600    #[serde(default)]
601    pub allowed_headers: Vec<String>,
602    /// Allow credentials (cookies, authorization headers)
603    pub allow_credentials: bool,
604}
605
606/// Production-like rate limiting configuration
607#[derive(Debug, Clone, Serialize, Deserialize)]
608#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
609pub struct ProductionRateLimitConfig {
610    /// Requests per minute allowed
611    pub requests_per_minute: u32,
612    /// Burst capacity (maximum requests in a short burst)
613    pub burst: u32,
614    /// Enable per-IP rate limiting
615    pub per_ip: bool,
616}
617
618/// Production-like OAuth configuration
619#[derive(Debug, Clone, Serialize, Deserialize)]
620#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
621pub struct ProductionOAuthConfig {
622    /// OAuth2 client ID
623    pub client_id: String,
624    /// OAuth2 client secret
625    pub client_secret: String,
626    /// Token introspection URL
627    pub introspection_url: String,
628    /// Authorization server URL
629    pub auth_url: Option<String>,
630    /// Token URL
631    pub token_url: Option<String>,
632    /// Expected token type hint
633    pub token_type_hint: Option<String>,
634}
635
636impl From<ProductionOAuthConfig> for OAuth2Config {
637    /// Convert ProductionOAuthConfig to OAuth2Config for use in auth middleware
638    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/// Performance and resource configuration
651#[derive(Debug, Clone, Serialize, Deserialize)]
652#[serde(default)]
653#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
654pub struct PerformanceConfig {
655    /// Response compression configuration
656    pub compression: CompressionConfig,
657    /// Connection pooling configuration
658    pub connection_pool: ConnectionPoolConfig,
659    /// Request limits configuration
660    pub request_limits: RequestLimitsConfig,
661    /// Worker thread configuration
662    pub workers: WorkerConfig,
663    /// Circuit breaker configuration
664    pub circuit_breaker: CircuitBreakerConfig,
665}
666
667impl Default for PerformanceConfig {
668    fn default() -> Self {
669        Self {
670            compression: CompressionConfig::default(),
671            connection_pool: ConnectionPoolConfig::default(),
672            request_limits: RequestLimitsConfig::default(),
673            workers: WorkerConfig::default(),
674            circuit_breaker: CircuitBreakerConfig::default(),
675        }
676    }
677}
678
679/// Response compression configuration
680#[derive(Debug, Clone, Serialize, Deserialize)]
681#[serde(default)]
682#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
683pub struct CompressionConfig {
684    /// Enable response compression
685    pub enabled: bool,
686    /// Compression algorithm: gzip, deflate, br (brotli), zstd
687    pub algorithm: String,
688    /// Minimum response size to compress (bytes)
689    pub min_size: usize,
690    /// Compression level (1-9 for gzip/deflate, 0-11 for brotli, 1-22 for zstd)
691    pub level: u32,
692    /// Content types to compress (e.g., ["application/json", "text/html"])
693    pub content_types: Vec<String>,
694}
695
696impl Default for CompressionConfig {
697    fn default() -> Self {
698        Self {
699            enabled: true,
700            algorithm: "gzip".to_string(),
701            min_size: 1024, // 1KB
702            level: 6,
703            content_types: vec![
704                "application/json".to_string(),
705                "application/xml".to_string(),
706                "text/plain".to_string(),
707                "text/html".to_string(),
708                "text/css".to_string(),
709                "application/javascript".to_string(),
710            ],
711        }
712    }
713}
714
715/// Connection pooling configuration for downstream services
716#[derive(Debug, Clone, Serialize, Deserialize)]
717#[serde(default)]
718#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
719pub struct ConnectionPoolConfig {
720    /// Maximum idle connections per host
721    pub max_idle_per_host: usize,
722    /// Maximum total connections
723    pub max_connections: usize,
724    /// Idle connection timeout in seconds
725    pub idle_timeout_secs: u64,
726    /// Connection acquire timeout in milliseconds
727    pub acquire_timeout_ms: u64,
728    /// Enable connection pooling
729    pub enabled: bool,
730}
731
732impl Default for ConnectionPoolConfig {
733    fn default() -> Self {
734        Self {
735            max_idle_per_host: 10,
736            max_connections: 100,
737            idle_timeout_secs: 90,
738            acquire_timeout_ms: 5000,
739            enabled: true,
740        }
741    }
742}
743
744/// Request limits configuration
745#[derive(Debug, Clone, Serialize, Deserialize)]
746#[serde(default)]
747#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
748pub struct RequestLimitsConfig {
749    /// Maximum request body size in bytes (default: 10MB)
750    pub max_body_size: usize,
751    /// Maximum header size in bytes
752    pub max_header_size: usize,
753    /// Maximum number of headers
754    pub max_headers: usize,
755    /// Maximum URI length
756    pub max_uri_length: usize,
757    /// Per-route body size limits (path pattern -> max bytes)
758    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
759    pub per_route_limits: HashMap<String, usize>,
760}
761
762impl Default for RequestLimitsConfig {
763    fn default() -> Self {
764        Self {
765            max_body_size: 10 * 1024 * 1024, // 10MB
766            max_header_size: 16 * 1024,      // 16KB
767            max_headers: 100,
768            max_uri_length: 8192,
769            per_route_limits: HashMap::new(),
770        }
771    }
772}
773
774/// Worker thread configuration
775#[derive(Debug, Clone, Serialize, Deserialize)]
776#[serde(default)]
777#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
778pub struct WorkerConfig {
779    /// Number of worker threads (0 = auto-detect based on CPU cores)
780    pub threads: usize,
781    /// Blocking thread pool size for CPU-intensive work
782    pub blocking_threads: usize,
783    /// Thread stack size in bytes
784    pub stack_size: usize,
785    /// Thread name prefix
786    pub name_prefix: String,
787}
788
789impl Default for WorkerConfig {
790    fn default() -> Self {
791        Self {
792            threads: 0, // auto-detect
793            blocking_threads: 512,
794            stack_size: 2 * 1024 * 1024, // 2MB
795            name_prefix: "mockforge-worker".to_string(),
796        }
797    }
798}
799
800/// Circuit breaker configuration
801#[derive(Debug, Clone, Serialize, Deserialize)]
802#[serde(default)]
803#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
804pub struct CircuitBreakerConfig {
805    /// Enable circuit breaker
806    pub enabled: bool,
807    /// Failure threshold before opening circuit
808    pub failure_threshold: u32,
809    /// Success threshold before closing circuit
810    pub success_threshold: u32,
811    /// Half-open timeout in seconds (time before trying again after opening)
812    pub half_open_timeout_secs: u64,
813    /// Sliding window size for tracking failures
814    pub window_size: u32,
815    /// Per-endpoint circuit breaker configuration
816    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
817    pub per_endpoint: HashMap<String, EndpointCircuitBreakerConfig>,
818}
819
820impl Default for CircuitBreakerConfig {
821    fn default() -> Self {
822        Self {
823            enabled: false,
824            failure_threshold: 5,
825            success_threshold: 2,
826            half_open_timeout_secs: 30,
827            window_size: 10,
828            per_endpoint: HashMap::new(),
829        }
830    }
831}
832
833/// Per-endpoint circuit breaker configuration
834#[derive(Debug, Clone, Serialize, Deserialize)]
835#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
836pub struct EndpointCircuitBreakerConfig {
837    /// Failure threshold for this endpoint
838    pub failure_threshold: u32,
839    /// Success threshold for this endpoint
840    pub success_threshold: u32,
841    /// Half-open timeout in seconds
842    pub half_open_timeout_secs: u64,
843}
844
845/// Configuration hot-reload settings
846#[derive(Debug, Clone, Serialize, Deserialize)]
847#[serde(default)]
848#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
849pub struct ConfigHotReloadConfig {
850    /// Enable configuration hot-reload
851    pub enabled: bool,
852    /// Check interval in seconds
853    pub check_interval_secs: u64,
854    /// Debounce delay in milliseconds (prevent rapid reloads)
855    pub debounce_delay_ms: u64,
856    /// Paths to watch for changes (config files, fixture directories)
857    #[serde(default, skip_serializing_if = "Vec::is_empty")]
858    pub watch_paths: Vec<String>,
859    /// Reload on OpenAPI spec changes
860    pub reload_on_spec_change: bool,
861    /// Reload on fixture file changes
862    pub reload_on_fixture_change: bool,
863    /// Reload on plugin changes
864    pub reload_on_plugin_change: bool,
865    /// Graceful reload (wait for in-flight requests)
866    pub graceful_reload: bool,
867    /// Graceful reload timeout in seconds
868    pub graceful_timeout_secs: u64,
869    /// Validate config before applying reload
870    pub validate_before_reload: bool,
871    /// Rollback to previous config on reload failure
872    pub rollback_on_failure: bool,
873}
874
875impl Default for ConfigHotReloadConfig {
876    fn default() -> Self {
877        Self {
878            enabled: false,
879            check_interval_secs: 5,
880            debounce_delay_ms: 1000,
881            watch_paths: Vec::new(),
882            reload_on_spec_change: true,
883            reload_on_fixture_change: true,
884            reload_on_plugin_change: true,
885            graceful_reload: true,
886            graceful_timeout_secs: 30,
887            validate_before_reload: true,
888            rollback_on_failure: true,
889        }
890    }
891}
892
893/// Secret backend provider type
894#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
895#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
896#[serde(rename_all = "lowercase")]
897pub enum SecretBackendType {
898    /// No secret backend (use environment variables directly)
899    #[default]
900    None,
901    /// HashCorp Vault
902    Vault,
903    /// AWS Secrets Manager
904    AwsSecretsManager,
905    /// Azure Key Vault
906    AzureKeyVault,
907    /// Google Cloud Secret Manager
908    GcpSecretManager,
909    /// Kubernetes Secrets
910    Kubernetes,
911    /// Local encrypted file
912    EncryptedFile,
913}
914
915/// Secret backend configuration
916#[derive(Debug, Clone, Serialize, Deserialize)]
917#[serde(default)]
918#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
919pub struct SecretBackendConfig {
920    /// Secret backend provider
921    pub provider: SecretBackendType,
922    /// Vault-specific configuration
923    #[serde(skip_serializing_if = "Option::is_none")]
924    pub vault: Option<VaultConfig>,
925    /// AWS Secrets Manager configuration
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub aws: Option<AwsSecretsConfig>,
928    /// Azure Key Vault configuration
929    #[serde(skip_serializing_if = "Option::is_none")]
930    pub azure: Option<AzureKeyVaultConfig>,
931    /// GCP Secret Manager configuration
932    #[serde(skip_serializing_if = "Option::is_none")]
933    pub gcp: Option<GcpSecretManagerConfig>,
934    /// Kubernetes secrets configuration
935    #[serde(skip_serializing_if = "Option::is_none")]
936    pub kubernetes: Option<KubernetesSecretsConfig>,
937    /// Encrypted file configuration
938    #[serde(skip_serializing_if = "Option::is_none")]
939    pub encrypted_file: Option<EncryptedFileConfig>,
940    /// Secret key mappings (config key -> secret path)
941    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
942    pub mappings: HashMap<String, String>,
943    /// Cache secrets in memory (seconds, 0 = no caching)
944    pub cache_ttl_secs: u64,
945    /// Retry configuration for secret retrieval
946    pub retry_attempts: u32,
947    /// Retry delay in milliseconds
948    pub retry_delay_ms: u64,
949}
950
951impl Default for SecretBackendConfig {
952    fn default() -> Self {
953        Self {
954            provider: SecretBackendType::None,
955            vault: None,
956            aws: None,
957            azure: None,
958            gcp: None,
959            kubernetes: None,
960            encrypted_file: None,
961            mappings: HashMap::new(),
962            cache_ttl_secs: 300, // 5 minutes
963            retry_attempts: 3,
964            retry_delay_ms: 1000,
965        }
966    }
967}
968
969/// HashCorp Vault configuration
970#[derive(Debug, Clone, Serialize, Deserialize)]
971#[serde(default)]
972#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
973pub struct VaultConfig {
974    /// Vault server address
975    pub address: String,
976    /// Vault namespace (for enterprise)
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub namespace: Option<String>,
979    /// Authentication method
980    pub auth_method: VaultAuthMethod,
981    /// Vault token (for token auth)
982    #[serde(skip_serializing_if = "Option::is_none")]
983    pub token: Option<String>,
984    /// Role ID (for AppRole auth)
985    #[serde(skip_serializing_if = "Option::is_none")]
986    pub role_id: Option<String>,
987    /// Secret ID (for AppRole auth)
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub secret_id: Option<String>,
990    /// Kubernetes role (for Kubernetes auth)
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub kubernetes_role: Option<String>,
993    /// Secret engine mount path
994    pub mount_path: String,
995    /// Secret path prefix
996    pub path_prefix: String,
997    /// TLS CA certificate path
998    #[serde(skip_serializing_if = "Option::is_none")]
999    pub ca_cert_path: Option<String>,
1000    /// Skip TLS verification (not recommended for production)
1001    pub skip_verify: bool,
1002    /// Request timeout in seconds
1003    pub timeout_secs: u64,
1004}
1005
1006impl Default for VaultConfig {
1007    fn default() -> Self {
1008        Self {
1009            address: "http://127.0.0.1:8200".to_string(),
1010            namespace: None,
1011            auth_method: VaultAuthMethod::Token,
1012            token: None,
1013            role_id: None,
1014            secret_id: None,
1015            kubernetes_role: None,
1016            mount_path: "secret".to_string(),
1017            path_prefix: "mockforge".to_string(),
1018            ca_cert_path: None,
1019            skip_verify: false,
1020            timeout_secs: 30,
1021        }
1022    }
1023}
1024
1025/// Vault authentication methods
1026#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
1027#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1028#[serde(rename_all = "lowercase")]
1029pub enum VaultAuthMethod {
1030    /// Token authentication
1031    #[default]
1032    Token,
1033    /// AppRole authentication
1034    AppRole,
1035    /// Kubernetes authentication
1036    Kubernetes,
1037    /// AWS IAM authentication
1038    AwsIam,
1039    /// GitHub authentication
1040    GitHub,
1041    /// LDAP authentication
1042    Ldap,
1043    /// Userpass authentication
1044    Userpass,
1045}
1046
1047/// AWS Secrets Manager configuration
1048#[derive(Debug, Clone, Serialize, Deserialize)]
1049#[serde(default)]
1050#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1051pub struct AwsSecretsConfig {
1052    /// AWS region
1053    pub region: String,
1054    /// Secret name prefix
1055    pub prefix: String,
1056    /// Use IAM role (if false, uses access keys)
1057    pub use_iam_role: bool,
1058    /// AWS access key ID
1059    #[serde(skip_serializing_if = "Option::is_none")]
1060    pub access_key_id: Option<String>,
1061    /// AWS secret access key
1062    #[serde(skip_serializing_if = "Option::is_none")]
1063    pub secret_access_key: Option<String>,
1064    /// Endpoint URL (for LocalStack testing)
1065    #[serde(skip_serializing_if = "Option::is_none")]
1066    pub endpoint_url: Option<String>,
1067}
1068
1069impl Default for AwsSecretsConfig {
1070    fn default() -> Self {
1071        Self {
1072            region: "us-east-1".to_string(),
1073            prefix: "mockforge".to_string(),
1074            use_iam_role: true,
1075            access_key_id: None,
1076            secret_access_key: None,
1077            endpoint_url: None,
1078        }
1079    }
1080}
1081
1082/// Azure Key Vault configuration
1083#[derive(Debug, Clone, Serialize, Deserialize)]
1084#[serde(default)]
1085#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1086pub struct AzureKeyVaultConfig {
1087    /// Key Vault URL
1088    pub vault_url: String,
1089    /// Tenant ID
1090    #[serde(skip_serializing_if = "Option::is_none")]
1091    pub tenant_id: Option<String>,
1092    /// Client ID
1093    #[serde(skip_serializing_if = "Option::is_none")]
1094    pub client_id: Option<String>,
1095    /// Client secret
1096    #[serde(skip_serializing_if = "Option::is_none")]
1097    pub client_secret: Option<String>,
1098    /// Use managed identity
1099    pub use_managed_identity: bool,
1100    /// Secret name prefix
1101    pub prefix: String,
1102}
1103
1104impl Default for AzureKeyVaultConfig {
1105    fn default() -> Self {
1106        Self {
1107            vault_url: String::new(),
1108            tenant_id: None,
1109            client_id: None,
1110            client_secret: None,
1111            use_managed_identity: true,
1112            prefix: "mockforge".to_string(),
1113        }
1114    }
1115}
1116
1117/// GCP Secret Manager configuration
1118#[derive(Debug, Clone, Serialize, Deserialize)]
1119#[serde(default)]
1120#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1121pub struct GcpSecretManagerConfig {
1122    /// GCP project ID
1123    pub project_id: String,
1124    /// Secret name prefix
1125    pub prefix: String,
1126    /// Service account key file path
1127    #[serde(skip_serializing_if = "Option::is_none")]
1128    pub credentials_file: Option<String>,
1129    /// Use default credentials (ADC)
1130    pub use_default_credentials: bool,
1131}
1132
1133impl Default for GcpSecretManagerConfig {
1134    fn default() -> Self {
1135        Self {
1136            project_id: String::new(),
1137            prefix: "mockforge".to_string(),
1138            credentials_file: None,
1139            use_default_credentials: true,
1140        }
1141    }
1142}
1143
1144/// Kubernetes Secrets configuration
1145#[derive(Debug, Clone, Serialize, Deserialize)]
1146#[serde(default)]
1147#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1148pub struct KubernetesSecretsConfig {
1149    /// Namespace to read secrets from
1150    pub namespace: String,
1151    /// Secret name prefix
1152    pub prefix: String,
1153    /// Label selector
1154    #[serde(skip_serializing_if = "Option::is_none")]
1155    pub label_selector: Option<String>,
1156    /// Use in-cluster config
1157    pub in_cluster: bool,
1158    /// Kubeconfig path (if not in-cluster)
1159    #[serde(skip_serializing_if = "Option::is_none")]
1160    pub kubeconfig_path: Option<String>,
1161}
1162
1163impl Default for KubernetesSecretsConfig {
1164    fn default() -> Self {
1165        Self {
1166            namespace: "default".to_string(),
1167            prefix: "mockforge".to_string(),
1168            label_selector: None,
1169            in_cluster: true,
1170            kubeconfig_path: None,
1171        }
1172    }
1173}
1174
1175/// Encrypted file configuration
1176#[derive(Debug, Clone, Serialize, Deserialize)]
1177#[serde(default)]
1178#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1179pub struct EncryptedFileConfig {
1180    /// Path to encrypted secrets file
1181    pub file_path: String,
1182    /// Encryption algorithm
1183    pub algorithm: String,
1184    /// Key derivation function
1185    pub kdf: String,
1186    /// Master key (from env var)
1187    #[serde(skip_serializing_if = "Option::is_none")]
1188    pub master_key_env: Option<String>,
1189    /// Key file path
1190    #[serde(skip_serializing_if = "Option::is_none")]
1191    pub key_file: Option<String>,
1192}
1193
1194impl Default for EncryptedFileConfig {
1195    fn default() -> Self {
1196        Self {
1197            file_path: "secrets.enc".to_string(),
1198            algorithm: "aes-256-gcm".to_string(),
1199            kdf: "argon2id".to_string(),
1200            master_key_env: Some("MOCKFORGE_MASTER_KEY".to_string()),
1201            key_file: None,
1202        }
1203    }
1204}
1205
1206/// Plugin runtime resource configuration
1207#[derive(Debug, Clone, Serialize, Deserialize)]
1208#[serde(default)]
1209#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1210pub struct PluginResourceConfig {
1211    /// Enable plugin system
1212    pub enabled: bool,
1213    /// Maximum memory per plugin in bytes (default: 10MB)
1214    pub max_memory_per_plugin: usize,
1215    /// Maximum CPU usage per plugin (0.0-1.0, default: 0.5 = 50%)
1216    pub max_cpu_per_plugin: f64,
1217    /// Maximum execution time per plugin in milliseconds (default: 5000ms)
1218    pub max_execution_time_ms: u64,
1219    /// Allow plugins network access
1220    pub allow_network_access: bool,
1221    /// Filesystem paths plugins can access (empty = no fs access)
1222    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1223    pub allowed_fs_paths: Vec<String>,
1224    /// Maximum concurrent plugin executions
1225    pub max_concurrent_executions: usize,
1226    /// Plugin cache directory
1227    #[serde(skip_serializing_if = "Option::is_none")]
1228    pub cache_dir: Option<String>,
1229    /// Enable debug logging for plugins
1230    pub debug_logging: bool,
1231    /// Maximum WASM module size in bytes (default: 5MB)
1232    pub max_module_size: usize,
1233    /// Maximum table elements per plugin
1234    pub max_table_elements: usize,
1235    /// Maximum WASM stack size in bytes (default: 2MB)
1236    pub max_stack_size: usize,
1237}
1238
1239impl Default for PluginResourceConfig {
1240    fn default() -> Self {
1241        Self {
1242            enabled: true,
1243            max_memory_per_plugin: 10 * 1024 * 1024, // 10MB
1244            max_cpu_per_plugin: 0.5,                 // 50% of one core
1245            max_execution_time_ms: 5000,             // 5 seconds
1246            allow_network_access: false,
1247            allowed_fs_paths: Vec::new(),
1248            max_concurrent_executions: 10,
1249            cache_dir: None,
1250            debug_logging: false,
1251            max_module_size: 5 * 1024 * 1024, // 5MB
1252            max_table_elements: 1000,
1253            max_stack_size: 2 * 1024 * 1024, // 2MB
1254        }
1255    }
1256}
1257
1258/// Protocol enable/disable configuration
1259#[derive(Debug, Clone, Serialize, Deserialize)]
1260#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1261pub struct ProtocolConfig {
1262    /// Enable this protocol
1263    pub enabled: bool,
1264}
1265
1266/// Protocols configuration
1267#[derive(Debug, Clone, Serialize, Deserialize)]
1268#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1269pub struct ProtocolsConfig {
1270    /// HTTP protocol configuration
1271    pub http: ProtocolConfig,
1272    /// GraphQL protocol configuration
1273    pub graphql: ProtocolConfig,
1274    /// gRPC protocol configuration
1275    pub grpc: ProtocolConfig,
1276    /// WebSocket protocol configuration
1277    pub websocket: ProtocolConfig,
1278    /// SMTP protocol configuration
1279    pub smtp: ProtocolConfig,
1280    /// MQTT protocol configuration
1281    pub mqtt: ProtocolConfig,
1282    /// FTP protocol configuration
1283    pub ftp: ProtocolConfig,
1284    /// Kafka protocol configuration
1285    pub kafka: ProtocolConfig,
1286    /// RabbitMQ protocol configuration
1287    pub rabbitmq: ProtocolConfig,
1288    /// AMQP protocol configuration
1289    pub amqp: ProtocolConfig,
1290    /// TCP protocol configuration
1291    pub tcp: ProtocolConfig,
1292}
1293
1294impl Default for ProtocolsConfig {
1295    fn default() -> Self {
1296        Self {
1297            http: ProtocolConfig { enabled: true },
1298            graphql: ProtocolConfig { enabled: true },
1299            grpc: ProtocolConfig { enabled: true },
1300            websocket: ProtocolConfig { enabled: true },
1301            smtp: ProtocolConfig { enabled: false },
1302            mqtt: ProtocolConfig { enabled: true },
1303            ftp: ProtocolConfig { enabled: false },
1304            kafka: ProtocolConfig { enabled: false },
1305            rabbitmq: ProtocolConfig { enabled: false },
1306            amqp: ProtocolConfig { enabled: false },
1307            tcp: ProtocolConfig { enabled: false },
1308        }
1309    }
1310}
1311
1312/// Reality slider configuration for YAML config files
1313///
1314/// This is a simplified configuration that stores just the level.
1315/// The full RealityConfig with all subsystem settings is generated
1316/// automatically from the level via the RealityEngine.
1317#[derive(Debug, Clone, Serialize, Deserialize)]
1318#[serde(default)]
1319#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1320pub struct RealitySliderConfig {
1321    /// Reality level (1-5)
1322    pub level: RealityLevel,
1323    /// Whether to enable reality slider (if false, uses individual subsystem configs)
1324    pub enabled: bool,
1325}
1326
1327impl Default for RealitySliderConfig {
1328    fn default() -> Self {
1329        Self {
1330            level: RealityLevel::ModerateRealism,
1331            enabled: true,
1332        }
1333    }
1334}
1335
1336/// Server configuration
1337#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1338#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1339#[serde(default)]
1340pub struct ServerConfig {
1341    /// HTTP server configuration
1342    pub http: HttpConfig,
1343    /// WebSocket server configuration
1344    pub websocket: WebSocketConfig,
1345    /// GraphQL server configuration
1346    pub graphql: GraphQLConfig,
1347    /// gRPC server configuration
1348    pub grpc: GrpcConfig,
1349    /// MQTT server configuration
1350    pub mqtt: MqttConfig,
1351    /// SMTP server configuration
1352    pub smtp: SmtpConfig,
1353    /// FTP server configuration
1354    pub ftp: FtpConfig,
1355    /// Kafka server configuration
1356    pub kafka: KafkaConfig,
1357    /// AMQP server configuration
1358    pub amqp: AmqpConfig,
1359    /// TCP server configuration
1360    pub tcp: TcpConfig,
1361    /// Admin UI configuration
1362    pub admin: AdminConfig,
1363    /// Request chaining configuration
1364    pub chaining: ChainingConfig,
1365    /// Core MockForge configuration
1366    pub core: CoreConfig,
1367    /// Logging configuration
1368    pub logging: LoggingConfig,
1369    /// Data generation configuration
1370    pub data: DataConfig,
1371    /// MockAI (Behavioral Mock Intelligence) configuration
1372    #[serde(default)]
1373    pub mockai: MockAIConfig,
1374    /// Observability configuration (metrics, tracing)
1375    pub observability: ObservabilityConfig,
1376    /// Multi-tenant workspace configuration
1377    pub multi_tenant: crate::multi_tenant::MultiTenantConfig,
1378    /// Custom routes configuration
1379    #[serde(default)]
1380    pub routes: Vec<RouteConfig>,
1381    /// Protocol enable/disable configuration
1382    #[serde(default)]
1383    pub protocols: ProtocolsConfig,
1384    /// Named configuration profiles (dev, ci, demo, etc.)
1385    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1386    pub profiles: HashMap<String, ProfileConfig>,
1387    /// Deceptive deploy configuration for production-like mock APIs
1388    #[serde(default)]
1389    pub deceptive_deploy: DeceptiveDeployConfig,
1390    /// Behavioral cloning configuration
1391    #[serde(default, skip_serializing_if = "Option::is_none")]
1392    pub behavioral_cloning: Option<BehavioralCloningConfig>,
1393    /// Reality slider configuration for unified realism control
1394    #[serde(default)]
1395    pub reality: RealitySliderConfig,
1396    /// Reality Continuum configuration for blending mock and real data sources
1397    #[serde(default)]
1398    pub reality_continuum: crate::reality_continuum::ContinuumConfig,
1399    /// Security monitoring and SIEM configuration
1400    #[serde(default)]
1401    pub security: SecurityConfig,
1402    /// Drift budget and contract monitoring configuration
1403    #[serde(default)]
1404    pub drift_budget: crate::contract_drift::DriftBudgetConfig,
1405    /// Incident management configuration
1406    #[serde(default)]
1407    pub incidents: IncidentConfig,
1408    /// PR generation configuration
1409    #[serde(default)]
1410    pub pr_generation: crate::pr_generation::PRGenerationConfig,
1411    /// Consumer contracts configuration
1412    #[serde(default)]
1413    pub consumer_contracts: ConsumerContractsConfig,
1414    /// Contracts configuration (fitness rules, etc.)
1415    #[serde(default)]
1416    pub contracts: ContractsConfig,
1417    /// Behavioral Economics Engine configuration
1418    #[serde(default)]
1419    pub behavioral_economics: BehavioralEconomicsConfig,
1420    /// Drift Learning configuration
1421    #[serde(default)]
1422    pub drift_learning: DriftLearningConfig,
1423    /// Organization AI controls configuration (YAML defaults, DB overrides)
1424    #[serde(default)]
1425    pub org_ai_controls: crate::ai_studio::org_controls::OrgAiControlsConfig,
1426    /// Performance and resource configuration
1427    #[serde(default)]
1428    pub performance: PerformanceConfig,
1429    /// Plugin resource limits configuration
1430    #[serde(default)]
1431    pub plugins: PluginResourceConfig,
1432    /// Configuration hot-reload settings
1433    #[serde(default)]
1434    pub hot_reload: ConfigHotReloadConfig,
1435    /// Secret backend configuration
1436    #[serde(default)]
1437    pub secrets: SecretBackendConfig,
1438}
1439
1440/// Profile configuration - a partial ServerConfig that overrides base settings
1441#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1442#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1443#[serde(default)]
1444pub struct ProfileConfig {
1445    /// HTTP server configuration overrides
1446    #[serde(skip_serializing_if = "Option::is_none")]
1447    pub http: Option<HttpConfig>,
1448    /// WebSocket server configuration overrides
1449    #[serde(skip_serializing_if = "Option::is_none")]
1450    pub websocket: Option<WebSocketConfig>,
1451    /// GraphQL server configuration overrides
1452    #[serde(skip_serializing_if = "Option::is_none")]
1453    pub graphql: Option<GraphQLConfig>,
1454    /// gRPC server configuration overrides
1455    #[serde(skip_serializing_if = "Option::is_none")]
1456    pub grpc: Option<GrpcConfig>,
1457    /// MQTT server configuration overrides
1458    #[serde(skip_serializing_if = "Option::is_none")]
1459    pub mqtt: Option<MqttConfig>,
1460    /// SMTP server configuration overrides
1461    #[serde(skip_serializing_if = "Option::is_none")]
1462    pub smtp: Option<SmtpConfig>,
1463    /// FTP server configuration overrides
1464    #[serde(skip_serializing_if = "Option::is_none")]
1465    pub ftp: Option<FtpConfig>,
1466    /// Kafka server configuration overrides
1467    #[serde(skip_serializing_if = "Option::is_none")]
1468    pub kafka: Option<KafkaConfig>,
1469    /// AMQP server configuration overrides
1470    #[serde(skip_serializing_if = "Option::is_none")]
1471    pub amqp: Option<AmqpConfig>,
1472    /// TCP server configuration overrides
1473    #[serde(skip_serializing_if = "Option::is_none")]
1474    pub tcp: Option<TcpConfig>,
1475    /// Admin UI configuration overrides
1476    #[serde(skip_serializing_if = "Option::is_none")]
1477    pub admin: Option<AdminConfig>,
1478    /// Request chaining configuration overrides
1479    #[serde(skip_serializing_if = "Option::is_none")]
1480    pub chaining: Option<ChainingConfig>,
1481    /// Core MockForge configuration overrides
1482    #[serde(skip_serializing_if = "Option::is_none")]
1483    pub core: Option<CoreConfig>,
1484    /// Logging configuration overrides
1485    #[serde(skip_serializing_if = "Option::is_none")]
1486    pub logging: Option<LoggingConfig>,
1487    /// Data generation configuration overrides
1488    #[serde(skip_serializing_if = "Option::is_none")]
1489    pub data: Option<DataConfig>,
1490    /// MockAI configuration overrides
1491    #[serde(skip_serializing_if = "Option::is_none")]
1492    pub mockai: Option<MockAIConfig>,
1493    /// Observability configuration overrides
1494    #[serde(skip_serializing_if = "Option::is_none")]
1495    pub observability: Option<ObservabilityConfig>,
1496    /// Multi-tenant workspace configuration overrides
1497    #[serde(skip_serializing_if = "Option::is_none")]
1498    pub multi_tenant: Option<crate::multi_tenant::MultiTenantConfig>,
1499    /// Custom routes configuration overrides
1500    #[serde(skip_serializing_if = "Option::is_none")]
1501    pub routes: Option<Vec<RouteConfig>>,
1502    /// Protocol enable/disable configuration overrides
1503    #[serde(skip_serializing_if = "Option::is_none")]
1504    pub protocols: Option<ProtocolsConfig>,
1505    /// Deceptive deploy configuration overrides
1506    #[serde(skip_serializing_if = "Option::is_none")]
1507    pub deceptive_deploy: Option<DeceptiveDeployConfig>,
1508    /// Reality slider configuration overrides
1509    #[serde(skip_serializing_if = "Option::is_none")]
1510    pub reality: Option<RealitySliderConfig>,
1511    /// Reality Continuum configuration overrides
1512    #[serde(skip_serializing_if = "Option::is_none")]
1513    pub reality_continuum: Option<crate::reality_continuum::ContinuumConfig>,
1514    /// Security configuration overrides
1515    #[serde(skip_serializing_if = "Option::is_none")]
1516    pub security: Option<SecurityConfig>,
1517}
1518
1519// Default is derived for ServerConfig
1520
1521/// HTTP validation configuration
1522#[derive(Debug, Clone, Serialize, Deserialize)]
1523#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1524pub struct HttpValidationConfig {
1525    /// Request validation mode: off, warn, enforce
1526    pub mode: String,
1527}
1528
1529/// HTTP CORS configuration
1530#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1531#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1532pub struct HttpCorsConfig {
1533    /// Enable CORS
1534    pub enabled: bool,
1535    /// Allowed origins
1536    #[serde(default)]
1537    pub allowed_origins: Vec<String>,
1538    /// Allowed methods
1539    #[serde(default)]
1540    pub allowed_methods: Vec<String>,
1541    /// Allowed headers
1542    #[serde(default)]
1543    pub allowed_headers: Vec<String>,
1544    /// Allow credentials (cookies, authorization headers)
1545    /// Note: Cannot be true when using wildcard origin (*)
1546    #[serde(default = "default_cors_allow_credentials")]
1547    pub allow_credentials: bool,
1548}
1549
1550fn default_cors_allow_credentials() -> bool {
1551    false
1552}
1553
1554/// HTTP server configuration
1555#[derive(Debug, Clone, Serialize, Deserialize)]
1556#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1557#[serde(default)]
1558pub struct HttpConfig {
1559    /// Enable HTTP server
1560    pub enabled: bool,
1561    /// Server port
1562    pub port: u16,
1563    /// Host address
1564    pub host: String,
1565    /// Path to OpenAPI spec file for HTTP server
1566    pub openapi_spec: Option<String>,
1567    /// CORS configuration
1568    pub cors: Option<HttpCorsConfig>,
1569    /// Request timeout in seconds
1570    pub request_timeout_secs: u64,
1571    /// Request validation configuration
1572    pub validation: Option<HttpValidationConfig>,
1573    /// Aggregate validation errors into JSON array
1574    pub aggregate_validation_errors: bool,
1575    /// Validate responses (warn-only logging)
1576    pub validate_responses: bool,
1577    /// Expand templating tokens in responses/examples
1578    pub response_template_expand: bool,
1579    /// Validation error HTTP status (e.g., 400 or 422)
1580    pub validation_status: Option<u16>,
1581    /// Per-route overrides: key "METHOD path" => mode (off/warn/enforce)
1582    pub validation_overrides: std::collections::HashMap<String, String>,
1583    /// When embedding Admin UI under HTTP, skip validation for the mounted prefix
1584    pub skip_admin_validation: bool,
1585    /// Authentication configuration
1586    pub auth: Option<AuthConfig>,
1587    /// TLS/HTTPS configuration
1588    #[serde(skip_serializing_if = "Option::is_none")]
1589    pub tls: Option<HttpTlsConfig>,
1590}
1591
1592impl Default for HttpConfig {
1593    fn default() -> Self {
1594        Self {
1595            enabled: true,
1596            port: 3000,
1597            host: "0.0.0.0".to_string(),
1598            openapi_spec: None,
1599            cors: Some(HttpCorsConfig {
1600                enabled: true,
1601                allowed_origins: vec!["*".to_string()],
1602                allowed_methods: vec![
1603                    "GET".to_string(),
1604                    "POST".to_string(),
1605                    "PUT".to_string(),
1606                    "DELETE".to_string(),
1607                    "PATCH".to_string(),
1608                    "OPTIONS".to_string(),
1609                ],
1610                allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
1611                allow_credentials: false, // Must be false when using wildcard origin
1612            }),
1613            request_timeout_secs: 30,
1614            validation: Some(HttpValidationConfig {
1615                mode: "enforce".to_string(),
1616            }),
1617            aggregate_validation_errors: true,
1618            validate_responses: false,
1619            response_template_expand: false,
1620            validation_status: None,
1621            validation_overrides: std::collections::HashMap::new(),
1622            skip_admin_validation: true,
1623            auth: None,
1624            tls: None,
1625        }
1626    }
1627}
1628
1629/// HTTP TLS/HTTPS configuration
1630#[derive(Debug, Clone, Serialize, Deserialize)]
1631#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1632pub struct HttpTlsConfig {
1633    /// Enable TLS/HTTPS
1634    pub enabled: bool,
1635    /// Path to TLS certificate file (PEM format)
1636    pub cert_file: String,
1637    /// Path to TLS private key file (PEM format)
1638    pub key_file: String,
1639    /// Path to CA certificate file for mutual TLS (optional)
1640    #[serde(skip_serializing_if = "Option::is_none")]
1641    pub ca_file: Option<String>,
1642    /// Minimum TLS version (default: "1.2")
1643    #[serde(default = "default_tls_min_version")]
1644    pub min_version: String,
1645    /// Cipher suites to use (default: safe defaults)
1646    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1647    pub cipher_suites: Vec<String>,
1648    /// Require client certificate (mutual TLS)
1649    #[serde(default)]
1650    pub require_client_cert: bool,
1651    /// Mutual TLS mode: "off" (default), "optional", "required"
1652    #[serde(default = "default_mtls_mode")]
1653    pub mtls_mode: String,
1654}
1655
1656fn default_mtls_mode() -> String {
1657    "off".to_string()
1658}
1659
1660fn default_tls_min_version() -> String {
1661    "1.2".to_string()
1662}
1663
1664impl Default for HttpTlsConfig {
1665    fn default() -> Self {
1666        Self {
1667            enabled: true,
1668            cert_file: String::new(),
1669            key_file: String::new(),
1670            ca_file: None,
1671            min_version: "1.2".to_string(),
1672            cipher_suites: Vec::new(),
1673            require_client_cert: false,
1674            mtls_mode: "off".to_string(),
1675        }
1676    }
1677}
1678
1679/// WebSocket server configuration
1680#[derive(Debug, Clone, Serialize, Deserialize)]
1681#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1682#[serde(default)]
1683pub struct WebSocketConfig {
1684    /// Enable WebSocket server
1685    pub enabled: bool,
1686    /// Server port
1687    pub port: u16,
1688    /// Host address
1689    pub host: String,
1690    /// Replay file path
1691    pub replay_file: Option<String>,
1692    /// Connection timeout in seconds
1693    pub connection_timeout_secs: u64,
1694}
1695
1696impl Default for WebSocketConfig {
1697    fn default() -> Self {
1698        Self {
1699            enabled: true,
1700            port: 3001,
1701            host: "0.0.0.0".to_string(),
1702            replay_file: None,
1703            connection_timeout_secs: 300,
1704        }
1705    }
1706}
1707
1708/// gRPC server configuration
1709#[derive(Debug, Clone, Serialize, Deserialize)]
1710#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1711#[serde(default)]
1712pub struct GrpcConfig {
1713    /// Enable gRPC server
1714    pub enabled: bool,
1715    /// Server port
1716    pub port: u16,
1717    /// Host address
1718    pub host: String,
1719    /// Proto files directory
1720    pub proto_dir: Option<String>,
1721    /// TLS configuration
1722    pub tls: Option<TlsConfig>,
1723}
1724
1725impl Default for GrpcConfig {
1726    fn default() -> Self {
1727        Self {
1728            enabled: true,
1729            port: 50051,
1730            host: "0.0.0.0".to_string(),
1731            proto_dir: None,
1732            tls: None,
1733        }
1734    }
1735}
1736
1737/// GraphQL server configuration
1738#[derive(Debug, Clone, Serialize, Deserialize)]
1739#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1740#[serde(default)]
1741pub struct GraphQLConfig {
1742    /// Enable GraphQL server
1743    pub enabled: bool,
1744    /// Server port
1745    pub port: u16,
1746    /// Host address
1747    pub host: String,
1748    /// GraphQL schema file path (.graphql or .gql)
1749    pub schema_path: Option<String>,
1750    /// Handlers directory for custom resolvers
1751    pub handlers_dir: Option<String>,
1752    /// Enable GraphQL Playground UI
1753    pub playground_enabled: bool,
1754    /// Upstream GraphQL server URL for passthrough
1755    pub upstream_url: Option<String>,
1756    /// Enable introspection queries
1757    pub introspection_enabled: bool,
1758}
1759
1760impl Default for GraphQLConfig {
1761    fn default() -> Self {
1762        Self {
1763            enabled: true,
1764            port: 4000,
1765            host: "0.0.0.0".to_string(),
1766            schema_path: None,
1767            handlers_dir: None,
1768            playground_enabled: true,
1769            upstream_url: None,
1770            introspection_enabled: true,
1771        }
1772    }
1773}
1774
1775/// TLS configuration for gRPC
1776#[derive(Debug, Clone, Serialize, Deserialize)]
1777#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1778pub struct TlsConfig {
1779    /// Certificate file path
1780    pub cert_path: String,
1781    /// Private key file path
1782    pub key_path: String,
1783}
1784
1785/// MQTT server configuration
1786#[derive(Debug, Clone, Serialize, Deserialize)]
1787#[serde(default)]
1788#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1789pub struct MqttConfig {
1790    /// Enable MQTT server
1791    pub enabled: bool,
1792    /// Server port
1793    pub port: u16,
1794    /// Host address
1795    pub host: String,
1796    /// Maximum connections
1797    pub max_connections: usize,
1798    /// Maximum packet size
1799    pub max_packet_size: usize,
1800    /// Keep-alive timeout in seconds
1801    pub keep_alive_secs: u16,
1802    /// Directory containing fixture files
1803    pub fixtures_dir: Option<std::path::PathBuf>,
1804    /// Enable retained messages
1805    pub enable_retained_messages: bool,
1806    /// Maximum retained messages
1807    pub max_retained_messages: usize,
1808}
1809
1810impl Default for MqttConfig {
1811    fn default() -> Self {
1812        Self {
1813            enabled: false,
1814            port: 1883,
1815            host: "0.0.0.0".to_string(),
1816            max_connections: 1000,
1817            max_packet_size: 268435456, // 256 MB
1818            keep_alive_secs: 60,
1819            fixtures_dir: None,
1820            enable_retained_messages: true,
1821            max_retained_messages: 10000,
1822        }
1823    }
1824}
1825
1826/// SMTP server configuration
1827#[derive(Debug, Clone, Serialize, Deserialize)]
1828#[serde(default)]
1829#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1830pub struct SmtpConfig {
1831    /// Enable SMTP server
1832    pub enabled: bool,
1833    /// Server port
1834    pub port: u16,
1835    /// Host address
1836    pub host: String,
1837    /// Server hostname for SMTP greeting
1838    pub hostname: String,
1839    /// Directory containing fixture files
1840    pub fixtures_dir: Option<std::path::PathBuf>,
1841    /// Connection timeout in seconds
1842    pub timeout_secs: u64,
1843    /// Maximum connections
1844    pub max_connections: usize,
1845    /// Enable mailbox storage
1846    pub enable_mailbox: bool,
1847    /// Maximum mailbox size
1848    pub max_mailbox_messages: usize,
1849    /// Enable STARTTLS support
1850    pub enable_starttls: bool,
1851    /// Path to TLS certificate file
1852    pub tls_cert_path: Option<std::path::PathBuf>,
1853    /// Path to TLS private key file
1854    pub tls_key_path: Option<std::path::PathBuf>,
1855}
1856
1857impl Default for SmtpConfig {
1858    fn default() -> Self {
1859        Self {
1860            enabled: false,
1861            port: 1025,
1862            host: "0.0.0.0".to_string(),
1863            hostname: "mockforge-smtp".to_string(),
1864            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
1865            timeout_secs: 300,
1866            max_connections: 10,
1867            enable_mailbox: true,
1868            max_mailbox_messages: 1000,
1869            enable_starttls: false,
1870            tls_cert_path: None,
1871            tls_key_path: None,
1872        }
1873    }
1874}
1875
1876/// FTP server configuration
1877#[derive(Debug, Clone, Serialize, Deserialize)]
1878#[serde(default)]
1879#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1880pub struct FtpConfig {
1881    /// Enable FTP server
1882    pub enabled: bool,
1883    /// Server port
1884    pub port: u16,
1885    /// Host address
1886    pub host: String,
1887    /// Passive mode port range
1888    pub passive_ports: (u16, u16),
1889    /// Maximum connections
1890    pub max_connections: usize,
1891    /// Connection timeout in seconds
1892    pub timeout_secs: u64,
1893    /// Allow anonymous access
1894    pub allow_anonymous: bool,
1895    /// Fixtures directory
1896    pub fixtures_dir: Option<std::path::PathBuf>,
1897    /// Virtual root directory
1898    pub virtual_root: std::path::PathBuf,
1899}
1900
1901impl Default for FtpConfig {
1902    fn default() -> Self {
1903        Self {
1904            enabled: false,
1905            port: 2121,
1906            host: "0.0.0.0".to_string(),
1907            passive_ports: (50000, 51000),
1908            max_connections: 100,
1909            timeout_secs: 300,
1910            allow_anonymous: true,
1911            fixtures_dir: None,
1912            virtual_root: std::path::PathBuf::from("/mockforge"),
1913        }
1914    }
1915}
1916
1917/// Kafka server configuration
1918#[derive(Debug, Clone, Serialize, Deserialize)]
1919#[serde(default)]
1920#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1921pub struct KafkaConfig {
1922    /// Enable Kafka server
1923    pub enabled: bool,
1924    /// Server port
1925    pub port: u16,
1926    /// Host address
1927    pub host: String,
1928    /// Broker ID
1929    pub broker_id: i32,
1930    /// Maximum connections
1931    pub max_connections: usize,
1932    /// Log retention time in milliseconds
1933    pub log_retention_ms: i64,
1934    /// Log segment size in bytes
1935    pub log_segment_bytes: i64,
1936    /// Fixtures directory
1937    pub fixtures_dir: Option<std::path::PathBuf>,
1938    /// Auto-create topics
1939    pub auto_create_topics: bool,
1940    /// Default number of partitions for new topics
1941    pub default_partitions: i32,
1942    /// Default replication factor for new topics
1943    pub default_replication_factor: i16,
1944}
1945
1946impl Default for KafkaConfig {
1947    fn default() -> Self {
1948        Self {
1949            enabled: false,
1950            port: 9092, // Standard Kafka port
1951            host: "0.0.0.0".to_string(),
1952            broker_id: 1,
1953            max_connections: 1000,
1954            log_retention_ms: 604800000,   // 7 days
1955            log_segment_bytes: 1073741824, // 1 GB
1956            fixtures_dir: None,
1957            auto_create_topics: true,
1958            default_partitions: 3,
1959            default_replication_factor: 1,
1960        }
1961    }
1962}
1963
1964/// AMQP server configuration
1965#[derive(Debug, Clone, Serialize, Deserialize)]
1966#[serde(default)]
1967#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1968pub struct AmqpConfig {
1969    /// Enable AMQP server
1970    pub enabled: bool,
1971    /// Server port
1972    pub port: u16,
1973    /// Host address
1974    pub host: String,
1975    /// Maximum connections
1976    pub max_connections: usize,
1977    /// Maximum channels per connection
1978    pub max_channels_per_connection: u16,
1979    /// Frame max size
1980    pub frame_max: u32,
1981    /// Heartbeat interval in seconds
1982    pub heartbeat_interval: u16,
1983    /// Fixtures directory
1984    pub fixtures_dir: Option<std::path::PathBuf>,
1985    /// Virtual hosts
1986    pub virtual_hosts: Vec<String>,
1987    /// Enable TLS
1988    pub tls_enabled: bool,
1989    /// TLS port (5671 is standard AMQPS port)
1990    pub tls_port: u16,
1991    /// Path to TLS certificate file (PEM format)
1992    pub tls_cert_path: Option<std::path::PathBuf>,
1993    /// Path to TLS private key file (PEM format)
1994    pub tls_key_path: Option<std::path::PathBuf>,
1995    /// Path to CA certificate for client verification (optional)
1996    pub tls_ca_path: Option<std::path::PathBuf>,
1997    /// Require client certificate authentication
1998    pub tls_client_auth: bool,
1999}
2000
2001impl Default for AmqpConfig {
2002    fn default() -> Self {
2003        Self {
2004            enabled: false,
2005            port: 5672, // Standard AMQP port
2006            host: "0.0.0.0".to_string(),
2007            max_connections: 1000,
2008            max_channels_per_connection: 100,
2009            frame_max: 131072, // 128 KB
2010            heartbeat_interval: 60,
2011            fixtures_dir: None,
2012            virtual_hosts: vec!["/".to_string()],
2013            tls_enabled: false,
2014            tls_port: 5671, // Standard AMQPS port
2015            tls_cert_path: None,
2016            tls_key_path: None,
2017            tls_ca_path: None,
2018            tls_client_auth: false,
2019        }
2020    }
2021}
2022
2023/// TCP server configuration
2024#[derive(Debug, Clone, Serialize, Deserialize)]
2025#[serde(default)]
2026#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2027pub struct TcpConfig {
2028    /// Enable TCP server
2029    pub enabled: bool,
2030    /// Server port
2031    pub port: u16,
2032    /// Host address
2033    pub host: String,
2034    /// Maximum connections
2035    pub max_connections: usize,
2036    /// Connection timeout in seconds
2037    pub timeout_secs: u64,
2038    /// Directory containing fixture files
2039    pub fixtures_dir: Option<std::path::PathBuf>,
2040    /// Enable echo mode (echo received data back)
2041    pub echo_mode: bool,
2042    /// Enable TLS support
2043    pub enable_tls: bool,
2044    /// Path to TLS certificate file
2045    pub tls_cert_path: Option<std::path::PathBuf>,
2046    /// Path to TLS private key file
2047    pub tls_key_path: Option<std::path::PathBuf>,
2048}
2049
2050impl Default for TcpConfig {
2051    fn default() -> Self {
2052        Self {
2053            enabled: false,
2054            port: 9999,
2055            host: "0.0.0.0".to_string(),
2056            max_connections: 1000,
2057            timeout_secs: 300,
2058            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
2059            echo_mode: true,
2060            enable_tls: false,
2061            tls_cert_path: None,
2062            tls_key_path: None,
2063        }
2064    }
2065}
2066
2067/// Admin UI configuration
2068#[derive(Debug, Clone, Serialize, Deserialize)]
2069#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2070#[serde(default)]
2071pub struct AdminConfig {
2072    /// Enable admin UI
2073    pub enabled: bool,
2074    /// Admin UI port
2075    pub port: u16,
2076    /// Host address
2077    pub host: String,
2078    /// Authentication required
2079    pub auth_required: bool,
2080    /// Admin username (if auth required)
2081    pub username: Option<String>,
2082    /// Admin password (if auth required)
2083    pub password: Option<String>,
2084    /// Optional mount path to embed Admin UI under HTTP server (e.g., "/admin")
2085    pub mount_path: Option<String>,
2086    /// Enable Admin API endpoints (under `__mockforge`)
2087    pub api_enabled: bool,
2088    /// Prometheus server URL for analytics queries
2089    pub prometheus_url: String,
2090}
2091
2092impl Default for AdminConfig {
2093    fn default() -> Self {
2094        // Default to 0.0.0.0 if running in Docker (detected via common Docker env vars)
2095        // This makes Admin UI accessible from outside the container by default
2096        let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
2097            || std::env::var("container").is_ok()
2098            || std::path::Path::new("/.dockerenv").exists()
2099        {
2100            "0.0.0.0".to_string()
2101        } else {
2102            "127.0.0.1".to_string()
2103        };
2104
2105        Self {
2106            enabled: false,
2107            port: 9080,
2108            host: default_host,
2109            auth_required: false,
2110            username: None,
2111            password: None,
2112            mount_path: None,
2113            api_enabled: true,
2114            prometheus_url: "http://localhost:9090".to_string(),
2115        }
2116    }
2117}
2118
2119/// Logging configuration
2120#[derive(Debug, Clone, Serialize, Deserialize)]
2121#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2122#[serde(default)]
2123pub struct LoggingConfig {
2124    /// Log level
2125    pub level: String,
2126    /// Enable JSON logging
2127    pub json_format: bool,
2128    /// Log file path (optional)
2129    pub file_path: Option<String>,
2130    /// Maximum log file size in MB
2131    pub max_file_size_mb: u64,
2132    /// Maximum number of log files to keep
2133    pub max_files: u32,
2134}
2135
2136impl Default for LoggingConfig {
2137    fn default() -> Self {
2138        Self {
2139            level: "info".to_string(),
2140            json_format: false,
2141            file_path: None,
2142            max_file_size_mb: 10,
2143            max_files: 5,
2144        }
2145    }
2146}
2147
2148/// Request chaining configuration for multi-step request workflows
2149#[derive(Debug, Clone, Serialize, Deserialize)]
2150#[serde(default, rename_all = "camelCase")]
2151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2152pub struct ChainingConfig {
2153    /// Enable request chaining
2154    pub enabled: bool,
2155    /// Maximum chain length to prevent infinite loops
2156    pub max_chain_length: usize,
2157    /// Global timeout for chain execution in seconds
2158    pub global_timeout_secs: u64,
2159    /// Enable parallel execution when dependencies allow
2160    pub enable_parallel_execution: bool,
2161}
2162
2163impl Default for ChainingConfig {
2164    fn default() -> Self {
2165        Self {
2166            enabled: false,
2167            max_chain_length: 20,
2168            global_timeout_secs: 300,
2169            enable_parallel_execution: false,
2170        }
2171    }
2172}
2173
2174/// Data generation configuration
2175#[derive(Debug, Clone, Serialize, Deserialize)]
2176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2177#[serde(default)]
2178pub struct DataConfig {
2179    /// Default number of rows to generate
2180    pub default_rows: usize,
2181    /// Default output format
2182    pub default_format: String,
2183    /// Faker locale
2184    pub locale: String,
2185    /// Custom faker templates
2186    pub templates: HashMap<String, String>,
2187    /// RAG configuration
2188    pub rag: RagConfig,
2189    /// Active persona profile domain (e.g., "finance", "ecommerce", "healthcare")
2190    #[serde(skip_serializing_if = "Option::is_none")]
2191    pub persona_domain: Option<String>,
2192    /// Enable persona-based consistency
2193    #[serde(default = "default_false")]
2194    pub persona_consistency_enabled: bool,
2195    /// Persona registry configuration
2196    #[serde(skip_serializing_if = "Option::is_none")]
2197    pub persona_registry: Option<PersonaRegistryConfig>,
2198}
2199
2200impl Default for DataConfig {
2201    fn default() -> Self {
2202        Self {
2203            default_rows: 100,
2204            default_format: "json".to_string(),
2205            locale: "en".to_string(),
2206            templates: HashMap::new(),
2207            rag: RagConfig::default(),
2208            persona_domain: None,
2209            persona_consistency_enabled: false,
2210            persona_registry: None,
2211        }
2212    }
2213}
2214
2215/// RAG configuration
2216#[derive(Debug, Clone, Serialize, Deserialize)]
2217#[serde(default)]
2218#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2219pub struct RagConfig {
2220    /// Enable RAG by default
2221    pub enabled: bool,
2222    /// LLM provider (openai, anthropic, ollama, openai_compatible)
2223    #[serde(default)]
2224    pub provider: String,
2225    /// API endpoint for LLM
2226    pub api_endpoint: Option<String>,
2227    /// API key for LLM
2228    pub api_key: Option<String>,
2229    /// Model name
2230    pub model: Option<String>,
2231    /// Maximum tokens for generation
2232    #[serde(default = "default_max_tokens")]
2233    pub max_tokens: usize,
2234    /// Temperature for generation (0.0 to 2.0)
2235    #[serde(default = "default_temperature")]
2236    pub temperature: f64,
2237    /// Context window size
2238    pub context_window: usize,
2239    /// Enable caching
2240    #[serde(default = "default_true")]
2241    pub caching: bool,
2242    /// Cache TTL in seconds
2243    #[serde(default = "default_cache_ttl")]
2244    pub cache_ttl_secs: u64,
2245    /// Request timeout in seconds
2246    #[serde(default = "default_timeout")]
2247    pub timeout_secs: u64,
2248    /// Maximum retries for failed requests
2249    #[serde(default = "default_max_retries")]
2250    pub max_retries: usize,
2251}
2252
2253fn default_max_tokens() -> usize {
2254    1024
2255}
2256
2257fn default_temperature() -> f64 {
2258    0.7
2259}
2260
2261fn default_true() -> bool {
2262    true
2263}
2264
2265fn default_cache_ttl() -> u64 {
2266    3600
2267}
2268
2269fn default_timeout() -> u64 {
2270    30
2271}
2272
2273fn default_max_retries() -> usize {
2274    3
2275}
2276
2277fn default_false() -> bool {
2278    false
2279}
2280
2281impl Default for RagConfig {
2282    fn default() -> Self {
2283        Self {
2284            enabled: false,
2285            provider: "openai".to_string(),
2286            api_endpoint: None,
2287            api_key: None,
2288            model: Some("gpt-3.5-turbo".to_string()),
2289            max_tokens: default_max_tokens(),
2290            temperature: default_temperature(),
2291            context_window: 4000,
2292            caching: default_true(),
2293            cache_ttl_secs: default_cache_ttl(),
2294            timeout_secs: default_timeout(),
2295            max_retries: default_max_retries(),
2296        }
2297    }
2298}
2299
2300/// Persona registry configuration
2301#[derive(Debug, Clone, Serialize, Deserialize)]
2302#[serde(default)]
2303#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2304#[derive(Default)]
2305pub struct PersonaRegistryConfig {
2306    /// Enable persistence (save personas to disk)
2307    #[serde(default = "default_false")]
2308    pub persistent: bool,
2309    /// Storage path for persistent personas
2310    #[serde(skip_serializing_if = "Option::is_none")]
2311    pub storage_path: Option<String>,
2312    /// Default traits for new personas
2313    #[serde(default)]
2314    pub default_traits: HashMap<String, String>,
2315}
2316
2317/// MockAI (Behavioral Mock Intelligence) configuration
2318#[derive(Debug, Clone, Serialize, Deserialize)]
2319#[serde(default)]
2320#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2321pub struct MockAIConfig {
2322    /// Enable MockAI features
2323    pub enabled: bool,
2324    /// Intelligent behavior configuration
2325    pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
2326    /// Auto-learn from examples
2327    pub auto_learn: bool,
2328    /// Enable mutation detection
2329    pub mutation_detection: bool,
2330    /// Enable AI-driven validation errors
2331    pub ai_validation_errors: bool,
2332    /// Enable context-aware pagination
2333    pub intelligent_pagination: bool,
2334    /// Endpoints to enable MockAI for (empty = all endpoints)
2335    #[serde(default)]
2336    pub enabled_endpoints: Vec<String>,
2337}
2338
2339impl Default for MockAIConfig {
2340    fn default() -> Self {
2341        Self {
2342            enabled: false,
2343            intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
2344            auto_learn: true,
2345            mutation_detection: true,
2346            ai_validation_errors: true,
2347            intelligent_pagination: true,
2348            enabled_endpoints: Vec::new(),
2349        }
2350    }
2351}
2352
2353/// Observability configuration for metrics and distributed tracing
2354#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2355#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2356#[serde(default)]
2357pub struct ObservabilityConfig {
2358    /// Prometheus metrics configuration
2359    pub prometheus: PrometheusConfig,
2360    /// OpenTelemetry distributed tracing configuration
2361    pub opentelemetry: Option<OpenTelemetryConfig>,
2362    /// API Flight Recorder configuration
2363    pub recorder: Option<RecorderConfig>,
2364    /// Chaos engineering configuration
2365    pub chaos: Option<ChaosEngConfig>,
2366}
2367
2368/// Security monitoring and SIEM configuration
2369#[derive(Debug, Clone, Serialize, Deserialize)]
2370#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2371#[serde(default)]
2372#[derive(Default)]
2373pub struct SecurityConfig {
2374    /// Security monitoring configuration
2375    pub monitoring: SecurityMonitoringConfig,
2376}
2377
2378/// Security monitoring configuration
2379#[derive(Debug, Clone, Serialize, Deserialize)]
2380#[serde(default)]
2381#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2382#[derive(Default)]
2383pub struct SecurityMonitoringConfig {
2384    /// SIEM integration configuration
2385    pub siem: crate::security::siem::SiemConfig,
2386    /// Access review configuration
2387    pub access_review: crate::security::access_review::AccessReviewConfig,
2388    /// Privileged access management configuration
2389    pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
2390    /// Change management configuration
2391    pub change_management: crate::security::change_management::ChangeManagementConfig,
2392    /// Compliance dashboard configuration
2393    pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
2394    /// Risk assessment configuration
2395    pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
2396}
2397
2398/// Prometheus metrics configuration
2399#[derive(Debug, Clone, Serialize, Deserialize)]
2400#[serde(default)]
2401#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2402pub struct PrometheusConfig {
2403    /// Enable Prometheus metrics endpoint
2404    pub enabled: bool,
2405    /// Port for metrics endpoint
2406    pub port: u16,
2407    /// Host for metrics endpoint
2408    pub host: String,
2409    /// Path for metrics endpoint
2410    pub path: String,
2411}
2412
2413impl Default for PrometheusConfig {
2414    fn default() -> Self {
2415        Self {
2416            enabled: true,
2417            port: 9090,
2418            host: "0.0.0.0".to_string(),
2419            path: "/metrics".to_string(),
2420        }
2421    }
2422}
2423
2424/// OpenTelemetry distributed tracing configuration
2425#[derive(Debug, Clone, Serialize, Deserialize)]
2426#[serde(default)]
2427#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2428pub struct OpenTelemetryConfig {
2429    /// Enable OpenTelemetry tracing
2430    pub enabled: bool,
2431    /// Service name for traces
2432    pub service_name: String,
2433    /// Deployment environment (development, staging, production)
2434    pub environment: String,
2435    /// Jaeger endpoint for trace export
2436    pub jaeger_endpoint: String,
2437    /// OTLP endpoint (alternative to Jaeger)
2438    pub otlp_endpoint: Option<String>,
2439    /// Protocol: grpc or http
2440    pub protocol: String,
2441    /// Sampling rate (0.0 to 1.0)
2442    pub sampling_rate: f64,
2443}
2444
2445impl Default for OpenTelemetryConfig {
2446    fn default() -> Self {
2447        Self {
2448            enabled: false,
2449            service_name: "mockforge".to_string(),
2450            environment: "development".to_string(),
2451            jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
2452            otlp_endpoint: Some("http://localhost:4317".to_string()),
2453            protocol: "grpc".to_string(),
2454            sampling_rate: 1.0,
2455        }
2456    }
2457}
2458
2459/// API Flight Recorder configuration
2460#[derive(Debug, Clone, Serialize, Deserialize)]
2461#[serde(default)]
2462#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2463pub struct RecorderConfig {
2464    /// Enable recording
2465    pub enabled: bool,
2466    /// Database file path
2467    pub database_path: String,
2468    /// Enable management API
2469    pub api_enabled: bool,
2470    /// Management API port (if different from main port)
2471    pub api_port: Option<u16>,
2472    /// Maximum number of requests to store (0 for unlimited)
2473    pub max_requests: i64,
2474    /// Auto-delete requests older than N days (0 to disable)
2475    pub retention_days: i64,
2476    /// Record HTTP requests
2477    pub record_http: bool,
2478    /// Record gRPC requests
2479    pub record_grpc: bool,
2480    /// Record WebSocket messages
2481    pub record_websocket: bool,
2482    /// Record GraphQL requests
2483    pub record_graphql: bool,
2484    /// Record proxied requests (requests that are forwarded to real backends)
2485    /// When enabled, proxied requests/responses will be recorded with metadata indicating proxy source
2486    #[serde(default = "default_true")]
2487    pub record_proxy: bool,
2488}
2489
2490impl Default for RecorderConfig {
2491    fn default() -> Self {
2492        Self {
2493            enabled: false,
2494            database_path: "./mockforge-recordings.db".to_string(),
2495            api_enabled: true,
2496            api_port: None,
2497            max_requests: 10000,
2498            retention_days: 7,
2499            record_http: true,
2500            record_grpc: true,
2501            record_websocket: true,
2502            record_graphql: true,
2503            record_proxy: true,
2504        }
2505    }
2506}
2507
2508/// Chaos engineering configuration
2509#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2510#[serde(default)]
2511#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2512pub struct ChaosEngConfig {
2513    /// Enable chaos engineering
2514    pub enabled: bool,
2515    /// Latency injection configuration
2516    pub latency: Option<LatencyInjectionConfig>,
2517    /// Fault injection configuration
2518    pub fault_injection: Option<FaultConfig>,
2519    /// Rate limiting configuration
2520    pub rate_limit: Option<RateLimitingConfig>,
2521    /// Traffic shaping configuration
2522    pub traffic_shaping: Option<NetworkShapingConfig>,
2523    /// Predefined scenario to use
2524    pub scenario: Option<String>,
2525}
2526
2527/// Latency injection configuration for chaos engineering
2528#[derive(Debug, Clone, Serialize, Deserialize)]
2529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2530pub struct LatencyInjectionConfig {
2531    /// Enable latency injection
2532    pub enabled: bool,
2533    /// Fixed delay to inject (in milliseconds)
2534    pub fixed_delay_ms: Option<u64>,
2535    /// Random delay range (min_ms, max_ms) in milliseconds
2536    pub random_delay_range_ms: Option<(u64, u64)>,
2537    /// Jitter percentage to add variance to delays (0.0 to 1.0)
2538    pub jitter_percent: f64,
2539    /// Probability of injecting latency (0.0 to 1.0)
2540    pub probability: f64,
2541}
2542
2543/// Fault injection configuration for chaos engineering
2544#[derive(Debug, Clone, Serialize, Deserialize)]
2545#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2546pub struct FaultConfig {
2547    /// Enable fault injection
2548    pub enabled: bool,
2549    /// HTTP status codes to randomly return (e.g., [500, 502, 503])
2550    pub http_errors: Vec<u16>,
2551    /// Probability of returning HTTP errors (0.0 to 1.0)
2552    pub http_error_probability: f64,
2553    /// Enable connection errors (connection refused, reset, etc.)
2554    pub connection_errors: bool,
2555    /// Probability of connection errors (0.0 to 1.0)
2556    pub connection_error_probability: f64,
2557    /// Enable timeout errors
2558    pub timeout_errors: bool,
2559    /// Timeout duration in milliseconds
2560    pub timeout_ms: u64,
2561    /// Probability of timeout errors (0.0 to 1.0)
2562    pub timeout_probability: f64,
2563}
2564
2565/// Rate limiting configuration for traffic control
2566#[derive(Debug, Clone, Serialize, Deserialize)]
2567#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2568pub struct RateLimitingConfig {
2569    /// Enable rate limiting
2570    pub enabled: bool,
2571    /// Maximum requests per second allowed
2572    pub requests_per_second: u32,
2573    /// Maximum burst size before rate limiting kicks in
2574    pub burst_size: u32,
2575    /// Apply rate limiting per IP address
2576    pub per_ip: bool,
2577    /// Apply rate limiting per endpoint/path
2578    pub per_endpoint: bool,
2579}
2580
2581/// Network shaping configuration for simulating network conditions
2582#[derive(Debug, Clone, Serialize, Deserialize)]
2583#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2584pub struct NetworkShapingConfig {
2585    /// Enable network shaping
2586    pub enabled: bool,
2587    /// Bandwidth limit in bits per second
2588    pub bandwidth_limit_bps: u64,
2589    /// Packet loss percentage (0.0 to 1.0)
2590    pub packet_loss_percent: f64,
2591    /// Maximum concurrent connections allowed
2592    pub max_connections: u32,
2593}
2594
2595/// Load configuration from file
2596pub async fn load_config<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2597    let content = fs::read_to_string(&path)
2598        .await
2599        .map_err(|e| Error::generic(format!("Failed to read config file: {}", e)))?;
2600
2601    // Parse config with improved error messages
2602    let config: ServerConfig = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2603        || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2604    {
2605        serde_yaml::from_str(&content).map_err(|e| {
2606            // Improve error message with field path context
2607            let error_msg = e.to_string();
2608            let mut full_msg = format!("Failed to parse YAML config: {}", error_msg);
2609
2610            // Add helpful context for common errors
2611            if error_msg.contains("missing field") {
2612                full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
2613                full_msg.push_str(
2614                    "\n   Omit fields you don't need - MockForge will use sensible defaults.",
2615                );
2616                full_msg.push_str("\n   See config.template.yaml for all available options.");
2617            } else if error_msg.contains("unknown field") {
2618                full_msg.push_str("\n\n💡 Check for typos in field names.");
2619                full_msg.push_str("\n   See config.template.yaml for valid field names.");
2620            }
2621
2622            Error::generic(full_msg)
2623        })?
2624    } else {
2625        serde_json::from_str(&content).map_err(|e| {
2626            // Improve error message with field path context
2627            let error_msg = e.to_string();
2628            let mut full_msg = format!("Failed to parse JSON config: {}", error_msg);
2629
2630            // Add helpful context for common errors
2631            if error_msg.contains("missing field") {
2632                full_msg.push_str("\n\n💡 Most configuration fields are optional with defaults.");
2633                full_msg.push_str(
2634                    "\n   Omit fields you don't need - MockForge will use sensible defaults.",
2635                );
2636                full_msg.push_str("\n   See config.template.yaml for all available options.");
2637            } else if error_msg.contains("unknown field") {
2638                full_msg.push_str("\n\n💡 Check for typos in field names.");
2639                full_msg.push_str("\n   See config.template.yaml for valid field names.");
2640            }
2641
2642            Error::generic(full_msg)
2643        })?
2644    };
2645
2646    Ok(config)
2647}
2648
2649/// Save configuration to file
2650pub async fn save_config<P: AsRef<Path>>(path: P, config: &ServerConfig) -> Result<()> {
2651    let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
2652        || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
2653    {
2654        serde_yaml::to_string(config)
2655            .map_err(|e| Error::generic(format!("Failed to serialize config to YAML: {}", e)))?
2656    } else {
2657        serde_json::to_string_pretty(config)
2658            .map_err(|e| Error::generic(format!("Failed to serialize config to JSON: {}", e)))?
2659    };
2660
2661    fs::write(path, content)
2662        .await
2663        .map_err(|e| Error::generic(format!("Failed to write config file: {}", e)))?;
2664
2665    Ok(())
2666}
2667
2668/// Load configuration with fallback to default
2669pub async fn load_config_with_fallback<P: AsRef<Path>>(path: P) -> ServerConfig {
2670    match load_config(&path).await {
2671        Ok(config) => {
2672            tracing::info!("Loaded configuration from {:?}", path.as_ref());
2673            config
2674        }
2675        Err(e) => {
2676            tracing::warn!(
2677                "Failed to load config from {:?}: {}. Using defaults.",
2678                path.as_ref(),
2679                e
2680            );
2681            ServerConfig::default()
2682        }
2683    }
2684}
2685
2686/// Create default configuration file
2687pub async fn create_default_config<P: AsRef<Path>>(path: P) -> Result<()> {
2688    let config = ServerConfig::default();
2689    save_config(path, &config).await?;
2690    Ok(())
2691}
2692
2693/// Environment variable overrides for configuration
2694pub fn apply_env_overrides(mut config: ServerConfig) -> ServerConfig {
2695    // HTTP server overrides
2696    if let Ok(port) = std::env::var("MOCKFORGE_HTTP_PORT") {
2697        if let Ok(port_num) = port.parse() {
2698            config.http.port = port_num;
2699        }
2700    }
2701
2702    if let Ok(host) = std::env::var("MOCKFORGE_HTTP_HOST") {
2703        config.http.host = host;
2704    }
2705
2706    // WebSocket server overrides
2707    if let Ok(port) = std::env::var("MOCKFORGE_WS_PORT") {
2708        if let Ok(port_num) = port.parse() {
2709            config.websocket.port = port_num;
2710        }
2711    }
2712
2713    // gRPC server overrides
2714    if let Ok(port) = std::env::var("MOCKFORGE_GRPC_PORT") {
2715        if let Ok(port_num) = port.parse() {
2716            config.grpc.port = port_num;
2717        }
2718    }
2719
2720    // SMTP server overrides
2721    if let Ok(port) = std::env::var("MOCKFORGE_SMTP_PORT") {
2722        if let Ok(port_num) = port.parse() {
2723            config.smtp.port = port_num;
2724        }
2725    }
2726
2727    if let Ok(host) = std::env::var("MOCKFORGE_SMTP_HOST") {
2728        config.smtp.host = host;
2729    }
2730
2731    if let Ok(enabled) = std::env::var("MOCKFORGE_SMTP_ENABLED") {
2732        config.smtp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2733    }
2734
2735    if let Ok(hostname) = std::env::var("MOCKFORGE_SMTP_HOSTNAME") {
2736        config.smtp.hostname = hostname;
2737    }
2738
2739    // TCP server overrides
2740    if let Ok(port) = std::env::var("MOCKFORGE_TCP_PORT") {
2741        if let Ok(port_num) = port.parse() {
2742            config.tcp.port = port_num;
2743        }
2744    }
2745
2746    if let Ok(host) = std::env::var("MOCKFORGE_TCP_HOST") {
2747        config.tcp.host = host;
2748    }
2749
2750    if let Ok(enabled) = std::env::var("MOCKFORGE_TCP_ENABLED") {
2751        config.tcp.enabled = enabled == "1" || enabled.eq_ignore_ascii_case("true");
2752    }
2753
2754    // Admin UI overrides
2755    if let Ok(port) = std::env::var("MOCKFORGE_ADMIN_PORT") {
2756        if let Ok(port_num) = port.parse() {
2757            config.admin.port = port_num;
2758        }
2759    }
2760
2761    if std::env::var("MOCKFORGE_ADMIN_ENABLED").unwrap_or_default() == "true" {
2762        config.admin.enabled = true;
2763    }
2764
2765    // Admin UI host override - critical for Docker deployments
2766    if let Ok(host) = std::env::var("MOCKFORGE_ADMIN_HOST") {
2767        config.admin.host = host;
2768    }
2769
2770    if let Ok(mount_path) = std::env::var("MOCKFORGE_ADMIN_MOUNT_PATH") {
2771        if !mount_path.trim().is_empty() {
2772            config.admin.mount_path = Some(mount_path);
2773        }
2774    }
2775
2776    if let Ok(api_enabled) = std::env::var("MOCKFORGE_ADMIN_API_ENABLED") {
2777        let on = api_enabled == "1" || api_enabled.eq_ignore_ascii_case("true");
2778        config.admin.api_enabled = on;
2779    }
2780
2781    if let Ok(prometheus_url) = std::env::var("PROMETHEUS_URL") {
2782        config.admin.prometheus_url = prometheus_url;
2783    }
2784
2785    // Core configuration overrides
2786    if let Ok(latency_enabled) = std::env::var("MOCKFORGE_LATENCY_ENABLED") {
2787        let enabled = latency_enabled == "1" || latency_enabled.eq_ignore_ascii_case("true");
2788        config.core.latency_enabled = enabled;
2789    }
2790
2791    if let Ok(failures_enabled) = std::env::var("MOCKFORGE_FAILURES_ENABLED") {
2792        let enabled = failures_enabled == "1" || failures_enabled.eq_ignore_ascii_case("true");
2793        config.core.failures_enabled = enabled;
2794    }
2795
2796    if let Ok(overrides_enabled) = std::env::var("MOCKFORGE_OVERRIDES_ENABLED") {
2797        let enabled = overrides_enabled == "1" || overrides_enabled.eq_ignore_ascii_case("true");
2798        config.core.overrides_enabled = enabled;
2799    }
2800
2801    if let Ok(traffic_shaping_enabled) = std::env::var("MOCKFORGE_TRAFFIC_SHAPING_ENABLED") {
2802        let enabled =
2803            traffic_shaping_enabled == "1" || traffic_shaping_enabled.eq_ignore_ascii_case("true");
2804        config.core.traffic_shaping_enabled = enabled;
2805    }
2806
2807    // Traffic shaping overrides
2808    if let Ok(bandwidth_enabled) = std::env::var("MOCKFORGE_BANDWIDTH_ENABLED") {
2809        let enabled = bandwidth_enabled == "1" || bandwidth_enabled.eq_ignore_ascii_case("true");
2810        config.core.traffic_shaping.bandwidth.enabled = enabled;
2811    }
2812
2813    if let Ok(max_bytes_per_sec) = std::env::var("MOCKFORGE_BANDWIDTH_MAX_BYTES_PER_SEC") {
2814        if let Ok(bytes) = max_bytes_per_sec.parse() {
2815            config.core.traffic_shaping.bandwidth.max_bytes_per_sec = bytes;
2816            config.core.traffic_shaping.bandwidth.enabled = true;
2817        }
2818    }
2819
2820    if let Ok(burst_capacity) = std::env::var("MOCKFORGE_BANDWIDTH_BURST_CAPACITY_BYTES") {
2821        if let Ok(bytes) = burst_capacity.parse() {
2822            config.core.traffic_shaping.bandwidth.burst_capacity_bytes = bytes;
2823        }
2824    }
2825
2826    if let Ok(burst_loss_enabled) = std::env::var("MOCKFORGE_BURST_LOSS_ENABLED") {
2827        let enabled = burst_loss_enabled == "1" || burst_loss_enabled.eq_ignore_ascii_case("true");
2828        config.core.traffic_shaping.burst_loss.enabled = enabled;
2829    }
2830
2831    if let Ok(burst_probability) = std::env::var("MOCKFORGE_BURST_LOSS_PROBABILITY") {
2832        if let Ok(prob) = burst_probability.parse::<f64>() {
2833            config.core.traffic_shaping.burst_loss.burst_probability = prob.clamp(0.0, 1.0);
2834            config.core.traffic_shaping.burst_loss.enabled = true;
2835        }
2836    }
2837
2838    if let Ok(burst_duration) = std::env::var("MOCKFORGE_BURST_LOSS_DURATION_MS") {
2839        if let Ok(ms) = burst_duration.parse() {
2840            config.core.traffic_shaping.burst_loss.burst_duration_ms = ms;
2841        }
2842    }
2843
2844    if let Ok(loss_rate) = std::env::var("MOCKFORGE_BURST_LOSS_RATE") {
2845        if let Ok(rate) = loss_rate.parse::<f64>() {
2846            config.core.traffic_shaping.burst_loss.loss_rate_during_burst = rate.clamp(0.0, 1.0);
2847        }
2848    }
2849
2850    if let Ok(recovery_time) = std::env::var("MOCKFORGE_BURST_LOSS_RECOVERY_MS") {
2851        if let Ok(ms) = recovery_time.parse() {
2852            config.core.traffic_shaping.burst_loss.recovery_time_ms = ms;
2853        }
2854    }
2855
2856    // Logging overrides
2857    if let Ok(level) = std::env::var("MOCKFORGE_LOG_LEVEL") {
2858        config.logging.level = level;
2859    }
2860
2861    config
2862}
2863
2864/// Validate configuration
2865pub fn validate_config(config: &ServerConfig) -> Result<()> {
2866    // Validate port ranges
2867    if config.http.port == 0 {
2868        return Err(Error::generic("HTTP port cannot be 0"));
2869    }
2870    if config.websocket.port == 0 {
2871        return Err(Error::generic("WebSocket port cannot be 0"));
2872    }
2873    if config.grpc.port == 0 {
2874        return Err(Error::generic("gRPC port cannot be 0"));
2875    }
2876    if config.admin.port == 0 {
2877        return Err(Error::generic("Admin port cannot be 0"));
2878    }
2879
2880    // Check for port conflicts
2881    let ports = [
2882        ("HTTP", config.http.port),
2883        ("WebSocket", config.websocket.port),
2884        ("gRPC", config.grpc.port),
2885        ("Admin", config.admin.port),
2886    ];
2887
2888    for i in 0..ports.len() {
2889        for j in (i + 1)..ports.len() {
2890            if ports[i].1 == ports[j].1 {
2891                return Err(Error::generic(format!(
2892                    "Port conflict: {} and {} both use port {}",
2893                    ports[i].0, ports[j].0, ports[i].1
2894                )));
2895            }
2896        }
2897    }
2898
2899    // Validate log level
2900    let valid_levels = ["trace", "debug", "info", "warn", "error"];
2901    if !valid_levels.contains(&config.logging.level.as_str()) {
2902        return Err(Error::generic(format!(
2903            "Invalid log level: {}. Valid levels: {}",
2904            config.logging.level,
2905            valid_levels.join(", ")
2906        )));
2907    }
2908
2909    Ok(())
2910}
2911
2912/// Apply a profile to a base configuration
2913pub fn apply_profile(mut base: ServerConfig, profile: ProfileConfig) -> ServerConfig {
2914    // Macro to merge optional fields
2915    macro_rules! merge_field {
2916        ($field:ident) => {
2917            if let Some(override_val) = profile.$field {
2918                base.$field = override_val;
2919            }
2920        };
2921    }
2922
2923    merge_field!(http);
2924    merge_field!(websocket);
2925    merge_field!(graphql);
2926    merge_field!(grpc);
2927    merge_field!(mqtt);
2928    merge_field!(smtp);
2929    merge_field!(ftp);
2930    merge_field!(kafka);
2931    merge_field!(amqp);
2932    merge_field!(tcp);
2933    merge_field!(admin);
2934    merge_field!(chaining);
2935    merge_field!(core);
2936    merge_field!(logging);
2937    merge_field!(data);
2938    merge_field!(mockai);
2939    merge_field!(observability);
2940    merge_field!(multi_tenant);
2941    merge_field!(routes);
2942    merge_field!(protocols);
2943
2944    base
2945}
2946
2947/// Load configuration with profile support
2948pub async fn load_config_with_profile<P: AsRef<Path>>(
2949    path: P,
2950    profile_name: Option<&str>,
2951) -> Result<ServerConfig> {
2952    // Use load_config_auto to support all formats
2953    let mut config = load_config_auto(&path).await?;
2954
2955    // Apply profile if specified
2956    if let Some(profile) = profile_name {
2957        if let Some(profile_config) = config.profiles.remove(profile) {
2958            tracing::info!("Applying profile: {}", profile);
2959            config = apply_profile(config, profile_config);
2960        } else {
2961            return Err(Error::generic(format!(
2962                "Profile '{}' not found in configuration. Available profiles: {}",
2963                profile,
2964                config.profiles.keys().map(|k| k.as_str()).collect::<Vec<_>>().join(", ")
2965            )));
2966        }
2967    }
2968
2969    // Clear profiles from final config to save memory
2970    config.profiles.clear();
2971
2972    Ok(config)
2973}
2974
2975/// Load configuration from TypeScript/JavaScript file
2976pub async fn load_config_from_js<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
2977    use rquickjs::{Context, Runtime};
2978
2979    let content = fs::read_to_string(&path)
2980        .await
2981        .map_err(|e| Error::generic(format!("Failed to read JS/TS config file: {}", e)))?;
2982
2983    // Create a JavaScript runtime
2984    let runtime = Runtime::new()
2985        .map_err(|e| Error::generic(format!("Failed to create JS runtime: {}", e)))?;
2986    let context = Context::full(&runtime)
2987        .map_err(|e| Error::generic(format!("Failed to create JS context: {}", e)))?;
2988
2989    context.with(|ctx| {
2990        // For TypeScript files, we need to strip type annotations
2991        // This is a simple approach - for production, consider using a proper TS compiler
2992        let js_content = if path
2993            .as_ref()
2994            .extension()
2995            .and_then(|s| s.to_str())
2996            .map(|ext| ext == "ts")
2997            .unwrap_or(false)
2998        {
2999            strip_typescript_types(&content)?
3000        } else {
3001            content
3002        };
3003
3004        // Evaluate the config file
3005        let result: rquickjs::Value = ctx
3006            .eval(js_content.as_bytes())
3007            .map_err(|e| Error::generic(format!("Failed to evaluate JS config: {}", e)))?;
3008
3009        // Convert to JSON string
3010        let json_str: String = ctx
3011            .json_stringify(result)
3012            .map_err(|e| Error::generic(format!("Failed to stringify JS config: {}", e)))?
3013            .ok_or_else(|| Error::generic("JS config returned undefined"))?
3014            .get()
3015            .map_err(|e| Error::generic(format!("Failed to get JSON string: {}", e)))?;
3016
3017        // Parse JSON into ServerConfig
3018        serde_json::from_str(&json_str).map_err(|e| {
3019            Error::generic(format!("Failed to parse JS config as ServerConfig: {}", e))
3020        })
3021    })
3022}
3023
3024/// Simple TypeScript type stripper (removes type annotations)
3025/// Note: This is a basic implementation. For production use, consider using swc or esbuild
3026///
3027/// # Errors
3028/// Returns an error if regex compilation fails. This should never happen with static patterns,
3029/// but we handle it gracefully to prevent panics.
3030fn strip_typescript_types(content: &str) -> Result<String> {
3031    use regex::Regex;
3032
3033    let mut result = content.to_string();
3034
3035    // Compile regex patterns with error handling
3036    // Note: These patterns are statically known and should never fail,
3037    // but we handle errors to prevent panics in edge cases
3038
3039    // Remove interface declarations (handles multi-line)
3040    let interface_re = Regex::new(r"(?ms)interface\s+\w+\s*\{[^}]*\}\s*")
3041        .map_err(|e| Error::generic(format!("Failed to compile interface regex: {}", e)))?;
3042    result = interface_re.replace_all(&result, "").to_string();
3043
3044    // Remove type aliases
3045    let type_alias_re = Regex::new(r"(?m)^type\s+\w+\s*=\s*[^;]+;\s*")
3046        .map_err(|e| Error::generic(format!("Failed to compile type alias regex: {}", e)))?;
3047    result = type_alias_re.replace_all(&result, "").to_string();
3048
3049    // Remove type annotations (: Type)
3050    let type_annotation_re = Regex::new(r":\s*[A-Z]\w*(<[^>]+>)?(\[\])?")
3051        .map_err(|e| Error::generic(format!("Failed to compile type annotation regex: {}", e)))?;
3052    result = type_annotation_re.replace_all(&result, "").to_string();
3053
3054    // Remove type imports and exports
3055    let type_import_re = Regex::new(r"(?m)^(import|export)\s+type\s+.*$")
3056        .map_err(|e| Error::generic(format!("Failed to compile type import regex: {}", e)))?;
3057    result = type_import_re.replace_all(&result, "").to_string();
3058
3059    // Remove as Type
3060    let as_type_re = Regex::new(r"\s+as\s+\w+")
3061        .map_err(|e| Error::generic(format!("Failed to compile 'as type' regex: {}", e)))?;
3062    result = as_type_re.replace_all(&result, "").to_string();
3063
3064    Ok(result)
3065}
3066
3067/// Enhanced load_config that supports multiple formats including JS/TS
3068pub async fn load_config_auto<P: AsRef<Path>>(path: P) -> Result<ServerConfig> {
3069    let ext = path.as_ref().extension().and_then(|s| s.to_str()).unwrap_or("");
3070
3071    match ext {
3072        "ts" | "js" => load_config_from_js(&path).await,
3073        "yaml" | "yml" | "json" => load_config(&path).await,
3074        _ => Err(Error::generic(format!(
3075            "Unsupported config file format: {}. Supported: .ts, .js, .yaml, .yml, .json",
3076            ext
3077        ))),
3078    }
3079}
3080
3081/// Discover configuration file with support for all formats
3082pub async fn discover_config_file_all_formats() -> Result<std::path::PathBuf> {
3083    let current_dir = std::env::current_dir()
3084        .map_err(|e| Error::generic(format!("Failed to get current directory: {}", e)))?;
3085
3086    let config_names = vec![
3087        "mockforge.config.ts",
3088        "mockforge.config.js",
3089        "mockforge.yaml",
3090        "mockforge.yml",
3091        ".mockforge.yaml",
3092        ".mockforge.yml",
3093    ];
3094
3095    // Check current directory
3096    for name in &config_names {
3097        let path = current_dir.join(name);
3098        if tokio::fs::metadata(&path).await.is_ok() {
3099            return Ok(path);
3100        }
3101    }
3102
3103    // Check parent directories (up to 5 levels)
3104    let mut dir = current_dir.clone();
3105    for _ in 0..5 {
3106        if let Some(parent) = dir.parent() {
3107            for name in &config_names {
3108                let path = parent.join(name);
3109                if tokio::fs::metadata(&path).await.is_ok() {
3110                    return Ok(path);
3111                }
3112            }
3113            dir = parent.to_path_buf();
3114        } else {
3115            break;
3116        }
3117    }
3118
3119    Err(Error::generic(
3120        "No configuration file found. Expected one of: mockforge.config.ts, mockforge.config.js, mockforge.yaml, mockforge.yml",
3121    ))
3122}
3123
3124#[cfg(test)]
3125mod tests {
3126    use super::*;
3127
3128    #[test]
3129    fn test_default_config() {
3130        let config = ServerConfig::default();
3131        assert_eq!(config.http.port, 3000);
3132        assert_eq!(config.websocket.port, 3001);
3133        assert_eq!(config.grpc.port, 50051);
3134        assert_eq!(config.admin.port, 9080);
3135    }
3136
3137    #[test]
3138    fn test_config_validation() {
3139        let mut config = ServerConfig::default();
3140        assert!(validate_config(&config).is_ok());
3141
3142        // Test port conflict
3143        config.websocket.port = config.http.port;
3144        assert!(validate_config(&config).is_err());
3145
3146        // Test invalid log level
3147        config.websocket.port = 3001; // Fix port conflict
3148        config.logging.level = "invalid".to_string();
3149        assert!(validate_config(&config).is_err());
3150    }
3151
3152    #[test]
3153    fn test_apply_profile() {
3154        let mut base = ServerConfig::default();
3155        assert_eq!(base.http.port, 3000);
3156
3157        let mut profile = ProfileConfig::default();
3158        profile.http = Some(HttpConfig {
3159            port: 8080,
3160            ..Default::default()
3161        });
3162        profile.logging = Some(LoggingConfig {
3163            level: "debug".to_string(),
3164            ..Default::default()
3165        });
3166
3167        let merged = apply_profile(base, profile);
3168        assert_eq!(merged.http.port, 8080);
3169        assert_eq!(merged.logging.level, "debug");
3170        assert_eq!(merged.websocket.port, 3001); // Unchanged
3171    }
3172
3173    #[test]
3174    fn test_strip_typescript_types() {
3175        let ts_code = r#"
3176interface Config {
3177    port: number;
3178    host: string;
3179}
3180
3181const config: Config = {
3182    port: 3000,
3183    host: "localhost"
3184} as Config;
3185"#;
3186
3187        let stripped = strip_typescript_types(ts_code).expect("Should strip TypeScript types");
3188        assert!(!stripped.contains("interface"));
3189        assert!(!stripped.contains(": Config"));
3190        assert!(!stripped.contains("as Config"));
3191    }
3192}