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}