use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Clone, Deserialize)]
pub struct ProxyConfig {
pub server: ServerConfig,
pub upstream: UpstreamConfig,
#[serde(default)]
pub middleware: MiddlewareConfig,
#[serde(default)]
pub audit: AuditConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AuditConfig {
#[serde(default = "default_audit_enabled")]
pub enabled: bool,
#[serde(default)]
pub file_path: Option<String>,
#[serde(default)]
pub redaction_patterns: Vec<String>,
}
impl Default for AuditConfig {
fn default() -> Self {
Self {
enabled: true,
file_path: None,
redaction_patterns: Vec::new(),
}
}
}
fn default_audit_enabled() -> bool {
true
}
#[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_listen_addr")]
pub listen_addr: String,
#[serde(default = "default_listen_port")]
pub listen_port: u16,
#[serde(default = "default_max_body_bytes")]
pub max_body_bytes: usize,
#[serde(default = "default_upstream_timeout_secs")]
pub upstream_timeout_secs: u64,
#[serde(default = "default_header_read_timeout_secs")]
pub header_read_timeout_secs: u64,
#[serde(default = "default_max_connections")]
pub max_connections: usize,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UpstreamConfig {
pub url: String,
}
fn default_max_body_bytes() -> usize {
10 * 1024 * 1024
}
fn default_upstream_timeout_secs() -> u64 {
30
}
fn default_header_read_timeout_secs() -> u64 {
10
}
fn default_max_connections() -> usize {
1024
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct MiddlewareConfig {
#[serde(default)]
pub blocked_paths: Vec<String>,
#[serde(default)]
pub required_headers: Vec<String>,
}
fn default_listen_addr() -> String {
"127.0.0.1".to_string()
}
fn default_listen_port() -> u16 {
8080
}
impl ProxyConfig {
pub fn from_file(path: &Path) -> anyhow::Result<Self> {
let contents = std::fs::read_to_string(path)?;
let config: ProxyConfig = toml::from_str(&contents)?;
Ok(config)
}
pub fn parse(s: &str) -> anyhow::Result<Self> {
let config: ProxyConfig = toml::from_str(s)?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_minimal_config() {
let toml = r#"
[server]
listen_addr = "0.0.0.0"
listen_port = 9090
[upstream]
url = "http://localhost:3000"
"#;
let config = ProxyConfig::parse(toml).unwrap();
assert_eq!(config.server.listen_addr, "0.0.0.0");
assert_eq!(config.server.listen_port, 9090);
assert_eq!(config.upstream.url, "http://localhost:3000");
assert!(config.middleware.blocked_paths.is_empty());
}
#[test]
fn parse_config_with_middleware() {
let toml = r#"
[server]
listen_port = 8080
[upstream]
url = "http://backend:8081"
[middleware]
blocked_paths = ["/admin", "/secret"]
required_headers = ["x-api-key"]
"#;
let config = ProxyConfig::parse(toml).unwrap();
assert_eq!(config.middleware.blocked_paths.len(), 2);
assert_eq!(config.middleware.required_headers, vec!["x-api-key"]);
}
#[test]
fn defaults_applied() {
let toml = r#"
[server]
[upstream]
url = "http://localhost:3000"
"#;
let config = ProxyConfig::parse(toml).unwrap();
assert_eq!(config.server.listen_addr, "127.0.0.1");
assert_eq!(config.server.listen_port, 8080);
}
}