Skip to main content

tower_resilience_chaos/
lib.rs

1//! Chaos engineering layer for Tower services.
2//!
3//! This crate provides a chaos engineering layer that can inject failures and latency
4//! into Tower services for testing resilience patterns. It's designed to help you verify
5//! that your circuit breakers, retries, timeouts, and other resilience mechanisms work
6//! correctly under adverse conditions.
7//!
8//! # Features
9//!
10//! - **Error Injection**: Inject errors at a configurable rate
11//! - **Latency Injection**: Add random delays to requests
12//! - **Deterministic Testing**: Use seeds for reproducible chaos
13//! - **Event System**: Monitor chaos injection via event listeners
14//! - **Composable**: Works with all other tower-resilience patterns
15//!
16//! # Safety
17//!
18//! **WARNING**: This layer is intended for testing and development only. Never use it in
19//! production environments. Consider using feature flags or environment checks to ensure
20//! chaos layers are only enabled in non-production environments.
21//!
22//! # Basic Example
23//!
24//! ## Latency-Only Chaos (no type parameters needed!)
25//!
26//! ```rust
27//! use tower::ServiceBuilder;
28//! use tower_resilience_chaos::ChaosLayer;
29//! use std::time::Duration;
30//!
31//! # async fn example() {
32//! // No type parameters required for latency-only chaos!
33//! let chaos = ChaosLayer::builder()
34//!     .name("api-chaos")
35//!     .latency_rate(0.2)  // 20% of requests delayed
36//!     .min_latency(Duration::from_millis(50))
37//!     .max_latency(Duration::from_millis(200))
38//!     .build();
39//!
40//! let service = ServiceBuilder::new()
41//!     .layer(chaos)
42//!     .service_fn(|req: String| async move {
43//!         Ok::<String, std::io::Error>(format!("Response to: {}", req))
44//!     });
45//! # }
46//! ```
47//!
48//! ## Error Injection (types inferred from closure)
49//!
50//! ```rust
51//! use tower::ServiceBuilder;
52//! use tower_resilience_chaos::ChaosLayer;
53//! use std::time::Duration;
54//!
55//! # async fn example() {
56//! // Types inferred from the error_fn closure signature
57//! let chaos = ChaosLayer::builder()
58//!     .name("api-chaos")
59//!     .error_rate(0.1)  // 10% of requests fail
60//!     .error_fn(|_req: &String| {
61//!         std::io::Error::new(std::io::ErrorKind::Other, "chaos error!")
62//!     })
63//!     .latency_rate(0.2)  // 20% of remaining requests delayed
64//!     .min_latency(Duration::from_millis(50))
65//!     .max_latency(Duration::from_millis(200))
66//!     .build();
67//!
68//! let service = ServiceBuilder::new()
69//!     .layer(chaos)
70//!     .service_fn(|req: String| async move {
71//!         Ok::<String, std::io::Error>(format!("Response to: {}", req))
72//!     });
73//! # }
74//! ```
75//!
76//! # Testing Circuit Breakers
77//!
78//! ```rust,ignore
79//! use tower::ServiceBuilder;
80//! use tower_resilience_chaos::ChaosLayer;
81//! use tower_resilience_circuitbreaker::CircuitBreakerLayer;
82//! use std::time::Duration;
83//!
84//! # async fn example() {
85//! // Create a chaos layer that fails 60% of requests
86//! // Types inferred from closure signature
87//! let chaos = ChaosLayer::builder()
88//!     .name("circuit-breaker-test")
89//!     .error_rate(0.6)
90//!     .error_fn(|_req: &String| {
91//!         std::io::Error::new(std::io::ErrorKind::Other, "simulated failure")
92//!     })
93//!     .build();
94//!
95//! // Wrap with a circuit breaker
96//! let circuit_breaker = CircuitBreakerLayer::builder()
97//!     .name("test-breaker")
98//!     .failure_rate_threshold(0.5)
99//!     .sliding_window_size(10)
100//!     .build();
101//!
102//! let service = ServiceBuilder::new()
103//!     .layer(circuit_breaker)
104//!     .layer(chaos)
105//!     .service_fn(|req: String| async move {
106//!         Ok::<String, std::io::Error>(req)
107//!     });
108//!
109//! // Make requests - circuit breaker should open after ~5 failures
110//! # }
111//! ```
112//!
113//! # Deterministic Testing
114//!
115//! Use a seed for reproducible chaos injection:
116//!
117//! ```rust
118//! use tower_resilience_chaos::ChaosLayer;
119//!
120//! # async fn example() {
121//! let chaos = ChaosLayer::builder()
122//!     .error_rate(0.5)
123//!     .error_fn(|_req: &()| {
124//!         std::io::Error::new(std::io::ErrorKind::Other, "chaos")
125//!     })
126//!     .seed(42)  // Same seed = same sequence of failures
127//!     .build();
128//!
129//! // Running the same test multiple times will produce the same results
130//! # }
131//! ```
132//!
133//! # Event Monitoring
134//!
135//! Track chaos injection with event listeners:
136//!
137//! ```rust
138//! use tower_resilience_chaos::ChaosLayer;
139//! use std::sync::atomic::{AtomicUsize, Ordering};
140//! use std::sync::Arc;
141//! use std::time::Duration;
142//!
143//! # async fn example() {
144//! let errors = Arc::new(AtomicUsize::new(0));
145//! let latencies = Arc::new(AtomicUsize::new(0));
146//!
147//! let e = errors.clone();
148//! let l = latencies.clone();
149//!
150//! let chaos = ChaosLayer::builder()
151//!     .error_rate(0.1)
152//!     .error_fn(|_req: &()| {
153//!         std::io::Error::new(std::io::ErrorKind::Other, "chaos")
154//!     })
155//!     .latency_rate(0.2)
156//!     .on_error_injected(move || {
157//!         e.fetch_add(1, Ordering::SeqCst);
158//!     })
159//!     .on_latency_injected(move |delay: Duration| {
160//!         l.fetch_add(1, Ordering::SeqCst);
161//!     })
162//!     .build();
163//!
164//! // After running tests, check counters
165//! println!("Errors injected: {}", errors.load(Ordering::SeqCst));
166//! println!("Latencies injected: {}", latencies.load(Ordering::SeqCst));
167//! # }
168//! ```
169//!
170//! # Latency Injection Only
171//!
172//! Test timeout handling without errors (no type parameters needed!):
173//!
174//! ```rust
175//! use tower_resilience_chaos::ChaosLayer;
176//! use std::time::Duration;
177//!
178//! # async fn example() {
179//! // No type parameters required for latency-only chaos!
180//! let chaos = ChaosLayer::builder()
181//!     .latency_rate(0.5)  // 50% of requests delayed
182//!     .min_latency(Duration::from_millis(100))
183//!     .max_latency(Duration::from_millis(500))
184//!     .build();
185//!
186//! // Use with TimeLimiter to test timeout behavior
187//! # }
188//! ```
189
190/// Configuration types for chaos injection.
191pub mod config;
192/// Event types emitted by chaos injection.
193pub mod events;
194/// Tower `Layer` implementation for chaos injection.
195pub mod layer;
196/// Tower `Service` implementation for chaos injection.
197pub mod service;
198
199pub use config::{
200    ChaosConfig, ChaosConfigBuilder, ChaosConfigBuilderWithRate, CustomErrorFn, ErrorInjector,
201    NoErrorInjection,
202};
203pub use events::ChaosEvent;
204pub use layer::ChaosLayer;
205pub use service::Chaos;
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use std::sync::atomic::{AtomicUsize, Ordering};
211    use std::sync::Arc;
212    use std::time::Duration;
213    use tower::{Layer, Service, ServiceExt};
214
215    #[tokio::test]
216    async fn test_no_chaos_passes_through() {
217        // Latency-only chaos - no type parameters needed!
218        let chaos = ChaosLayer::builder().build();
219
220        let mut service = chaos.layer(tower::service_fn(|req: String| async move {
221            Ok::<String, ()>(format!("echo: {}", req))
222        }));
223
224        let response = service
225            .ready()
226            .await
227            .unwrap()
228            .call("test".to_string())
229            .await
230            .unwrap();
231
232        assert_eq!(response, "echo: test");
233    }
234
235    #[tokio::test]
236    async fn test_error_injection_with_seed() {
237        // Error injection - types inferred from closure
238        let chaos = ChaosLayer::builder()
239            .error_rate(1.0) // Always fail
240            .error_fn(|_req: &String| "chaos error")
241            .seed(42)
242            .build();
243
244        let mut service = chaos.layer(tower::service_fn(|req: String| async move {
245            Ok::<String, &'static str>(req)
246        }));
247
248        let result = service
249            .ready()
250            .await
251            .unwrap()
252            .call("test".to_string())
253            .await;
254
255        assert!(result.is_err());
256        assert_eq!(result.unwrap_err(), "chaos error");
257    }
258
259    #[tokio::test]
260    async fn test_latency_injection() {
261        // Latency-only chaos - no type parameters needed!
262        let chaos = ChaosLayer::builder()
263            .latency_rate(1.0) // Always add latency
264            .min_latency(Duration::from_millis(50))
265            .max_latency(Duration::from_millis(50))
266            .seed(42)
267            .build();
268
269        let mut service = chaos.layer(tower::service_fn(|req: String| async move {
270            Ok::<String, ()>(req)
271        }));
272
273        let start = std::time::Instant::now();
274        let _response = service
275            .ready()
276            .await
277            .unwrap()
278            .call("test".to_string())
279            .await
280            .unwrap();
281        let elapsed = start.elapsed();
282
283        // Should have at least 50ms delay (with some tolerance for Windows)
284        assert!(
285            elapsed.as_millis() >= 40,
286            "Expected at least 40ms, got {}ms",
287            elapsed.as_millis()
288        );
289    }
290
291    #[tokio::test]
292    async fn test_event_listeners() {
293        let error_count = Arc::new(AtomicUsize::new(0));
294        let latency_count = Arc::new(AtomicUsize::new(0));
295        let pass_count = Arc::new(AtomicUsize::new(0));
296
297        let e = error_count.clone();
298        let l = latency_count.clone();
299        let p = pass_count.clone();
300
301        // Latency-only chaos with event listeners - no type parameters needed!
302        let chaos = ChaosLayer::builder()
303            .latency_rate(0.0)
304            .on_error_injected(move || {
305                e.fetch_add(1, Ordering::SeqCst);
306            })
307            .on_latency_injected(move |_delay| {
308                l.fetch_add(1, Ordering::SeqCst);
309            })
310            .on_passed_through(move || {
311                p.fetch_add(1, Ordering::SeqCst);
312            })
313            .build();
314
315        let mut service = chaos.layer(tower::service_fn(|req: String| async move {
316            Ok::<String, &'static str>(req)
317        }));
318
319        // Make a request that should pass through
320        let _response = service
321            .ready()
322            .await
323            .unwrap()
324            .call("test".to_string())
325            .await
326            .unwrap();
327
328        assert_eq!(error_count.load(Ordering::SeqCst), 0);
329        assert_eq!(latency_count.load(Ordering::SeqCst), 0);
330        assert_eq!(pass_count.load(Ordering::SeqCst), 1);
331    }
332
333    #[tokio::test]
334    async fn test_deterministic_behavior() {
335        // Create two services with the same seed
336        // Types inferred from closure
337        let make_service = || {
338            let chaos = ChaosLayer::builder()
339                .error_rate(0.5)
340                .error_fn(|_req: &String| "error")
341                .seed(123)
342                .build();
343
344            chaos.layer(tower::service_fn(|req: String| async move {
345                Ok::<String, &'static str>(req)
346            }))
347        };
348
349        let mut service1 = make_service();
350        let mut service2 = make_service();
351
352        // Make the same sequence of requests
353        for i in 0..10 {
354            let req = format!("req{}", i);
355            let result1 = service1.ready().await.unwrap().call(req.clone()).await;
356            let result2 = service2.ready().await.unwrap().call(req).await;
357
358            // Results should be identical
359            assert_eq!(result1.is_ok(), result2.is_ok());
360        }
361    }
362}