duckduckgo_core/rate_limit/progress.rs
1//! Public types backing the `RateLimiter` progress hook. The CLI
2//! installs a closure of type [`ProgressHook`] which the limiter calls
3//! before each cooldown / spacing sleep so the user can see a
4//! transparent "[INFO] rate-limit ..." line on stderr while the
5//! request is gated. Library callers that don't need progress
6//! reporting can ignore this module entirely; the hook is opt-in.
7
8use std::sync::Arc;
9use std::time::Duration;
10
11/// Which kind of wait the limiter is currently performing. Surfaces in
12/// `RateLimitProgress` events so callers can render distinct user-facing
13/// messages for the post-block cooldown vs. the inter-request spacing
14/// gap.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum RateLimitWait {
17 /// Post-block cooldown after a 202/403/429/anomaly response.
18 Cooldown,
19 /// Inter-request spacing (`base_spacing` or `slow_spacing`).
20 Spacing,
21}
22
23impl RateLimitWait {
24 /// Stable lowercase token suitable for short user-facing messages.
25 pub fn as_token(&self) -> &'static str {
26 match self {
27 Self::Cooldown => "cooldown",
28 Self::Spacing => "spacing",
29 }
30 }
31}
32
33/// One progress observation for an in-flight rate-limit wait. Emitted
34/// once per acquire cycle (the inner sleep is bounded so a long
35/// cooldown still yields periodic updates without a separate timer
36/// task).
37///
38/// `total ≈ elapsed + remaining` at the moment of emission, modulo
39/// jitter and small clock drift.
40#[derive(Clone, Copy, Debug)]
41pub struct RateLimitProgress {
42 pub kind: RateLimitWait,
43 pub elapsed: Duration,
44 pub remaining: Duration,
45 pub total: Duration,
46 pub consecutive_blocks: u32,
47}
48
49/// Optional callback the limiter invokes before each wait sleep so a
50/// host (typically the CLI) can render a transparent progress line. The
51/// closure must be cheap; it is called while holding no locks but on
52/// the hot path of the limiter loop.
53pub type ProgressHook = Arc<dyn Fn(RateLimitProgress) + Send + Sync>;
54
55#[cfg(test)]
56mod tests {
57 use super::RateLimitWait;
58
59 #[test]
60 fn wait_tokens_are_stable() {
61 assert_eq!(RateLimitWait::Cooldown.as_token(), "cooldown");
62 assert_eq!(RateLimitWait::Spacing.as_token(), "spacing");
63 }
64}