@@ -51,6 +51,17 @@ impl RateLimitLayer {
5151 /// * `requests` - Maximum number of requests allowed in the given period
5252 /// * `period` - The time period for the rate limit
5353 ///
54+ /// # Panics
55+ ///
56+ /// Panics if `requests` is `0` or `period` is [`Duration::ZERO`]. Either
57+ /// value would produce a degenerate token bucket whose math cannot
58+ /// represent a finite rate: a zero budget would stall every request
59+ /// indefinitely on the first acquire, and a zero refill period would
60+ /// taint the refill rate with `inf`/`NaN` so subsequent acquires return
61+ /// implementation-defined wait times. If you want to disable pacing,
62+ /// leave the relevant axis on [`ProviderConfig`](crate::provider::ProviderConfig)
63+ /// unset (i.e. `None`) instead of passing zero.
64+ ///
5465 /// # Example
5566 ///
5667 /// ```rust
@@ -63,7 +74,18 @@ impl RateLimitLayer {
6374 /// // Allow 100 requests per minute
6475 /// let layer = RateLimitLayer::new(100, Duration::from_secs(60));
6576 /// ```
77+ #[ track_caller]
6678 pub fn new ( requests : u32 , period : Duration ) -> Self {
79+ assert ! (
80+ requests > 0 ,
81+ "RateLimitLayer requires requests > 0; got 0. \
82+ To disable rate limiting, leave the axis unset on ProviderConfig instead of passing zero."
83+ ) ;
84+ assert ! (
85+ !period. is_zero( ) ,
86+ "RateLimitLayer requires period > 0; got Duration::ZERO. \
87+ To disable pacing, leave min_delay unset on ProviderConfig instead of passing Duration::ZERO."
88+ ) ;
6789 Self {
6890 state : Arc :: new ( Mutex :: new ( RateLimitState :: new ( requests, period) ) ) ,
6991 }
@@ -73,6 +95,11 @@ impl RateLimitLayer {
7395 ///
7496 /// This is a convenience constructor for common rate limiting scenarios.
7597 ///
98+ /// # Panics
99+ ///
100+ /// Panics if `requests` is `0`. See [`RateLimitLayer::new`] for the
101+ /// reasoning and how to express "no rate limit" instead.
102+ ///
76103 /// # Example
77104 ///
78105 /// ```rust
@@ -81,6 +108,7 @@ impl RateLimitLayer {
81108 /// // 25 requests per second
82109 /// let layer = RateLimitLayer::per_second(25);
83110 /// ```
111+ #[ track_caller]
84112 pub fn per_second ( requests : u32 ) -> Self {
85113 Self :: new ( requests, Duration :: from_secs ( 1 ) )
86114 }
@@ -90,6 +118,11 @@ impl RateLimitLayer {
90118 /// This is useful when you want to ensure a fixed delay between
91119 /// consecutive requests rather than allowing bursts.
92120 ///
121+ /// # Panics
122+ ///
123+ /// Panics if `delay` is [`Duration::ZERO`]. See [`RateLimitLayer::new`]
124+ /// for the reasoning and how to express "no pacing" instead.
125+ ///
93126 /// # Example
94127 ///
95128 /// ```rust
@@ -99,6 +132,7 @@ impl RateLimitLayer {
99132 /// // At least 100ms between requests (max 10 req/s)
100133 /// let layer = RateLimitLayer::with_min_delay(Duration::from_millis(100));
101134 /// ```
135+ #[ track_caller]
102136 pub fn with_min_delay ( delay : Duration ) -> Self {
103137 Self :: new ( 1 , delay)
104138 }
@@ -269,6 +303,40 @@ mod tests {
269303 assert ! ( layer. state. lock( ) . await . capacity == 25 ) ;
270304 }
271305
306+ // The three panic tests below pin the constructor contract documented in
307+ // each `# Panics` section. Without them, `RateLimitLayer::per_second(0)`
308+ // builds a layer that stalls every request indefinitely on the first
309+ // acquire (capacity = 0, refill_rate = 0 ⇒ wait_nanos = +inf), and
310+ // `RateLimitLayer::with_min_delay(Duration::ZERO)` builds one whose
311+ // refill math taints `tokens` with `NaN` so every subsequent acquire
312+ // returns implementation-defined wait times. Catching this at
313+ // construction makes the misconfiguration visible immediately instead
314+ // of as flaky throughput or a hung provider in production.
315+
316+ #[ test]
317+ #[ should_panic( expected = "requests > 0" ) ]
318+ fn test_rate_limit_layer_new_rejects_zero_requests ( ) {
319+ let _ = RateLimitLayer :: new ( 0 , Duration :: from_secs ( 1 ) ) ;
320+ }
321+
322+ #[ test]
323+ #[ should_panic( expected = "period > 0" ) ]
324+ fn test_rate_limit_layer_new_rejects_zero_period ( ) {
325+ let _ = RateLimitLayer :: new ( 10 , Duration :: ZERO ) ;
326+ }
327+
328+ #[ test]
329+ #[ should_panic( expected = "requests > 0" ) ]
330+ fn test_rate_limit_per_second_rejects_zero ( ) {
331+ let _ = RateLimitLayer :: per_second ( 0 ) ;
332+ }
333+
334+ #[ test]
335+ #[ should_panic( expected = "period > 0" ) ]
336+ fn test_rate_limit_with_min_delay_rejects_zero ( ) {
337+ let _ = RateLimitLayer :: with_min_delay ( Duration :: ZERO ) ;
338+ }
339+
272340 #[ tokio:: test]
273341 async fn test_rate_limit_enforces_rate ( ) {
274342 // Service that returns immediately
0 commit comments