Skip to main content

mockforge_core/config/
protocol.rs

1//! Protocol-specific configuration types
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::Path;
6
7use super::auth::AuthConfig;
8
9/// HTTP validation configuration
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12pub struct HttpValidationConfig {
13    /// Request validation mode: off, warn, enforce
14    pub mode: String,
15}
16
17/// HTTP CORS configuration
18#[derive(Debug, Clone, Serialize, Deserialize, Default)]
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
20pub struct HttpCorsConfig {
21    /// Enable CORS
22    pub enabled: bool,
23    /// Allowed origins
24    #[serde(default)]
25    pub allowed_origins: Vec<String>,
26    /// Allowed methods
27    #[serde(default)]
28    pub allowed_methods: Vec<String>,
29    /// Allowed headers
30    #[serde(default)]
31    pub allowed_headers: Vec<String>,
32    /// Allow credentials (cookies, authorization headers)
33    /// Note: Cannot be true when using wildcard origin (*)
34    #[serde(default = "default_cors_allow_credentials")]
35    pub allow_credentials: bool,
36}
37
38fn default_cors_allow_credentials() -> bool {
39    false
40}
41
42/// HTTP server configuration
43#[derive(Debug, Clone, Serialize, Deserialize)]
44#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
45#[serde(default)]
46pub struct HttpConfig {
47    /// Enable HTTP server
48    pub enabled: bool,
49    /// Server port
50    pub port: u16,
51    /// Host address
52    pub host: String,
53    /// Path to OpenAPI spec file for HTTP server
54    pub openapi_spec: Option<String>,
55    /// CORS configuration
56    pub cors: Option<HttpCorsConfig>,
57    /// Request timeout in seconds
58    pub request_timeout_secs: u64,
59    /// Request validation configuration
60    pub validation: Option<HttpValidationConfig>,
61    /// Aggregate validation errors into JSON array
62    pub aggregate_validation_errors: bool,
63    /// Validate responses (warn-only logging)
64    pub validate_responses: bool,
65    /// Expand templating tokens in responses/examples
66    pub response_template_expand: bool,
67    /// Validation error HTTP status (e.g., 400 or 422)
68    pub validation_status: Option<u16>,
69    /// Per-route overrides: key "METHOD path" => mode (off/warn/enforce)
70    pub validation_overrides: HashMap<String, String>,
71    /// When embedding Admin UI under HTTP, skip validation for the mounted prefix
72    pub skip_admin_validation: bool,
73    /// Authentication configuration
74    pub auth: Option<AuthConfig>,
75    /// TLS/HTTPS configuration
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub tls: Option<HttpTlsConfig>,
78}
79
80impl Default for HttpConfig {
81    fn default() -> Self {
82        Self {
83            enabled: true,
84            port: 3000,
85            host: "0.0.0.0".to_string(),
86            openapi_spec: None,
87            cors: Some(HttpCorsConfig {
88                enabled: true,
89                allowed_origins: vec!["*".to_string()],
90                allowed_methods: vec![
91                    "GET".to_string(),
92                    "POST".to_string(),
93                    "PUT".to_string(),
94                    "DELETE".to_string(),
95                    "PATCH".to_string(),
96                    "OPTIONS".to_string(),
97                ],
98                allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
99                allow_credentials: false, // Must be false when using wildcard origin
100            }),
101            request_timeout_secs: 30,
102            validation: Some(HttpValidationConfig {
103                mode: "enforce".to_string(),
104            }),
105            aggregate_validation_errors: true,
106            validate_responses: false,
107            response_template_expand: false,
108            validation_status: None,
109            validation_overrides: HashMap::new(),
110            skip_admin_validation: true,
111            auth: None,
112            tls: None,
113        }
114    }
115}
116
117/// HTTP TLS/HTTPS configuration
118#[derive(Debug, Clone, Serialize, Deserialize)]
119#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
120pub struct HttpTlsConfig {
121    /// Enable TLS/HTTPS
122    pub enabled: bool,
123    /// Path to TLS certificate file (PEM format)
124    pub cert_file: String,
125    /// Path to TLS private key file (PEM format)
126    pub key_file: String,
127    /// Path to CA certificate file for mutual TLS (optional)
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub ca_file: Option<String>,
130    /// Minimum TLS version (default: "1.2")
131    #[serde(default = "default_tls_min_version")]
132    pub min_version: String,
133    /// Cipher suites to use (default: safe defaults)
134    #[serde(default, skip_serializing_if = "Vec::is_empty")]
135    pub cipher_suites: Vec<String>,
136    /// Require client certificate (mutual TLS)
137    #[serde(default)]
138    pub require_client_cert: bool,
139    /// Mutual TLS mode: "off" (default), "optional", "required"
140    #[serde(default = "default_mtls_mode")]
141    pub mtls_mode: String,
142}
143
144fn default_mtls_mode() -> String {
145    "off".to_string()
146}
147
148fn default_tls_min_version() -> String {
149    "1.2".to_string()
150}
151
152impl Default for HttpTlsConfig {
153    fn default() -> Self {
154        Self {
155            enabled: true,
156            cert_file: String::new(),
157            key_file: String::new(),
158            ca_file: None,
159            min_version: "1.2".to_string(),
160            cipher_suites: Vec::new(),
161            require_client_cert: false,
162            mtls_mode: "off".to_string(),
163        }
164    }
165}
166
167/// WebSocket server configuration
168#[derive(Debug, Clone, Serialize, Deserialize)]
169#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
170#[serde(default)]
171pub struct WebSocketConfig {
172    /// Enable WebSocket server
173    pub enabled: bool,
174    /// Server port
175    pub port: u16,
176    /// Host address
177    pub host: String,
178    /// Replay file path
179    pub replay_file: Option<String>,
180    /// Connection timeout in seconds
181    pub connection_timeout_secs: u64,
182}
183
184impl Default for WebSocketConfig {
185    fn default() -> Self {
186        Self {
187            enabled: true,
188            port: 3001,
189            host: "0.0.0.0".to_string(),
190            replay_file: None,
191            connection_timeout_secs: 300,
192        }
193    }
194}
195
196/// gRPC server configuration
197#[derive(Debug, Clone, Serialize, Deserialize)]
198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
199#[serde(default)]
200pub struct GrpcConfig {
201    /// Enable gRPC server
202    pub enabled: bool,
203    /// Server port
204    pub port: u16,
205    /// Host address
206    pub host: String,
207    /// Proto files directory
208    pub proto_dir: Option<String>,
209    /// TLS configuration
210    pub tls: Option<TlsConfig>,
211}
212
213impl Default for GrpcConfig {
214    fn default() -> Self {
215        Self {
216            enabled: true,
217            port: 50051,
218            host: "0.0.0.0".to_string(),
219            proto_dir: None,
220            tls: None,
221        }
222    }
223}
224
225/// GraphQL server configuration
226#[derive(Debug, Clone, Serialize, Deserialize)]
227#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
228#[serde(default)]
229pub struct GraphQLConfig {
230    /// Enable GraphQL server
231    pub enabled: bool,
232    /// Server port
233    pub port: u16,
234    /// Host address
235    pub host: String,
236    /// GraphQL schema file path (.graphql or .gql)
237    pub schema_path: Option<String>,
238    /// Handlers directory for custom resolvers
239    pub handlers_dir: Option<String>,
240    /// Enable GraphQL Playground UI
241    pub playground_enabled: bool,
242    /// Upstream GraphQL server URL for passthrough
243    pub upstream_url: Option<String>,
244    /// Enable introspection queries
245    pub introspection_enabled: bool,
246}
247
248impl Default for GraphQLConfig {
249    fn default() -> Self {
250        Self {
251            enabled: true,
252            port: 4000,
253            host: "0.0.0.0".to_string(),
254            schema_path: None,
255            handlers_dir: None,
256            playground_enabled: true,
257            upstream_url: None,
258            introspection_enabled: true,
259        }
260    }
261}
262
263/// TLS configuration for gRPC
264#[derive(Debug, Clone, Serialize, Deserialize)]
265#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
266pub struct TlsConfig {
267    /// Certificate file path
268    pub cert_path: String,
269    /// Private key file path
270    pub key_path: String,
271}
272
273/// MQTT server configuration
274#[derive(Debug, Clone, Serialize, Deserialize)]
275#[serde(default)]
276#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
277pub struct MqttConfig {
278    /// Enable MQTT server
279    pub enabled: bool,
280    /// Server port
281    pub port: u16,
282    /// Host address
283    pub host: String,
284    /// Maximum connections
285    pub max_connections: usize,
286    /// Maximum packet size
287    pub max_packet_size: usize,
288    /// Keep-alive timeout in seconds
289    pub keep_alive_secs: u16,
290    /// Directory containing fixture files
291    pub fixtures_dir: Option<std::path::PathBuf>,
292    /// Enable retained messages
293    pub enable_retained_messages: bool,
294    /// Maximum retained messages
295    pub max_retained_messages: usize,
296}
297
298impl Default for MqttConfig {
299    fn default() -> Self {
300        Self {
301            enabled: false,
302            port: 1883,
303            host: "0.0.0.0".to_string(),
304            max_connections: 1000,
305            max_packet_size: 268435456, // 256 MB
306            keep_alive_secs: 60,
307            fixtures_dir: None,
308            enable_retained_messages: true,
309            max_retained_messages: 10000,
310        }
311    }
312}
313
314/// SMTP server configuration
315#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(default)]
317#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
318pub struct SmtpConfig {
319    /// Enable SMTP server
320    pub enabled: bool,
321    /// Server port
322    pub port: u16,
323    /// Host address
324    pub host: String,
325    /// Server hostname for SMTP greeting
326    pub hostname: String,
327    /// Directory containing fixture files
328    pub fixtures_dir: Option<std::path::PathBuf>,
329    /// Connection timeout in seconds
330    pub timeout_secs: u64,
331    /// Maximum connections
332    pub max_connections: usize,
333    /// Enable mailbox storage
334    pub enable_mailbox: bool,
335    /// Maximum mailbox size
336    pub max_mailbox_messages: usize,
337    /// Enable STARTTLS support
338    pub enable_starttls: bool,
339    /// Path to TLS certificate file
340    pub tls_cert_path: Option<std::path::PathBuf>,
341    /// Path to TLS private key file
342    pub tls_key_path: Option<std::path::PathBuf>,
343}
344
345impl Default for SmtpConfig {
346    fn default() -> Self {
347        Self {
348            enabled: false,
349            port: 1025,
350            host: "0.0.0.0".to_string(),
351            hostname: "mockforge-smtp".to_string(),
352            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
353            timeout_secs: 300,
354            max_connections: 10,
355            enable_mailbox: true,
356            max_mailbox_messages: 1000,
357            enable_starttls: false,
358            tls_cert_path: None,
359            tls_key_path: None,
360        }
361    }
362}
363
364/// FTP server configuration
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(default)]
367#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
368pub struct FtpConfig {
369    /// Enable FTP server
370    pub enabled: bool,
371    /// Server port
372    pub port: u16,
373    /// Host address
374    pub host: String,
375    /// Passive mode port range
376    pub passive_ports: (u16, u16),
377    /// Maximum connections
378    pub max_connections: usize,
379    /// Connection timeout in seconds
380    pub timeout_secs: u64,
381    /// Allow anonymous access
382    pub allow_anonymous: bool,
383    /// Fixtures directory
384    pub fixtures_dir: Option<std::path::PathBuf>,
385    /// Virtual root directory
386    pub virtual_root: std::path::PathBuf,
387}
388
389impl Default for FtpConfig {
390    fn default() -> Self {
391        Self {
392            enabled: false,
393            port: 2121,
394            host: "0.0.0.0".to_string(),
395            passive_ports: (50000, 51000),
396            max_connections: 100,
397            timeout_secs: 300,
398            allow_anonymous: true,
399            fixtures_dir: None,
400            virtual_root: std::path::PathBuf::from("/mockforge"),
401        }
402    }
403}
404
405/// Kafka server configuration
406#[derive(Debug, Clone, Serialize, Deserialize)]
407#[serde(default)]
408#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
409pub struct KafkaConfig {
410    /// Enable Kafka server
411    pub enabled: bool,
412    /// Server port
413    pub port: u16,
414    /// Host address
415    pub host: String,
416    /// Broker ID
417    pub broker_id: i32,
418    /// Maximum connections
419    pub max_connections: usize,
420    /// Log retention time in milliseconds
421    pub log_retention_ms: i64,
422    /// Log segment size in bytes
423    pub log_segment_bytes: i64,
424    /// Fixtures directory
425    pub fixtures_dir: Option<std::path::PathBuf>,
426    /// Auto-create topics
427    pub auto_create_topics: bool,
428    /// Default number of partitions for new topics
429    pub default_partitions: i32,
430    /// Default replication factor for new topics
431    pub default_replication_factor: i16,
432    /// Hostname returned in Kafka MetadataResponse so clients reach the
433    /// broker after the bootstrap handshake. Defaults to `host`. On
434    /// hosted-mock deployments the orchestrator sets this to the public
435    /// `<app>.fly.dev` (or custom) hostname so external Kafka clients can
436    /// route correctly. The mockforge-kafka broker itself must consume this
437    /// value when constructing metadata responses for the wiring to be
438    /// observable end-to-end (tracked in #231).
439    pub advertised_host: Option<String>,
440    /// Public port returned in Kafka MetadataResponse alongside
441    /// `advertised_host`. Defaults to `port`. Useful when Fly maps a
442    /// different public port (currently we keep them aligned at 9092).
443    pub advertised_port: Option<u16>,
444}
445
446impl Default for KafkaConfig {
447    fn default() -> Self {
448        Self {
449            enabled: false,
450            port: 9092, // Standard Kafka port
451            host: "0.0.0.0".to_string(),
452            broker_id: 1,
453            max_connections: 1000,
454            log_retention_ms: 604800000,   // 7 days
455            log_segment_bytes: 1073741824, // 1 GB
456            fixtures_dir: None,
457            auto_create_topics: true,
458            default_partitions: 3,
459            default_replication_factor: 1,
460            advertised_host: None,
461            advertised_port: None,
462        }
463    }
464}
465
466/// AMQP server configuration
467#[derive(Debug, Clone, Serialize, Deserialize)]
468#[serde(default)]
469#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
470pub struct AmqpConfig {
471    /// Enable AMQP server
472    pub enabled: bool,
473    /// Server port
474    pub port: u16,
475    /// Host address
476    pub host: String,
477    /// Maximum connections
478    pub max_connections: usize,
479    /// Maximum channels per connection
480    pub max_channels_per_connection: u16,
481    /// Frame max size
482    pub frame_max: u32,
483    /// Heartbeat interval in seconds
484    pub heartbeat_interval: u16,
485    /// Fixtures directory
486    pub fixtures_dir: Option<std::path::PathBuf>,
487    /// Virtual hosts
488    pub virtual_hosts: Vec<String>,
489    /// Enable TLS
490    pub tls_enabled: bool,
491    /// TLS port (5671 is standard AMQPS port)
492    pub tls_port: u16,
493    /// Path to TLS certificate file (PEM format)
494    pub tls_cert_path: Option<std::path::PathBuf>,
495    /// Path to TLS private key file (PEM format)
496    pub tls_key_path: Option<std::path::PathBuf>,
497    /// Path to CA certificate for client verification (optional)
498    pub tls_ca_path: Option<std::path::PathBuf>,
499    /// Require client certificate authentication
500    pub tls_client_auth: bool,
501}
502
503impl Default for AmqpConfig {
504    fn default() -> Self {
505        Self {
506            enabled: false,
507            port: 5672, // Standard AMQP port
508            host: "0.0.0.0".to_string(),
509            max_connections: 1000,
510            max_channels_per_connection: 100,
511            frame_max: 131072, // 128 KB
512            heartbeat_interval: 60,
513            fixtures_dir: None,
514            virtual_hosts: vec!["/".to_string()],
515            tls_enabled: false,
516            tls_port: 5671, // Standard AMQPS port
517            tls_cert_path: None,
518            tls_key_path: None,
519            tls_ca_path: None,
520            tls_client_auth: false,
521        }
522    }
523}
524
525/// TCP server configuration
526#[derive(Debug, Clone, Serialize, Deserialize)]
527#[serde(default)]
528#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
529pub struct TcpConfig {
530    /// Enable TCP server
531    pub enabled: bool,
532    /// Server port
533    pub port: u16,
534    /// Host address
535    pub host: String,
536    /// Maximum connections
537    pub max_connections: usize,
538    /// Connection timeout in seconds
539    pub timeout_secs: u64,
540    /// Directory containing fixture files
541    pub fixtures_dir: Option<std::path::PathBuf>,
542    /// Enable echo mode (echo received data back)
543    pub echo_mode: bool,
544    /// Enable TLS support
545    pub enable_tls: bool,
546    /// Path to TLS certificate file
547    pub tls_cert_path: Option<std::path::PathBuf>,
548    /// Path to TLS private key file
549    pub tls_key_path: Option<std::path::PathBuf>,
550}
551
552impl Default for TcpConfig {
553    fn default() -> Self {
554        Self {
555            enabled: false,
556            port: 9999,
557            host: "0.0.0.0".to_string(),
558            max_connections: 1000,
559            timeout_secs: 300,
560            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
561            echo_mode: true,
562            enable_tls: false,
563            tls_cert_path: None,
564            tls_key_path: None,
565        }
566    }
567}
568
569/// Admin UI configuration
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
572#[serde(default)]
573pub struct AdminConfig {
574    /// Enable admin UI
575    pub enabled: bool,
576    /// Admin UI port
577    pub port: u16,
578    /// Host address
579    pub host: String,
580    /// Authentication required
581    pub auth_required: bool,
582    /// Admin username (if auth required)
583    pub username: Option<String>,
584    /// Admin password (if auth required)
585    pub password: Option<String>,
586    /// Optional mount path to embed Admin UI under HTTP server (e.g., "/admin")
587    pub mount_path: Option<String>,
588    /// Enable Admin API endpoints (under `__mockforge`)
589    pub api_enabled: bool,
590    /// Prometheus server URL for analytics queries
591    pub prometheus_url: String,
592}
593
594impl Default for AdminConfig {
595    fn default() -> Self {
596        // Default to 0.0.0.0 if running in Docker (detected via common Docker env vars)
597        // This makes Admin UI accessible from outside the container by default
598        let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
599            || std::env::var("container").is_ok()
600            || Path::new("/.dockerenv").exists()
601        {
602            "0.0.0.0".to_string()
603        } else {
604            "127.0.0.1".to_string()
605        };
606
607        Self {
608            enabled: false,
609            port: 9080,
610            host: default_host,
611            auth_required: false,
612            username: None,
613            password: None,
614            mount_path: None,
615            api_enabled: true,
616            prometheus_url: "http://localhost:9090".to_string(),
617        }
618    }
619}
620
621/// Protocol enable/disable configuration
622#[derive(Debug, Clone, Serialize, Deserialize)]
623#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
624pub struct ProtocolConfig {
625    /// Enable this protocol
626    pub enabled: bool,
627}
628
629/// Protocols configuration
630#[derive(Debug, Clone, Serialize, Deserialize)]
631#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
632pub struct ProtocolsConfig {
633    /// HTTP protocol configuration
634    pub http: ProtocolConfig,
635    /// GraphQL protocol configuration
636    pub graphql: ProtocolConfig,
637    /// gRPC protocol configuration
638    pub grpc: ProtocolConfig,
639    /// WebSocket protocol configuration
640    pub websocket: ProtocolConfig,
641    /// SMTP protocol configuration
642    pub smtp: ProtocolConfig,
643    /// MQTT protocol configuration
644    pub mqtt: ProtocolConfig,
645    /// FTP protocol configuration
646    pub ftp: ProtocolConfig,
647    /// Kafka protocol configuration
648    pub kafka: ProtocolConfig,
649    /// RabbitMQ protocol configuration
650    pub rabbitmq: ProtocolConfig,
651    /// AMQP protocol configuration
652    pub amqp: ProtocolConfig,
653    /// TCP protocol configuration
654    pub tcp: ProtocolConfig,
655}
656
657impl Default for ProtocolsConfig {
658    fn default() -> Self {
659        Self {
660            http: ProtocolConfig { enabled: true },
661            graphql: ProtocolConfig { enabled: true },
662            grpc: ProtocolConfig { enabled: true },
663            websocket: ProtocolConfig { enabled: true },
664            smtp: ProtocolConfig { enabled: false },
665            mqtt: ProtocolConfig { enabled: true },
666            ftp: ProtocolConfig { enabled: false },
667            kafka: ProtocolConfig { enabled: false },
668            rabbitmq: ProtocolConfig { enabled: false },
669            amqp: ProtocolConfig { enabled: false },
670            tcp: ProtocolConfig { enabled: false },
671        }
672    }
673}