Skip to main content

brainwires_proxy/
config.rs

1use std::collections::HashMap;
2use std::net::SocketAddr;
3use std::path::PathBuf;
4use std::time::Duration;
5
6/// Top-level proxy configuration.
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct ProxyConfig {
9    /// Where the proxy listens for incoming connections.
10    pub listener: ListenerConfig,
11    /// Where the proxy forwards traffic.
12    pub upstream: UpstreamConfig,
13    /// Maximum request body size in bytes (0 = unlimited).
14    #[serde(default = "default_max_body_size")]
15    pub max_body_size: usize,
16    /// Request timeout.
17    #[serde(with = "humantime_serde", default = "default_timeout")]
18    pub timeout: Duration,
19    /// Enable the traffic inspector.
20    #[serde(default)]
21    pub inspector: InspectorConfig,
22    /// Extra key-value metadata.
23    #[serde(default)]
24    pub metadata: HashMap<String, String>,
25}
26
27fn default_max_body_size() -> usize {
28    10 * 1024 * 1024 // 10 MiB
29}
30
31fn default_timeout() -> Duration {
32    Duration::from_secs(30)
33}
34
35impl Default for ProxyConfig {
36    fn default() -> Self {
37        Self {
38            listener: ListenerConfig::default(),
39            upstream: UpstreamConfig::default(),
40            max_body_size: default_max_body_size(),
41            timeout: default_timeout(),
42            inspector: InspectorConfig::default(),
43            metadata: HashMap::new(),
44        }
45    }
46}
47
48/// Configuration for the proxy listener (inbound side).
49#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
50#[serde(tag = "type", rename_all = "snake_case")]
51pub enum ListenerConfig {
52    /// Listen on a TCP socket.
53    Tcp { addr: SocketAddr },
54    /// Listen on a Unix domain socket.
55    Unix { path: PathBuf },
56}
57
58impl Default for ListenerConfig {
59    fn default() -> Self {
60        Self::Tcp {
61            addr: SocketAddr::from(([127, 0, 0, 1], 8080)),
62        }
63    }
64}
65
66/// Configuration for the upstream target (outbound side).
67#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
68#[serde(tag = "type", rename_all = "snake_case")]
69pub enum UpstreamConfig {
70    /// Connect to a URL (HTTP/HTTPS/WS/WSS).
71    Url { url: String },
72    /// Connect to a TCP host:port.
73    Tcp { host: String, port: u16 },
74    /// Connect to a Unix domain socket.
75    Unix { path: PathBuf },
76}
77
78impl Default for UpstreamConfig {
79    fn default() -> Self {
80        Self::Url {
81            url: "http://localhost:3000".to_string(),
82        }
83    }
84}
85
86/// Inspector subsystem configuration.
87#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
88pub struct InspectorConfig {
89    /// Enable traffic capture.
90    #[serde(default)]
91    pub enabled: bool,
92    /// Maximum events stored in the ring buffer.
93    #[serde(default = "default_event_capacity")]
94    pub event_capacity: usize,
95    /// Broadcast channel capacity.
96    #[serde(default = "default_broadcast_capacity")]
97    pub broadcast_capacity: usize,
98    /// If set, bind the inspector HTTP API on this address.
99    pub api_addr: Option<SocketAddr>,
100}
101
102fn default_event_capacity() -> usize {
103    10_000
104}
105
106fn default_broadcast_capacity() -> usize {
107    256
108}
109
110impl Default for InspectorConfig {
111    fn default() -> Self {
112        Self {
113            enabled: false,
114            event_capacity: default_event_capacity(),
115            broadcast_capacity: default_broadcast_capacity(),
116            api_addr: None,
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn default_config() {
127        let config = ProxyConfig::default();
128        assert_eq!(config.max_body_size, 10 * 1024 * 1024);
129        assert_eq!(config.timeout, Duration::from_secs(30));
130        assert!(!config.inspector.enabled);
131        assert!(config.metadata.is_empty());
132    }
133
134    #[test]
135    fn config_serde_roundtrip() {
136        let config = ProxyConfig::default();
137        let json = serde_json::to_string(&config).unwrap();
138        let deserialized: ProxyConfig = serde_json::from_str(&json).unwrap();
139        assert_eq!(deserialized.max_body_size, config.max_body_size);
140        assert_eq!(deserialized.timeout, config.timeout);
141    }
142
143    #[test]
144    fn listener_config_tcp() {
145        let listener = ListenerConfig::Tcp {
146            addr: "0.0.0.0:9090".parse().unwrap(),
147        };
148        let json = serde_json::to_string(&listener).unwrap();
149        assert!(json.contains("tcp"));
150        assert!(json.contains("9090"));
151    }
152
153    #[test]
154    fn upstream_config_url() {
155        let upstream = UpstreamConfig::Url {
156            url: "https://api.example.com".to_string(),
157        };
158        let json = serde_json::to_string(&upstream).unwrap();
159        assert!(json.contains("api.example.com"));
160    }
161
162    #[test]
163    fn inspector_config_defaults() {
164        let config = InspectorConfig::default();
165        assert!(!config.enabled);
166        assert_eq!(config.event_capacity, 10_000);
167        assert_eq!(config.broadcast_capacity, 256);
168        assert!(config.api_addr.is_none());
169    }
170}
171
172/// Serde helper for `Duration` via humantime strings.
173mod humantime_serde {
174    use serde::{Deserialize, Deserializer, Serializer};
175    use std::time::Duration;
176
177    pub fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
178        s.serialize_u64(d.as_secs())
179    }
180
181    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
182        let secs = u64::deserialize(d)?;
183        Ok(Duration::from_secs(secs))
184    }
185}