Skip to main content

mockforge_config/
lib.rs

1//! Configuration types for MockForge
2//!
3//! This crate contains pure configuration data types used across the MockForge workspace.
4//! It is a leaf crate with no internal MockForge dependencies, containing only structs
5//! and enums that are serializable with serde.
6//!
7//! Types that require I/O, validation logic, or depend on core-specific types remain
8//! in `mockforge-core`.
9
10// These are data-only config structs; allow patterns standard for configuration types.
11#![allow(
12    clippy::doc_markdown,
13    clippy::struct_excessive_bools,
14    clippy::must_use_candidate,
15    clippy::missing_const_for_fn,
16    clippy::unreadable_literal,
17    clippy::unnecessary_self_imports,
18    clippy::return_self_not_must_use,
19    clippy::missing_errors_doc,
20    clippy::missing_panics_doc,
21    clippy::module_name_repetitions,
22    clippy::use_self,
23    clippy::derive_partial_eq_without_eq
24)]
25
26use serde::{Deserialize, Serialize};
27use std::collections::HashMap;
28
29// ─── Protocol Configs ────────────────────────────────────────────────────────
30
31/// HTTP validation configuration
32#[derive(Debug, Clone, Serialize, Deserialize)]
33#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
34pub struct HttpValidationConfig {
35    /// Request validation mode: off, warn, enforce
36    pub mode: String,
37}
38
39/// HTTP CORS configuration
40#[derive(Debug, Clone, Serialize, Deserialize, Default)]
41#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
42pub struct HttpCorsConfig {
43    /// Enable CORS
44    pub enabled: bool,
45    /// Allowed origins
46    #[serde(default)]
47    pub allowed_origins: Vec<String>,
48    /// Allowed methods
49    #[serde(default)]
50    pub allowed_methods: Vec<String>,
51    /// Allowed headers
52    #[serde(default)]
53    pub allowed_headers: Vec<String>,
54    /// Allow credentials (cookies, authorization headers)
55    /// Note: Cannot be true when using wildcard origin (*)
56    #[serde(default = "default_cors_allow_credentials")]
57    pub allow_credentials: bool,
58}
59
60fn default_cors_allow_credentials() -> bool {
61    false
62}
63
64/// HTTP server configuration
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
67#[serde(default)]
68pub struct HttpConfig {
69    /// Enable HTTP server
70    pub enabled: bool,
71    /// Server port
72    pub port: u16,
73    /// Host address
74    pub host: String,
75    /// Path to OpenAPI spec file for HTTP server
76    pub openapi_spec: Option<String>,
77    /// CORS configuration
78    pub cors: Option<HttpCorsConfig>,
79    /// Request timeout in seconds
80    pub request_timeout_secs: u64,
81    /// Request validation configuration
82    pub validation: Option<HttpValidationConfig>,
83    /// Aggregate validation errors into JSON array
84    pub aggregate_validation_errors: bool,
85    /// Validate responses (warn-only logging)
86    pub validate_responses: bool,
87    /// Expand templating tokens in responses/examples
88    pub response_template_expand: bool,
89    /// Validation error HTTP status (e.g., 400 or 422)
90    pub validation_status: Option<u16>,
91    /// Per-route overrides: key "METHOD path" => mode (off/warn/enforce)
92    pub validation_overrides: HashMap<String, String>,
93    /// When embedding Admin UI under HTTP, skip validation for the mounted prefix
94    pub skip_admin_validation: bool,
95    /// Authentication configuration
96    pub auth: Option<AuthConfig>,
97    /// TLS/HTTPS configuration
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub tls: Option<HttpTlsConfig>,
100}
101
102impl Default for HttpConfig {
103    fn default() -> Self {
104        Self {
105            enabled: true,
106            port: 3000,
107            host: "0.0.0.0".to_string(),
108            openapi_spec: None,
109            cors: Some(HttpCorsConfig {
110                enabled: true,
111                allowed_origins: vec!["*".to_string()],
112                allowed_methods: vec![
113                    "GET".to_string(),
114                    "POST".to_string(),
115                    "PUT".to_string(),
116                    "DELETE".to_string(),
117                    "PATCH".to_string(),
118                    "OPTIONS".to_string(),
119                ],
120                allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
121                allow_credentials: false, // Must be false when using wildcard origin
122            }),
123            request_timeout_secs: 30,
124            validation: Some(HttpValidationConfig {
125                mode: "enforce".to_string(),
126            }),
127            aggregate_validation_errors: true,
128            validate_responses: false,
129            response_template_expand: false,
130            validation_status: None,
131            validation_overrides: HashMap::new(),
132            skip_admin_validation: true,
133            auth: None,
134            tls: None,
135        }
136    }
137}
138
139/// HTTP TLS/HTTPS configuration
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
142pub struct HttpTlsConfig {
143    /// Enable TLS/HTTPS
144    pub enabled: bool,
145    /// Path to TLS certificate file (PEM format)
146    pub cert_file: String,
147    /// Path to TLS private key file (PEM format)
148    pub key_file: String,
149    /// Path to CA certificate file for mutual TLS (optional)
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub ca_file: Option<String>,
152    /// Minimum TLS version (default: "1.2")
153    #[serde(default = "default_tls_min_version")]
154    pub min_version: String,
155    /// Cipher suites to use (default: safe defaults)
156    #[serde(default, skip_serializing_if = "Vec::is_empty")]
157    pub cipher_suites: Vec<String>,
158    /// Require client certificate (mutual TLS)
159    #[serde(default)]
160    pub require_client_cert: bool,
161    /// Mutual TLS mode: "off" (default), "optional", "required"
162    #[serde(default = "default_mtls_mode")]
163    pub mtls_mode: String,
164}
165
166fn default_mtls_mode() -> String {
167    "off".to_string()
168}
169
170fn default_tls_min_version() -> String {
171    "1.2".to_string()
172}
173
174impl Default for HttpTlsConfig {
175    fn default() -> Self {
176        Self {
177            enabled: true,
178            cert_file: String::new(),
179            key_file: String::new(),
180            ca_file: None,
181            min_version: "1.2".to_string(),
182            cipher_suites: Vec::new(),
183            require_client_cert: false,
184            mtls_mode: "off".to_string(),
185        }
186    }
187}
188
189/// WebSocket server configuration
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
192#[serde(default)]
193pub struct WebSocketConfig {
194    /// Enable WebSocket server
195    pub enabled: bool,
196    /// Server port
197    pub port: u16,
198    /// Host address
199    pub host: String,
200    /// Replay file path
201    pub replay_file: Option<String>,
202    /// Connection timeout in seconds
203    pub connection_timeout_secs: u64,
204}
205
206impl Default for WebSocketConfig {
207    fn default() -> Self {
208        Self {
209            enabled: true,
210            port: 3001,
211            host: "0.0.0.0".to_string(),
212            replay_file: None,
213            connection_timeout_secs: 300,
214        }
215    }
216}
217
218/// gRPC server configuration
219#[derive(Debug, Clone, Serialize, Deserialize)]
220#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
221#[serde(default)]
222pub struct GrpcConfig {
223    /// Enable gRPC server
224    pub enabled: bool,
225    /// Server port
226    pub port: u16,
227    /// Host address
228    pub host: String,
229    /// Proto files directory
230    pub proto_dir: Option<String>,
231    /// TLS configuration
232    pub tls: Option<TlsConfig>,
233}
234
235impl Default for GrpcConfig {
236    fn default() -> Self {
237        Self {
238            enabled: true,
239            port: 50051,
240            host: "0.0.0.0".to_string(),
241            proto_dir: None,
242            tls: None,
243        }
244    }
245}
246
247/// GraphQL server configuration
248#[derive(Debug, Clone, Serialize, Deserialize)]
249#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
250#[serde(default)]
251pub struct GraphQLConfig {
252    /// Enable GraphQL server
253    pub enabled: bool,
254    /// Server port
255    pub port: u16,
256    /// Host address
257    pub host: String,
258    /// GraphQL schema file path (.graphql or .gql)
259    pub schema_path: Option<String>,
260    /// Handlers directory for custom resolvers
261    pub handlers_dir: Option<String>,
262    /// Enable GraphQL Playground UI
263    pub playground_enabled: bool,
264    /// Upstream GraphQL server URL for passthrough
265    pub upstream_url: Option<String>,
266    /// Enable introspection queries
267    pub introspection_enabled: bool,
268}
269
270impl Default for GraphQLConfig {
271    fn default() -> Self {
272        Self {
273            enabled: true,
274            port: 4000,
275            host: "0.0.0.0".to_string(),
276            schema_path: None,
277            handlers_dir: None,
278            playground_enabled: true,
279            upstream_url: None,
280            introspection_enabled: true,
281        }
282    }
283}
284
285/// TLS configuration for gRPC
286#[derive(Debug, Clone, Serialize, Deserialize)]
287#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
288pub struct TlsConfig {
289    /// Certificate file path
290    pub cert_path: String,
291    /// Private key file path
292    pub key_path: String,
293}
294
295/// MQTT server configuration
296#[derive(Debug, Clone, Serialize, Deserialize)]
297#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
298#[serde(default)]
299pub struct MqttConfig {
300    /// Enable MQTT server
301    pub enabled: bool,
302    /// Server port
303    pub port: u16,
304    /// Host address
305    pub host: String,
306    /// Maximum connections
307    pub max_connections: usize,
308    /// Maximum packet size
309    pub max_packet_size: usize,
310    /// Keep-alive timeout in seconds
311    pub keep_alive_secs: u16,
312    /// Directory containing fixture files
313    pub fixtures_dir: Option<std::path::PathBuf>,
314    /// Enable retained messages
315    pub enable_retained_messages: bool,
316    /// Maximum retained messages
317    pub max_retained_messages: usize,
318}
319
320impl Default for MqttConfig {
321    fn default() -> Self {
322        Self {
323            enabled: false,
324            port: 1883,
325            host: "0.0.0.0".to_string(),
326            max_connections: 1000,
327            max_packet_size: 268435456, // 256 MB
328            keep_alive_secs: 60,
329            fixtures_dir: None,
330            enable_retained_messages: true,
331            max_retained_messages: 10000,
332        }
333    }
334}
335
336/// SMTP server configuration
337#[derive(Debug, Clone, Serialize, Deserialize)]
338#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
339#[serde(default)]
340pub struct SmtpConfig {
341    /// Enable SMTP server
342    pub enabled: bool,
343    /// Server port
344    pub port: u16,
345    /// Host address
346    pub host: String,
347    /// Server hostname for SMTP greeting
348    pub hostname: String,
349    /// Directory containing fixture files
350    pub fixtures_dir: Option<std::path::PathBuf>,
351    /// Connection timeout in seconds
352    pub timeout_secs: u64,
353    /// Maximum connections
354    pub max_connections: usize,
355    /// Enable mailbox storage
356    pub enable_mailbox: bool,
357    /// Maximum mailbox size
358    pub max_mailbox_messages: usize,
359    /// Enable STARTTLS support
360    pub enable_starttls: bool,
361    /// Path to TLS certificate file
362    pub tls_cert_path: Option<std::path::PathBuf>,
363    /// Path to TLS private key file
364    pub tls_key_path: Option<std::path::PathBuf>,
365}
366
367impl Default for SmtpConfig {
368    fn default() -> Self {
369        Self {
370            enabled: false,
371            port: 1025,
372            host: "0.0.0.0".to_string(),
373            hostname: "mockforge-smtp".to_string(),
374            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
375            timeout_secs: 300,
376            max_connections: 10,
377            enable_mailbox: true,
378            max_mailbox_messages: 1000,
379            enable_starttls: false,
380            tls_cert_path: None,
381            tls_key_path: None,
382        }
383    }
384}
385
386/// FTP server configuration
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
389#[serde(default)]
390pub struct FtpConfig {
391    /// Enable FTP server
392    pub enabled: bool,
393    /// Server port
394    pub port: u16,
395    /// Host address
396    pub host: String,
397    /// Passive mode port range
398    pub passive_ports: (u16, u16),
399    /// Maximum connections
400    pub max_connections: usize,
401    /// Connection timeout in seconds
402    pub timeout_secs: u64,
403    /// Allow anonymous access
404    pub allow_anonymous: bool,
405    /// Fixtures directory
406    pub fixtures_dir: Option<std::path::PathBuf>,
407    /// Virtual root directory
408    pub virtual_root: std::path::PathBuf,
409}
410
411impl Default for FtpConfig {
412    fn default() -> Self {
413        Self {
414            enabled: false,
415            port: 2121,
416            host: "0.0.0.0".to_string(),
417            passive_ports: (50000, 51000),
418            max_connections: 100,
419            timeout_secs: 300,
420            allow_anonymous: true,
421            fixtures_dir: None,
422            virtual_root: std::path::PathBuf::from("/mockforge"),
423        }
424    }
425}
426
427/// Kafka server configuration
428#[derive(Debug, Clone, Serialize, Deserialize)]
429#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
430#[serde(default)]
431pub struct KafkaConfig {
432    /// Enable Kafka server
433    pub enabled: bool,
434    /// Server port
435    pub port: u16,
436    /// Host address
437    pub host: String,
438    /// Broker ID
439    pub broker_id: i32,
440    /// Maximum connections
441    pub max_connections: usize,
442    /// Log retention time in milliseconds
443    pub log_retention_ms: i64,
444    /// Log segment size in bytes
445    pub log_segment_bytes: i64,
446    /// Fixtures directory
447    pub fixtures_dir: Option<std::path::PathBuf>,
448    /// Auto-create topics
449    pub auto_create_topics: bool,
450    /// Default number of partitions for new topics
451    pub default_partitions: i32,
452    /// Default replication factor for new topics
453    pub default_replication_factor: i16,
454}
455
456impl Default for KafkaConfig {
457    fn default() -> Self {
458        Self {
459            enabled: false,
460            port: 9092, // Standard Kafka port
461            host: "0.0.0.0".to_string(),
462            broker_id: 1,
463            max_connections: 1000,
464            log_retention_ms: 604800000,   // 7 days
465            log_segment_bytes: 1073741824, // 1 GB
466            fixtures_dir: None,
467            auto_create_topics: true,
468            default_partitions: 3,
469            default_replication_factor: 1,
470        }
471    }
472}
473
474/// AMQP server configuration
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
477#[serde(default)]
478pub struct AmqpConfig {
479    /// Enable AMQP server
480    pub enabled: bool,
481    /// Server port
482    pub port: u16,
483    /// Host address
484    pub host: String,
485    /// Maximum connections
486    pub max_connections: usize,
487    /// Maximum channels per connection
488    pub max_channels_per_connection: u16,
489    /// Frame max size
490    pub frame_max: u32,
491    /// Heartbeat interval in seconds
492    pub heartbeat_interval: u16,
493    /// Fixtures directory
494    pub fixtures_dir: Option<std::path::PathBuf>,
495    /// Virtual hosts
496    pub virtual_hosts: Vec<String>,
497    /// Enable TLS
498    pub tls_enabled: bool,
499    /// TLS port (5671 is standard AMQPS port)
500    pub tls_port: u16,
501    /// Path to TLS certificate file (PEM format)
502    pub tls_cert_path: Option<std::path::PathBuf>,
503    /// Path to TLS private key file (PEM format)
504    pub tls_key_path: Option<std::path::PathBuf>,
505    /// Path to CA certificate for client verification (optional)
506    pub tls_ca_path: Option<std::path::PathBuf>,
507    /// Require client certificate authentication
508    pub tls_client_auth: bool,
509}
510
511impl Default for AmqpConfig {
512    fn default() -> Self {
513        Self {
514            enabled: false,
515            port: 5672, // Standard AMQP port
516            host: "0.0.0.0".to_string(),
517            max_connections: 1000,
518            max_channels_per_connection: 100,
519            frame_max: 131072, // 128 KB
520            heartbeat_interval: 60,
521            fixtures_dir: None,
522            virtual_hosts: vec!["/".to_string()],
523            tls_enabled: false,
524            tls_port: 5671, // Standard AMQPS port
525            tls_cert_path: None,
526            tls_key_path: None,
527            tls_ca_path: None,
528            tls_client_auth: false,
529        }
530    }
531}
532
533/// TCP server configuration
534#[derive(Debug, Clone, Serialize, Deserialize)]
535#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
536#[serde(default)]
537pub struct TcpConfig {
538    /// Enable TCP server
539    pub enabled: bool,
540    /// Server port
541    pub port: u16,
542    /// Host address
543    pub host: String,
544    /// Maximum connections
545    pub max_connections: usize,
546    /// Connection timeout in seconds
547    pub timeout_secs: u64,
548    /// Directory containing fixture files
549    pub fixtures_dir: Option<std::path::PathBuf>,
550    /// Enable echo mode (echo received data back)
551    pub echo_mode: bool,
552    /// Enable TLS support
553    pub enable_tls: bool,
554    /// Path to TLS certificate file
555    pub tls_cert_path: Option<std::path::PathBuf>,
556    /// Path to TLS private key file
557    pub tls_key_path: Option<std::path::PathBuf>,
558}
559
560impl Default for TcpConfig {
561    fn default() -> Self {
562        Self {
563            enabled: false,
564            port: 9999,
565            host: "0.0.0.0".to_string(),
566            max_connections: 1000,
567            timeout_secs: 300,
568            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
569            echo_mode: true,
570            enable_tls: false,
571            tls_cert_path: None,
572            tls_key_path: None,
573        }
574    }
575}
576
577/// Admin UI configuration
578#[derive(Debug, Clone, Serialize, Deserialize)]
579#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
580#[serde(default)]
581pub struct AdminConfig {
582    /// Enable admin UI
583    pub enabled: bool,
584    /// Admin UI port
585    pub port: u16,
586    /// Host address
587    pub host: String,
588    /// Authentication required
589    pub auth_required: bool,
590    /// Admin username (if auth required)
591    pub username: Option<String>,
592    /// Admin password (if auth required)
593    pub password: Option<String>,
594    /// Optional mount path to embed Admin UI under HTTP server (e.g., "/admin")
595    pub mount_path: Option<String>,
596    /// Enable Admin API endpoints (under `__mockforge`)
597    pub api_enabled: bool,
598    /// Prometheus server URL for analytics queries
599    pub prometheus_url: String,
600}
601
602impl Default for AdminConfig {
603    fn default() -> Self {
604        // Default to 0.0.0.0 if running in Docker (detected via common Docker env vars)
605        // This makes Admin UI accessible from outside the container by default
606        let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
607            || std::env::var("container").is_ok()
608            || std::path::Path::new("/.dockerenv").exists()
609        {
610            "0.0.0.0".to_string()
611        } else {
612            "127.0.0.1".to_string()
613        };
614
615        Self {
616            enabled: false,
617            port: 9080,
618            host: default_host,
619            auth_required: false,
620            username: None,
621            password: None,
622            mount_path: None,
623            api_enabled: true,
624            prometheus_url: "http://localhost:9090".to_string(),
625        }
626    }
627}
628
629/// Logging configuration
630#[derive(Debug, Clone, Serialize, Deserialize)]
631#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
632#[serde(default)]
633pub struct LoggingConfig {
634    /// Log level
635    pub level: String,
636    /// Enable JSON logging
637    pub json_format: bool,
638    /// Log file path (optional)
639    pub file_path: Option<String>,
640    /// Maximum log file size in MB
641    pub max_file_size_mb: u64,
642    /// Maximum number of log files to keep
643    pub max_files: u32,
644}
645
646impl Default for LoggingConfig {
647    fn default() -> Self {
648        Self {
649            level: "info".to_string(),
650            json_format: false,
651            file_path: None,
652            max_file_size_mb: 10,
653            max_files: 5,
654        }
655    }
656}
657
658/// Protocol enable/disable configuration
659#[derive(Debug, Clone, Serialize, Deserialize)]
660#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
661pub struct ProtocolConfig {
662    /// Enable this protocol
663    pub enabled: bool,
664}
665
666/// Protocols configuration
667#[derive(Debug, Clone, Serialize, Deserialize)]
668#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
669pub struct ProtocolsConfig {
670    /// HTTP protocol configuration
671    pub http: ProtocolConfig,
672    /// GraphQL protocol configuration
673    pub graphql: ProtocolConfig,
674    /// gRPC protocol configuration
675    pub grpc: ProtocolConfig,
676    /// WebSocket protocol configuration
677    pub websocket: ProtocolConfig,
678    /// SMTP protocol configuration
679    pub smtp: ProtocolConfig,
680    /// MQTT protocol configuration
681    pub mqtt: ProtocolConfig,
682    /// FTP protocol configuration
683    pub ftp: ProtocolConfig,
684    /// Kafka protocol configuration
685    pub kafka: ProtocolConfig,
686    /// RabbitMQ protocol configuration
687    pub rabbitmq: ProtocolConfig,
688    /// AMQP protocol configuration
689    pub amqp: ProtocolConfig,
690    /// TCP protocol configuration
691    pub tcp: ProtocolConfig,
692}
693
694impl Default for ProtocolsConfig {
695    fn default() -> Self {
696        Self {
697            http: ProtocolConfig { enabled: true },
698            graphql: ProtocolConfig { enabled: true },
699            grpc: ProtocolConfig { enabled: true },
700            websocket: ProtocolConfig { enabled: true },
701            smtp: ProtocolConfig { enabled: false },
702            mqtt: ProtocolConfig { enabled: true },
703            ftp: ProtocolConfig { enabled: false },
704            kafka: ProtocolConfig { enabled: false },
705            rabbitmq: ProtocolConfig { enabled: false },
706            amqp: ProtocolConfig { enabled: false },
707            tcp: ProtocolConfig { enabled: false },
708        }
709    }
710}
711
712// ─── Auth Configs ────────────────────────────────────────────────────────────
713
714/// Authentication configuration for HTTP requests
715#[derive(Debug, Clone, Serialize, Deserialize)]
716#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
717#[serde(default)]
718pub struct AuthConfig {
719    /// JWT configuration
720    pub jwt: Option<JwtConfig>,
721    /// OAuth2 configuration
722    pub oauth2: Option<OAuth2Config>,
723    /// Basic auth configuration
724    pub basic_auth: Option<BasicAuthConfig>,
725    /// API key configuration
726    pub api_key: Option<ApiKeyConfig>,
727    /// Whether to require authentication for all requests
728    pub require_auth: bool,
729}
730
731/// JWT authentication configuration
732#[derive(Debug, Clone, Serialize, Deserialize)]
733#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
734pub struct JwtConfig {
735    /// JWT secret key for HMAC algorithms
736    pub secret: Option<String>,
737    /// RSA public key PEM for RSA algorithms
738    pub rsa_public_key: Option<String>,
739    /// ECDSA public key PEM for ECDSA algorithms
740    pub ecdsa_public_key: Option<String>,
741    /// Expected issuer
742    pub issuer: Option<String>,
743    /// Expected audience
744    pub audience: Option<String>,
745    /// Supported algorithms (defaults to HS256, RS256, ES256)
746    pub algorithms: Vec<String>,
747}
748
749/// OAuth2 configuration
750#[derive(Debug, Clone, Serialize, Deserialize)]
751#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
752pub struct OAuth2Config {
753    /// OAuth2 client ID
754    pub client_id: String,
755    /// OAuth2 client secret
756    pub client_secret: String,
757    /// Token introspection URL
758    pub introspection_url: String,
759    /// Authorization server URL
760    pub auth_url: Option<String>,
761    /// Token URL
762    pub token_url: Option<String>,
763    /// Expected token type
764    pub token_type_hint: Option<String>,
765}
766
767/// Basic authentication configuration
768#[derive(Debug, Clone, Serialize, Deserialize)]
769#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
770pub struct BasicAuthConfig {
771    /// Username/password pairs
772    pub credentials: HashMap<String, String>,
773}
774
775/// API key configuration
776#[derive(Debug, Clone, Serialize, Deserialize)]
777#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
778pub struct ApiKeyConfig {
779    /// Expected header name (default: X-API-Key)
780    pub header_name: String,
781    /// Expected query parameter name
782    pub query_name: Option<String>,
783    /// Valid API keys
784    pub keys: Vec<String>,
785}
786
787impl Default for AuthConfig {
788    fn default() -> Self {
789        Self {
790            jwt: None,
791            oauth2: None,
792            basic_auth: None,
793            api_key: Some(ApiKeyConfig {
794                header_name: "X-API-Key".to_string(),
795                query_name: None,
796                keys: vec![],
797            }),
798            require_auth: false,
799        }
800    }
801}
802
803// ─── Route Configs ───────────────────────────────────────────────────────────
804
805/// Route configuration for custom HTTP routes
806#[derive(Debug, Clone, Serialize, Deserialize)]
807#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
808pub struct RouteConfig {
809    /// Route path (supports path parameters like /users/{id})
810    pub path: String,
811    /// HTTP method
812    pub method: String,
813    /// Request configuration
814    pub request: Option<RouteRequestConfig>,
815    /// Response configuration
816    pub response: RouteResponseConfig,
817    /// Per-route fault injection configuration
818    #[serde(default)]
819    pub fault_injection: Option<RouteFaultInjectionConfig>,
820    /// Per-route latency configuration
821    #[serde(default)]
822    pub latency: Option<RouteLatencyConfig>,
823}
824
825/// Request configuration for routes
826#[derive(Debug, Clone, Serialize, Deserialize)]
827#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
828pub struct RouteRequestConfig {
829    /// Request validation configuration
830    pub validation: Option<RouteValidationConfig>,
831}
832
833/// Response configuration for routes
834#[derive(Debug, Clone, Serialize, Deserialize)]
835#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
836pub struct RouteResponseConfig {
837    /// HTTP status code
838    pub status: u16,
839    /// Response headers
840    #[serde(default)]
841    pub headers: HashMap<String, String>,
842    /// Response body
843    pub body: Option<serde_json::Value>,
844}
845
846/// Validation configuration for routes
847#[derive(Debug, Clone, Serialize, Deserialize)]
848#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
849pub struct RouteValidationConfig {
850    /// JSON schema for request validation
851    pub schema: serde_json::Value,
852}
853
854/// Per-route fault injection configuration
855#[derive(Debug, Clone, Serialize, Deserialize)]
856#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
857pub struct RouteFaultInjectionConfig {
858    /// Enable fault injection for this route
859    pub enabled: bool,
860    /// Probability of injecting a fault (0.0-1.0)
861    pub probability: f64,
862    /// Fault types to inject
863    pub fault_types: Vec<RouteFaultType>,
864}
865
866/// Fault types that can be injected per route
867#[derive(Debug, Clone, Serialize, Deserialize)]
868#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
869#[serde(tag = "type", rename_all = "snake_case")]
870pub enum RouteFaultType {
871    /// HTTP error with status code
872    HttpError {
873        /// HTTP status code to return
874        status_code: u16,
875        /// Optional error message
876        message: Option<String>,
877    },
878    /// Connection error
879    ConnectionError {
880        /// Optional error message
881        message: Option<String>,
882    },
883    /// Timeout error
884    Timeout {
885        /// Timeout duration in milliseconds
886        duration_ms: u64,
887        /// Optional error message
888        message: Option<String>,
889    },
890    /// Partial response (truncate at percentage)
891    PartialResponse {
892        /// Percentage of response to truncate (0.0-100.0)
893        truncate_percent: f64,
894    },
895    /// Payload corruption
896    PayloadCorruption {
897        /// Type of corruption to apply
898        corruption_type: String,
899    },
900}
901
902/// Per-route latency configuration
903#[derive(Debug, Clone, Serialize, Deserialize)]
904#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
905pub struct RouteLatencyConfig {
906    /// Enable latency injection for this route
907    pub enabled: bool,
908    /// Probability of applying latency (0.0-1.0)
909    pub probability: f64,
910    /// Fixed delay in milliseconds
911    pub fixed_delay_ms: Option<u64>,
912    /// Random delay range (min_ms, max_ms)
913    pub random_delay_range_ms: Option<(u64, u64)>,
914    /// Jitter percentage (0.0-100.0)
915    pub jitter_percent: f64,
916    /// Latency distribution type
917    #[serde(default)]
918    pub distribution: LatencyDistribution,
919}
920
921/// Latency distribution type
922#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
923#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
924#[serde(rename_all = "snake_case")]
925#[derive(Default)]
926pub enum LatencyDistribution {
927    /// Fixed delay
928    #[default]
929    Fixed,
930    /// Normal distribution (requires mean and std_dev)
931    Normal {
932        /// Mean delay in milliseconds
933        mean_ms: f64,
934        /// Standard deviation in milliseconds
935        std_dev_ms: f64,
936    },
937    /// Exponential distribution (requires lambda)
938    Exponential {
939        /// Lambda parameter for exponential distribution
940        lambda: f64,
941    },
942    /// Uniform distribution (uses random_delay_range_ms)
943    Uniform,
944}
945
946impl Default for RouteFaultInjectionConfig {
947    fn default() -> Self {
948        Self {
949            enabled: false,
950            probability: 0.0,
951            fault_types: Vec::new(),
952        }
953    }
954}
955
956impl Default for RouteLatencyConfig {
957    fn default() -> Self {
958        Self {
959            enabled: false,
960            probability: 1.0,
961            fixed_delay_ms: None,
962            random_delay_range_ms: None,
963            jitter_percent: 0.0,
964            distribution: LatencyDistribution::Fixed,
965        }
966    }
967}
968
969// ─── Performance Configs ─────────────────────────────────────────────────────
970
971/// Performance and resource configuration
972#[derive(Debug, Clone, Serialize, Deserialize)]
973#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
974#[serde(default)]
975#[derive(Default)]
976pub struct PerformanceConfig {
977    /// Response compression configuration
978    pub compression: CompressionConfig,
979    /// Connection pooling configuration
980    pub connection_pool: ConnectionPoolConfig,
981    /// Request limits configuration
982    pub request_limits: RequestLimitsConfig,
983    /// Worker thread configuration
984    pub workers: WorkerConfig,
985    /// Circuit breaker configuration
986    pub circuit_breaker: CircuitBreakerConfig,
987}
988
989/// Response compression configuration
990#[derive(Debug, Clone, Serialize, Deserialize)]
991#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
992#[serde(default)]
993pub struct CompressionConfig {
994    /// Enable response compression
995    pub enabled: bool,
996    /// Compression algorithm: gzip, deflate, br (brotli), zstd
997    pub algorithm: String,
998    /// Minimum response size to compress (bytes)
999    pub min_size: usize,
1000    /// Compression level (1-9 for gzip/deflate, 0-11 for brotli, 1-22 for zstd)
1001    pub level: u32,
1002    /// Content types to compress (e.g., `["application/json", "text/html"]`)
1003    pub content_types: Vec<String>,
1004}
1005
1006impl Default for CompressionConfig {
1007    fn default() -> Self {
1008        Self {
1009            enabled: true,
1010            algorithm: "gzip".to_string(),
1011            min_size: 1024, // 1KB
1012            level: 6,
1013            content_types: vec![
1014                "application/json".to_string(),
1015                "application/xml".to_string(),
1016                "text/plain".to_string(),
1017                "text/html".to_string(),
1018                "text/css".to_string(),
1019                "application/javascript".to_string(),
1020            ],
1021        }
1022    }
1023}
1024
1025/// Connection pooling configuration for downstream services
1026#[derive(Debug, Clone, Serialize, Deserialize)]
1027#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1028#[serde(default)]
1029pub struct ConnectionPoolConfig {
1030    /// Maximum idle connections per host
1031    pub max_idle_per_host: usize,
1032    /// Maximum total connections
1033    pub max_connections: usize,
1034    /// Idle connection timeout in seconds
1035    pub idle_timeout_secs: u64,
1036    /// Connection acquire timeout in milliseconds
1037    pub acquire_timeout_ms: u64,
1038    /// Enable connection pooling
1039    pub enabled: bool,
1040}
1041
1042impl Default for ConnectionPoolConfig {
1043    fn default() -> Self {
1044        Self {
1045            max_idle_per_host: 10,
1046            max_connections: 100,
1047            idle_timeout_secs: 90,
1048            acquire_timeout_ms: 5000,
1049            enabled: true,
1050        }
1051    }
1052}
1053
1054/// Request limits configuration
1055#[derive(Debug, Clone, Serialize, Deserialize)]
1056#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1057#[serde(default)]
1058pub struct RequestLimitsConfig {
1059    /// Maximum request body size in bytes (default: 10MB)
1060    pub max_body_size: usize,
1061    /// Maximum header size in bytes
1062    pub max_header_size: usize,
1063    /// Maximum number of headers
1064    pub max_headers: usize,
1065    /// Maximum URI length
1066    pub max_uri_length: usize,
1067    /// Per-route body size limits (path pattern -> max bytes)
1068    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1069    pub per_route_limits: HashMap<String, usize>,
1070}
1071
1072impl Default for RequestLimitsConfig {
1073    fn default() -> Self {
1074        Self {
1075            max_body_size: 10 * 1024 * 1024, // 10MB
1076            max_header_size: 16 * 1024,      // 16KB
1077            max_headers: 100,
1078            max_uri_length: 8192,
1079            per_route_limits: HashMap::new(),
1080        }
1081    }
1082}
1083
1084/// Worker thread configuration
1085#[derive(Debug, Clone, Serialize, Deserialize)]
1086#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1087#[serde(default)]
1088pub struct WorkerConfig {
1089    /// Number of worker threads (0 = auto-detect based on CPU cores)
1090    pub threads: usize,
1091    /// Blocking thread pool size for CPU-intensive work
1092    pub blocking_threads: usize,
1093    /// Thread stack size in bytes
1094    pub stack_size: usize,
1095    /// Thread name prefix
1096    pub name_prefix: String,
1097}
1098
1099impl Default for WorkerConfig {
1100    fn default() -> Self {
1101        Self {
1102            threads: 0, // auto-detect
1103            blocking_threads: 512,
1104            stack_size: 2 * 1024 * 1024, // 2MB
1105            name_prefix: "mockforge-worker".to_string(),
1106        }
1107    }
1108}
1109
1110/// Circuit breaker configuration
1111#[derive(Debug, Clone, Serialize, Deserialize)]
1112#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1113#[serde(default)]
1114pub struct CircuitBreakerConfig {
1115    /// Enable circuit breaker
1116    pub enabled: bool,
1117    /// Failure threshold before opening circuit
1118    pub failure_threshold: u32,
1119    /// Success threshold before closing circuit
1120    pub success_threshold: u32,
1121    /// Half-open timeout in seconds (time before trying again after opening)
1122    pub half_open_timeout_secs: u64,
1123    /// Sliding window size for tracking failures
1124    pub window_size: u32,
1125    /// Per-endpoint circuit breaker configuration
1126    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1127    pub per_endpoint: HashMap<String, EndpointCircuitBreakerConfig>,
1128}
1129
1130impl Default for CircuitBreakerConfig {
1131    fn default() -> Self {
1132        Self {
1133            enabled: false,
1134            failure_threshold: 5,
1135            success_threshold: 2,
1136            half_open_timeout_secs: 30,
1137            window_size: 10,
1138            per_endpoint: HashMap::new(),
1139        }
1140    }
1141}
1142
1143/// Per-endpoint circuit breaker configuration
1144#[derive(Debug, Clone, Serialize, Deserialize)]
1145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1146pub struct EndpointCircuitBreakerConfig {
1147    /// Failure threshold for this endpoint
1148    pub failure_threshold: u32,
1149    /// Success threshold for this endpoint
1150    pub success_threshold: u32,
1151    /// Half-open timeout in seconds
1152    pub half_open_timeout_secs: u64,
1153}
1154
1155// ─── Security Configs ────────────────────────────────────────────────────────
1156
1157/// Secret backend provider type
1158#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
1159#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1160#[serde(rename_all = "lowercase")]
1161pub enum SecretBackendType {
1162    /// No secret backend (use environment variables directly)
1163    #[default]
1164    None,
1165    /// HashCorp Vault
1166    Vault,
1167    /// AWS Secrets Manager
1168    AwsSecretsManager,
1169    /// Azure Key Vault
1170    AzureKeyVault,
1171    /// Google Cloud Secret Manager
1172    GcpSecretManager,
1173    /// Kubernetes Secrets
1174    Kubernetes,
1175    /// Local encrypted file
1176    EncryptedFile,
1177}
1178
1179/// Secret backend configuration
1180#[derive(Debug, Clone, Serialize, Deserialize)]
1181#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1182#[serde(default)]
1183pub struct SecretBackendConfig {
1184    /// Secret backend provider
1185    pub provider: SecretBackendType,
1186    /// Vault-specific configuration
1187    #[serde(skip_serializing_if = "Option::is_none")]
1188    pub vault: Option<VaultConfig>,
1189    /// AWS Secrets Manager configuration
1190    #[serde(skip_serializing_if = "Option::is_none")]
1191    pub aws: Option<AwsSecretsConfig>,
1192    /// Azure Key Vault configuration
1193    #[serde(skip_serializing_if = "Option::is_none")]
1194    pub azure: Option<AzureKeyVaultConfig>,
1195    /// GCP Secret Manager configuration
1196    #[serde(skip_serializing_if = "Option::is_none")]
1197    pub gcp: Option<GcpSecretManagerConfig>,
1198    /// Kubernetes secrets configuration
1199    #[serde(skip_serializing_if = "Option::is_none")]
1200    pub kubernetes: Option<KubernetesSecretsConfig>,
1201    /// Encrypted file configuration
1202    #[serde(skip_serializing_if = "Option::is_none")]
1203    pub encrypted_file: Option<EncryptedFileConfig>,
1204    /// Secret key mappings (config key -> secret path)
1205    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1206    pub mappings: HashMap<String, String>,
1207    /// Cache secrets in memory (seconds, 0 = no caching)
1208    pub cache_ttl_secs: u64,
1209    /// Retry configuration for secret retrieval
1210    pub retry_attempts: u32,
1211    /// Retry delay in milliseconds
1212    pub retry_delay_ms: u64,
1213}
1214
1215impl Default for SecretBackendConfig {
1216    fn default() -> Self {
1217        Self {
1218            provider: SecretBackendType::None,
1219            vault: None,
1220            aws: None,
1221            azure: None,
1222            gcp: None,
1223            kubernetes: None,
1224            encrypted_file: None,
1225            mappings: HashMap::new(),
1226            cache_ttl_secs: 300, // 5 minutes
1227            retry_attempts: 3,
1228            retry_delay_ms: 1000,
1229        }
1230    }
1231}
1232
1233/// HashCorp Vault configuration
1234#[derive(Debug, Clone, Serialize, Deserialize)]
1235#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1236#[serde(default)]
1237pub struct VaultConfig {
1238    /// Vault server address
1239    pub address: String,
1240    /// Vault namespace (for enterprise)
1241    #[serde(skip_serializing_if = "Option::is_none")]
1242    pub namespace: Option<String>,
1243    /// Authentication method
1244    pub auth_method: VaultAuthMethod,
1245    /// Vault token (for token auth)
1246    #[serde(skip_serializing_if = "Option::is_none")]
1247    pub token: Option<String>,
1248    /// Role ID (for AppRole auth)
1249    #[serde(skip_serializing_if = "Option::is_none")]
1250    pub role_id: Option<String>,
1251    /// Secret ID (for AppRole auth)
1252    #[serde(skip_serializing_if = "Option::is_none")]
1253    pub secret_id: Option<String>,
1254    /// Kubernetes role (for Kubernetes auth)
1255    #[serde(skip_serializing_if = "Option::is_none")]
1256    pub kubernetes_role: Option<String>,
1257    /// Secret engine mount path
1258    pub mount_path: String,
1259    /// Secret path prefix
1260    pub path_prefix: String,
1261    /// TLS CA certificate path
1262    #[serde(skip_serializing_if = "Option::is_none")]
1263    pub ca_cert_path: Option<String>,
1264    /// Skip TLS verification (not recommended for production)
1265    pub skip_verify: bool,
1266    /// Request timeout in seconds
1267    pub timeout_secs: u64,
1268}
1269
1270impl Default for VaultConfig {
1271    fn default() -> Self {
1272        Self {
1273            address: "http://127.0.0.1:8200".to_string(),
1274            namespace: None,
1275            auth_method: VaultAuthMethod::Token,
1276            token: None,
1277            role_id: None,
1278            secret_id: None,
1279            kubernetes_role: None,
1280            mount_path: "secret".to_string(),
1281            path_prefix: "mockforge".to_string(),
1282            ca_cert_path: None,
1283            skip_verify: false,
1284            timeout_secs: 30,
1285        }
1286    }
1287}
1288
1289/// Vault authentication methods
1290#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
1291#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1292#[serde(rename_all = "lowercase")]
1293pub enum VaultAuthMethod {
1294    /// Token authentication
1295    #[default]
1296    Token,
1297    /// AppRole authentication
1298    AppRole,
1299    /// Kubernetes authentication
1300    Kubernetes,
1301    /// AWS IAM authentication
1302    AwsIam,
1303    /// GitHub authentication
1304    GitHub,
1305    /// LDAP authentication
1306    Ldap,
1307    /// Userpass authentication
1308    Userpass,
1309}
1310
1311/// AWS Secrets Manager configuration
1312#[derive(Debug, Clone, Serialize, Deserialize)]
1313#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1314#[serde(default)]
1315pub struct AwsSecretsConfig {
1316    /// AWS region
1317    pub region: String,
1318    /// Secret name prefix
1319    pub prefix: String,
1320    /// Use IAM role (if false, uses access keys)
1321    pub use_iam_role: bool,
1322    /// AWS access key ID
1323    #[serde(skip_serializing_if = "Option::is_none")]
1324    pub access_key_id: Option<String>,
1325    /// AWS secret access key
1326    #[serde(skip_serializing_if = "Option::is_none")]
1327    pub secret_access_key: Option<String>,
1328    /// Endpoint URL (for LocalStack testing)
1329    #[serde(skip_serializing_if = "Option::is_none")]
1330    pub endpoint_url: Option<String>,
1331}
1332
1333impl Default for AwsSecretsConfig {
1334    fn default() -> Self {
1335        Self {
1336            region: "us-east-1".to_string(),
1337            prefix: "mockforge".to_string(),
1338            use_iam_role: true,
1339            access_key_id: None,
1340            secret_access_key: None,
1341            endpoint_url: None,
1342        }
1343    }
1344}
1345
1346/// Azure Key Vault configuration
1347#[derive(Debug, Clone, Serialize, Deserialize)]
1348#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1349#[serde(default)]
1350pub struct AzureKeyVaultConfig {
1351    /// Key Vault URL
1352    pub vault_url: String,
1353    /// Tenant ID
1354    #[serde(skip_serializing_if = "Option::is_none")]
1355    pub tenant_id: Option<String>,
1356    /// Client ID
1357    #[serde(skip_serializing_if = "Option::is_none")]
1358    pub client_id: Option<String>,
1359    /// Client secret
1360    #[serde(skip_serializing_if = "Option::is_none")]
1361    pub client_secret: Option<String>,
1362    /// Use managed identity
1363    pub use_managed_identity: bool,
1364    /// Secret name prefix
1365    pub prefix: String,
1366}
1367
1368impl Default for AzureKeyVaultConfig {
1369    fn default() -> Self {
1370        Self {
1371            vault_url: String::new(),
1372            tenant_id: None,
1373            client_id: None,
1374            client_secret: None,
1375            use_managed_identity: true,
1376            prefix: "mockforge".to_string(),
1377        }
1378    }
1379}
1380
1381/// GCP Secret Manager configuration
1382#[derive(Debug, Clone, Serialize, Deserialize)]
1383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1384#[serde(default)]
1385pub struct GcpSecretManagerConfig {
1386    /// GCP project ID
1387    pub project_id: String,
1388    /// Secret name prefix
1389    pub prefix: String,
1390    /// Service account key file path
1391    #[serde(skip_serializing_if = "Option::is_none")]
1392    pub credentials_file: Option<String>,
1393    /// Use default credentials (ADC)
1394    pub use_default_credentials: bool,
1395}
1396
1397impl Default for GcpSecretManagerConfig {
1398    fn default() -> Self {
1399        Self {
1400            project_id: String::new(),
1401            prefix: "mockforge".to_string(),
1402            credentials_file: None,
1403            use_default_credentials: true,
1404        }
1405    }
1406}
1407
1408/// Kubernetes Secrets configuration
1409#[derive(Debug, Clone, Serialize, Deserialize)]
1410#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1411#[serde(default)]
1412pub struct KubernetesSecretsConfig {
1413    /// Namespace to read secrets from
1414    pub namespace: String,
1415    /// Secret name prefix
1416    pub prefix: String,
1417    /// Label selector
1418    #[serde(skip_serializing_if = "Option::is_none")]
1419    pub label_selector: Option<String>,
1420    /// Use in-cluster config
1421    pub in_cluster: bool,
1422    /// Kubeconfig path (if not in-cluster)
1423    #[serde(skip_serializing_if = "Option::is_none")]
1424    pub kubeconfig_path: Option<String>,
1425}
1426
1427impl Default for KubernetesSecretsConfig {
1428    fn default() -> Self {
1429        Self {
1430            namespace: "default".to_string(),
1431            prefix: "mockforge".to_string(),
1432            label_selector: None,
1433            in_cluster: true,
1434            kubeconfig_path: None,
1435        }
1436    }
1437}
1438
1439/// Encrypted file configuration
1440#[derive(Debug, Clone, Serialize, Deserialize)]
1441#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1442#[serde(default)]
1443pub struct EncryptedFileConfig {
1444    /// Path to encrypted secrets file
1445    pub file_path: String,
1446    /// Encryption algorithm
1447    pub algorithm: String,
1448    /// Key derivation function
1449    pub kdf: String,
1450    /// Master key (from env var)
1451    #[serde(skip_serializing_if = "Option::is_none")]
1452    pub master_key_env: Option<String>,
1453    /// Key file path
1454    #[serde(skip_serializing_if = "Option::is_none")]
1455    pub key_file: Option<String>,
1456}
1457
1458impl Default for EncryptedFileConfig {
1459    fn default() -> Self {
1460        Self {
1461            file_path: "secrets.enc".to_string(),
1462            algorithm: "aes-256-gcm".to_string(),
1463            kdf: "argon2id".to_string(),
1464            master_key_env: Some("MOCKFORGE_MASTER_KEY".to_string()),
1465            key_file: None,
1466        }
1467    }
1468}
1469
1470// ─── Behavioral Configs ─────────────────────────────────────────────────────
1471
1472/// Behavioral cloning configuration
1473#[derive(Debug, Clone, Serialize, Deserialize)]
1474#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1475#[serde(default)]
1476pub struct BehavioralCloningConfig {
1477    /// Whether behavioral cloning is enabled
1478    pub enabled: bool,
1479    /// Path to recorder database (defaults to ./recordings.db)
1480    pub database_path: Option<String>,
1481    /// Enable middleware to apply learned behavior
1482    pub enable_middleware: bool,
1483    /// Minimum frequency threshold for sequence learning (0.0 to 1.0)
1484    pub min_sequence_frequency: f64,
1485    /// Minimum requests per trace for sequence discovery
1486    pub min_requests_per_trace: Option<i32>,
1487    /// Flow recording configuration
1488    #[serde(default)]
1489    pub flow_recording: FlowRecordingConfig,
1490    /// Scenario replay configuration
1491    #[serde(default)]
1492    pub scenario_replay: ScenarioReplayConfig,
1493}
1494
1495/// Flow recording configuration
1496#[derive(Debug, Clone, Serialize, Deserialize)]
1497#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1498#[serde(default)]
1499pub struct FlowRecordingConfig {
1500    /// Whether flow recording is enabled
1501    pub enabled: bool,
1502    /// How to group requests into flows (trace_id, session_id, ip_time_window)
1503    pub group_by: String,
1504    /// Time window in seconds for IP-based grouping
1505    pub time_window_seconds: u64,
1506}
1507
1508impl Default for FlowRecordingConfig {
1509    fn default() -> Self {
1510        Self {
1511            enabled: true,
1512            group_by: "trace_id".to_string(),
1513            time_window_seconds: 300, // 5 minutes
1514        }
1515    }
1516}
1517
1518/// Scenario replay configuration
1519#[derive(Debug, Clone, Serialize, Deserialize)]
1520#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1521#[serde(default)]
1522pub struct ScenarioReplayConfig {
1523    /// Whether scenario replay is enabled
1524    pub enabled: bool,
1525    /// Default replay mode (strict or flex)
1526    pub default_mode: String,
1527    /// List of scenario IDs to activate on startup
1528    pub active_scenarios: Vec<String>,
1529}
1530
1531impl Default for ScenarioReplayConfig {
1532    fn default() -> Self {
1533        Self {
1534            enabled: true,
1535            default_mode: "strict".to_string(),
1536            active_scenarios: Vec::new(),
1537        }
1538    }
1539}
1540
1541impl Default for BehavioralCloningConfig {
1542    fn default() -> Self {
1543        Self {
1544            enabled: false,
1545            database_path: None,
1546            enable_middleware: false,
1547            min_sequence_frequency: 0.1, // 10% default
1548            min_requests_per_trace: None,
1549            flow_recording: FlowRecordingConfig::default(),
1550            scenario_replay: ScenarioReplayConfig::default(),
1551        }
1552    }
1553}
1554
1555// ─── Contracts Configs ───────────────────────────────────────────────────────
1556
1557/// Consumer contracts configuration
1558#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1559#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1560#[serde(default)]
1561pub struct ConsumerContractsConfig {
1562    /// Whether consumer contracts are enabled
1563    pub enabled: bool,
1564    /// Auto-register consumers from requests
1565    pub auto_register: bool,
1566    /// Track field usage
1567    pub track_usage: bool,
1568}
1569
1570/// Contracts configuration for fitness rules and contract management
1571#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1572#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1573#[serde(default)]
1574pub struct ContractsConfig {
1575    /// Fitness rules for contract validation
1576    pub fitness_rules: Vec<FitnessRuleConfig>,
1577}
1578
1579/// Configuration for a fitness rule (YAML config format)
1580#[derive(Debug, Clone, Serialize, Deserialize)]
1581#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1582pub struct FitnessRuleConfig {
1583    /// Human-readable name for the fitness rule
1584    pub name: String,
1585    /// Scope where this rule applies (endpoint pattern, service name, or "global")
1586    pub scope: String,
1587    /// Type of fitness rule
1588    #[serde(rename = "type")]
1589    pub rule_type: FitnessRuleType,
1590    /// Maximum percent increase for response size (for response_size_delta type)
1591    #[serde(skip_serializing_if = "Option::is_none")]
1592    pub max_percent_increase: Option<f64>,
1593    /// Maximum number of fields (for field_count type)
1594    #[serde(skip_serializing_if = "Option::is_none")]
1595    pub max_fields: Option<u32>,
1596    /// Maximum schema depth (for schema_complexity type)
1597    #[serde(skip_serializing_if = "Option::is_none")]
1598    pub max_depth: Option<u32>,
1599}
1600
1601/// Type of fitness rule (YAML config format)
1602#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1603#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1604#[serde(rename_all = "snake_case")]
1605pub enum FitnessRuleType {
1606    /// Response size must not increase by more than max_percent_increase
1607    ResponseSizeDelta,
1608    /// No new required fields allowed
1609    NoNewRequiredFields,
1610    /// Field count must not exceed max_fields
1611    FieldCount,
1612    /// Schema complexity (depth) must not exceed max_depth
1613    SchemaComplexity,
1614}
1615
1616// ─── Drift Learning Configs ──────────────────────────────────────────────────
1617
1618/// Drift Learning configuration
1619#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1620#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1621#[serde(default)]
1622pub struct DriftLearningConfig {
1623    /// Enable or disable drift learning
1624    pub enabled: bool,
1625    /// Learning mode (behavioral, statistical, hybrid)
1626    #[serde(default)]
1627    pub mode: DriftLearningMode,
1628    /// How quickly mocks adapt to new patterns (0.0 - 1.0)
1629    #[serde(default = "default_learning_sensitivity")]
1630    pub sensitivity: f64,
1631    /// How quickly old patterns are forgotten (0.0 - 1.0)
1632    #[serde(default = "default_learning_decay")]
1633    pub decay: f64,
1634    /// Minimum number of samples required to learn a pattern
1635    #[serde(default = "default_min_samples")]
1636    pub min_samples: u64,
1637    /// Enable persona-specific behavior adaptation
1638    #[serde(default)]
1639    pub persona_adaptation: bool,
1640    /// Opt-in configuration for specific personas to learn
1641    #[serde(default)]
1642    pub persona_learning: HashMap<String, bool>, // persona_id -> enabled
1643    /// Opt-in configuration for specific endpoints to learn
1644    #[serde(default)]
1645    pub endpoint_learning: HashMap<String, bool>, // endpoint_pattern -> enabled
1646}
1647
1648/// Drift learning mode
1649#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
1650#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1651#[serde(rename_all = "snake_case")]
1652pub enum DriftLearningMode {
1653    /// Behavioral learning - adapts to behavior patterns
1654    #[default]
1655    Behavioral,
1656    /// Statistical learning - adapts to statistical patterns
1657    Statistical,
1658    /// Hybrid - combines behavioral and statistical
1659    Hybrid,
1660}
1661
1662fn default_learning_sensitivity() -> f64 {
1663    0.2
1664}
1665
1666fn default_learning_decay() -> f64 {
1667    0.05
1668}
1669
1670fn default_min_samples() -> u64 {
1671    10
1672}
1673
1674// ─── Deployment Configs ──────────────────────────────────────────────────────
1675
1676/// Production-like CORS configuration
1677#[derive(Debug, Clone, Serialize, Deserialize)]
1678#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1679pub struct ProductionCorsConfig {
1680    /// Allowed origins (use "*" for all origins)
1681    #[serde(default)]
1682    pub allowed_origins: Vec<String>,
1683    /// Allowed HTTP methods
1684    #[serde(default)]
1685    pub allowed_methods: Vec<String>,
1686    /// Allowed headers (use "*" for all headers)
1687    #[serde(default)]
1688    pub allowed_headers: Vec<String>,
1689    /// Allow credentials (cookies, authorization headers)
1690    pub allow_credentials: bool,
1691}
1692
1693/// Production-like rate limiting configuration
1694#[derive(Debug, Clone, Serialize, Deserialize)]
1695#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1696pub struct ProductionRateLimitConfig {
1697    /// Requests per minute allowed
1698    pub requests_per_minute: u32,
1699    /// Burst capacity (maximum requests in a short burst)
1700    pub burst: u32,
1701    /// Enable per-IP rate limiting
1702    pub per_ip: bool,
1703}
1704
1705/// Production-like OAuth configuration
1706#[derive(Debug, Clone, Serialize, Deserialize)]
1707#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1708pub struct ProductionOAuthConfig {
1709    /// OAuth2 client ID
1710    pub client_id: String,
1711    /// OAuth2 client secret
1712    pub client_secret: String,
1713    /// Token introspection URL
1714    pub introspection_url: String,
1715    /// Authorization server URL
1716    pub auth_url: Option<String>,
1717    /// Token URL
1718    pub token_url: Option<String>,
1719    /// Expected token type hint
1720    pub token_type_hint: Option<String>,
1721}
1722
1723impl From<ProductionOAuthConfig> for OAuth2Config {
1724    /// Convert ProductionOAuthConfig to OAuth2Config for use in auth middleware
1725    fn from(prod: ProductionOAuthConfig) -> Self {
1726        OAuth2Config {
1727            client_id: prod.client_id,
1728            client_secret: prod.client_secret,
1729            introspection_url: prod.introspection_url,
1730            auth_url: prod.auth_url,
1731            token_url: prod.token_url,
1732            token_type_hint: prod.token_type_hint,
1733        }
1734    }
1735}
1736
1737// ─── Plugin Configs ──────────────────────────────────────────────────────────
1738
1739/// Plugin runtime resource configuration
1740#[derive(Debug, Clone, Serialize, Deserialize)]
1741#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1742#[serde(default)]
1743pub struct PluginResourceConfig {
1744    /// Enable plugin system
1745    pub enabled: bool,
1746    /// Maximum memory per plugin in bytes (default: 10MB)
1747    pub max_memory_per_plugin: usize,
1748    /// Maximum CPU usage per plugin (0.0-1.0, default: 0.5 = 50%)
1749    pub max_cpu_per_plugin: f64,
1750    /// Maximum execution time per plugin in milliseconds (default: 5000ms)
1751    pub max_execution_time_ms: u64,
1752    /// Allow plugins network access
1753    pub allow_network_access: bool,
1754    /// Filesystem paths plugins can access (empty = no fs access)
1755    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1756    pub allowed_fs_paths: Vec<String>,
1757    /// Maximum concurrent plugin executions
1758    pub max_concurrent_executions: usize,
1759    /// Plugin cache directory
1760    #[serde(skip_serializing_if = "Option::is_none")]
1761    pub cache_dir: Option<String>,
1762    /// Enable debug logging for plugins
1763    pub debug_logging: bool,
1764    /// Maximum WASM module size in bytes (default: 5MB)
1765    pub max_module_size: usize,
1766    /// Maximum table elements per plugin
1767    pub max_table_elements: usize,
1768    /// Maximum WASM stack size in bytes (default: 2MB)
1769    pub max_stack_size: usize,
1770}
1771
1772impl Default for PluginResourceConfig {
1773    fn default() -> Self {
1774        Self {
1775            enabled: true,
1776            max_memory_per_plugin: 10 * 1024 * 1024, // 10MB
1777            max_cpu_per_plugin: 0.5,                 // 50% of one core
1778            max_execution_time_ms: 5000,             // 5 seconds
1779            allow_network_access: false,
1780            allowed_fs_paths: Vec::new(),
1781            max_concurrent_executions: 10,
1782            cache_dir: None,
1783            debug_logging: false,
1784            max_module_size: 5 * 1024 * 1024, // 5MB
1785            max_table_elements: 1000,
1786            max_stack_size: 2 * 1024 * 1024, // 2MB
1787        }
1788    }
1789}
1790
1791// ─── Hot Reload Configs ──────────────────────────────────────────────────────
1792
1793/// Configuration hot-reload settings
1794#[derive(Debug, Clone, Serialize, Deserialize)]
1795#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1796#[serde(default)]
1797pub struct ConfigHotReloadConfig {
1798    /// Enable configuration hot-reload
1799    pub enabled: bool,
1800    /// Check interval in seconds
1801    pub check_interval_secs: u64,
1802    /// Debounce delay in milliseconds (prevent rapid reloads)
1803    pub debounce_delay_ms: u64,
1804    /// Paths to watch for changes (config files, fixture directories)
1805    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1806    pub watch_paths: Vec<String>,
1807    /// Reload on OpenAPI spec changes
1808    pub reload_on_spec_change: bool,
1809    /// Reload on fixture file changes
1810    pub reload_on_fixture_change: bool,
1811    /// Reload on plugin changes
1812    pub reload_on_plugin_change: bool,
1813    /// Graceful reload (wait for in-flight requests)
1814    pub graceful_reload: bool,
1815    /// Graceful reload timeout in seconds
1816    pub graceful_timeout_secs: u64,
1817    /// Validate config before applying reload
1818    pub validate_before_reload: bool,
1819    /// Rollback to previous config on reload failure
1820    pub rollback_on_failure: bool,
1821}
1822
1823impl Default for ConfigHotReloadConfig {
1824    fn default() -> Self {
1825        Self {
1826            enabled: false,
1827            check_interval_secs: 5,
1828            debounce_delay_ms: 1000,
1829            watch_paths: Vec::new(),
1830            reload_on_spec_change: true,
1831            reload_on_fixture_change: true,
1832            reload_on_plugin_change: true,
1833            graceful_reload: true,
1834            graceful_timeout_secs: 30,
1835            validate_before_reload: true,
1836            rollback_on_failure: true,
1837        }
1838    }
1839}
1840
1841// ─── Chaining Config ─────────────────────────────────────────────────────────
1842
1843/// Request chaining configuration for multi-step request workflows
1844#[derive(Debug, Clone, Serialize, Deserialize)]
1845#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1846#[serde(default, rename_all = "camelCase")]
1847pub struct ChainingConfig {
1848    /// Enable request chaining
1849    pub enabled: bool,
1850    /// Maximum chain length to prevent infinite loops
1851    pub max_chain_length: usize,
1852    /// Global timeout for chain execution in seconds
1853    pub global_timeout_secs: u64,
1854    /// Enable parallel execution when dependencies allow
1855    pub enable_parallel_execution: bool,
1856}
1857
1858impl Default for ChainingConfig {
1859    fn default() -> Self {
1860        Self {
1861            enabled: false,
1862            max_chain_length: 20,
1863            global_timeout_secs: 300,
1864            enable_parallel_execution: false,
1865        }
1866    }
1867}
1868
1869// ─── Data Configs ────────────────────────────────────────────────────────────
1870
1871/// Data generation configuration
1872#[derive(Debug, Clone, Serialize, Deserialize)]
1873#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1874#[serde(default)]
1875pub struct DataConfig {
1876    /// Default number of rows to generate
1877    pub default_rows: usize,
1878    /// Default output format
1879    pub default_format: String,
1880    /// Faker locale
1881    pub locale: String,
1882    /// Custom faker templates
1883    pub templates: HashMap<String, String>,
1884    /// RAG configuration
1885    pub rag: RagConfig,
1886    /// Active persona profile domain (e.g., "finance", "ecommerce", "healthcare")
1887    #[serde(skip_serializing_if = "Option::is_none")]
1888    pub persona_domain: Option<String>,
1889    /// Enable persona-based consistency
1890    #[serde(default = "default_false")]
1891    pub persona_consistency_enabled: bool,
1892    /// Persona registry configuration
1893    #[serde(skip_serializing_if = "Option::is_none")]
1894    pub persona_registry: Option<PersonaRegistryConfig>,
1895}
1896
1897impl Default for DataConfig {
1898    fn default() -> Self {
1899        Self {
1900            default_rows: 100,
1901            default_format: "json".to_string(),
1902            locale: "en".to_string(),
1903            templates: HashMap::new(),
1904            rag: RagConfig::default(),
1905            persona_domain: None,
1906            persona_consistency_enabled: false,
1907            persona_registry: None,
1908        }
1909    }
1910}
1911
1912/// RAG configuration
1913#[derive(Debug, Clone, Serialize, Deserialize)]
1914#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1915#[serde(default)]
1916pub struct RagConfig {
1917    /// Enable RAG by default
1918    pub enabled: bool,
1919    /// LLM provider (openai, anthropic, ollama, openai_compatible)
1920    #[serde(default)]
1921    pub provider: String,
1922    /// API endpoint for LLM
1923    pub api_endpoint: Option<String>,
1924    /// API key for LLM
1925    pub api_key: Option<String>,
1926    /// Model name
1927    pub model: Option<String>,
1928    /// Maximum tokens for generation
1929    #[serde(default = "default_max_tokens")]
1930    pub max_tokens: usize,
1931    /// Temperature for generation (0.0 to 2.0)
1932    #[serde(default = "default_temperature")]
1933    pub temperature: f64,
1934    /// Context window size
1935    pub context_window: usize,
1936    /// Enable caching
1937    #[serde(default = "default_true")]
1938    pub caching: bool,
1939    /// Cache TTL in seconds
1940    #[serde(default = "default_cache_ttl")]
1941    pub cache_ttl_secs: u64,
1942    /// Request timeout in seconds
1943    #[serde(default = "default_timeout")]
1944    pub timeout_secs: u64,
1945    /// Maximum retries for failed requests
1946    #[serde(default = "default_max_retries")]
1947    pub max_retries: usize,
1948}
1949
1950fn default_max_tokens() -> usize {
1951    1024
1952}
1953
1954fn default_temperature() -> f64 {
1955    0.7
1956}
1957
1958fn default_true() -> bool {
1959    true
1960}
1961
1962fn default_false() -> bool {
1963    false
1964}
1965
1966fn default_cache_ttl() -> u64 {
1967    3600
1968}
1969
1970fn default_timeout() -> u64 {
1971    30
1972}
1973
1974fn default_max_retries() -> usize {
1975    3
1976}
1977
1978impl Default for RagConfig {
1979    fn default() -> Self {
1980        Self {
1981            enabled: false,
1982            provider: "openai".to_string(),
1983            api_endpoint: None,
1984            api_key: None,
1985            model: Some("gpt-3.5-turbo".to_string()),
1986            max_tokens: default_max_tokens(),
1987            temperature: default_temperature(),
1988            context_window: 4000,
1989            caching: default_true(),
1990            cache_ttl_secs: default_cache_ttl(),
1991            timeout_secs: default_timeout(),
1992            max_retries: default_max_retries(),
1993        }
1994    }
1995}
1996
1997/// Persona registry configuration
1998#[derive(Debug, Clone, Serialize, Deserialize)]
1999#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2000#[serde(default)]
2001#[derive(Default)]
2002pub struct PersonaRegistryConfig {
2003    /// Enable persistence (save personas to disk)
2004    #[serde(default = "default_false")]
2005    pub persistent: bool,
2006    /// Storage path for persistent personas
2007    #[serde(skip_serializing_if = "Option::is_none")]
2008    pub storage_path: Option<String>,
2009    /// Default traits for new personas
2010    #[serde(default)]
2011    pub default_traits: HashMap<String, String>,
2012}
2013
2014// ─── Observability Configs ───────────────────────────────────────────────────
2015
2016/// Prometheus metrics configuration
2017#[derive(Debug, Clone, Serialize, Deserialize)]
2018#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2019#[serde(default)]
2020pub struct PrometheusConfig {
2021    /// Enable Prometheus metrics endpoint
2022    pub enabled: bool,
2023    /// Port for metrics endpoint
2024    pub port: u16,
2025    /// Host for metrics endpoint
2026    pub host: String,
2027    /// Path for metrics endpoint
2028    pub path: String,
2029}
2030
2031impl Default for PrometheusConfig {
2032    fn default() -> Self {
2033        Self {
2034            enabled: true,
2035            port: 9090,
2036            host: "0.0.0.0".to_string(),
2037            path: "/metrics".to_string(),
2038        }
2039    }
2040}
2041
2042/// OpenTelemetry distributed tracing configuration
2043#[derive(Debug, Clone, Serialize, Deserialize)]
2044#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2045#[serde(default)]
2046pub struct OpenTelemetryConfig {
2047    /// Enable OpenTelemetry tracing
2048    pub enabled: bool,
2049    /// Service name for traces
2050    pub service_name: String,
2051    /// Deployment environment (development, staging, production)
2052    pub environment: String,
2053    /// Jaeger endpoint for trace export
2054    pub jaeger_endpoint: String,
2055    /// OTLP endpoint (alternative to Jaeger)
2056    pub otlp_endpoint: Option<String>,
2057    /// Protocol: grpc or http
2058    pub protocol: String,
2059    /// Sampling rate (0.0 to 1.0)
2060    pub sampling_rate: f64,
2061}
2062
2063impl Default for OpenTelemetryConfig {
2064    fn default() -> Self {
2065        Self {
2066            enabled: false,
2067            service_name: "mockforge".to_string(),
2068            environment: "development".to_string(),
2069            jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
2070            otlp_endpoint: Some("http://localhost:4317".to_string()),
2071            protocol: "grpc".to_string(),
2072            sampling_rate: 1.0,
2073        }
2074    }
2075}
2076
2077/// API Flight Recorder configuration
2078#[derive(Debug, Clone, Serialize, Deserialize)]
2079#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2080#[serde(default)]
2081pub struct RecorderConfig {
2082    /// Enable recording
2083    pub enabled: bool,
2084    /// Database file path
2085    pub database_path: String,
2086    /// Enable management API
2087    pub api_enabled: bool,
2088    /// Management API port (if different from main port)
2089    pub api_port: Option<u16>,
2090    /// Maximum number of requests to store (0 for unlimited)
2091    pub max_requests: i64,
2092    /// Auto-delete requests older than N days (0 to disable)
2093    pub retention_days: i64,
2094    /// Record HTTP requests
2095    pub record_http: bool,
2096    /// Record gRPC requests
2097    pub record_grpc: bool,
2098    /// Record WebSocket messages
2099    pub record_websocket: bool,
2100    /// Record GraphQL requests
2101    pub record_graphql: bool,
2102    /// Record proxied requests (requests that are forwarded to real backends)
2103    /// When enabled, proxied requests/responses will be recorded with metadata indicating proxy source
2104    #[serde(default = "default_true")]
2105    pub record_proxy: bool,
2106}
2107
2108impl Default for RecorderConfig {
2109    fn default() -> Self {
2110        Self {
2111            enabled: false,
2112            database_path: "./mockforge-recordings.db".to_string(),
2113            api_enabled: true,
2114            api_port: None,
2115            max_requests: 10000,
2116            retention_days: 7,
2117            record_http: true,
2118            record_grpc: true,
2119            record_websocket: true,
2120            record_graphql: true,
2121            record_proxy: true,
2122        }
2123    }
2124}
2125
2126/// Observability configuration for metrics and distributed tracing
2127#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2128#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2129#[serde(default)]
2130pub struct ObservabilityConfig {
2131    /// Prometheus metrics configuration
2132    pub prometheus: PrometheusConfig,
2133    /// OpenTelemetry distributed tracing configuration
2134    pub opentelemetry: Option<OpenTelemetryConfig>,
2135    /// API Flight Recorder configuration
2136    pub recorder: Option<RecorderConfig>,
2137    /// Chaos engineering configuration
2138    pub chaos: Option<ChaosEngConfig>,
2139}
2140
2141// ─── Chaos Engineering Configs ───────────────────────────────────────────────
2142
2143/// Chaos engineering configuration
2144#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2146#[serde(default)]
2147pub struct ChaosEngConfig {
2148    /// Enable chaos engineering
2149    pub enabled: bool,
2150    /// Latency injection configuration
2151    pub latency: Option<LatencyInjectionConfig>,
2152    /// Fault injection configuration
2153    pub fault_injection: Option<FaultConfig>,
2154    /// Rate limiting configuration
2155    pub rate_limit: Option<RateLimitingConfig>,
2156    /// Traffic shaping configuration
2157    pub traffic_shaping: Option<NetworkShapingConfig>,
2158    /// Predefined scenario to use
2159    pub scenario: Option<String>,
2160}
2161
2162/// Latency injection configuration for chaos engineering
2163#[derive(Debug, Clone, Serialize, Deserialize)]
2164#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2165pub struct LatencyInjectionConfig {
2166    /// Enable latency injection
2167    pub enabled: bool,
2168    /// Fixed delay to inject (in milliseconds)
2169    pub fixed_delay_ms: Option<u64>,
2170    /// Random delay range (min_ms, max_ms) in milliseconds
2171    pub random_delay_range_ms: Option<(u64, u64)>,
2172    /// Jitter percentage to add variance to delays (0.0 to 1.0)
2173    pub jitter_percent: f64,
2174    /// Probability of injecting latency (0.0 to 1.0)
2175    pub probability: f64,
2176}
2177
2178/// Fault injection configuration for chaos engineering
2179#[derive(Debug, Clone, Serialize, Deserialize)]
2180#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2181pub struct FaultConfig {
2182    /// Enable fault injection
2183    pub enabled: bool,
2184    /// HTTP status codes to randomly return (e.g., [500, 502, 503])
2185    pub http_errors: Vec<u16>,
2186    /// Probability of returning HTTP errors (0.0 to 1.0)
2187    pub http_error_probability: f64,
2188    /// Enable connection errors (connection refused, reset, etc.)
2189    pub connection_errors: bool,
2190    /// Probability of connection errors (0.0 to 1.0)
2191    pub connection_error_probability: f64,
2192    /// Enable timeout errors
2193    pub timeout_errors: bool,
2194    /// Timeout duration in milliseconds
2195    pub timeout_ms: u64,
2196    /// Probability of timeout errors (0.0 to 1.0)
2197    pub timeout_probability: f64,
2198}
2199
2200/// Rate limiting configuration for traffic control
2201#[derive(Debug, Clone, Serialize, Deserialize)]
2202#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2203pub struct RateLimitingConfig {
2204    /// Enable rate limiting
2205    pub enabled: bool,
2206    /// Maximum requests per second allowed
2207    pub requests_per_second: u32,
2208    /// Maximum burst size before rate limiting kicks in
2209    pub burst_size: u32,
2210    /// Apply rate limiting per IP address
2211    pub per_ip: bool,
2212    /// Apply rate limiting per endpoint/path
2213    pub per_endpoint: bool,
2214}
2215
2216/// Network shaping configuration for simulating network conditions
2217#[derive(Debug, Clone, Serialize, Deserialize)]
2218#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2219pub struct NetworkShapingConfig {
2220    /// Enable network shaping
2221    pub enabled: bool,
2222    /// Bandwidth limit in bits per second
2223    pub bandwidth_limit_bps: u64,
2224    /// Packet loss percentage (0.0 to 1.0)
2225    pub packet_loss_percent: f64,
2226    /// Maximum concurrent connections allowed
2227    pub max_connections: u32,
2228}
2229
2230// ─── Incident Storage Config ─────────────────────────────────────────────────
2231
2232/// Incident storage configuration
2233#[derive(Debug, Clone, Serialize, Deserialize)]
2234#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2235pub struct IncidentStorageConfig {
2236    /// Use in-memory cache (default: true)
2237    pub use_cache: bool,
2238    /// Use database persistence (default: true)
2239    pub use_database: bool,
2240    /// Retention period for resolved incidents (days)
2241    pub retention_days: u32,
2242}
2243
2244impl Default for IncidentStorageConfig {
2245    fn default() -> Self {
2246        Self {
2247            use_cache: true,
2248            use_database: true,
2249            retention_days: 90,
2250        }
2251    }
2252}
2253
2254// ─── Reality Level ───────────────────────────────────────────────────────────
2255
2256/// Reality level for mock environments (1-5)
2257///
2258/// Each level represents a different degree of realism, from simple static mocks
2259/// to full production-like chaos behavior.
2260#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
2261#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2262#[serde(rename_all = "snake_case")]
2263#[derive(Default)]
2264pub enum RealityLevel {
2265    /// Level 1: Static Stubs - Simple, instant responses with no chaos
2266    StaticStubs = 1,
2267    /// Level 2: Light Simulation - Minimal latency, basic intelligence
2268    LightSimulation = 2,
2269    /// Level 3: Moderate Realism - Some chaos, moderate latency, full intelligence
2270    #[default]
2271    ModerateRealism = 3,
2272    /// Level 4: High Realism - Increased chaos, realistic latency, session state
2273    HighRealism = 4,
2274    /// Level 5: Production Chaos - Maximum chaos, production-like latency, full features
2275    ProductionChaos = 5,
2276}
2277
2278impl RealityLevel {
2279    /// Get the numeric value (1-5)
2280    pub fn value(&self) -> u8 {
2281        *self as u8
2282    }
2283
2284    /// Get a human-readable name
2285    pub fn name(&self) -> &'static str {
2286        match self {
2287            RealityLevel::StaticStubs => "Static Stubs",
2288            RealityLevel::LightSimulation => "Light Simulation",
2289            RealityLevel::ModerateRealism => "Moderate Realism",
2290            RealityLevel::HighRealism => "High Realism",
2291            RealityLevel::ProductionChaos => "Production Chaos",
2292        }
2293    }
2294
2295    /// Get a short description
2296    pub fn description(&self) -> &'static str {
2297        match self {
2298            RealityLevel::StaticStubs => "Simple, instant responses with no chaos",
2299            RealityLevel::LightSimulation => "Minimal latency, basic intelligence",
2300            RealityLevel::ModerateRealism => "Some chaos, moderate latency, full intelligence",
2301            RealityLevel::HighRealism => "Increased chaos, realistic latency, session state",
2302            RealityLevel::ProductionChaos => {
2303                "Maximum chaos, production-like latency, full features"
2304            }
2305        }
2306    }
2307
2308    /// Create from numeric value (1-5)
2309    pub fn from_value(value: u8) -> Option<Self> {
2310        match value {
2311            1 => Some(RealityLevel::StaticStubs),
2312            2 => Some(RealityLevel::LightSimulation),
2313            3 => Some(RealityLevel::ModerateRealism),
2314            4 => Some(RealityLevel::HighRealism),
2315            5 => Some(RealityLevel::ProductionChaos),
2316            _ => None,
2317        }
2318    }
2319
2320    /// Get all available levels
2321    pub fn all() -> Vec<Self> {
2322        vec![
2323            RealityLevel::StaticStubs,
2324            RealityLevel::LightSimulation,
2325            RealityLevel::ModerateRealism,
2326            RealityLevel::HighRealism,
2327            RealityLevel::ProductionChaos,
2328        ]
2329    }
2330}
2331
2332/// Reality slider configuration for YAML config files
2333///
2334/// This is a simplified configuration that stores just the level.
2335/// The full RealityConfig with all subsystem settings is generated
2336/// automatically from the level via the RealityEngine.
2337#[derive(Debug, Clone, Serialize, Deserialize)]
2338#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2339#[serde(default)]
2340pub struct RealitySliderConfig {
2341    /// Reality level (1-5)
2342    pub level: RealityLevel,
2343    /// Whether to enable reality slider (if false, uses individual subsystem configs)
2344    pub enabled: bool,
2345}
2346
2347impl Default for RealitySliderConfig {
2348    fn default() -> Self {
2349        Self {
2350            level: RealityLevel::ModerateRealism,
2351            enabled: true,
2352        }
2353    }
2354}