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