1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8pub enum CorruptionType {
9 None,
11 RandomBytes,
13 Truncate,
15 BitFlip,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21#[serde(rename_all = "snake_case", tag = "type")]
22pub enum ErrorPattern {
23 Burst {
25 count: usize,
27 interval_ms: u64,
29 },
30 Random {
32 probability: f64,
34 },
35 Sequential {
37 sequence: Vec<u16>,
39 },
40}
41
42impl Default for ErrorPattern {
43 fn default() -> Self {
44 ErrorPattern::Random { probability: 0.1 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Default)]
50pub struct ChaosConfig {
51 pub enabled: bool,
53 pub latency: Option<LatencyConfig>,
55 pub fault_injection: Option<FaultInjectionConfig>,
57 pub rate_limit: Option<RateLimitConfig>,
59 pub traffic_shaping: Option<TrafficShapingConfig>,
61 pub circuit_breaker: Option<CircuitBreakerConfig>,
63 pub bulkhead: Option<BulkheadConfig>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct LatencyConfig {
70 pub enabled: bool,
72 pub fixed_delay_ms: Option<u64>,
74 pub random_delay_range_ms: Option<(u64, u64)>,
76 pub jitter_percent: f64,
78 pub probability: f64,
80}
81
82impl Default for LatencyConfig {
83 fn default() -> Self {
84 Self {
85 enabled: false,
86 fixed_delay_ms: None,
87 random_delay_range_ms: None,
88 jitter_percent: 0.0,
89 probability: 1.0,
90 }
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct FaultInjectionConfig {
97 pub enabled: bool,
99 pub http_errors: Vec<u16>,
101 pub http_error_probability: f64,
103 pub connection_errors: bool,
105 pub connection_error_probability: f64,
107 pub timeout_errors: bool,
109 pub timeout_ms: u64,
111 pub timeout_probability: f64,
113 pub partial_responses: bool,
115 pub partial_response_probability: f64,
117 pub payload_corruption: bool,
119 pub payload_corruption_probability: f64,
121 pub corruption_type: CorruptionType,
123 #[serde(default)]
125 pub error_pattern: Option<ErrorPattern>,
126 #[serde(default)]
128 pub mockai_enabled: bool,
129}
130
131impl Default for FaultInjectionConfig {
132 fn default() -> Self {
133 Self {
134 enabled: false,
135 http_errors: vec![500, 502, 503, 504],
136 http_error_probability: 0.1,
137 connection_errors: false,
138 connection_error_probability: 0.05,
139 timeout_errors: false,
140 timeout_ms: 5000,
141 timeout_probability: 0.05,
142 partial_responses: false,
143 partial_response_probability: 0.05,
144 payload_corruption: false,
145 payload_corruption_probability: 0.05,
146 corruption_type: CorruptionType::None,
147 error_pattern: None,
148 mockai_enabled: false,
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct RateLimitConfig {
156 pub enabled: bool,
158 pub requests_per_second: u32,
160 pub burst_size: u32,
162 pub per_ip: bool,
164 pub per_endpoint: bool,
166}
167
168impl Default for RateLimitConfig {
169 fn default() -> Self {
170 Self {
171 enabled: false,
172 requests_per_second: 100,
173 burst_size: 10,
174 per_ip: false,
175 per_endpoint: false,
176 }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct TrafficShapingConfig {
183 pub enabled: bool,
185 pub bandwidth_limit_bps: u64,
187 pub packet_loss_percent: f64,
189 pub max_connections: u32,
191 pub connection_timeout_ms: u64,
193}
194
195impl Default for TrafficShapingConfig {
196 fn default() -> Self {
197 Self {
198 enabled: false,
199 bandwidth_limit_bps: 0,
200 packet_loss_percent: 0.0,
201 max_connections: 0,
202 connection_timeout_ms: 30000,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct CircuitBreakerConfig {
210 pub enabled: bool,
212 pub failure_threshold: u64,
214 pub success_threshold: u64,
216 pub timeout_ms: u64,
218 pub half_open_max_requests: u32,
220 pub failure_rate_threshold: f64,
222 pub min_requests_for_rate: u64,
224 pub rolling_window_ms: u64,
226}
227
228impl Default for CircuitBreakerConfig {
229 fn default() -> Self {
230 Self {
231 enabled: false,
232 failure_threshold: 5,
233 success_threshold: 2,
234 timeout_ms: 60000,
235 half_open_max_requests: 3,
236 failure_rate_threshold: 50.0,
237 min_requests_for_rate: 10,
238 rolling_window_ms: 10000,
239 }
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct BulkheadConfig {
246 pub enabled: bool,
248 pub max_concurrent_requests: u32,
250 pub max_queue_size: u32,
252 pub queue_timeout_ms: u64,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct NetworkProfile {
259 pub name: String,
261 pub description: String,
263 pub chaos_config: ChaosConfig,
265 #[serde(default)]
267 pub tags: Vec<String>,
268 #[serde(default)]
270 pub builtin: bool,
271}
272
273impl NetworkProfile {
274 pub fn new(name: String, description: String, chaos_config: ChaosConfig) -> Self {
276 Self {
277 name,
278 description,
279 chaos_config,
280 tags: Vec::new(),
281 builtin: false,
282 }
283 }
284
285 pub fn predefined_profiles() -> Vec<Self> {
287 vec![
288 Self {
290 name: "slow_3g".to_string(),
291 description:
292 "Simulates slow 3G network: 400ms latency, 1% packet loss, 400KB/s bandwidth"
293 .to_string(),
294 chaos_config: ChaosConfig {
295 enabled: true,
296 latency: Some(LatencyConfig {
297 enabled: true,
298 fixed_delay_ms: Some(400),
299 random_delay_range_ms: Some((300, 500)),
300 jitter_percent: 10.0,
301 probability: 1.0,
302 }),
303 fault_injection: None,
304 rate_limit: None,
305 traffic_shaping: Some(TrafficShapingConfig {
306 enabled: true,
307 bandwidth_limit_bps: 400_000, packet_loss_percent: 1.0,
309 max_connections: 0,
310 connection_timeout_ms: 30000,
311 }),
312 circuit_breaker: None,
313 bulkhead: None,
314 },
315 tags: vec!["mobile".to_string(), "slow".to_string(), "3g".to_string()],
316 builtin: true,
317 },
318 Self {
320 name: "fast_3g".to_string(),
321 description:
322 "Simulates fast 3G network: 150ms latency, 0.5% packet loss, 1.5MB/s bandwidth"
323 .to_string(),
324 chaos_config: ChaosConfig {
325 enabled: true,
326 latency: Some(LatencyConfig {
327 enabled: true,
328 fixed_delay_ms: Some(150),
329 random_delay_range_ms: Some((100, 200)),
330 jitter_percent: 5.0,
331 probability: 1.0,
332 }),
333 fault_injection: None,
334 rate_limit: None,
335 traffic_shaping: Some(TrafficShapingConfig {
336 enabled: true,
337 bandwidth_limit_bps: 1_500_000, packet_loss_percent: 0.5,
339 max_connections: 0,
340 connection_timeout_ms: 30000,
341 }),
342 circuit_breaker: None,
343 bulkhead: None,
344 },
345 tags: vec!["mobile".to_string(), "fast".to_string(), "3g".to_string()],
346 builtin: true,
347 },
348 Self {
350 name: "flaky_wifi".to_string(),
351 description:
352 "Simulates flaky Wi-Fi: 50ms latency, 5% packet loss, random connection errors"
353 .to_string(),
354 chaos_config: ChaosConfig {
355 enabled: true,
356 latency: Some(LatencyConfig {
357 enabled: true,
358 fixed_delay_ms: Some(50),
359 random_delay_range_ms: Some((30, 100)),
360 jitter_percent: 20.0,
361 probability: 1.0,
362 }),
363 fault_injection: Some(FaultInjectionConfig {
364 enabled: true,
365 http_errors: vec![500, 502, 503],
366 http_error_probability: 0.05, connection_errors: true,
368 connection_error_probability: 0.03, timeout_errors: false,
370 timeout_ms: 5000,
371 timeout_probability: 0.0,
372 partial_responses: false,
373 partial_response_probability: 0.0,
374 payload_corruption: false,
375 payload_corruption_probability: 0.0,
376 corruption_type: CorruptionType::None,
377 error_pattern: None,
378 mockai_enabled: false,
379 }),
380 rate_limit: None,
381 traffic_shaping: Some(TrafficShapingConfig {
382 enabled: true,
383 bandwidth_limit_bps: 0, packet_loss_percent: 5.0,
385 max_connections: 0,
386 connection_timeout_ms: 30000,
387 }),
388 circuit_breaker: None,
389 bulkhead: None,
390 },
391 tags: vec![
392 "wifi".to_string(),
393 "unstable".to_string(),
394 "wireless".to_string(),
395 ],
396 builtin: true,
397 },
398 Self {
400 name: "cable".to_string(),
401 description:
402 "Simulates cable internet: 20ms latency, no packet loss, 10MB/s bandwidth"
403 .to_string(),
404 chaos_config: ChaosConfig {
405 enabled: true,
406 latency: Some(LatencyConfig {
407 enabled: true,
408 fixed_delay_ms: Some(20),
409 random_delay_range_ms: Some((10, 30)),
410 jitter_percent: 2.0,
411 probability: 1.0,
412 }),
413 fault_injection: None,
414 rate_limit: None,
415 traffic_shaping: Some(TrafficShapingConfig {
416 enabled: true,
417 bandwidth_limit_bps: 10_000_000, packet_loss_percent: 0.0,
419 max_connections: 0,
420 connection_timeout_ms: 30000,
421 }),
422 circuit_breaker: None,
423 bulkhead: None,
424 },
425 tags: vec![
426 "broadband".to_string(),
427 "fast".to_string(),
428 "stable".to_string(),
429 ],
430 builtin: true,
431 },
432 Self {
434 name: "dialup".to_string(),
435 description:
436 "Simulates dial-up connection: 2000ms latency, 2% packet loss, 50KB/s bandwidth"
437 .to_string(),
438 chaos_config: ChaosConfig {
439 enabled: true,
440 latency: Some(LatencyConfig {
441 enabled: true,
442 fixed_delay_ms: Some(2000),
443 random_delay_range_ms: Some((1500, 2500)),
444 jitter_percent: 15.0,
445 probability: 1.0,
446 }),
447 fault_injection: None,
448 rate_limit: None,
449 traffic_shaping: Some(TrafficShapingConfig {
450 enabled: true,
451 bandwidth_limit_bps: 50_000, packet_loss_percent: 2.0,
453 max_connections: 0,
454 connection_timeout_ms: 60000, }),
456 circuit_breaker: None,
457 bulkhead: None,
458 },
459 tags: vec![
460 "dialup".to_string(),
461 "slow".to_string(),
462 "legacy".to_string(),
463 ],
464 builtin: true,
465 },
466 ]
467 }
468}
469
470impl Default for BulkheadConfig {
471 fn default() -> Self {
472 Self {
473 enabled: false,
474 max_concurrent_requests: 100,
475 max_queue_size: 10,
476 queue_timeout_ms: 5000,
477 }
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[test]
487 fn test_corruption_type_variants() {
488 let none = CorruptionType::None;
489 let random_bytes = CorruptionType::RandomBytes;
490 let truncate = CorruptionType::Truncate;
491 let bit_flip = CorruptionType::BitFlip;
492
493 assert!(matches!(none, CorruptionType::None));
494 assert!(matches!(random_bytes, CorruptionType::RandomBytes));
495 assert!(matches!(truncate, CorruptionType::Truncate));
496 assert!(matches!(bit_flip, CorruptionType::BitFlip));
497 }
498
499 #[test]
500 fn test_corruption_type_serialize() {
501 let ct = CorruptionType::RandomBytes;
502 let json = serde_json::to_string(&ct).unwrap();
503 assert!(json.contains("random_bytes"));
504 }
505
506 #[test]
507 fn test_corruption_type_deserialize() {
508 let json = r#""bit_flip""#;
509 let ct: CorruptionType = serde_json::from_str(json).unwrap();
510 assert!(matches!(ct, CorruptionType::BitFlip));
511 }
512
513 #[test]
515 fn test_error_pattern_default() {
516 let pattern = ErrorPattern::default();
517 assert!(
518 matches!(pattern, ErrorPattern::Random { probability } if (probability - 0.1).abs() < f64::EPSILON)
519 );
520 }
521
522 #[test]
523 fn test_error_pattern_burst() {
524 let pattern = ErrorPattern::Burst {
525 count: 5,
526 interval_ms: 1000,
527 };
528 if let ErrorPattern::Burst { count, interval_ms } = pattern {
529 assert_eq!(count, 5);
530 assert_eq!(interval_ms, 1000);
531 } else {
532 panic!("Expected Burst pattern");
533 }
534 }
535
536 #[test]
537 fn test_error_pattern_sequential() {
538 let pattern = ErrorPattern::Sequential {
539 sequence: vec![500, 502, 503],
540 };
541 if let ErrorPattern::Sequential { sequence } = pattern {
542 assert_eq!(sequence.len(), 3);
543 assert!(sequence.contains(&500));
544 } else {
545 panic!("Expected Sequential pattern");
546 }
547 }
548
549 #[test]
550 fn test_error_pattern_serialize() {
551 let pattern = ErrorPattern::Burst {
552 count: 3,
553 interval_ms: 500,
554 };
555 let json = serde_json::to_string(&pattern).unwrap();
556 assert!(json.contains("burst"));
557 assert!(json.contains("count"));
558 }
559
560 #[test]
562 fn test_chaos_config_default() {
563 let config = ChaosConfig::default();
564 assert!(!config.enabled);
565 assert!(config.latency.is_none());
566 assert!(config.fault_injection.is_none());
567 assert!(config.rate_limit.is_none());
568 assert!(config.traffic_shaping.is_none());
569 assert!(config.circuit_breaker.is_none());
570 assert!(config.bulkhead.is_none());
571 }
572
573 #[test]
574 fn test_chaos_config_with_latency() {
575 let config = ChaosConfig {
576 enabled: true,
577 latency: Some(LatencyConfig::default()),
578 ..Default::default()
579 };
580 assert!(config.enabled);
581 assert!(config.latency.is_some());
582 }
583
584 #[test]
585 fn test_chaos_config_serialize() {
586 let config = ChaosConfig::default();
587 let json = serde_json::to_string(&config).unwrap();
588 assert!(json.contains("enabled"));
589 }
590
591 #[test]
593 fn test_latency_config_default() {
594 let config = LatencyConfig::default();
595 assert!(!config.enabled);
596 assert!(config.fixed_delay_ms.is_none());
597 assert!(config.random_delay_range_ms.is_none());
598 assert_eq!(config.jitter_percent, 0.0);
599 assert_eq!(config.probability, 1.0);
600 }
601
602 #[test]
603 fn test_latency_config_with_fixed_delay() {
604 let config = LatencyConfig {
605 enabled: true,
606 fixed_delay_ms: Some(100),
607 ..Default::default()
608 };
609 assert_eq!(config.fixed_delay_ms, Some(100));
610 }
611
612 #[test]
613 fn test_latency_config_with_random_range() {
614 let config = LatencyConfig {
615 enabled: true,
616 random_delay_range_ms: Some((50, 150)),
617 ..Default::default()
618 };
619 let (min, max) = config.random_delay_range_ms.unwrap();
620 assert_eq!(min, 50);
621 assert_eq!(max, 150);
622 }
623
624 #[test]
626 fn test_fault_injection_config_default() {
627 let config = FaultInjectionConfig::default();
628 assert!(!config.enabled);
629 assert_eq!(config.http_errors, vec![500, 502, 503, 504]);
630 assert_eq!(config.http_error_probability, 0.1);
631 assert!(!config.connection_errors);
632 assert_eq!(config.corruption_type, CorruptionType::None);
633 assert!(!config.mockai_enabled);
634 }
635
636 #[test]
637 fn test_fault_injection_config_with_errors() {
638 let config = FaultInjectionConfig {
639 enabled: true,
640 http_errors: vec![400, 401, 403, 404],
641 http_error_probability: 0.5,
642 ..Default::default()
643 };
644 assert_eq!(config.http_errors.len(), 4);
645 assert!(config.http_errors.contains(&401));
646 }
647
648 #[test]
650 fn test_rate_limit_config_default() {
651 let config = RateLimitConfig::default();
652 assert!(!config.enabled);
653 assert_eq!(config.requests_per_second, 100);
654 assert_eq!(config.burst_size, 10);
655 assert!(!config.per_ip);
656 assert!(!config.per_endpoint);
657 }
658
659 #[test]
661 fn test_traffic_shaping_config_default() {
662 let config = TrafficShapingConfig::default();
663 assert!(!config.enabled);
664 assert_eq!(config.bandwidth_limit_bps, 0);
665 assert_eq!(config.packet_loss_percent, 0.0);
666 assert_eq!(config.max_connections, 0);
667 assert_eq!(config.connection_timeout_ms, 30000);
668 }
669
670 #[test]
672 fn test_circuit_breaker_config_default() {
673 let config = CircuitBreakerConfig::default();
674 assert!(!config.enabled);
675 assert_eq!(config.failure_threshold, 5);
676 assert_eq!(config.success_threshold, 2);
677 assert_eq!(config.timeout_ms, 60000);
678 assert_eq!(config.half_open_max_requests, 3);
679 assert_eq!(config.failure_rate_threshold, 50.0);
680 }
681
682 #[test]
684 fn test_bulkhead_config_default() {
685 let config = BulkheadConfig::default();
686 assert!(!config.enabled);
687 assert_eq!(config.max_concurrent_requests, 100);
688 assert_eq!(config.max_queue_size, 10);
689 assert_eq!(config.queue_timeout_ms, 5000);
690 }
691
692 #[test]
694 fn test_network_profile_new() {
695 let profile = NetworkProfile::new(
696 "test-profile".to_string(),
697 "A test profile".to_string(),
698 ChaosConfig::default(),
699 );
700 assert_eq!(profile.name, "test-profile");
701 assert_eq!(profile.description, "A test profile");
702 assert!(profile.tags.is_empty());
703 assert!(!profile.builtin);
704 }
705
706 #[test]
707 fn test_network_profile_predefined() {
708 let profiles = NetworkProfile::predefined_profiles();
709 assert!(!profiles.is_empty());
710
711 let names: Vec<_> = profiles.iter().map(|p| p.name.as_str()).collect();
713 assert!(names.contains(&"slow_3g"));
714 assert!(names.contains(&"fast_3g"));
715 assert!(names.contains(&"flaky_wifi"));
716 assert!(names.contains(&"cable"));
717 assert!(names.contains(&"dialup"));
718 }
719
720 #[test]
721 fn test_network_profile_predefined_are_builtin() {
722 let profiles = NetworkProfile::predefined_profiles();
723 for profile in &profiles {
724 assert!(profile.builtin, "Profile {} should be builtin", profile.name);
725 assert!(profile.chaos_config.enabled, "Profile {} should be enabled", profile.name);
726 }
727 }
728
729 #[test]
730 fn test_network_profile_slow_3g_has_latency() {
731 let profiles = NetworkProfile::predefined_profiles();
732 let slow_3g = profiles.iter().find(|p| p.name == "slow_3g").unwrap();
733
734 assert!(slow_3g.chaos_config.latency.is_some());
735 let latency = slow_3g.chaos_config.latency.as_ref().unwrap();
736 assert!(latency.enabled);
737 assert_eq!(latency.fixed_delay_ms, Some(400));
738 }
739
740 #[test]
741 fn test_network_profile_flaky_wifi_has_fault_injection() {
742 let profiles = NetworkProfile::predefined_profiles();
743 let flaky_wifi = profiles.iter().find(|p| p.name == "flaky_wifi").unwrap();
744
745 assert!(flaky_wifi.chaos_config.fault_injection.is_some());
746 let fault = flaky_wifi.chaos_config.fault_injection.as_ref().unwrap();
747 assert!(fault.enabled);
748 assert!(fault.connection_errors);
749 }
750
751 #[test]
752 fn test_network_profile_serialize() {
753 let profile =
754 NetworkProfile::new("test".to_string(), "desc".to_string(), ChaosConfig::default());
755 let json = serde_json::to_string(&profile).unwrap();
756 assert!(json.contains("test"));
757 assert!(json.contains("desc"));
758 }
759
760 #[test]
761 fn test_network_profile_deserialize() {
762 let json = r#"{"name":"test","description":"desc","chaos_config":{"enabled":false},"tags":[],"builtin":false}"#;
763 let profile: NetworkProfile = serde_json::from_str(json).unwrap();
764 assert_eq!(profile.name, "test");
765 assert!(!profile.builtin);
766 }
767
768 #[test]
769 fn test_network_profile_clone() {
770 let profile = NetworkProfile::new(
771 "clone-test".to_string(),
772 "Clone test".to_string(),
773 ChaosConfig::default(),
774 );
775 let cloned = profile.clone();
776 assert_eq!(profile.name, cloned.name);
777 assert_eq!(profile.description, cloned.description);
778 }
779}