Skip to main content

mockforge_core/config/
operational.rs

1//! Operational configuration types (performance, secrets, logging, observability, chaos, etc.)
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::auth::OAuth2Config;
7
8/// Deceptive deploy configuration for production-like mock APIs
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(default)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[derive(Default)]
13pub struct DeceptiveDeployConfig {
14    /// Enable deceptive deploy mode
15    pub enabled: bool,
16    /// Production-like CORS configuration
17    pub cors: Option<ProductionCorsConfig>,
18    /// Production-like rate limiting
19    pub rate_limit: Option<ProductionRateLimitConfig>,
20    /// Production-like headers to add to all responses
21    #[serde(default)]
22    pub headers: HashMap<String, String>,
23    /// OAuth configuration for production-like auth flows
24    pub oauth: Option<ProductionOAuthConfig>,
25    /// Custom domain for deployment
26    pub custom_domain: Option<String>,
27    /// Auto-start tunnel when deploying
28    pub auto_tunnel: bool,
29    /// Deceptive canary mode configuration
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub canary: Option<crate::deceptive_canary::DeceptiveCanaryConfig>,
32}
33
34impl DeceptiveDeployConfig {
35    /// Generate production-like configuration preset
36    pub fn production_preset() -> Self {
37        let mut headers = HashMap::new();
38        headers.insert("X-API-Version".to_string(), "1.0".to_string());
39        headers.insert("X-Request-ID".to_string(), "{{uuid}}".to_string());
40        headers.insert("X-Powered-By".to_string(), "MockForge".to_string());
41
42        Self {
43            enabled: true,
44            cors: Some(ProductionCorsConfig {
45                allowed_origins: vec!["*".to_string()],
46                allowed_methods: vec![
47                    "GET".to_string(),
48                    "POST".to_string(),
49                    "PUT".to_string(),
50                    "DELETE".to_string(),
51                    "PATCH".to_string(),
52                    "OPTIONS".to_string(),
53                ],
54                allowed_headers: vec!["*".to_string()],
55                allow_credentials: true,
56            }),
57            rate_limit: Some(ProductionRateLimitConfig {
58                requests_per_minute: 1000,
59                burst: 2000,
60                per_ip: true,
61            }),
62            headers,
63            oauth: None, // Configured separately
64            custom_domain: None,
65            auto_tunnel: true,
66            canary: None,
67        }
68    }
69}
70
71/// Production-like CORS configuration
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
74pub struct ProductionCorsConfig {
75    /// Allowed origins (use "*" for all origins)
76    #[serde(default)]
77    pub allowed_origins: Vec<String>,
78    /// Allowed HTTP methods
79    #[serde(default)]
80    pub allowed_methods: Vec<String>,
81    /// Allowed headers (use "*" for all headers)
82    #[serde(default)]
83    pub allowed_headers: Vec<String>,
84    /// Allow credentials (cookies, authorization headers)
85    pub allow_credentials: bool,
86}
87
88/// Production-like rate limiting configuration
89#[derive(Debug, Clone, Serialize, Deserialize)]
90#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
91pub struct ProductionRateLimitConfig {
92    /// Requests per minute allowed
93    pub requests_per_minute: u32,
94    /// Burst capacity (maximum requests in a short burst)
95    pub burst: u32,
96    /// Enable per-IP rate limiting
97    pub per_ip: bool,
98}
99
100/// Production-like OAuth configuration
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
103pub struct ProductionOAuthConfig {
104    /// OAuth2 client ID
105    pub client_id: String,
106    /// OAuth2 client secret
107    pub client_secret: String,
108    /// Token introspection URL
109    pub introspection_url: String,
110    /// Authorization server URL
111    pub auth_url: Option<String>,
112    /// Token URL
113    pub token_url: Option<String>,
114    /// Expected token type hint
115    pub token_type_hint: Option<String>,
116}
117
118impl From<ProductionOAuthConfig> for OAuth2Config {
119    /// Convert ProductionOAuthConfig to OAuth2Config for use in auth middleware
120    fn from(prod: ProductionOAuthConfig) -> Self {
121        OAuth2Config {
122            client_id: prod.client_id,
123            client_secret: prod.client_secret,
124            introspection_url: prod.introspection_url,
125            auth_url: prod.auth_url,
126            token_url: prod.token_url,
127            token_type_hint: prod.token_type_hint,
128        }
129    }
130}
131
132/// Performance and resource configuration
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(default)]
135#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
136#[derive(Default)]
137pub struct PerformanceConfig {
138    /// Response compression configuration
139    pub compression: CompressionConfig,
140    /// Connection pooling configuration
141    pub connection_pool: ConnectionPoolConfig,
142    /// Request limits configuration
143    pub request_limits: RequestLimitsConfig,
144    /// Worker thread configuration
145    pub workers: WorkerConfig,
146    /// Circuit breaker configuration
147    pub circuit_breaker: CircuitBreakerConfig,
148}
149
150/// Response compression configuration
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(default)]
153#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
154pub struct CompressionConfig {
155    /// Enable response compression
156    pub enabled: bool,
157    /// Compression algorithm: gzip, deflate, br (brotli), zstd
158    pub algorithm: String,
159    /// Minimum response size to compress (bytes)
160    pub min_size: usize,
161    /// Compression level (1-9 for gzip/deflate, 0-11 for brotli, 1-22 for zstd)
162    pub level: u32,
163    /// Content types to compress (e.g., ["application/json", "text/html"])
164    pub content_types: Vec<String>,
165}
166
167impl Default for CompressionConfig {
168    fn default() -> Self {
169        Self {
170            enabled: true,
171            algorithm: "gzip".to_string(),
172            min_size: 1024, // 1KB
173            level: 6,
174            content_types: vec![
175                "application/json".to_string(),
176                "application/xml".to_string(),
177                "text/plain".to_string(),
178                "text/html".to_string(),
179                "text/css".to_string(),
180                "application/javascript".to_string(),
181            ],
182        }
183    }
184}
185
186/// Connection pooling configuration for downstream services
187#[derive(Debug, Clone, Serialize, Deserialize)]
188#[serde(default)]
189#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
190pub struct ConnectionPoolConfig {
191    /// Maximum idle connections per host
192    pub max_idle_per_host: usize,
193    /// Maximum total connections
194    pub max_connections: usize,
195    /// Idle connection timeout in seconds
196    pub idle_timeout_secs: u64,
197    /// Connection acquire timeout in milliseconds
198    pub acquire_timeout_ms: u64,
199    /// Enable connection pooling
200    pub enabled: bool,
201}
202
203impl Default for ConnectionPoolConfig {
204    fn default() -> Self {
205        Self {
206            max_idle_per_host: 10,
207            max_connections: 100,
208            idle_timeout_secs: 90,
209            acquire_timeout_ms: 5000,
210            enabled: true,
211        }
212    }
213}
214
215/// Request limits configuration
216#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(default)]
218#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
219pub struct RequestLimitsConfig {
220    /// Maximum request body size in bytes (default: 10MB)
221    pub max_body_size: usize,
222    /// Maximum header size in bytes
223    pub max_header_size: usize,
224    /// Maximum number of headers
225    pub max_headers: usize,
226    /// Maximum URI length
227    pub max_uri_length: usize,
228    /// Per-route body size limits (path pattern -> max bytes)
229    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
230    pub per_route_limits: HashMap<String, usize>,
231}
232
233impl Default for RequestLimitsConfig {
234    fn default() -> Self {
235        Self {
236            max_body_size: 10 * 1024 * 1024, // 10MB
237            max_header_size: 16 * 1024,      // 16KB
238            max_headers: 100,
239            max_uri_length: 8192,
240            per_route_limits: HashMap::new(),
241        }
242    }
243}
244
245/// Worker thread configuration
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(default)]
248#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
249pub struct WorkerConfig {
250    /// Number of worker threads (0 = auto-detect based on CPU cores)
251    pub threads: usize,
252    /// Blocking thread pool size for CPU-intensive work
253    pub blocking_threads: usize,
254    /// Thread stack size in bytes
255    pub stack_size: usize,
256    /// Thread name prefix
257    pub name_prefix: String,
258}
259
260impl Default for WorkerConfig {
261    fn default() -> Self {
262        Self {
263            threads: 0, // auto-detect
264            blocking_threads: 512,
265            stack_size: 2 * 1024 * 1024, // 2MB
266            name_prefix: "mockforge-worker".to_string(),
267        }
268    }
269}
270
271/// Circuit breaker configuration
272#[derive(Debug, Clone, Serialize, Deserialize)]
273#[serde(default)]
274#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
275pub struct CircuitBreakerConfig {
276    /// Enable circuit breaker
277    pub enabled: bool,
278    /// Failure threshold before opening circuit
279    pub failure_threshold: u32,
280    /// Success threshold before closing circuit
281    pub success_threshold: u32,
282    /// Half-open timeout in seconds (time before trying again after opening)
283    pub half_open_timeout_secs: u64,
284    /// Sliding window size for tracking failures
285    pub window_size: u32,
286    /// Per-endpoint circuit breaker configuration
287    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
288    pub per_endpoint: HashMap<String, EndpointCircuitBreakerConfig>,
289}
290
291impl Default for CircuitBreakerConfig {
292    fn default() -> Self {
293        Self {
294            enabled: false,
295            failure_threshold: 5,
296            success_threshold: 2,
297            half_open_timeout_secs: 30,
298            window_size: 10,
299            per_endpoint: HashMap::new(),
300        }
301    }
302}
303
304/// Per-endpoint circuit breaker configuration
305#[derive(Debug, Clone, Serialize, Deserialize)]
306#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
307pub struct EndpointCircuitBreakerConfig {
308    /// Failure threshold for this endpoint
309    pub failure_threshold: u32,
310    /// Success threshold for this endpoint
311    pub success_threshold: u32,
312    /// Half-open timeout in seconds
313    pub half_open_timeout_secs: u64,
314}
315
316/// Configuration hot-reload settings
317#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(default)]
319#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
320pub struct ConfigHotReloadConfig {
321    /// Enable configuration hot-reload
322    pub enabled: bool,
323    /// Check interval in seconds
324    pub check_interval_secs: u64,
325    /// Debounce delay in milliseconds (prevent rapid reloads)
326    pub debounce_delay_ms: u64,
327    /// Paths to watch for changes (config files, fixture directories)
328    #[serde(default, skip_serializing_if = "Vec::is_empty")]
329    pub watch_paths: Vec<String>,
330    /// Reload on OpenAPI spec changes
331    pub reload_on_spec_change: bool,
332    /// Reload on fixture file changes
333    pub reload_on_fixture_change: bool,
334    /// Reload on plugin changes
335    pub reload_on_plugin_change: bool,
336    /// Graceful reload (wait for in-flight requests)
337    pub graceful_reload: bool,
338    /// Graceful reload timeout in seconds
339    pub graceful_timeout_secs: u64,
340    /// Validate config before applying reload
341    pub validate_before_reload: bool,
342    /// Rollback to previous config on reload failure
343    pub rollback_on_failure: bool,
344}
345
346impl Default for ConfigHotReloadConfig {
347    fn default() -> Self {
348        Self {
349            enabled: false,
350            check_interval_secs: 5,
351            debounce_delay_ms: 1000,
352            watch_paths: Vec::new(),
353            reload_on_spec_change: true,
354            reload_on_fixture_change: true,
355            reload_on_plugin_change: true,
356            graceful_reload: true,
357            graceful_timeout_secs: 30,
358            validate_before_reload: true,
359            rollback_on_failure: true,
360        }
361    }
362}
363
364/// Secret backend provider type
365#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
366#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
367#[serde(rename_all = "lowercase")]
368pub enum SecretBackendType {
369    /// No secret backend (use environment variables directly)
370    #[default]
371    None,
372    /// HashCorp Vault
373    Vault,
374    /// AWS Secrets Manager
375    AwsSecretsManager,
376    /// Azure Key Vault
377    AzureKeyVault,
378    /// Google Cloud Secret Manager
379    GcpSecretManager,
380    /// Kubernetes Secrets
381    Kubernetes,
382    /// Local encrypted file
383    EncryptedFile,
384}
385
386/// Secret backend configuration
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[serde(default)]
389#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
390pub struct SecretBackendConfig {
391    /// Secret backend provider
392    pub provider: SecretBackendType,
393    /// Vault-specific configuration
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub vault: Option<VaultConfig>,
396    /// AWS Secrets Manager configuration
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub aws: Option<AwsSecretsConfig>,
399    /// Azure Key Vault configuration
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub azure: Option<AzureKeyVaultConfig>,
402    /// GCP Secret Manager configuration
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub gcp: Option<GcpSecretManagerConfig>,
405    /// Kubernetes secrets configuration
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub kubernetes: Option<KubernetesSecretsConfig>,
408    /// Encrypted file configuration
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub encrypted_file: Option<EncryptedFileConfig>,
411    /// Secret key mappings (config key -> secret path)
412    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
413    pub mappings: HashMap<String, String>,
414    /// Cache secrets in memory (seconds, 0 = no caching)
415    pub cache_ttl_secs: u64,
416    /// Retry configuration for secret retrieval
417    pub retry_attempts: u32,
418    /// Retry delay in milliseconds
419    pub retry_delay_ms: u64,
420}
421
422impl Default for SecretBackendConfig {
423    fn default() -> Self {
424        Self {
425            provider: SecretBackendType::None,
426            vault: None,
427            aws: None,
428            azure: None,
429            gcp: None,
430            kubernetes: None,
431            encrypted_file: None,
432            mappings: HashMap::new(),
433            cache_ttl_secs: 300, // 5 minutes
434            retry_attempts: 3,
435            retry_delay_ms: 1000,
436        }
437    }
438}
439
440/// HashCorp Vault configuration
441#[derive(Debug, Clone, Serialize, Deserialize)]
442#[serde(default)]
443#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
444pub struct VaultConfig {
445    /// Vault server address
446    pub address: String,
447    /// Vault namespace (for enterprise)
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub namespace: Option<String>,
450    /// Authentication method
451    pub auth_method: VaultAuthMethod,
452    /// Vault token (for token auth)
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub token: Option<String>,
455    /// Role ID (for AppRole auth)
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub role_id: Option<String>,
458    /// Secret ID (for AppRole auth)
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub secret_id: Option<String>,
461    /// Kubernetes role (for Kubernetes auth)
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub kubernetes_role: Option<String>,
464    /// Secret engine mount path
465    pub mount_path: String,
466    /// Secret path prefix
467    pub path_prefix: String,
468    /// TLS CA certificate path
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub ca_cert_path: Option<String>,
471    /// Skip TLS verification (not recommended for production)
472    pub skip_verify: bool,
473    /// Request timeout in seconds
474    pub timeout_secs: u64,
475}
476
477impl Default for VaultConfig {
478    fn default() -> Self {
479        Self {
480            address: "http://127.0.0.1:8200".to_string(),
481            namespace: None,
482            auth_method: VaultAuthMethod::Token,
483            token: None,
484            role_id: None,
485            secret_id: None,
486            kubernetes_role: None,
487            mount_path: "secret".to_string(),
488            path_prefix: "mockforge".to_string(),
489            ca_cert_path: None,
490            skip_verify: false,
491            timeout_secs: 30,
492        }
493    }
494}
495
496/// Vault authentication methods
497#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
498#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
499#[serde(rename_all = "lowercase")]
500pub enum VaultAuthMethod {
501    /// Token authentication
502    #[default]
503    Token,
504    /// AppRole authentication
505    AppRole,
506    /// Kubernetes authentication
507    Kubernetes,
508    /// AWS IAM authentication
509    AwsIam,
510    /// GitHub authentication
511    GitHub,
512    /// LDAP authentication
513    Ldap,
514    /// Userpass authentication
515    Userpass,
516}
517
518/// AWS Secrets Manager configuration
519#[derive(Debug, Clone, Serialize, Deserialize)]
520#[serde(default)]
521#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
522pub struct AwsSecretsConfig {
523    /// AWS region
524    pub region: String,
525    /// Secret name prefix
526    pub prefix: String,
527    /// Use IAM role (if false, uses access keys)
528    pub use_iam_role: bool,
529    /// AWS access key ID
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub access_key_id: Option<String>,
532    /// AWS secret access key
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub secret_access_key: Option<String>,
535    /// Endpoint URL (for LocalStack testing)
536    #[serde(skip_serializing_if = "Option::is_none")]
537    pub endpoint_url: Option<String>,
538}
539
540impl Default for AwsSecretsConfig {
541    fn default() -> Self {
542        Self {
543            region: "us-east-1".to_string(),
544            prefix: "mockforge".to_string(),
545            use_iam_role: true,
546            access_key_id: None,
547            secret_access_key: None,
548            endpoint_url: None,
549        }
550    }
551}
552
553/// Azure Key Vault configuration
554#[derive(Debug, Clone, Serialize, Deserialize)]
555#[serde(default)]
556#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
557pub struct AzureKeyVaultConfig {
558    /// Key Vault URL
559    pub vault_url: String,
560    /// Tenant ID
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub tenant_id: Option<String>,
563    /// Client ID
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub client_id: Option<String>,
566    /// Client secret
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub client_secret: Option<String>,
569    /// Use managed identity
570    pub use_managed_identity: bool,
571    /// Secret name prefix
572    pub prefix: String,
573}
574
575impl Default for AzureKeyVaultConfig {
576    fn default() -> Self {
577        Self {
578            vault_url: String::new(),
579            tenant_id: None,
580            client_id: None,
581            client_secret: None,
582            use_managed_identity: true,
583            prefix: "mockforge".to_string(),
584        }
585    }
586}
587
588/// GCP Secret Manager configuration
589#[derive(Debug, Clone, Serialize, Deserialize)]
590#[serde(default)]
591#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
592pub struct GcpSecretManagerConfig {
593    /// GCP project ID
594    pub project_id: String,
595    /// Secret name prefix
596    pub prefix: String,
597    /// Service account key file path
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub credentials_file: Option<String>,
600    /// Use default credentials (ADC)
601    pub use_default_credentials: bool,
602}
603
604impl Default for GcpSecretManagerConfig {
605    fn default() -> Self {
606        Self {
607            project_id: String::new(),
608            prefix: "mockforge".to_string(),
609            credentials_file: None,
610            use_default_credentials: true,
611        }
612    }
613}
614
615/// Kubernetes Secrets configuration
616#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(default)]
618#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
619pub struct KubernetesSecretsConfig {
620    /// Namespace to read secrets from
621    pub namespace: String,
622    /// Secret name prefix
623    pub prefix: String,
624    /// Label selector
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub label_selector: Option<String>,
627    /// Use in-cluster config
628    pub in_cluster: bool,
629    /// Kubeconfig path (if not in-cluster)
630    #[serde(skip_serializing_if = "Option::is_none")]
631    pub kubeconfig_path: Option<String>,
632}
633
634impl Default for KubernetesSecretsConfig {
635    fn default() -> Self {
636        Self {
637            namespace: "default".to_string(),
638            prefix: "mockforge".to_string(),
639            label_selector: None,
640            in_cluster: true,
641            kubeconfig_path: None,
642        }
643    }
644}
645
646/// Encrypted file configuration
647#[derive(Debug, Clone, Serialize, Deserialize)]
648#[serde(default)]
649#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
650pub struct EncryptedFileConfig {
651    /// Path to encrypted secrets file
652    pub file_path: String,
653    /// Encryption algorithm
654    pub algorithm: String,
655    /// Key derivation function
656    pub kdf: String,
657    /// Master key (from env var)
658    #[serde(skip_serializing_if = "Option::is_none")]
659    pub master_key_env: Option<String>,
660    /// Key file path
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub key_file: Option<String>,
663}
664
665impl Default for EncryptedFileConfig {
666    fn default() -> Self {
667        Self {
668            file_path: "secrets.enc".to_string(),
669            algorithm: "aes-256-gcm".to_string(),
670            kdf: "argon2id".to_string(),
671            master_key_env: Some("MOCKFORGE_MASTER_KEY".to_string()),
672            key_file: None,
673        }
674    }
675}
676
677/// Plugin runtime resource configuration
678#[derive(Debug, Clone, Serialize, Deserialize)]
679#[serde(default)]
680#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
681pub struct PluginResourceConfig {
682    /// Enable plugin system
683    pub enabled: bool,
684    /// Maximum memory per plugin in bytes (default: 10MB)
685    pub max_memory_per_plugin: usize,
686    /// Maximum CPU usage per plugin (0.0-1.0, default: 0.5 = 50%)
687    pub max_cpu_per_plugin: f64,
688    /// Maximum execution time per plugin in milliseconds (default: 5000ms)
689    pub max_execution_time_ms: u64,
690    /// Allow plugins network access
691    pub allow_network_access: bool,
692    /// Filesystem paths plugins can access (empty = no fs access)
693    #[serde(default, skip_serializing_if = "Vec::is_empty")]
694    pub allowed_fs_paths: Vec<String>,
695    /// Maximum concurrent plugin executions
696    pub max_concurrent_executions: usize,
697    /// Plugin cache directory
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub cache_dir: Option<String>,
700    /// Enable debug logging for plugins
701    pub debug_logging: bool,
702    /// Maximum WASM module size in bytes (default: 5MB)
703    pub max_module_size: usize,
704    /// Maximum table elements per plugin
705    pub max_table_elements: usize,
706    /// Maximum WASM stack size in bytes (default: 2MB)
707    pub max_stack_size: usize,
708}
709
710impl Default for PluginResourceConfig {
711    fn default() -> Self {
712        Self {
713            enabled: true,
714            max_memory_per_plugin: 10 * 1024 * 1024, // 10MB
715            max_cpu_per_plugin: 0.5,                 // 50% of one core
716            max_execution_time_ms: 5000,             // 5 seconds
717            allow_network_access: false,
718            allowed_fs_paths: Vec::new(),
719            max_concurrent_executions: 10,
720            cache_dir: None,
721            debug_logging: false,
722            max_module_size: 5 * 1024 * 1024, // 5MB
723            max_table_elements: 1000,
724            max_stack_size: 2 * 1024 * 1024, // 2MB
725        }
726    }
727}
728
729/// Logging configuration
730#[derive(Debug, Clone, Serialize, Deserialize)]
731#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
732#[serde(default)]
733pub struct LoggingConfig {
734    /// Log level
735    pub level: String,
736    /// Enable JSON logging
737    pub json_format: bool,
738    /// Log file path (optional)
739    pub file_path: Option<String>,
740    /// Maximum log file size in MB
741    pub max_file_size_mb: u64,
742    /// Maximum number of log files to keep
743    pub max_files: u32,
744}
745
746impl Default for LoggingConfig {
747    fn default() -> Self {
748        Self {
749            level: "info".to_string(),
750            json_format: false,
751            file_path: None,
752            max_file_size_mb: 10,
753            max_files: 5,
754        }
755    }
756}
757
758/// Request chaining configuration for multi-step request workflows
759#[derive(Debug, Clone, Serialize, Deserialize)]
760#[serde(default, rename_all = "camelCase")]
761#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
762pub struct ChainingConfig {
763    /// Enable request chaining
764    pub enabled: bool,
765    /// Maximum chain length to prevent infinite loops
766    pub max_chain_length: usize,
767    /// Global timeout for chain execution in seconds
768    pub global_timeout_secs: u64,
769    /// Enable parallel execution when dependencies allow
770    pub enable_parallel_execution: bool,
771}
772
773impl Default for ChainingConfig {
774    fn default() -> Self {
775        Self {
776            enabled: false,
777            max_chain_length: 20,
778            global_timeout_secs: 300,
779            enable_parallel_execution: false,
780        }
781    }
782}
783
784/// Data generation configuration
785#[derive(Debug, Clone, Serialize, Deserialize)]
786#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
787#[serde(default)]
788pub struct DataConfig {
789    /// Default number of rows to generate
790    pub default_rows: usize,
791    /// Default output format
792    pub default_format: String,
793    /// Faker locale
794    pub locale: String,
795    /// Custom faker templates
796    pub templates: HashMap<String, String>,
797    /// RAG configuration
798    pub rag: RagConfig,
799    /// Active persona profile domain (e.g., "finance", "ecommerce", "healthcare")
800    #[serde(skip_serializing_if = "Option::is_none")]
801    pub persona_domain: Option<String>,
802    /// Enable persona-based consistency
803    #[serde(default = "default_false")]
804    pub persona_consistency_enabled: bool,
805    /// Persona registry configuration
806    #[serde(skip_serializing_if = "Option::is_none")]
807    pub persona_registry: Option<PersonaRegistryConfig>,
808}
809
810impl Default for DataConfig {
811    fn default() -> Self {
812        Self {
813            default_rows: 100,
814            default_format: "json".to_string(),
815            locale: "en".to_string(),
816            templates: HashMap::new(),
817            rag: RagConfig::default(),
818            persona_domain: None,
819            persona_consistency_enabled: false,
820            persona_registry: None,
821        }
822    }
823}
824
825/// RAG configuration
826#[derive(Debug, Clone, Serialize, Deserialize)]
827#[serde(default)]
828#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
829pub struct RagConfig {
830    /// Enable RAG by default
831    pub enabled: bool,
832    /// LLM provider (openai, anthropic, ollama, openai_compatible)
833    #[serde(default)]
834    pub provider: String,
835    /// API endpoint for LLM
836    pub api_endpoint: Option<String>,
837    /// API key for LLM
838    pub api_key: Option<String>,
839    /// Model name
840    pub model: Option<String>,
841    /// Maximum tokens for generation
842    #[serde(default = "default_max_tokens")]
843    pub max_tokens: usize,
844    /// Temperature for generation (0.0 to 2.0)
845    #[serde(default = "default_temperature")]
846    pub temperature: f64,
847    /// Context window size
848    pub context_window: usize,
849    /// Enable caching
850    #[serde(default = "default_true")]
851    pub caching: bool,
852    /// Cache TTL in seconds
853    #[serde(default = "default_cache_ttl")]
854    pub cache_ttl_secs: u64,
855    /// Request timeout in seconds
856    #[serde(default = "default_timeout")]
857    pub timeout_secs: u64,
858    /// Maximum retries for failed requests
859    #[serde(default = "default_max_retries")]
860    pub max_retries: usize,
861}
862
863fn default_max_tokens() -> usize {
864    1024
865}
866
867fn default_temperature() -> f64 {
868    0.7
869}
870
871fn default_true() -> bool {
872    true
873}
874
875fn default_cache_ttl() -> u64 {
876    3600
877}
878
879fn default_timeout() -> u64 {
880    30
881}
882
883fn default_max_retries() -> usize {
884    3
885}
886
887pub(crate) fn default_false() -> bool {
888    false
889}
890
891impl Default for RagConfig {
892    fn default() -> Self {
893        Self {
894            enabled: false,
895            provider: "openai".to_string(),
896            api_endpoint: None,
897            api_key: None,
898            model: Some("gpt-3.5-turbo".to_string()),
899            max_tokens: default_max_tokens(),
900            temperature: default_temperature(),
901            context_window: 4000,
902            caching: default_true(),
903            cache_ttl_secs: default_cache_ttl(),
904            timeout_secs: default_timeout(),
905            max_retries: default_max_retries(),
906        }
907    }
908}
909
910/// Persona registry configuration
911#[derive(Debug, Clone, Serialize, Deserialize)]
912#[serde(default)]
913#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
914#[derive(Default)]
915pub struct PersonaRegistryConfig {
916    /// Enable persistence (save personas to disk)
917    #[serde(default = "default_false")]
918    pub persistent: bool,
919    /// Storage path for persistent personas
920    #[serde(skip_serializing_if = "Option::is_none")]
921    pub storage_path: Option<String>,
922    /// Default traits for new personas
923    #[serde(default)]
924    pub default_traits: HashMap<String, String>,
925}
926
927/// MockAI (Behavioral Mock Intelligence) configuration
928#[derive(Debug, Clone, Serialize, Deserialize)]
929#[serde(default)]
930#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
931pub struct MockAIConfig {
932    /// Enable MockAI features
933    pub enabled: bool,
934    /// Intelligent behavior configuration
935    pub intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig,
936    /// Auto-learn from examples
937    pub auto_learn: bool,
938    /// Enable mutation detection
939    pub mutation_detection: bool,
940    /// Enable AI-driven validation errors
941    pub ai_validation_errors: bool,
942    /// Enable context-aware pagination
943    pub intelligent_pagination: bool,
944    /// Endpoints to enable MockAI for (empty = all endpoints)
945    #[serde(default)]
946    pub enabled_endpoints: Vec<String>,
947}
948
949impl Default for MockAIConfig {
950    fn default() -> Self {
951        Self {
952            enabled: false,
953            intelligent_behavior: crate::intelligent_behavior::IntelligentBehaviorConfig::default(),
954            auto_learn: true,
955            mutation_detection: true,
956            ai_validation_errors: true,
957            intelligent_pagination: true,
958            enabled_endpoints: Vec::new(),
959        }
960    }
961}
962
963/// Observability configuration for metrics and distributed tracing
964#[derive(Debug, Clone, Serialize, Deserialize, Default)]
965#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
966#[serde(default)]
967pub struct ObservabilityConfig {
968    /// Prometheus metrics configuration
969    pub prometheus: PrometheusConfig,
970    /// OpenTelemetry distributed tracing configuration
971    pub opentelemetry: Option<OpenTelemetryConfig>,
972    /// API Flight Recorder configuration
973    pub recorder: Option<RecorderConfig>,
974    /// Chaos engineering configuration
975    pub chaos: Option<ChaosEngConfig>,
976}
977
978/// Security monitoring and SIEM configuration
979#[derive(Debug, Clone, Serialize, Deserialize)]
980#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
981#[serde(default)]
982#[derive(Default)]
983pub struct SecurityConfig {
984    /// Security monitoring configuration
985    pub monitoring: SecurityMonitoringConfig,
986}
987
988/// Security monitoring configuration
989#[derive(Debug, Clone, Serialize, Deserialize)]
990#[serde(default)]
991#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
992#[derive(Default)]
993pub struct SecurityMonitoringConfig {
994    /// SIEM integration configuration
995    pub siem: crate::security::siem::SiemConfig,
996    /// Access review configuration
997    pub access_review: crate::security::access_review::AccessReviewConfig,
998    /// Privileged access management configuration
999    pub privileged_access: crate::security::privileged_access::PrivilegedAccessConfig,
1000    /// Change management configuration
1001    pub change_management: crate::security::change_management::ChangeManagementConfig,
1002    /// Compliance dashboard configuration
1003    pub compliance_dashboard: crate::security::compliance_dashboard::ComplianceDashboardConfig,
1004    /// Risk assessment configuration
1005    pub risk_assessment: crate::security::risk_assessment::RiskAssessmentConfig,
1006}
1007
1008/// Prometheus metrics configuration
1009#[derive(Debug, Clone, Serialize, Deserialize)]
1010#[serde(default)]
1011#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1012pub struct PrometheusConfig {
1013    /// Enable Prometheus metrics endpoint
1014    pub enabled: bool,
1015    /// Port for metrics endpoint
1016    pub port: u16,
1017    /// Host for metrics endpoint
1018    pub host: String,
1019    /// Path for metrics endpoint
1020    pub path: String,
1021}
1022
1023impl Default for PrometheusConfig {
1024    fn default() -> Self {
1025        Self {
1026            enabled: true,
1027            port: 9090,
1028            host: "0.0.0.0".to_string(),
1029            path: "/metrics".to_string(),
1030        }
1031    }
1032}
1033
1034/// OpenTelemetry distributed tracing configuration
1035#[derive(Debug, Clone, Serialize, Deserialize)]
1036#[serde(default)]
1037#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1038pub struct OpenTelemetryConfig {
1039    /// Enable OpenTelemetry tracing
1040    pub enabled: bool,
1041    /// Service name for traces
1042    pub service_name: String,
1043    /// Deployment environment (development, staging, production)
1044    pub environment: String,
1045    /// Jaeger endpoint for trace export
1046    pub jaeger_endpoint: String,
1047    /// OTLP endpoint (alternative to Jaeger)
1048    pub otlp_endpoint: Option<String>,
1049    /// Protocol: grpc or http
1050    pub protocol: String,
1051    /// Sampling rate (0.0 to 1.0)
1052    pub sampling_rate: f64,
1053}
1054
1055impl Default for OpenTelemetryConfig {
1056    fn default() -> Self {
1057        Self {
1058            enabled: false,
1059            service_name: "mockforge".to_string(),
1060            environment: "development".to_string(),
1061            jaeger_endpoint: "http://localhost:14268/api/traces".to_string(),
1062            otlp_endpoint: Some("http://localhost:4317".to_string()),
1063            protocol: "grpc".to_string(),
1064            sampling_rate: 1.0,
1065        }
1066    }
1067}
1068
1069/// API Flight Recorder configuration
1070#[derive(Debug, Clone, Serialize, Deserialize)]
1071#[serde(default)]
1072#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1073pub struct RecorderConfig {
1074    /// Enable recording
1075    pub enabled: bool,
1076    /// Database file path
1077    pub database_path: String,
1078    /// Enable management API
1079    pub api_enabled: bool,
1080    /// Management API port (if different from main port)
1081    pub api_port: Option<u16>,
1082    /// Maximum number of requests to store (0 for unlimited)
1083    pub max_requests: i64,
1084    /// Auto-delete requests older than N days (0 to disable)
1085    pub retention_days: i64,
1086    /// Record HTTP requests
1087    pub record_http: bool,
1088    /// Record gRPC requests
1089    pub record_grpc: bool,
1090    /// Record WebSocket messages
1091    pub record_websocket: bool,
1092    /// Record GraphQL requests
1093    pub record_graphql: bool,
1094    /// Record proxied requests (requests that are forwarded to real backends)
1095    /// When enabled, proxied requests/responses will be recorded with metadata indicating proxy source
1096    #[serde(default = "default_true")]
1097    pub record_proxy: bool,
1098}
1099
1100impl Default for RecorderConfig {
1101    fn default() -> Self {
1102        Self {
1103            enabled: false,
1104            database_path: "./mockforge-recordings.db".to_string(),
1105            api_enabled: true,
1106            api_port: None,
1107            max_requests: 10000,
1108            retention_days: 7,
1109            record_http: true,
1110            record_grpc: true,
1111            record_websocket: true,
1112            record_graphql: true,
1113            record_proxy: true,
1114        }
1115    }
1116}
1117
1118/// Chaos engineering configuration
1119#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1120#[serde(default)]
1121#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1122pub struct ChaosEngConfig {
1123    /// Enable chaos engineering
1124    pub enabled: bool,
1125    /// Latency injection configuration
1126    pub latency: Option<LatencyInjectionConfig>,
1127    /// Fault injection configuration
1128    pub fault_injection: Option<FaultConfig>,
1129    /// Rate limiting configuration
1130    pub rate_limit: Option<RateLimitingConfig>,
1131    /// Traffic shaping configuration
1132    pub traffic_shaping: Option<NetworkShapingConfig>,
1133    /// Predefined scenario to use
1134    pub scenario: Option<String>,
1135}
1136
1137/// Latency injection configuration for chaos engineering
1138#[derive(Debug, Clone, Serialize, Deserialize)]
1139#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1140pub struct LatencyInjectionConfig {
1141    /// Enable latency injection
1142    pub enabled: bool,
1143    /// Fixed delay to inject (in milliseconds)
1144    pub fixed_delay_ms: Option<u64>,
1145    /// Random delay range (min_ms, max_ms) in milliseconds
1146    pub random_delay_range_ms: Option<(u64, u64)>,
1147    /// Jitter percentage to add variance to delays (0.0 to 1.0)
1148    pub jitter_percent: f64,
1149    /// Probability of injecting latency (0.0 to 1.0)
1150    pub probability: f64,
1151}
1152
1153/// Fault injection configuration for chaos engineering
1154#[derive(Debug, Clone, Serialize, Deserialize)]
1155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1156pub struct FaultConfig {
1157    /// Enable fault injection
1158    pub enabled: bool,
1159    /// HTTP status codes to randomly return (e.g., [500, 502, 503])
1160    pub http_errors: Vec<u16>,
1161    /// Probability of returning HTTP errors (0.0 to 1.0)
1162    pub http_error_probability: f64,
1163    /// Enable connection errors (connection refused, reset, etc.)
1164    pub connection_errors: bool,
1165    /// Probability of connection errors (0.0 to 1.0)
1166    pub connection_error_probability: f64,
1167    /// Enable timeout errors
1168    pub timeout_errors: bool,
1169    /// Timeout duration in milliseconds
1170    pub timeout_ms: u64,
1171    /// Probability of timeout errors (0.0 to 1.0)
1172    pub timeout_probability: f64,
1173}
1174
1175/// Rate limiting configuration for traffic control
1176#[derive(Debug, Clone, Serialize, Deserialize)]
1177#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1178pub struct RateLimitingConfig {
1179    /// Enable rate limiting
1180    pub enabled: bool,
1181    /// Maximum requests per second allowed
1182    pub requests_per_second: u32,
1183    /// Maximum burst size before rate limiting kicks in
1184    pub burst_size: u32,
1185    /// Apply rate limiting per IP address
1186    pub per_ip: bool,
1187    /// Apply rate limiting per endpoint/path
1188    pub per_endpoint: bool,
1189}
1190
1191/// Network shaping configuration for simulating network conditions
1192#[derive(Debug, Clone, Serialize, Deserialize)]
1193#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1194pub struct NetworkShapingConfig {
1195    /// Enable network shaping
1196    pub enabled: bool,
1197    /// Bandwidth limit in bits per second
1198    pub bandwidth_limit_bps: u64,
1199    /// Packet loss percentage (0.0 to 1.0)
1200    pub packet_loss_percent: f64,
1201    /// Maximum concurrent connections allowed
1202    pub max_connections: u32,
1203}