1pub use camber::config::TlsConfig;
2use serde::Deserialize;
3use std::path::Path;
4
5#[derive(Debug, Deserialize)]
6pub struct Config {
7 listen: Option<Box<str>>,
8 connection_limit: Option<usize>,
9 tls: Option<TlsConfig>,
10 #[serde(rename = "site")]
11 sites: Vec<SiteConfig>,
12}
13
14#[derive(Debug, Deserialize)]
15pub struct SiteConfig {
16 host: Box<str>,
17 proxy: Option<Box<str>>,
18 root: Option<Box<str>>,
19 health_check: Option<Box<str>>,
20 health_interval: Option<u64>,
21}
22
23impl Config {
24 pub fn load(path: &Path) -> Result<Self, String> {
25 let config: Config = camber::config::load_config(path).map_err(|e| e.to_string())?;
26 config.validate()?;
27 Ok(config)
28 }
29
30 fn validate(&self) -> Result<(), String> {
31 if self.connection_limit == Some(0) {
32 return Err("connection_limit must be at least 1".to_owned());
33 }
34
35 for site in &self.sites {
36 if site.health_interval == Some(0) {
37 return Err(format!(
38 "site \"{}\" health_interval must be at least 1",
39 site.host
40 ));
41 }
42
43 match (&site.proxy, &site.root) {
44 (None, None) => {
45 return Err(format!(
46 "site \"{}\" must have at least \"proxy\" or \"root\"",
47 site.host
48 ));
49 }
50 _ => {}
51 }
52 }
53
54 if let Some(tls) = &self.tls {
55 tls.validate().map_err(|e| e.to_string())?;
56 }
57
58 Ok(())
59 }
60
61 pub fn listen(&self) -> &str {
62 self.listen.as_deref().unwrap_or("0.0.0.0:8080")
63 }
64
65 pub fn connection_limit(&self) -> Option<usize> {
66 self.connection_limit
67 }
68
69 pub fn tls(&self) -> Option<&TlsConfig> {
70 self.tls.as_ref()
71 }
72
73 pub fn sites(&self) -> &[SiteConfig] {
74 &self.sites
75 }
76
77 pub fn auto_tls_domains(&self) -> Box<[&str]> {
80 self.sites
81 .iter()
82 .map(|s| s.host())
83 .collect::<Vec<_>>()
84 .into_boxed_slice()
85 }
86}
87
88impl SiteConfig {
89 pub fn host(&self) -> &str {
90 &self.host
91 }
92
93 pub fn proxy(&self) -> Option<&str> {
94 self.proxy.as_deref()
95 }
96
97 pub fn root(&self) -> Option<&str> {
98 self.root.as_deref()
99 }
100
101 pub fn health_check(&self) -> Option<&str> {
102 self.health_check.as_deref()
103 }
104
105 pub fn health_interval(&self) -> Option<u64> {
106 self.health_interval
107 }
108}