1use std::{env, str::FromStr};
3use strum::Display;
4
5use crate::{constants::MINIMUM_SECRET_VALUE_LENGTH, models::SecretString};
6
7#[derive(Debug, Clone, PartialEq, Eq, Display)]
8pub enum RepositoryStorageType {
9 InMemory,
10 Redis,
11}
12
13impl FromStr for RepositoryStorageType {
14 type Err = String;
15
16 fn from_str(s: &str) -> Result<Self, Self::Err> {
17 match s.to_lowercase().as_str() {
18 "inmemory" | "in_memory" => Ok(Self::InMemory),
19 "redis" => Ok(Self::Redis),
20 _ => Err(format!("Invalid repository storage type: {}", s)),
21 }
22 }
23}
24
25#[derive(Debug, Clone)]
26pub struct ServerConfig {
27 pub host: String,
29 pub port: u16,
31 pub redis_url: String,
33 pub config_file_path: String,
35 pub api_key: SecretString,
37 pub rate_limit_requests_per_second: u64,
39 pub rate_limit_burst_size: u32,
41 pub metrics_port: u16,
43 pub enable_swagger: bool,
45 pub redis_connection_timeout_ms: u64,
47 pub redis_key_prefix: String,
49 pub rpc_timeout_ms: u64,
51 pub provider_max_retries: u8,
53 pub provider_retry_base_delay_ms: u64,
55 pub provider_retry_max_delay_ms: u64,
57 pub provider_max_failovers: u8,
59 pub repository_storage_type: RepositoryStorageType,
61 pub reset_storage_on_start: bool,
63 pub storage_encryption_key: Option<SecretString>,
65 pub transaction_expiration_hours: u64,
67}
68
69impl ServerConfig {
70 pub fn from_env() -> Self {
92 Self {
93 host: Self::get_host(),
94 port: Self::get_port(),
95 redis_url: Self::get_redis_url(), config_file_path: Self::get_config_file_path(),
97 api_key: Self::get_api_key(), rate_limit_requests_per_second: Self::get_rate_limit_requests_per_second(),
99 rate_limit_burst_size: Self::get_rate_limit_burst_size(),
100 metrics_port: Self::get_metrics_port(),
101 enable_swagger: Self::get_enable_swagger(),
102 redis_connection_timeout_ms: Self::get_redis_connection_timeout_ms(),
103 redis_key_prefix: Self::get_redis_key_prefix(),
104 rpc_timeout_ms: Self::get_rpc_timeout_ms(),
105 provider_max_retries: Self::get_provider_max_retries(),
106 provider_retry_base_delay_ms: Self::get_provider_retry_base_delay_ms(),
107 provider_retry_max_delay_ms: Self::get_provider_retry_max_delay_ms(),
108 provider_max_failovers: Self::get_provider_max_failovers(),
109 repository_storage_type: Self::get_repository_storage_type(),
110 reset_storage_on_start: Self::get_reset_storage_on_start(),
111 storage_encryption_key: Self::get_storage_encryption_key(),
112 transaction_expiration_hours: Self::get_transaction_expiration_hours(),
113 }
114 }
115
116 pub fn get_host() -> String {
120 env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string())
121 }
122
123 pub fn get_port() -> u16 {
125 env::var("APP_PORT")
126 .unwrap_or_else(|_| "8080".to_string())
127 .parse()
128 .unwrap_or(8080)
129 }
130
131 pub fn get_redis_url() -> String {
133 env::var("REDIS_URL").expect("REDIS_URL must be set")
134 }
135
136 pub fn get_redis_url_optional() -> Option<String> {
138 env::var("REDIS_URL").ok()
139 }
140
141 pub fn get_config_file_path() -> String {
143 let conf_dir = if env::var("IN_DOCKER")
144 .map(|val| val == "true")
145 .unwrap_or(false)
146 {
147 "config/".to_string()
148 } else {
149 env::var("CONFIG_DIR").unwrap_or_else(|_| "./config".to_string())
150 };
151
152 let conf_dir = format!("{}/", conf_dir.trim_end_matches('/'));
153 let config_file_name =
154 env::var("CONFIG_FILE_NAME").unwrap_or_else(|_| "config.json".to_string());
155
156 format!("{}{}", conf_dir, config_file_name)
157 }
158
159 pub fn get_api_key() -> SecretString {
161 let api_key = SecretString::new(&env::var("API_KEY").expect("API_KEY must be set"));
162
163 if !api_key.has_minimum_length(MINIMUM_SECRET_VALUE_LENGTH) {
164 panic!(
165 "Security error: API_KEY must be at least {} characters long",
166 MINIMUM_SECRET_VALUE_LENGTH
167 );
168 }
169
170 api_key
171 }
172
173 pub fn get_api_key_optional() -> Option<SecretString> {
175 env::var("API_KEY")
176 .ok()
177 .map(|key| SecretString::new(&key))
178 .filter(|key| key.has_minimum_length(MINIMUM_SECRET_VALUE_LENGTH))
179 }
180
181 pub fn get_rate_limit_requests_per_second() -> u64 {
183 env::var("RATE_LIMIT_REQUESTS_PER_SECOND")
184 .unwrap_or_else(|_| "100".to_string())
185 .parse()
186 .unwrap_or(100)
187 }
188
189 pub fn get_rate_limit_burst_size() -> u32 {
191 env::var("RATE_LIMIT_BURST_SIZE")
192 .unwrap_or_else(|_| "300".to_string())
193 .parse()
194 .unwrap_or(300)
195 }
196
197 pub fn get_metrics_port() -> u16 {
199 env::var("METRICS_PORT")
200 .unwrap_or_else(|_| "8081".to_string())
201 .parse()
202 .unwrap_or(8081)
203 }
204
205 pub fn get_enable_swagger() -> bool {
207 env::var("ENABLE_SWAGGER")
208 .map(|v| v.to_lowercase() == "true")
209 .unwrap_or(false)
210 }
211
212 pub fn get_redis_connection_timeout_ms() -> u64 {
214 env::var("REDIS_CONNECTION_TIMEOUT_MS")
215 .unwrap_or_else(|_| "10000".to_string())
216 .parse()
217 .unwrap_or(10000)
218 }
219
220 pub fn get_redis_key_prefix() -> String {
222 env::var("REDIS_KEY_PREFIX").unwrap_or_else(|_| "oz-relayer".to_string())
223 }
224
225 pub fn get_rpc_timeout_ms() -> u64 {
227 env::var("RPC_TIMEOUT_MS")
228 .unwrap_or_else(|_| "10000".to_string())
229 .parse()
230 .unwrap_or(10000)
231 }
232
233 pub fn get_provider_max_retries() -> u8 {
235 env::var("PROVIDER_MAX_RETRIES")
236 .unwrap_or_else(|_| "3".to_string())
237 .parse()
238 .unwrap_or(3)
239 }
240
241 pub fn get_provider_retry_base_delay_ms() -> u64 {
243 env::var("PROVIDER_RETRY_BASE_DELAY_MS")
244 .unwrap_or_else(|_| "100".to_string())
245 .parse()
246 .unwrap_or(100)
247 }
248
249 pub fn get_provider_retry_max_delay_ms() -> u64 {
251 env::var("PROVIDER_RETRY_MAX_DELAY_MS")
252 .unwrap_or_else(|_| "2000".to_string())
253 .parse()
254 .unwrap_or(2000)
255 }
256
257 pub fn get_provider_max_failovers() -> u8 {
259 env::var("PROVIDER_MAX_FAILOVERS")
260 .unwrap_or_else(|_| "3".to_string())
261 .parse()
262 .unwrap_or(3)
263 }
264
265 pub fn get_repository_storage_type() -> RepositoryStorageType {
267 env::var("REPOSITORY_STORAGE_TYPE")
268 .unwrap_or_else(|_| "in_memory".to_string())
269 .parse()
270 .unwrap_or(RepositoryStorageType::InMemory)
271 }
272
273 pub fn get_reset_storage_on_start() -> bool {
275 env::var("RESET_STORAGE_ON_START")
276 .map(|v| v.to_lowercase() == "true")
277 .unwrap_or(false)
278 }
279
280 pub fn get_storage_encryption_key() -> Option<SecretString> {
282 env::var("STORAGE_ENCRYPTION_KEY")
283 .map(|v| SecretString::new(&v))
284 .ok()
285 }
286
287 pub fn get_transaction_expiration_hours() -> u64 {
289 env::var("TRANSACTION_EXPIRATION_HOURS")
290 .unwrap_or_else(|_| "4".to_string())
291 .parse()
292 .unwrap_or(4)
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use lazy_static::lazy_static;
300 use std::env;
301 use std::sync::Mutex;
302
303 lazy_static! {
305 static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
306 }
307
308 fn setup() {
309 env::remove_var("HOST");
311 env::remove_var("APP_PORT");
312 env::remove_var("REDIS_URL");
313 env::remove_var("CONFIG_DIR");
314 env::remove_var("CONFIG_FILE_NAME");
315 env::remove_var("CONFIG_FILE_PATH");
316 env::remove_var("API_KEY");
317 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
318 env::remove_var("RATE_LIMIT_BURST_SIZE");
319 env::remove_var("METRICS_PORT");
320 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
321 env::remove_var("RPC_TIMEOUT_MS");
322 env::remove_var("PROVIDER_MAX_RETRIES");
323 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
324 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
325 env::remove_var("PROVIDER_MAX_FAILOVERS");
326 env::remove_var("REPOSITORY_STORAGE_TYPE");
327 env::remove_var("RESET_STORAGE_ON_START");
328 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
329 env::set_var("REDIS_URL", "redis://localhost:6379");
331 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
332 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
333 }
334
335 #[test]
336 fn test_default_values() {
337 let _lock = match ENV_MUTEX.lock() {
338 Ok(guard) => guard,
339 Err(poisoned) => poisoned.into_inner(),
340 };
341 setup();
342
343 let config = ServerConfig::from_env();
344
345 assert_eq!(config.host, "0.0.0.0");
346 assert_eq!(config.port, 8080);
347 assert_eq!(config.redis_url, "redis://localhost:6379");
348 assert_eq!(config.config_file_path, "./config/config.json");
349 assert_eq!(
350 config.api_key,
351 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
352 );
353 assert_eq!(config.rate_limit_requests_per_second, 100);
354 assert_eq!(config.rate_limit_burst_size, 300);
355 assert_eq!(config.metrics_port, 8081);
356 assert_eq!(config.redis_connection_timeout_ms, 5000);
357 assert_eq!(config.rpc_timeout_ms, 10000);
358 assert_eq!(config.provider_max_retries, 3);
359 assert_eq!(config.provider_retry_base_delay_ms, 100);
360 assert_eq!(config.provider_retry_max_delay_ms, 2000);
361 assert_eq!(config.provider_max_failovers, 3);
362 assert_eq!(
363 config.repository_storage_type,
364 RepositoryStorageType::InMemory
365 );
366 assert!(!config.reset_storage_on_start);
367 assert_eq!(config.transaction_expiration_hours, 4);
368 }
369
370 #[test]
371 fn test_invalid_port_values() {
372 let _lock = match ENV_MUTEX.lock() {
373 Ok(guard) => guard,
374 Err(poisoned) => poisoned.into_inner(),
375 };
376 setup();
377 env::set_var("REDIS_URL", "redis://localhost:6379");
378 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
379 env::set_var("APP_PORT", "not_a_number");
380 env::set_var("METRICS_PORT", "also_not_a_number");
381 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "invalid");
382 env::set_var("RATE_LIMIT_BURST_SIZE", "invalid");
383 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "invalid");
384 env::set_var("RPC_TIMEOUT_MS", "invalid");
385 env::set_var("PROVIDER_MAX_RETRIES", "invalid");
386 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "invalid");
387 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "invalid");
388 env::set_var("PROVIDER_MAX_FAILOVERS", "invalid");
389 env::set_var("REPOSITORY_STORAGE_TYPE", "invalid");
390 env::set_var("RESET_STORAGE_ON_START", "invalid");
391 env::set_var("TRANSACTION_EXPIRATION_HOURS", "invalid");
392 let config = ServerConfig::from_env();
393
394 assert_eq!(config.port, 8080);
396 assert_eq!(config.metrics_port, 8081);
397 assert_eq!(config.rate_limit_requests_per_second, 100);
398 assert_eq!(config.rate_limit_burst_size, 300);
399 assert_eq!(config.redis_connection_timeout_ms, 10000);
400 assert_eq!(config.rpc_timeout_ms, 10000);
401 assert_eq!(config.provider_max_retries, 3);
402 assert_eq!(config.provider_retry_base_delay_ms, 100);
403 assert_eq!(config.provider_retry_max_delay_ms, 2000);
404 assert_eq!(config.provider_max_failovers, 3);
405 assert_eq!(
406 config.repository_storage_type,
407 RepositoryStorageType::InMemory
408 );
409 assert!(!config.reset_storage_on_start);
410 assert_eq!(config.transaction_expiration_hours, 4);
411 }
412
413 #[test]
414 fn test_custom_values() {
415 let _lock = match ENV_MUTEX.lock() {
416 Ok(guard) => guard,
417 Err(poisoned) => poisoned.into_inner(),
418 };
419 setup();
420
421 env::set_var("HOST", "127.0.0.1");
422 env::set_var("APP_PORT", "9090");
423 env::set_var("REDIS_URL", "redis://custom:6379");
424 env::set_var("CONFIG_DIR", "custom");
425 env::set_var("CONFIG_FILE_NAME", "path.json");
426 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
427 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "200");
428 env::set_var("RATE_LIMIT_BURST_SIZE", "500");
429 env::set_var("METRICS_PORT", "9091");
430 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "10000");
431 env::set_var("RPC_TIMEOUT_MS", "33333");
432 env::set_var("PROVIDER_MAX_RETRIES", "5");
433 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
434 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "3000");
435 env::set_var("PROVIDER_MAX_FAILOVERS", "4");
436 env::set_var("REPOSITORY_STORAGE_TYPE", "in_memory");
437 env::set_var("RESET_STORAGE_ON_START", "true");
438 env::set_var("TRANSACTION_EXPIRATION_HOURS", "6");
439 let config = ServerConfig::from_env();
440
441 assert_eq!(config.host, "127.0.0.1");
442 assert_eq!(config.port, 9090);
443 assert_eq!(config.redis_url, "redis://custom:6379");
444 assert_eq!(config.config_file_path, "custom/path.json");
445 assert_eq!(
446 config.api_key,
447 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
448 );
449 assert_eq!(config.rate_limit_requests_per_second, 200);
450 assert_eq!(config.rate_limit_burst_size, 500);
451 assert_eq!(config.metrics_port, 9091);
452 assert_eq!(config.redis_connection_timeout_ms, 10000);
453 assert_eq!(config.rpc_timeout_ms, 33333);
454 assert_eq!(config.provider_max_retries, 5);
455 assert_eq!(config.provider_retry_base_delay_ms, 200);
456 assert_eq!(config.provider_retry_max_delay_ms, 3000);
457 assert_eq!(config.provider_max_failovers, 4);
458 assert_eq!(
459 config.repository_storage_type,
460 RepositoryStorageType::InMemory
461 );
462 assert!(config.reset_storage_on_start);
463 assert_eq!(config.transaction_expiration_hours, 6);
464 }
465
466 #[test]
467 #[should_panic(expected = "Security error: API_KEY must be at least 32 characters long")]
468 fn test_invalid_api_key_length() {
469 let _lock = match ENV_MUTEX.lock() {
470 Ok(guard) => guard,
471 Err(poisoned) => poisoned.into_inner(),
472 };
473 setup();
474 env::set_var("REDIS_URL", "redis://localhost:6379");
475 env::set_var("API_KEY", "insufficient_length");
476 env::set_var("APP_PORT", "8080");
477 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "100");
478 env::set_var("RATE_LIMIT_BURST_SIZE", "300");
479 env::set_var("METRICS_PORT", "9091");
480 env::set_var("TRANSACTION_EXPIRATION_HOURS", "4");
481
482 let _ = ServerConfig::from_env();
483
484 panic!("Test should have panicked before reaching here");
485 }
486
487 #[test]
489 fn test_individual_getters_with_defaults() {
490 let _lock = match ENV_MUTEX.lock() {
491 Ok(guard) => guard,
492 Err(poisoned) => poisoned.into_inner(),
493 };
494
495 env::remove_var("HOST");
497 env::remove_var("APP_PORT");
498 env::remove_var("REDIS_URL");
499 env::remove_var("CONFIG_DIR");
500 env::remove_var("CONFIG_FILE_NAME");
501 env::remove_var("API_KEY");
502 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
503 env::remove_var("RATE_LIMIT_BURST_SIZE");
504 env::remove_var("METRICS_PORT");
505 env::remove_var("ENABLE_SWAGGER");
506 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
507 env::remove_var("REDIS_KEY_PREFIX");
508 env::remove_var("RPC_TIMEOUT_MS");
509 env::remove_var("PROVIDER_MAX_RETRIES");
510 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
511 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
512 env::remove_var("PROVIDER_MAX_FAILOVERS");
513 env::remove_var("REPOSITORY_STORAGE_TYPE");
514 env::remove_var("RESET_STORAGE_ON_START");
515 env::remove_var("STORAGE_ENCRYPTION_KEY");
516 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
517
518 assert_eq!(ServerConfig::get_host(), "0.0.0.0");
520 assert_eq!(ServerConfig::get_port(), 8080);
521 assert_eq!(ServerConfig::get_redis_url_optional(), None);
522 assert_eq!(ServerConfig::get_config_file_path(), "./config/config.json");
523 assert_eq!(ServerConfig::get_api_key_optional(), None);
524 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 100);
525 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 300);
526 assert_eq!(ServerConfig::get_metrics_port(), 8081);
527 assert!(!ServerConfig::get_enable_swagger());
528 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 10000);
529 assert_eq!(ServerConfig::get_redis_key_prefix(), "oz-relayer");
530 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 10000);
531 assert_eq!(ServerConfig::get_provider_max_retries(), 3);
532 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 100);
533 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 2000);
534 assert_eq!(ServerConfig::get_provider_max_failovers(), 3);
535 assert_eq!(
536 ServerConfig::get_repository_storage_type(),
537 RepositoryStorageType::InMemory
538 );
539 assert!(!ServerConfig::get_reset_storage_on_start());
540 assert!(ServerConfig::get_storage_encryption_key().is_none());
541 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 4);
542 }
543
544 #[test]
545 fn test_individual_getters_with_custom_values() {
546 let _lock = match ENV_MUTEX.lock() {
547 Ok(guard) => guard,
548 Err(poisoned) => poisoned.into_inner(),
549 };
550
551 env::set_var("HOST", "192.168.1.1");
553 env::set_var("APP_PORT", "9999");
554 env::set_var("REDIS_URL", "redis://custom:6379");
555 env::set_var("CONFIG_DIR", "/custom/config");
556 env::set_var("CONFIG_FILE_NAME", "custom.json");
557 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
558 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "500");
559 env::set_var("RATE_LIMIT_BURST_SIZE", "1000");
560 env::set_var("METRICS_PORT", "9999");
561 env::set_var("ENABLE_SWAGGER", "true");
562 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
563 env::set_var("REDIS_KEY_PREFIX", "custom-prefix");
564 env::set_var("RPC_TIMEOUT_MS", "15000");
565 env::set_var("PROVIDER_MAX_RETRIES", "5");
566 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
567 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "5000");
568 env::set_var("PROVIDER_MAX_FAILOVERS", "10");
569 env::set_var("REPOSITORY_STORAGE_TYPE", "redis");
570 env::set_var("RESET_STORAGE_ON_START", "true");
571 env::set_var("STORAGE_ENCRYPTION_KEY", "my-encryption-key");
572 env::set_var("TRANSACTION_EXPIRATION_HOURS", "12");
573
574 assert_eq!(ServerConfig::get_host(), "192.168.1.1");
576 assert_eq!(ServerConfig::get_port(), 9999);
577 assert_eq!(
578 ServerConfig::get_redis_url_optional(),
579 Some("redis://custom:6379".to_string())
580 );
581 assert_eq!(
582 ServerConfig::get_config_file_path(),
583 "/custom/config/custom.json"
584 );
585 assert!(ServerConfig::get_api_key_optional().is_some());
586 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 500);
587 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 1000);
588 assert_eq!(ServerConfig::get_metrics_port(), 9999);
589 assert!(ServerConfig::get_enable_swagger());
590 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 5000);
591 assert_eq!(ServerConfig::get_redis_key_prefix(), "custom-prefix");
592 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 15000);
593 assert_eq!(ServerConfig::get_provider_max_retries(), 5);
594 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 200);
595 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 5000);
596 assert_eq!(ServerConfig::get_provider_max_failovers(), 10);
597 assert_eq!(
598 ServerConfig::get_repository_storage_type(),
599 RepositoryStorageType::Redis
600 );
601 assert!(ServerConfig::get_reset_storage_on_start());
602 assert!(ServerConfig::get_storage_encryption_key().is_some());
603 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 12);
604 }
605
606 #[test]
607 #[should_panic(expected = "REDIS_URL must be set")]
608 fn test_get_redis_url_panics_when_not_set() {
609 let _lock = match ENV_MUTEX.lock() {
610 Ok(guard) => guard,
611 Err(poisoned) => poisoned.into_inner(),
612 };
613
614 env::remove_var("REDIS_URL");
615 let _ = ServerConfig::get_redis_url();
616 }
617
618 #[test]
619 #[should_panic(expected = "API_KEY must be set")]
620 fn test_get_api_key_panics_when_not_set() {
621 let _lock = match ENV_MUTEX.lock() {
622 Ok(guard) => guard,
623 Err(poisoned) => poisoned.into_inner(),
624 };
625
626 env::remove_var("API_KEY");
627 let _ = ServerConfig::get_api_key();
628 }
629
630 #[test]
631 fn test_optional_getters_return_none_safely() {
632 let _lock = match ENV_MUTEX.lock() {
633 Ok(guard) => guard,
634 Err(poisoned) => poisoned.into_inner(),
635 };
636
637 env::remove_var("REDIS_URL");
638 env::remove_var("API_KEY");
639 env::remove_var("STORAGE_ENCRYPTION_KEY");
640
641 assert!(ServerConfig::get_redis_url_optional().is_none());
642 assert!(ServerConfig::get_api_key_optional().is_none());
643 assert!(ServerConfig::get_storage_encryption_key().is_none());
644 }
645
646 #[test]
647 fn test_refactored_from_env_equivalence() {
648 let _lock = match ENV_MUTEX.lock() {
649 Ok(guard) => guard,
650 Err(poisoned) => poisoned.into_inner(),
651 };
652 setup();
653
654 env::set_var("HOST", "custom-host");
656 env::set_var("APP_PORT", "7777");
657 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "250");
658 env::set_var("METRICS_PORT", "7778");
659 env::set_var("ENABLE_SWAGGER", "true");
660 env::set_var("PROVIDER_MAX_RETRIES", "7");
661 env::set_var("TRANSACTION_EXPIRATION_HOURS", "8");
662
663 let config = ServerConfig::from_env();
664
665 assert_eq!(config.host, ServerConfig::get_host());
667 assert_eq!(config.port, ServerConfig::get_port());
668 assert_eq!(config.redis_url, ServerConfig::get_redis_url());
669 assert_eq!(
670 config.config_file_path,
671 ServerConfig::get_config_file_path()
672 );
673 assert_eq!(config.api_key, ServerConfig::get_api_key());
674 assert_eq!(
675 config.rate_limit_requests_per_second,
676 ServerConfig::get_rate_limit_requests_per_second()
677 );
678 assert_eq!(
679 config.rate_limit_burst_size,
680 ServerConfig::get_rate_limit_burst_size()
681 );
682 assert_eq!(config.metrics_port, ServerConfig::get_metrics_port());
683 assert_eq!(config.enable_swagger, ServerConfig::get_enable_swagger());
684 assert_eq!(
685 config.redis_connection_timeout_ms,
686 ServerConfig::get_redis_connection_timeout_ms()
687 );
688 assert_eq!(
689 config.redis_key_prefix,
690 ServerConfig::get_redis_key_prefix()
691 );
692 assert_eq!(config.rpc_timeout_ms, ServerConfig::get_rpc_timeout_ms());
693 assert_eq!(
694 config.provider_max_retries,
695 ServerConfig::get_provider_max_retries()
696 );
697 assert_eq!(
698 config.provider_retry_base_delay_ms,
699 ServerConfig::get_provider_retry_base_delay_ms()
700 );
701 assert_eq!(
702 config.provider_retry_max_delay_ms,
703 ServerConfig::get_provider_retry_max_delay_ms()
704 );
705 assert_eq!(
706 config.provider_max_failovers,
707 ServerConfig::get_provider_max_failovers()
708 );
709 assert_eq!(
710 config.repository_storage_type,
711 ServerConfig::get_repository_storage_type()
712 );
713 assert_eq!(
714 config.reset_storage_on_start,
715 ServerConfig::get_reset_storage_on_start()
716 );
717 assert_eq!(
718 config.storage_encryption_key,
719 ServerConfig::get_storage_encryption_key()
720 );
721 assert_eq!(
722 config.transaction_expiration_hours,
723 ServerConfig::get_transaction_expiration_hours()
724 );
725 }
726}