1use serde::Deserialize;
4use std::path::Path;
5
6#[derive(Debug, Clone, Deserialize)]
8pub struct ProxyConfig {
9 pub server: ServerConfig,
11 pub upstream: UpstreamConfig,
13 #[serde(default)]
15 pub middleware: MiddlewareConfig,
16 #[serde(default)]
18 pub audit: AuditConfig,
19}
20
21#[derive(Debug, Clone, Deserialize)]
23pub struct AuditConfig {
24 #[serde(default = "default_audit_enabled")]
26 pub enabled: bool,
27 #[serde(default)]
29 pub file_path: Option<String>,
30 #[serde(default)]
32 pub redaction_patterns: Vec<String>,
33}
34
35impl Default for AuditConfig {
36 fn default() -> Self {
37 Self {
38 enabled: true,
39 file_path: None,
40 redaction_patterns: Vec::new(),
41 }
42 }
43}
44
45fn default_audit_enabled() -> bool {
46 true
47}
48
49#[derive(Debug, Clone, Deserialize)]
51pub struct ServerConfig {
52 #[serde(default = "default_listen_addr")]
54 pub listen_addr: String,
55 #[serde(default = "default_listen_port")]
57 pub listen_port: u16,
58}
59
60#[derive(Debug, Clone, Deserialize)]
62pub struct UpstreamConfig {
63 pub url: String,
65}
66
67#[derive(Debug, Clone, Default, Deserialize)]
69pub struct MiddlewareConfig {
70 #[serde(default)]
72 pub blocked_paths: Vec<String>,
73 #[serde(default)]
75 pub required_headers: Vec<String>,
76}
77
78fn default_listen_addr() -> String {
79 "127.0.0.1".to_string()
80}
81
82fn default_listen_port() -> u16 {
83 8080
84}
85
86impl ProxyConfig {
87 pub fn from_file(path: &Path) -> anyhow::Result<Self> {
89 let contents = std::fs::read_to_string(path)?;
90 let config: ProxyConfig = toml::from_str(&contents)?;
91 Ok(config)
92 }
93
94 pub fn parse(s: &str) -> anyhow::Result<Self> {
96 let config: ProxyConfig = toml::from_str(s)?;
97 Ok(config)
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn parse_minimal_config() {
107 let toml = r#"
108[server]
109listen_addr = "0.0.0.0"
110listen_port = 9090
111
112[upstream]
113url = "http://localhost:3000"
114"#;
115 let config = ProxyConfig::parse(toml).unwrap();
116 assert_eq!(config.server.listen_addr, "0.0.0.0");
117 assert_eq!(config.server.listen_port, 9090);
118 assert_eq!(config.upstream.url, "http://localhost:3000");
119 assert!(config.middleware.blocked_paths.is_empty());
120 }
121
122 #[test]
123 fn parse_config_with_middleware() {
124 let toml = r#"
125[server]
126listen_port = 8080
127
128[upstream]
129url = "http://backend:8081"
130
131[middleware]
132blocked_paths = ["/admin", "/secret"]
133required_headers = ["x-api-key"]
134"#;
135 let config = ProxyConfig::parse(toml).unwrap();
136 assert_eq!(config.middleware.blocked_paths.len(), 2);
137 assert_eq!(config.middleware.required_headers, vec!["x-api-key"]);
138 }
139
140 #[test]
141 fn defaults_applied() {
142 let toml = r#"
143[server]
144
145[upstream]
146url = "http://localhost:3000"
147"#;
148 let config = ProxyConfig::parse(toml).unwrap();
149 assert_eq!(config.server.listen_addr, "127.0.0.1");
150 assert_eq!(config.server.listen_port, 8080);
151 }
152}