1use crate::{ConfigError, ConfigResult};
4use serde::{Deserialize, Serialize};
5use validator::{Validate, ValidationError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
9#[serde(rename_all = "lowercase")]
10pub enum LogLevel {
11 Trace,
13 Debug,
15 Info,
17 Warn,
19 Error,
21}
22
23impl Default for LogLevel {
24 fn default() -> Self {
25 Self::Info
26 }
27}
28
29#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
31pub struct AppConfig {
32 #[serde(default = "default_port")]
34 #[validate(range(min = 1, max = 65535))]
35 pub port: u16,
36
37 #[serde(default)]
39 pub environment: Environment,
40
41 #[serde(default)]
43 pub log_level: LogLevel,
44
45 #[serde(default)]
47 pub use_testnet: bool,
48
49 #[serde(flatten)]
51 #[validate(nested)]
52 pub transaction: TransactionConfig,
53
54 #[serde(flatten)]
56 #[validate(nested)]
57 pub retry: RetryConfig,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
62#[serde(rename_all = "lowercase")]
63pub enum Environment {
64 Development,
66 Staging,
68 Production,
70}
71
72impl Default for Environment {
73 fn default() -> Self {
74 Self::Development
75 }
76}
77
78#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
80pub struct TransactionConfig {
81 #[serde(default = "default_max_gas_price")]
83 #[validate(range(min = 1))]
84 pub max_gas_price_gwei: u64,
85
86 #[serde(default = "default_priority_fee")]
88 pub priority_fee_gwei: u64,
89
90 #[serde(default = "default_slippage")]
92 #[validate(range(min = 0.0, max = 100.0))]
93 pub slippage_tolerance_percent: f64,
94
95 #[serde(default = "default_deadline")]
97 pub deadline_seconds: u64,
98}
99
100#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
102pub struct RetryConfig {
103 #[serde(default = "default_max_retries")]
105 #[validate(range(min = 1, max = 100))]
106 pub max_retry_attempts: u32,
107
108 #[serde(default = "default_retry_delay")]
110 pub retry_delay_ms: u64,
111
112 #[serde(default = "default_backoff_multiplier")]
114 #[validate(custom(function = "validate_backoff_multiplier"))]
115 pub retry_backoff_multiplier: f64,
116
117 #[serde(default = "default_max_retry_delay")]
119 pub max_retry_delay_ms: u64,
120}
121
122fn validate_backoff_multiplier(value: f64) -> Result<(), ValidationError> {
124 if value <= 1.0 {
125 return Err(ValidationError::new(
126 "retry_backoff_multiplier must be greater than 1.0",
127 ));
128 }
129 if value > 10.0 {
130 return Err(ValidationError::new(
131 "retry_backoff_multiplier must be at most 10.0",
132 ));
133 }
134 Ok(())
135}
136
137impl AppConfig {
138 pub fn validate(&self) -> ConfigResult<()> {
140 Validate::validate(self)
142 .map_err(|e| ConfigError::validation(format!("Validation failed: {}", e)))?;
143
144 if self.retry.max_retry_delay_ms < self.retry.retry_delay_ms {
146 return Err(ConfigError::validation(
147 "max_retry_delay_ms must be >= retry_delay_ms",
148 ));
149 }
150
151 Ok(())
152 }
153
154 pub fn validate_config(&self) -> ConfigResult<()> {
156 Validate::validate(self)
157 .map_err(|e| ConfigError::validation(format!("Validation failed: {}", e)))
158 }
159}
160
161impl RetryConfig {
162 pub fn validate_config(&self) -> ConfigResult<()> {
164 Validate::validate(self)
165 .map_err(|e| ConfigError::validation(format!("Validation failed: {}", e)))
166 }
167}
168
169fn default_port() -> u16 {
173 8080
174}
175fn default_max_gas_price() -> u64 {
176 100
177}
178fn default_priority_fee() -> u64 {
179 2
180}
181fn default_slippage() -> f64 {
182 0.5
183}
184fn default_deadline() -> u64 {
185 300
186} fn default_max_retries() -> u32 {
188 3
189}
190fn default_retry_delay() -> u64 {
191 1000
192}
193fn default_backoff_multiplier() -> f64 {
194 2.0
195}
196fn default_max_retry_delay() -> u64 {
197 30000
198}
199
200impl Default for AppConfig {
201 fn default() -> Self {
202 Self {
203 port: default_port(),
204 environment: Environment::default(),
205 log_level: LogLevel::default(),
206 use_testnet: false,
207 transaction: TransactionConfig::default(),
208 retry: RetryConfig::default(),
209 }
210 }
211}
212
213impl Default for TransactionConfig {
214 fn default() -> Self {
215 Self {
216 max_gas_price_gwei: default_max_gas_price(),
217 priority_fee_gwei: default_priority_fee(),
218 slippage_tolerance_percent: default_slippage(),
219 deadline_seconds: default_deadline(),
220 }
221 }
222}
223
224impl Default for RetryConfig {
225 fn default() -> Self {
226 Self {
227 max_retry_attempts: default_max_retries(),
228 retry_delay_ms: default_retry_delay(),
229 retry_backoff_multiplier: default_backoff_multiplier(),
230 max_retry_delay_ms: default_max_retry_delay(),
231 }
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_environment_default_should_return_development() {
241 assert_eq!(Environment::default(), Environment::Development);
242 }
243
244 #[test]
245 fn test_environment_serialization() {
246 let env = Environment::Production;
248 let json = serde_json::to_string(&env).unwrap();
249 assert_eq!(json, "\"production\"");
250
251 let deserialized: Environment = serde_json::from_str(&json).unwrap();
252 assert_eq!(deserialized, Environment::Production);
253 }
254
255 #[test]
256 fn test_default_functions() {
257 assert_eq!(default_port(), 8080);
258 assert_eq!(default_max_gas_price(), 100);
259 assert_eq!(default_priority_fee(), 2);
260 assert_eq!(default_slippage(), 0.5);
261 assert_eq!(default_deadline(), 300);
262 assert_eq!(default_max_retries(), 3);
263 assert_eq!(default_retry_delay(), 1000);
264 assert_eq!(default_backoff_multiplier(), 2.0);
265 assert_eq!(default_max_retry_delay(), 30000);
266 }
267
268 #[test]
269 fn test_app_config_default() {
270 let config = AppConfig::default();
271 assert_eq!(config.port, 8080);
272 assert_eq!(config.environment, Environment::Development);
273 assert_eq!(config.log_level, LogLevel::Info);
274 assert!(!config.use_testnet);
275 assert_eq!(config.transaction.max_gas_price_gwei, 100);
276 assert_eq!(config.retry.max_retry_attempts, 3);
277 }
278
279 #[test]
280 fn test_transaction_config_default() {
281 let config = TransactionConfig::default();
282 assert_eq!(config.max_gas_price_gwei, 100);
283 assert_eq!(config.priority_fee_gwei, 2);
284 assert_eq!(config.slippage_tolerance_percent, 0.5);
285 assert_eq!(config.deadline_seconds, 300);
286 }
287
288 #[test]
289 fn test_retry_config_default() {
290 let config = RetryConfig::default();
291 assert_eq!(config.max_retry_attempts, 3);
292 assert_eq!(config.retry_delay_ms, 1000);
293 assert_eq!(config.retry_backoff_multiplier, 2.0);
294 assert_eq!(config.max_retry_delay_ms, 30000);
295 }
296
297 #[test]
298 fn test_app_config_validate_when_valid_should_return_ok() {
299 let config = AppConfig::default();
300 assert!(config.validate().is_ok());
301 }
302
303 #[test]
304 fn test_log_level_enum_values() {
305 let levels = [
307 LogLevel::Trace,
308 LogLevel::Debug,
309 LogLevel::Info,
310 LogLevel::Warn,
311 LogLevel::Error,
312 ];
313
314 for level in levels {
315 let mut config = AppConfig::default();
316 config.log_level = level;
317 assert!(
318 config.validate().is_ok(),
319 "Log level {:?} should be valid",
320 level
321 );
322 }
323 }
324
325 #[test]
326 fn test_app_config_validate_when_zero_gas_price_should_return_err() {
327 let mut config = AppConfig::default();
328 config.transaction.max_gas_price_gwei = 0;
329
330 let result = config.validate();
331 assert!(result.is_err());
332 }
333
334 #[test]
335 fn test_app_config_validate_when_negative_slippage_should_return_err() {
336 let mut config = AppConfig::default();
337 config.transaction.slippage_tolerance_percent = -1.0;
338
339 let result = config.validate();
340 assert!(result.is_err());
341 }
342
343 #[test]
344 fn test_app_config_validate_when_excessive_slippage_should_return_err() {
345 let mut config = AppConfig::default();
346 config.transaction.slippage_tolerance_percent = 101.0;
347
348 let result = config.validate();
349 assert!(result.is_err());
350 }
351
352 #[test]
353 fn test_app_config_validate_when_boundary_slippage_should_return_ok() {
354 let mut config = AppConfig::default();
356
357 config.transaction.slippage_tolerance_percent = 0.0;
359 assert!(config.validate().is_ok());
360
361 config.transaction.slippage_tolerance_percent = 100.0;
363 assert!(config.validate().is_ok());
364 }
365
366 #[test]
367 fn test_app_config_validate_when_retry_validation_fails_should_return_err() {
368 let mut config = AppConfig::default();
369 config.retry.max_retry_attempts = 0;
370
371 let result = config.validate();
372 assert!(result.is_err());
373 }
374
375 #[test]
376 fn test_retry_config_validate_when_valid_should_return_ok() {
377 let config = RetryConfig::default();
378 assert!(Validate::validate(&config).is_ok());
379 }
380
381 #[test]
382 fn test_retry_config_validate_when_zero_attempts_should_return_err() {
383 let mut config = RetryConfig::default();
384 config.max_retry_attempts = 0;
385
386 let result = Validate::validate(&config);
387 assert!(result.is_err());
388 }
389
390 #[test]
391 fn test_retry_config_validate_when_backoff_multiplier_too_low_should_return_err() {
392 let mut config = RetryConfig::default();
393 config.retry_backoff_multiplier = 1.0;
394
395 let result = Validate::validate(&config);
396 assert!(result.is_err());
397 }
398
399 #[test]
400 fn test_retry_config_validate_when_backoff_multiplier_negative_should_return_err() {
401 let mut config = RetryConfig::default();
402 config.retry_backoff_multiplier = 0.5;
403
404 let result = Validate::validate(&config);
405 assert!(result.is_err());
406 }
407
408 #[test]
409 fn test_retry_config_validate_when_max_delay_less_than_initial_should_return_err() {
410 let mut config = AppConfig::default();
411 config.retry.retry_delay_ms = 5000;
412 config.retry.max_retry_delay_ms = 3000;
413
414 let result = config.validate();
415 assert!(result.is_err());
416 let error = result.unwrap_err();
417 assert!(error
418 .to_string()
419 .contains("max_retry_delay_ms must be >= retry_delay_ms"));
420 }
421
422 #[test]
423 fn test_retry_config_validate_when_max_delay_equals_initial_should_return_ok() {
424 let mut config = AppConfig::default();
425 config.retry.retry_delay_ms = 5000;
426 config.retry.max_retry_delay_ms = 5000;
427
428 assert!(config.validate().is_ok());
429 }
430
431 #[test]
432 fn test_app_config_serialization() {
433 let config = AppConfig::default();
434
435 let json = serde_json::to_string(&config).unwrap();
437 assert!(json.contains("\"port\":8080"));
438 assert!(json.contains("\"environment\":\"development\""));
439 assert!(json.contains("\"log_level\":\"info\""));
440
441 let deserialized: AppConfig = serde_json::from_str(&json).unwrap();
443 assert_eq!(deserialized.port, config.port);
444 assert_eq!(deserialized.environment, config.environment);
445 assert_eq!(deserialized.log_level, config.log_level);
446 }
447
448 #[test]
449 fn test_transaction_config_serialization() {
450 let config = TransactionConfig::default();
451
452 let json = serde_json::to_string(&config).unwrap();
453 assert!(json.contains("\"max_gas_price_gwei\":100"));
454 assert!(json.contains("\"priority_fee_gwei\":2"));
455 assert!(json.contains("\"slippage_tolerance_percent\":0.5"));
456 assert!(json.contains("\"deadline_seconds\":300"));
457
458 let deserialized: TransactionConfig = serde_json::from_str(&json).unwrap();
459 assert_eq!(deserialized.max_gas_price_gwei, config.max_gas_price_gwei);
460 assert_eq!(deserialized.priority_fee_gwei, config.priority_fee_gwei);
461 assert_eq!(
462 deserialized.slippage_tolerance_percent,
463 config.slippage_tolerance_percent
464 );
465 assert_eq!(deserialized.deadline_seconds, config.deadline_seconds);
466 }
467
468 #[test]
469 fn test_retry_config_serialization() {
470 let config = RetryConfig::default();
471
472 let json = serde_json::to_string(&config).unwrap();
473 assert!(json.contains("\"max_retry_attempts\":3"));
474 assert!(json.contains("\"retry_delay_ms\":1000"));
475 assert!(json.contains("\"retry_backoff_multiplier\":2.0"));
476 assert!(json.contains("\"max_retry_delay_ms\":30000"));
477
478 let deserialized: RetryConfig = serde_json::from_str(&json).unwrap();
479 assert_eq!(deserialized.max_retry_attempts, config.max_retry_attempts);
480 assert_eq!(deserialized.retry_delay_ms, config.retry_delay_ms);
481 assert_eq!(
482 deserialized.retry_backoff_multiplier,
483 config.retry_backoff_multiplier
484 );
485 assert_eq!(deserialized.max_retry_delay_ms, config.max_retry_delay_ms);
486 }
487
488 #[test]
489 fn test_environment_variants() {
490 assert_eq!(format!("{:?}", Environment::Development), "Development");
492 assert_eq!(format!("{:?}", Environment::Staging), "Staging");
493 assert_eq!(format!("{:?}", Environment::Production), "Production");
494
495 assert_eq!(Environment::Development, Environment::Development);
497 assert_ne!(Environment::Development, Environment::Production);
498 }
499
500 #[test]
501 fn test_config_debug_implementation() {
502 let config = AppConfig::default();
503 let debug_str = format!("{:?}", config);
504 assert!(debug_str.contains("AppConfig"));
505 assert!(debug_str.contains("port: 8080"));
506 assert!(debug_str.contains("environment: Development"));
507 }
508
509 #[test]
510 fn test_config_clone_implementation() {
511 let config = AppConfig::default();
512 let cloned = config.clone();
513 assert_eq!(config.port, cloned.port);
514 assert_eq!(config.environment, cloned.environment);
515 assert_eq!(config.log_level, cloned.log_level);
516 assert_eq!(config.use_testnet, cloned.use_testnet);
517 }
518
519 #[test]
520 fn test_app_config_with_custom_values() {
521 let mut config = AppConfig::default();
522 config.port = 3000;
523 config.environment = Environment::Production;
524 config.log_level = LogLevel::Debug;
525 config.use_testnet = true;
526 config.transaction.max_gas_price_gwei = 200;
527 config.retry.max_retry_attempts = 5;
528
529 assert!(config.validate().is_ok());
530 assert_eq!(config.port, 3000);
531 assert_eq!(config.environment, Environment::Production);
532 assert_eq!(config.log_level, LogLevel::Debug);
533 assert!(config.use_testnet);
534 assert_eq!(config.transaction.max_gas_price_gwei, 200);
535 assert_eq!(config.retry.max_retry_attempts, 5);
536 }
537}