mockforge_core/
config.rs

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