lastid_sdk/config/
network.rs1#[cfg(feature = "json-schema")]
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use crate::constants::{
14 DEFAULT_CONNECT_TIMEOUT_SECONDS, DEFAULT_CORRELATION_ID_HEADER,
15 DEFAULT_POOL_IDLE_TIMEOUT_SECONDS, DEFAULT_POOL_MAX_IDLE_PER_HOST,
16 DEFAULT_READ_TIMEOUT_SECONDS, DEFAULT_TIMEOUT_SECONDS,
17};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
34#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
35#[serde(default)]
36pub struct NetworkConfig {
37 #[serde(default)]
48 pub proxy_url: Option<String>,
49
50 #[serde(default)]
55 pub https_proxy_url: Option<String>,
56
57 #[serde(default)]
67 pub no_proxy: Option<String>,
68
69 #[serde(default = "default_connect_timeout")]
74 pub connect_timeout_seconds: u64,
75
76 #[serde(default = "default_read_timeout")]
81 pub read_timeout_seconds: u64,
82
83 #[serde(default = "default_timeout")]
88 pub request_timeout_seconds: u64,
89
90 #[serde(default = "default_pool_idle_timeout")]
95 pub pool_idle_timeout_seconds: u64,
96
97 #[serde(default = "default_pool_max_idle")]
102 pub pool_max_idle_per_host: usize,
103
104 #[serde(default = "default_correlation_header")]
111 pub correlation_id_header: String,
112
113 #[serde(default = "default_true")]
119 pub enable_correlation_ids: bool,
120}
121
122const fn default_connect_timeout() -> u64 {
123 DEFAULT_CONNECT_TIMEOUT_SECONDS
124}
125
126const fn default_read_timeout() -> u64 {
127 DEFAULT_READ_TIMEOUT_SECONDS
128}
129
130const fn default_timeout() -> u64 {
131 DEFAULT_TIMEOUT_SECONDS
132}
133
134const fn default_pool_idle_timeout() -> u64 {
135 DEFAULT_POOL_IDLE_TIMEOUT_SECONDS
136}
137
138const fn default_pool_max_idle() -> usize {
139 DEFAULT_POOL_MAX_IDLE_PER_HOST
140}
141
142fn default_correlation_header() -> String {
143 DEFAULT_CORRELATION_ID_HEADER.to_string()
144}
145
146const fn default_true() -> bool {
147 true
148}
149
150impl Default for NetworkConfig {
151 fn default() -> Self {
152 Self {
153 proxy_url: None,
154 https_proxy_url: None,
155 no_proxy: None,
156 connect_timeout_seconds: default_connect_timeout(),
157 read_timeout_seconds: default_read_timeout(),
158 request_timeout_seconds: default_timeout(),
159 pool_idle_timeout_seconds: default_pool_idle_timeout(),
160 pool_max_idle_per_host: default_pool_max_idle(),
161 correlation_id_header: default_correlation_header(),
162 enable_correlation_ids: !cfg!(target_arch = "wasm32"),
167 }
168 }
169}
170
171impl NetworkConfig {
172 pub fn merge(&mut self, other: &Self) {
176 if other.proxy_url.is_some() {
177 self.proxy_url.clone_from(&other.proxy_url);
178 }
179 if other.https_proxy_url.is_some() {
180 self.https_proxy_url.clone_from(&other.https_proxy_url);
181 }
182 if other.no_proxy.is_some() {
183 self.no_proxy.clone_from(&other.no_proxy);
184 }
185 if other.connect_timeout_seconds != default_connect_timeout() {
186 self.connect_timeout_seconds = other.connect_timeout_seconds;
187 }
188 if other.read_timeout_seconds != default_read_timeout() {
189 self.read_timeout_seconds = other.read_timeout_seconds;
190 }
191 if other.request_timeout_seconds != default_timeout() {
192 self.request_timeout_seconds = other.request_timeout_seconds;
193 }
194 if other.pool_idle_timeout_seconds != default_pool_idle_timeout() {
195 self.pool_idle_timeout_seconds = other.pool_idle_timeout_seconds;
196 }
197 if other.pool_max_idle_per_host != default_pool_max_idle() {
198 self.pool_max_idle_per_host = other.pool_max_idle_per_host;
199 }
200 if other.correlation_id_header != default_correlation_header() {
201 self.correlation_id_header
202 .clone_from(&other.correlation_id_header);
203 }
204 if !other.enable_correlation_ids {
205 self.enable_correlation_ids = false;
206 }
207 }
208
209 pub fn validate(&self) -> Result<(), crate::error::LastIDError> {
215 if self.connect_timeout_seconds == 0 {
216 return Err(crate::error::LastIDError::config(
217 "connect_timeout_seconds must be > 0",
218 ));
219 }
220 if self.read_timeout_seconds == 0 {
221 return Err(crate::error::LastIDError::config(
222 "read_timeout_seconds must be > 0",
223 ));
224 }
225 if self.request_timeout_seconds == 0 {
226 return Err(crate::error::LastIDError::config(
227 "request_timeout_seconds must be > 0",
228 ));
229 }
230 if self.pool_max_idle_per_host == 0 {
231 return Err(crate::error::LastIDError::config(
232 "pool_max_idle_per_host must be > 0",
233 ));
234 }
235 if self.correlation_id_header.is_empty() && self.enable_correlation_ids {
236 return Err(crate::error::LastIDError::config(
237 "correlation_id_header cannot be empty when correlation IDs are enabled",
238 ));
239 }
240 Ok(())
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_default_network_config() {
250 let config = NetworkConfig::default();
251
252 assert!(config.proxy_url.is_none());
253 assert_eq!(
254 config.connect_timeout_seconds,
255 DEFAULT_CONNECT_TIMEOUT_SECONDS
256 );
257 assert_eq!(config.read_timeout_seconds, DEFAULT_READ_TIMEOUT_SECONDS);
258 assert_eq!(config.request_timeout_seconds, DEFAULT_TIMEOUT_SECONDS);
259 assert_eq!(
260 config.pool_idle_timeout_seconds,
261 DEFAULT_POOL_IDLE_TIMEOUT_SECONDS
262 );
263 assert_eq!(
264 config.pool_max_idle_per_host,
265 DEFAULT_POOL_MAX_IDLE_PER_HOST
266 );
267 assert_eq!(config.correlation_id_header, DEFAULT_CORRELATION_ID_HEADER);
268 assert!(config.enable_correlation_ids);
269 }
270
271 #[test]
272 fn test_validate_valid_config() {
273 let config = NetworkConfig::default();
274 assert!(config.validate().is_ok());
275 }
276
277 #[test]
278 fn test_validate_zero_connect_timeout() {
279 let mut config = NetworkConfig::default();
280 config.connect_timeout_seconds = 0;
281 assert!(config.validate().is_err());
282 }
283
284 #[test]
285 fn test_validate_zero_read_timeout() {
286 let mut config = NetworkConfig::default();
287 config.read_timeout_seconds = 0;
288 assert!(config.validate().is_err());
289 }
290
291 #[test]
292 fn test_validate_empty_correlation_header() {
293 let mut config = NetworkConfig::default();
294 config.correlation_id_header = String::new();
295 config.enable_correlation_ids = true;
296 assert!(config.validate().is_err());
297 }
298
299 #[test]
300 fn test_validate_empty_header_when_disabled() {
301 let mut config = NetworkConfig::default();
302 config.correlation_id_header = String::new();
303 config.enable_correlation_ids = false;
304 assert!(config.validate().is_ok());
305 }
306
307 #[test]
308 fn test_merge() {
309 let mut base = NetworkConfig::default();
310
311 let other = NetworkConfig {
312 proxy_url: Some("http://proxy.example.com:8080".into()),
313 connect_timeout_seconds: 20,
314 ..Default::default()
315 };
316
317 base.merge(&other);
318
319 assert_eq!(base.proxy_url, Some("http://proxy.example.com:8080".into()));
320 assert_eq!(base.connect_timeout_seconds, 20);
321 assert_eq!(base.read_timeout_seconds, DEFAULT_READ_TIMEOUT_SECONDS);
323 }
324
325 #[test]
326 fn test_from_toml() {
327 let toml = r#"
328 proxy_url = "http://proxy.corp.example.com:8080"
329 connect_timeout_seconds = 15
330 read_timeout_seconds = 45
331 pool_max_idle_per_host = 10
332 correlation_id_header = "X-Trace-ID"
333 "#;
334
335 let config: NetworkConfig = toml::from_str(toml).expect("Valid TOML");
336
337 assert_eq!(
338 config.proxy_url,
339 Some("http://proxy.corp.example.com:8080".into())
340 );
341 assert_eq!(config.connect_timeout_seconds, 15);
342 assert_eq!(config.read_timeout_seconds, 45);
343 assert_eq!(config.pool_max_idle_per_host, 10);
344 assert_eq!(config.correlation_id_header, "X-Trace-ID");
345 }
346}