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    /// Per-method response overrides. First matching rule wins; rules with no
212    /// `match` block are catch-all rules.
213    #[serde(default, skip_serializing_if = "Vec::is_empty")]
214    pub overrides: Vec<GrpcOverride>,
215}
216
217impl Default for GrpcConfig {
218    fn default() -> Self {
219        Self {
220            enabled: true,
221            port: 50051,
222            host: "0.0.0.0".to_string(),
223            proto_dir: None,
224            tls: None,
225            overrides: Vec::new(),
226        }
227    }
228}
229
230/// A single per-method override rule for the gRPC mock.
231///
232/// Use this to return specific status codes or response bodies from a method
233/// without modifying the proto file. Rules are evaluated in declaration order;
234/// the first one whose service+method (and optional `match`) match the
235/// incoming request wins. Unmatched calls fall back to the default
236/// smart-mock-generation behavior.
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
239pub struct GrpcOverride {
240    /// Fully-qualified service name without leading dot, e.g. `myapp.OrderService`.
241    /// May also be the unqualified service name; matching is exact.
242    pub service: String,
243    /// Method name (case-sensitive, matches proto definition).
244    pub method: String,
245    /// Optional request-field-equality match. Keys are top-level field names of
246    /// the request message; values are stringified expected values. When
247    /// omitted, the rule matches every call to the named method.
248    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
249    pub r#match: HashMap<String, String>,
250    /// Response to return when this rule fires.
251    pub response: GrpcOverrideResponse,
252}
253
254/// Response shape for a `GrpcOverride`. Either `status` is set to a non-OK
255/// gRPC status code (in which case the call returns an error with `message`),
256/// or `body` is set to a JSON object that's serialized into the response
257/// message. Setting both is allowed but `status` wins when non-OK.
258#[derive(Debug, Clone, Default, Serialize, Deserialize)]
259#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
260#[serde(default)]
261pub struct GrpcOverrideResponse {
262    /// gRPC status code name (e.g. `OK`, `NOT_FOUND`, `PERMISSION_DENIED`).
263    /// Case-insensitive. Defaults to `OK` when omitted.
264    #[serde(default, skip_serializing_if = "Option::is_none")]
265    pub status: Option<String>,
266    /// Human-readable error message used when `status` is non-OK.
267    #[serde(default, skip_serializing_if = "Option::is_none")]
268    pub message: Option<String>,
269    /// Response body as a JSON object. Field names must match the response
270    /// message type from the proto. Ignored when `status` is non-OK.
271    #[serde(default, skip_serializing_if = "Option::is_none")]
272    pub body: Option<serde_json::Value>,
273}
274
275/// GraphQL server configuration
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
278#[serde(default)]
279pub struct GraphQLConfig {
280    /// Enable GraphQL server
281    pub enabled: bool,
282    /// Server port
283    pub port: u16,
284    /// Host address
285    pub host: String,
286    /// GraphQL schema file path (.graphql or .gql)
287    pub schema_path: Option<String>,
288    /// Handlers directory for custom resolvers
289    pub handlers_dir: Option<String>,
290    /// Enable GraphQL Playground UI
291    pub playground_enabled: bool,
292    /// Upstream GraphQL server URL for passthrough
293    pub upstream_url: Option<String>,
294    /// Enable introspection queries
295    pub introspection_enabled: bool,
296}
297
298impl Default for GraphQLConfig {
299    fn default() -> Self {
300        Self {
301            enabled: true,
302            port: 4000,
303            host: "0.0.0.0".to_string(),
304            schema_path: None,
305            handlers_dir: None,
306            playground_enabled: true,
307            upstream_url: None,
308            introspection_enabled: true,
309        }
310    }
311}
312
313/// TLS configuration for gRPC
314#[derive(Debug, Clone, Serialize, Deserialize)]
315#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
316pub struct TlsConfig {
317    /// Certificate file path
318    pub cert_path: String,
319    /// Private key file path
320    pub key_path: String,
321}
322
323/// MQTT server configuration
324#[derive(Debug, Clone, Serialize, Deserialize)]
325#[serde(default)]
326#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
327pub struct MqttConfig {
328    /// Enable MQTT server
329    pub enabled: bool,
330    /// Server port
331    pub port: u16,
332    /// Host address
333    pub host: String,
334    /// Maximum connections
335    pub max_connections: usize,
336    /// Maximum packet size
337    pub max_packet_size: usize,
338    /// Keep-alive timeout in seconds
339    pub keep_alive_secs: u16,
340    /// Directory containing fixture files
341    pub fixtures_dir: Option<std::path::PathBuf>,
342    /// Enable retained messages
343    pub enable_retained_messages: bool,
344    /// Maximum retained messages
345    pub max_retained_messages: usize,
346}
347
348impl Default for MqttConfig {
349    fn default() -> Self {
350        Self {
351            enabled: false,
352            port: 1883,
353            host: "0.0.0.0".to_string(),
354            max_connections: 1000,
355            max_packet_size: 268435456, // 256 MB
356            keep_alive_secs: 60,
357            fixtures_dir: None,
358            enable_retained_messages: true,
359            max_retained_messages: 10000,
360        }
361    }
362}
363
364/// SMTP server configuration
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(default)]
367#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
368pub struct SmtpConfig {
369    /// Enable SMTP server
370    pub enabled: bool,
371    /// Server port
372    pub port: u16,
373    /// Host address
374    pub host: String,
375    /// Server hostname for SMTP greeting
376    pub hostname: String,
377    /// Directory containing fixture files
378    pub fixtures_dir: Option<std::path::PathBuf>,
379    /// Connection timeout in seconds
380    pub timeout_secs: u64,
381    /// Maximum connections
382    pub max_connections: usize,
383    /// Enable mailbox storage
384    pub enable_mailbox: bool,
385    /// Maximum mailbox size
386    pub max_mailbox_messages: usize,
387    /// Enable STARTTLS support
388    pub enable_starttls: bool,
389    /// Path to TLS certificate file
390    pub tls_cert_path: Option<std::path::PathBuf>,
391    /// Path to TLS private key file
392    pub tls_key_path: Option<std::path::PathBuf>,
393}
394
395impl Default for SmtpConfig {
396    fn default() -> Self {
397        Self {
398            enabled: false,
399            port: 1025,
400            host: "0.0.0.0".to_string(),
401            hostname: "mockforge-smtp".to_string(),
402            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/smtp")),
403            timeout_secs: 300,
404            max_connections: 10,
405            enable_mailbox: true,
406            max_mailbox_messages: 1000,
407            enable_starttls: false,
408            tls_cert_path: None,
409            tls_key_path: None,
410        }
411    }
412}
413
414/// FTP server configuration
415#[derive(Debug, Clone, Serialize, Deserialize)]
416#[serde(default)]
417#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
418pub struct FtpConfig {
419    /// Enable FTP server
420    pub enabled: bool,
421    /// Server port
422    pub port: u16,
423    /// Host address
424    pub host: String,
425    /// Passive mode port range
426    pub passive_ports: (u16, u16),
427    /// Maximum connections
428    pub max_connections: usize,
429    /// Connection timeout in seconds
430    pub timeout_secs: u64,
431    /// Allow anonymous access
432    pub allow_anonymous: bool,
433    /// Fixtures directory
434    pub fixtures_dir: Option<std::path::PathBuf>,
435    /// Virtual root directory
436    pub virtual_root: std::path::PathBuf,
437}
438
439impl Default for FtpConfig {
440    fn default() -> Self {
441        Self {
442            enabled: false,
443            port: 2121,
444            host: "0.0.0.0".to_string(),
445            passive_ports: (50000, 51000),
446            max_connections: 100,
447            timeout_secs: 300,
448            allow_anonymous: true,
449            fixtures_dir: None,
450            virtual_root: std::path::PathBuf::from("/mockforge"),
451        }
452    }
453}
454
455/// Kafka server configuration
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(default)]
458#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
459pub struct KafkaConfig {
460    /// Enable Kafka server
461    pub enabled: bool,
462    /// Server port
463    pub port: u16,
464    /// Host address
465    pub host: String,
466    /// Broker ID
467    pub broker_id: i32,
468    /// Maximum connections
469    pub max_connections: usize,
470    /// Log retention time in milliseconds
471    pub log_retention_ms: i64,
472    /// Log segment size in bytes
473    pub log_segment_bytes: i64,
474    /// Fixtures directory
475    pub fixtures_dir: Option<std::path::PathBuf>,
476    /// Auto-create topics
477    pub auto_create_topics: bool,
478    /// Default number of partitions for new topics
479    pub default_partitions: i32,
480    /// Default replication factor for new topics
481    pub default_replication_factor: i16,
482    /// Hostname returned in Kafka MetadataResponse so clients reach the
483    /// broker after the bootstrap handshake. Defaults to `host`. On
484    /// hosted-mock deployments the orchestrator sets this to the public
485    /// `<app>.fly.dev` (or custom) hostname so external Kafka clients can
486    /// route correctly. The mockforge-kafka broker itself must consume this
487    /// value when constructing metadata responses for the wiring to be
488    /// observable end-to-end (tracked in #231).
489    pub advertised_host: Option<String>,
490    /// Public port returned in Kafka MetadataResponse alongside
491    /// `advertised_host`. Defaults to `port`. Useful when Fly maps a
492    /// different public port (currently we keep them aligned at 9092).
493    pub advertised_port: Option<u16>,
494    /// Topic → messages map of records to inject at broker startup, before
495    /// the broker accepts any client connections. A consumer reading from
496    /// the beginning of a seeded topic sees these records at offset 0+.
497    /// Topics referenced here are auto-created using `default_partitions`
498    /// and `default_replication_factor`.
499    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
500    pub seed_messages: HashMap<String, Vec<KafkaSeedMessage>>,
501    /// Fault-injection rules applied at the protocol-handler boundary.
502    /// Each rule fires either deterministically (no `probability`) or
503    /// stochastically (`probability: 0.0..=1.0`). Rules with `partition`
504    /// set target a single partition; without it, every partition of the
505    /// named topic.
506    #[serde(default, skip_serializing_if = "Vec::is_empty")]
507    pub faults: Vec<KafkaFault>,
508}
509
510impl Default for KafkaConfig {
511    fn default() -> Self {
512        Self {
513            enabled: false,
514            port: 9092, // Standard Kafka port
515            host: "0.0.0.0".to_string(),
516            broker_id: 1,
517            max_connections: 1000,
518            log_retention_ms: 604800000,   // 7 days
519            log_segment_bytes: 1073741824, // 1 GB
520            fixtures_dir: None,
521            auto_create_topics: true,
522            default_partitions: 3,
523            default_replication_factor: 1,
524            advertised_host: None,
525            advertised_port: None,
526            seed_messages: HashMap::new(),
527            faults: Vec::new(),
528        }
529    }
530}
531
532/// Kafka fault-injection rule. See `KafkaConfig::faults`.
533#[derive(Debug, Clone, Serialize, Deserialize)]
534#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
535pub struct KafkaFault {
536    /// Topic name to target. Required.
537    pub topic: String,
538    /// Specific partition to target. When omitted, the rule fires for any
539    /// partition of the topic.
540    #[serde(default, skip_serializing_if = "Option::is_none")]
541    pub partition: Option<i32>,
542    /// Which failure mode to inject.
543    pub kind: KafkaFaultKind,
544    /// Delay (in milliseconds) for `produce_throttle`. Ignored by other
545    /// kinds. Defaults to 0.
546    #[serde(default, skip_serializing_if = "Option::is_none")]
547    pub delay_ms: Option<u64>,
548    /// Stochastic firing probability in `0.0..=1.0`. `None` or `1.0` =
549    /// every request matching this rule fires the fault. `0.0` = never.
550    #[serde(default, skip_serializing_if = "Option::is_none")]
551    pub probability: Option<f64>,
552}
553
554/// Supported Kafka fault-injection kinds.
555///
556/// Each value matches a specific Kafka protocol error code the mock
557/// returns on the matching request type. Tests are deterministic when
558/// `probability` is None or 1.0.
559#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
560#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
561#[serde(rename_all = "snake_case")]
562pub enum KafkaFaultKind {
563    /// Delay produce response by `delay_ms` before allowing it through.
564    /// Exercises client retry-and-backoff paths.
565    ProduceThrottle,
566    /// Return NOT_LEADER_OR_FOLLOWER (error code 6) for matching produce
567    /// requests. Real clients re-fetch metadata and retry.
568    ProduceNotLeader,
569    /// Return OFFSET_OUT_OF_RANGE (error code 1) for matching fetch
570    /// requests. Real consumers handle this by resetting to the
571    /// configured `auto.offset.reset` policy.
572    OffsetOutOfRange,
573}
574
575/// A single message to inject into a topic's log at broker startup. See
576/// `KafkaConfig::seed_messages` for how these get wired in.
577#[derive(Debug, Clone, Serialize, Deserialize)]
578#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
579pub struct KafkaSeedMessage {
580    /// Optional record key. When present, the broker uses Kafka's
581    /// hash-on-key strategy to assign a partition; same key always lands
582    /// on the same partition. When absent, the round-robin counter picks.
583    #[serde(default, skip_serializing_if = "Option::is_none")]
584    pub key: Option<String>,
585    /// Record value. Stored verbatim — typically a JSON or text payload,
586    /// but raw UTF-8 strings are fine for tests.
587    pub value: String,
588    /// Optional record headers. Same shape as on-the-wire Kafka headers.
589    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
590    pub headers: HashMap<String, String>,
591}
592
593/// AMQP server configuration
594#[derive(Debug, Clone, Serialize, Deserialize)]
595#[serde(default)]
596#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
597pub struct AmqpConfig {
598    /// Enable AMQP server
599    pub enabled: bool,
600    /// Server port
601    pub port: u16,
602    /// Host address
603    pub host: String,
604    /// Maximum connections
605    pub max_connections: usize,
606    /// Maximum channels per connection
607    pub max_channels_per_connection: u16,
608    /// Frame max size
609    pub frame_max: u32,
610    /// Heartbeat interval in seconds
611    pub heartbeat_interval: u16,
612    /// Fixtures directory
613    pub fixtures_dir: Option<std::path::PathBuf>,
614    /// Virtual hosts
615    pub virtual_hosts: Vec<String>,
616    /// Enable TLS
617    pub tls_enabled: bool,
618    /// TLS port (5671 is standard AMQPS port)
619    pub tls_port: u16,
620    /// Path to TLS certificate file (PEM format)
621    pub tls_cert_path: Option<std::path::PathBuf>,
622    /// Path to TLS private key file (PEM format)
623    pub tls_key_path: Option<std::path::PathBuf>,
624    /// Path to CA certificate for client verification (optional)
625    pub tls_ca_path: Option<std::path::PathBuf>,
626    /// Require client certificate authentication
627    pub tls_client_auth: bool,
628}
629
630impl Default for AmqpConfig {
631    fn default() -> Self {
632        Self {
633            enabled: false,
634            port: 5672, // Standard AMQP port
635            host: "0.0.0.0".to_string(),
636            max_connections: 1000,
637            max_channels_per_connection: 100,
638            frame_max: 131072, // 128 KB
639            heartbeat_interval: 60,
640            fixtures_dir: None,
641            virtual_hosts: vec!["/".to_string()],
642            tls_enabled: false,
643            tls_port: 5671, // Standard AMQPS port
644            tls_cert_path: None,
645            tls_key_path: None,
646            tls_ca_path: None,
647            tls_client_auth: false,
648        }
649    }
650}
651
652/// TCP server configuration
653#[derive(Debug, Clone, Serialize, Deserialize)]
654#[serde(default)]
655#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
656pub struct TcpConfig {
657    /// Enable TCP server
658    pub enabled: bool,
659    /// Server port
660    pub port: u16,
661    /// Host address
662    pub host: String,
663    /// Maximum connections
664    pub max_connections: usize,
665    /// Connection timeout in seconds
666    pub timeout_secs: u64,
667    /// Directory containing fixture files
668    pub fixtures_dir: Option<std::path::PathBuf>,
669    /// Enable echo mode (echo received data back)
670    pub echo_mode: bool,
671    /// Enable TLS support
672    pub enable_tls: bool,
673    /// Path to TLS certificate file
674    pub tls_cert_path: Option<std::path::PathBuf>,
675    /// Path to TLS private key file
676    pub tls_key_path: Option<std::path::PathBuf>,
677}
678
679impl Default for TcpConfig {
680    fn default() -> Self {
681        Self {
682            enabled: false,
683            port: 9999,
684            host: "0.0.0.0".to_string(),
685            max_connections: 1000,
686            timeout_secs: 300,
687            fixtures_dir: Some(std::path::PathBuf::from("./fixtures/tcp")),
688            echo_mode: true,
689            enable_tls: false,
690            tls_cert_path: None,
691            tls_key_path: None,
692        }
693    }
694}
695
696/// Admin UI configuration
697#[derive(Debug, Clone, Serialize, Deserialize)]
698#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
699#[serde(default)]
700pub struct AdminConfig {
701    /// Enable admin UI
702    pub enabled: bool,
703    /// Admin UI port
704    pub port: u16,
705    /// Host address
706    pub host: String,
707    /// Authentication required
708    pub auth_required: bool,
709    /// Admin username (if auth required)
710    pub username: Option<String>,
711    /// Admin password (if auth required)
712    pub password: Option<String>,
713    /// Optional mount path to embed Admin UI under HTTP server (e.g., "/admin")
714    pub mount_path: Option<String>,
715    /// Enable Admin API endpoints (under `__mockforge`)
716    pub api_enabled: bool,
717    /// Prometheus server URL for analytics queries
718    pub prometheus_url: String,
719}
720
721impl Default for AdminConfig {
722    fn default() -> Self {
723        // Default to 0.0.0.0 if running in Docker (detected via common Docker env vars)
724        // This makes Admin UI accessible from outside the container by default
725        let default_host = if std::env::var("DOCKER_CONTAINER").is_ok()
726            || std::env::var("container").is_ok()
727            || Path::new("/.dockerenv").exists()
728        {
729            "0.0.0.0".to_string()
730        } else {
731            "127.0.0.1".to_string()
732        };
733
734        Self {
735            enabled: false,
736            port: 9080,
737            host: default_host,
738            auth_required: false,
739            username: None,
740            password: None,
741            mount_path: None,
742            api_enabled: true,
743            prometheus_url: "http://localhost:9090".to_string(),
744        }
745    }
746}
747
748/// Protocol enable/disable configuration
749#[derive(Debug, Clone, Serialize, Deserialize)]
750#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
751pub struct ProtocolConfig {
752    /// Enable this protocol
753    pub enabled: bool,
754}
755
756/// Protocols configuration
757#[derive(Debug, Clone, Serialize, Deserialize)]
758#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
759pub struct ProtocolsConfig {
760    /// HTTP protocol configuration
761    pub http: ProtocolConfig,
762    /// GraphQL protocol configuration
763    pub graphql: ProtocolConfig,
764    /// gRPC protocol configuration
765    pub grpc: ProtocolConfig,
766    /// WebSocket protocol configuration
767    pub websocket: ProtocolConfig,
768    /// SMTP protocol configuration
769    pub smtp: ProtocolConfig,
770    /// MQTT protocol configuration
771    pub mqtt: ProtocolConfig,
772    /// FTP protocol configuration
773    pub ftp: ProtocolConfig,
774    /// Kafka protocol configuration
775    pub kafka: ProtocolConfig,
776    /// RabbitMQ protocol configuration
777    pub rabbitmq: ProtocolConfig,
778    /// AMQP protocol configuration
779    pub amqp: ProtocolConfig,
780    /// TCP protocol configuration
781    pub tcp: ProtocolConfig,
782}
783
784impl Default for ProtocolsConfig {
785    fn default() -> Self {
786        Self {
787            http: ProtocolConfig { enabled: true },
788            graphql: ProtocolConfig { enabled: true },
789            grpc: ProtocolConfig { enabled: true },
790            websocket: ProtocolConfig { enabled: true },
791            smtp: ProtocolConfig { enabled: false },
792            mqtt: ProtocolConfig { enabled: true },
793            ftp: ProtocolConfig { enabled: false },
794            kafka: ProtocolConfig { enabled: false },
795            rabbitmq: ProtocolConfig { enabled: false },
796            amqp: ProtocolConfig { enabled: false },
797            tcp: ProtocolConfig { enabled: false },
798        }
799    }
800}