edgee_proxy/
config.rs

1use std::collections::HashSet;
2use std::sync::OnceLock;
3
4use edgee_components_runtime::config::ComponentsConfiguration;
5use edgee_dc_sdk::Autocapture;
6use serde::Deserialize;
7use tracing::level_filters::LevelFilter;
8use tracing::warn;
9
10static CONFIG: OnceLock<StaticConfiguration> = OnceLock::new();
11
12#[derive(Deserialize, Debug, Clone, Default)]
13pub struct StaticConfiguration {
14    #[serde(default)]
15    pub log: LogConfiguration,
16
17    pub http: Option<HttpConfiguration>,
18    pub https: Option<HttpsConfiguration>,
19
20    #[serde(default = "default_compute_config")]
21    pub compute: ComputeConfiguration,
22
23    pub monitor: Option<MonitorConfiguration>,
24
25    #[serde(default)]
26    pub routing: Vec<RoutingConfiguration>,
27
28    #[serde(default)]
29    pub components: ComponentsConfiguration,
30}
31
32fn default_compute_config() -> ComputeConfiguration {
33    ComputeConfiguration {
34        cookie_name: default_cookie_name(),
35        cookie_domain: None,
36        aes_key: default_aes_key(),
37        aes_iv: default_aes_iv(),
38        behind_proxy_cache: false,
39        proxy_only: false,
40        enforce_no_store_policy: false,
41        inject_sdk: false,
42        inject_sdk_position: "append".to_string(),
43        autocapture: Autocapture::default(),
44    }
45}
46
47impl StaticConfiguration {
48    pub fn validate(&self) -> Result<(), Vec<String>> {
49        let validators: Vec<Box<dyn Fn() -> Result<(), String>>> =
50            vec![Box::new(|| self.validate_no_duplicate_domains())];
51
52        let errors: Vec<String> = validators
53            .iter()
54            .filter_map(|validate| validate().err())
55            .collect();
56
57        if errors.is_empty() {
58            Ok(())
59        } else {
60            Err(errors)
61        }
62    }
63
64    fn validate_no_duplicate_domains(&self) -> Result<(), String> {
65        let mut seen = HashSet::new();
66        let mut duplicates = HashSet::new();
67
68        for route in &self.routing {
69            if !seen.insert(&route.domain) {
70                duplicates.insert(&route.domain);
71            }
72            let mut seen_redirection = HashSet::new();
73            let mut redirection_duplicates = HashSet::new();
74            for redirection in &route.redirections {
75                if !seen_redirection.insert(&redirection.source) {
76                    redirection_duplicates.insert(&redirection.source);
77                }
78            }
79
80            if !redirection_duplicates.is_empty() {
81                warn!("duplicate redirections found: {:?}", duplicates)
82            }
83        }
84
85        if !duplicates.is_empty() {
86            Err(format!("duplicate domains found: {duplicates:?}"))
87        } else {
88            Ok(())
89        }
90    }
91}
92
93#[serde_with::serde_as]
94#[derive(Deserialize, Debug, Clone)]
95pub struct LogConfiguration {
96    #[serde_as(as = "serde_with::DisplayFromStr")]
97    pub level: LevelFilter,
98    pub span: Option<String>,
99    pub trace_component: Option<String>,
100}
101
102impl Default for LogConfiguration {
103    fn default() -> Self {
104        Self {
105            level: LevelFilter::INFO,
106            span: None,
107            trace_component: None,
108        }
109    }
110}
111
112#[derive(Deserialize, Debug, Clone)]
113pub struct HttpConfiguration {
114    pub address: String,
115    #[serde(default)]
116    pub force_https: bool,
117}
118
119#[derive(Deserialize, Debug, Clone)]
120pub struct HttpsConfiguration {
121    pub address: String,
122    pub cert: String,
123    pub key: String,
124}
125
126#[derive(Deserialize, Debug, Clone)]
127pub struct MonitorConfiguration {
128    pub address: String,
129}
130
131#[derive(Deserialize, Debug, Clone, Default)]
132pub struct RoutingConfiguration {
133    // todo change by host
134    pub domain: String,
135    #[serde(default)]
136    pub rules: Vec<RoutingRulesConfiguration>,
137    pub backends: Vec<BackendConfiguration>,
138    #[serde(default)]
139    pub redirections: Vec<RedirectionsConfiguration>,
140}
141
142#[derive(Deserialize, Debug, Clone, Default)]
143pub struct RedirectionsConfiguration {
144    pub source: String,
145    pub target: String,
146}
147
148#[derive(Deserialize, Debug, Clone)]
149pub struct RoutingRulesConfiguration {
150    pub path: Option<String>,
151    pub path_prefix: Option<String>,
152    pub path_regexp: Option<String>,
153    pub rewrite: Option<String>,
154    pub backend: Option<String>,
155}
156
157impl Default for RoutingRulesConfiguration {
158    fn default() -> Self {
159        Self {
160            path: Default::default(),
161            path_prefix: Some(String::from("/")),
162            path_regexp: Default::default(),
163            rewrite: Default::default(),
164            backend: Default::default(),
165        }
166    }
167}
168
169#[derive(Deserialize, Debug, Clone, Default)]
170pub struct BackendConfiguration {
171    pub name: String,
172    #[serde(default)]
173    pub default: bool,
174    pub address: String,
175    pub enable_ssl: bool,
176    #[serde(default)]
177    pub compress: bool,
178}
179
180#[derive(Deserialize, Debug, Clone, Default)]
181pub struct ComputeConfiguration {
182    #[serde(default = "default_cookie_name")]
183    pub cookie_name: String,
184    pub cookie_domain: Option<String>,
185    #[serde(default = "default_aes_key")]
186    pub aes_key: String,
187    #[serde(default = "default_aes_iv")]
188    pub aes_iv: String,
189    #[serde(default)]
190    pub behind_proxy_cache: bool,
191    #[serde(default)]
192    pub proxy_only: bool,
193    #[serde(default)]
194    pub enforce_no_store_policy: bool,
195    #[serde(default)]
196    pub inject_sdk: bool,
197    #[serde(default)]
198    pub inject_sdk_position: String,
199    #[serde(default)]
200    pub autocapture: Autocapture,
201}
202
203fn default_cookie_name() -> String {
204    "edgee".to_string()
205}
206fn default_aes_key() -> String {
207    "_key.edgee.cloud".to_string()
208}
209fn default_aes_iv() -> String {
210    "__iv.edgee.cloud".to_string()
211}
212
213pub fn set(config: StaticConfiguration) {
214    CONFIG
215        .set(config)
216        .expect("should initialize config only once")
217}
218
219pub fn get() -> &'static StaticConfiguration {
220    CONFIG
221        .get()
222        .expect("config module should have been initialized")
223}
224
225#[cfg(test)]
226pub fn init_test_config() {
227    let config = StaticConfiguration {
228        log: Default::default(),
229        http: Some(HttpConfiguration {
230            address: "127.0.0.1:8080".to_string(),
231            force_https: false,
232        }),
233        https: Some(HttpsConfiguration {
234            address: "127.0.0.1:8443".to_string(),
235            cert: "cert.pem".to_string(),
236            key: "key.pem".to_string(),
237        }),
238        monitor: Some(MonitorConfiguration {
239            address: "127.0.0.1:9090".to_string(),
240        }),
241
242        routing: vec![RoutingConfiguration {
243            domain: "test.com".to_string(),
244            rules: vec![RoutingRulesConfiguration::default()],
245            backends: vec![BackendConfiguration::default()],
246            redirections: vec![RedirectionsConfiguration::default()],
247        }],
248        compute: default_compute_config(),
249        components: ComponentsConfiguration::default(),
250    };
251    config.validate().unwrap();
252    CONFIG.get_or_init(|| config);
253}