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 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}