Skip to main content

camber_cli/
config.rs

1/// Re-export of the shared TLS config block used by the proxy config.
2pub use camber::config::TlsConfig;
3use serde::Deserialize;
4use std::path::Path;
5
6/// Top-level proxy configuration loaded from TOML.
7#[derive(Debug, Deserialize)]
8pub struct Config {
9    listen: Option<Box<str>>,
10    connection_limit: Option<usize>,
11    tls: Option<TlsConfig>,
12    #[serde(rename = "site")]
13    sites: Vec<SiteConfig>,
14}
15
16/// Per-site virtual host configuration.
17#[derive(Debug, Deserialize)]
18pub struct SiteConfig {
19    host: Box<str>,
20    proxy: Option<Box<str>>,
21    root: Option<Box<str>>,
22    health_check: Option<Box<str>>,
23    health_interval: Option<u64>,
24}
25
26impl Config {
27    /// Load, parse, and validate a proxy config file.
28    pub fn load(path: &Path) -> Result<Self, String> {
29        let config: Config = camber::config::load_config(path).map_err(|e| e.to_string())?;
30        config.validate()?;
31        Ok(config)
32    }
33
34    fn validate(&self) -> Result<(), String> {
35        if self.connection_limit == Some(0) {
36            return Err("connection_limit must be at least 1".to_owned());
37        }
38
39        for site in &self.sites {
40            if site.health_interval == Some(0) {
41                return Err(format!(
42                    "site \"{}\" health_interval must be at least 1",
43                    site.host
44                ));
45            }
46
47            match (&site.proxy, &site.root) {
48                (None, None) => {
49                    return Err(format!(
50                        "site \"{}\" must have at least \"proxy\" or \"root\"",
51                        site.host
52                    ));
53                }
54                _ => {}
55            }
56        }
57
58        if let Some(tls) = &self.tls {
59            tls.validate().map_err(|e| e.to_string())?;
60        }
61
62        Ok(())
63    }
64
65    /// Return the bind address for the proxy.
66    ///
67    /// Defaults to `0.0.0.0:8080` when not specified.
68    pub fn listen(&self) -> &str {
69        self.listen.as_deref().unwrap_or("0.0.0.0:8080")
70    }
71
72    /// Return the configured global connection limit.
73    pub fn connection_limit(&self) -> Option<usize> {
74        self.connection_limit
75    }
76
77    /// Return the configured TLS block, if any.
78    pub fn tls(&self) -> Option<&TlsConfig> {
79        self.tls.as_ref()
80    }
81
82    /// Return all configured sites.
83    pub fn sites(&self) -> &[SiteConfig] {
84        &self.sites
85    }
86
87    /// Collect domain names from all site host fields.
88    /// Used when auto-TLS is enabled to pass domains to the ACME provider.
89    pub fn auto_tls_domains(&self) -> Box<[&str]> {
90        self.sites
91            .iter()
92            .map(|s| s.host())
93            .collect::<Vec<_>>()
94            .into_boxed_slice()
95    }
96}
97
98impl SiteConfig {
99    /// Return the host name matched by this site.
100    pub fn host(&self) -> &str {
101        &self.host
102    }
103
104    /// Return the proxy upstream URL, if configured.
105    pub fn proxy(&self) -> Option<&str> {
106        self.proxy.as_deref()
107    }
108
109    /// Return the local static file root, if configured.
110    pub fn root(&self) -> Option<&str> {
111        self.root.as_deref()
112    }
113
114    /// Return the health check path, if configured.
115    pub fn health_check(&self) -> Option<&str> {
116        self.health_check.as_deref()
117    }
118
119    /// Return the health check interval in seconds, if configured.
120    pub fn health_interval(&self) -> Option<u64> {
121        self.health_interval
122    }
123}