Skip to main content

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