rrw 0.1.2

A crate to easily build clients for REST-APIs.
Documentation
use std::time::{Duration, Instant};

use super::mechanism::ThrottleMechanism;

/// A very stupid [ThrottleMechanism], that makes sure every pair of requests has the given delay
/// to satisfy a given request-per-minute count.
///
/// You should probably not use such a mechanism, more sophisticated mechanisms will likely be
/// better in almost all cases.
///
/// # Examples
///
/// No delay needed:
///
/// ```rust
/// # use std::error::Error;
/// # use std::time::{Instant, Duration};
/// # use rrw::throttle::StupidThrottle;
/// # use rrw::throttle::ThrottleMechanism;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// // One request every 10 seconds;
/// let mut throttle = StupidThrottle::new(6.0);
/// let now = Instant::now();
/// let after_11_seconds = now + Duration::from_secs(11);
/// assert_eq!(Duration::from_secs(0), throttle.timeout_delay(now));
/// assert_eq!(
///     Duration::from_secs(0),
///     throttle.timeout_delay(after_11_seconds)
/// );
/// #
/// #     Ok(())
/// # }
/// ```
///
/// Delay needed:
///
/// ```rust
/// # use std::error::Error;
/// # use std::time::{Instant, Duration};
/// # use rrw::throttle::StupidThrottle;
/// # use rrw::throttle::ThrottleMechanism;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// // One request every 10 seconds;
/// let mut throttle = StupidThrottle::new(6.0);
/// let now = Instant::now();
/// let after_5_seconds = now + Duration::from_secs(5);
/// assert_eq!(Duration::from_secs(0), throttle.timeout_delay(now));
/// assert_eq!(
///     Duration::from_secs(5),
///     throttle.timeout_delay(after_5_seconds)
/// );
/// #
/// #     Ok(())
/// # }
/// ```

pub struct StupidThrottle {
    time_between_requests: Duration,
    last_request: Option<Instant>,
}

impl StupidThrottle {
    /// Create a new [StupidThrottle] that allows a maximum requests-per-minute.
    pub fn new(requests_per_minute: f32) -> Self {
        Self {
            time_between_requests: Duration::from_secs(60).div_f32(requests_per_minute),
            last_request: None,
        }
    }
}

impl ThrottleMechanism for StupidThrottle {
    fn timeout_delay(&mut self, now: Instant) -> std::time::Duration {
        if let Some(last) = self.last_request {
            // There has already been other requests.
            if now >= last {
                // This is the first request wanting to use a request-per-minute time.
                let duration = now - last;
                if duration >= self.time_between_requests {
                    // The last request was too old, the new request can go instantly.
                    self.last_request = Some(now);
                    Duration::ZERO
                } else {
                    // The last request has still some time to finish its request-per-minute frame.
                    // Wait so long.
                    let difference = self.time_between_requests - duration;
                    self.last_request = Some(now + difference);
                    difference
                }
            } else {
                // There were more than one request that have overlapping request-per-minute times.
                let duration = last - now + self.time_between_requests;
                self.last_request = Some(now + duration);
                duration
            }
        } else {
            // This is the first request to throttle.
            self.last_request = Some(now);
            Duration::ZERO
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn throttle_stupid_no_wait_needede() {
        // One request every 10 seconds.
        let mut stupid = StupidThrottle::new(6.0);
        let now = Instant::now();
        let after_11_seconds = now + Duration::from_secs(11);
        assert_eq!(Duration::from_secs(0), stupid.timeout_delay(now));
        assert_eq!(
            Duration::from_secs(0),
            stupid.timeout_delay(after_11_seconds)
        );
    }

    #[test]
    fn throttle_stupid_wait_needede() {
        // One request every 10 seconds.
        let mut stupid = StupidThrottle::new(6.0);
        let now = Instant::now();
        let after_5_seconds = now + Duration::from_secs(5);
        assert_eq!(Duration::from_secs(0), stupid.timeout_delay(now));
        assert_eq!(
            Duration::from_secs(5),
            stupid.timeout_delay(after_5_seconds)
        );
    }

    #[test]
    fn throttle_stupid_two_wait_needed() {
        // One request every 10 seconds.
        let mut stupid = StupidThrottle::new(6.0);
        let now = Instant::now();
        let after_5_seconds = now + Duration::from_secs(5);
        let after_7_seconds = now + Duration::from_secs(7);
        assert_eq!(Duration::from_secs(0), stupid.timeout_delay(now));
        assert_eq!(
            Duration::from_secs(5),
            stupid.timeout_delay(after_5_seconds)
        );
        assert_eq!(
            Duration::from_secs(13),
            stupid.timeout_delay(after_7_seconds)
        );
    }

    #[test]
    fn throttle_stupid_multiple_wait_needed() {
        // One request every 10 seconds.
        let mut stupid = StupidThrottle::new(6.0);
        let now = Instant::now();
        let after_3_seconds = now + Duration::from_secs(3);
        let after_5_seconds = now + Duration::from_secs(5);
        let after_7_seconds = now + Duration::from_secs(7);
        assert_eq!(Duration::from_secs(0), stupid.timeout_delay(now));
        assert_eq!(
            Duration::from_secs(7),
            stupid.timeout_delay(after_3_seconds)
        );
        assert_eq!(
            Duration::from_secs(15),
            stupid.timeout_delay(after_5_seconds)
        );
        assert_eq!(
            Duration::from_secs(23),
            stupid.timeout_delay(after_7_seconds)
        );
    }

    #[test]
    fn throttle_stupid_mixed_wait_needed() {
        // One request every 10 seconds.
        let mut stupid = StupidThrottle::new(6.0);
        let now = Instant::now();
        let after_5_seconds = now + Duration::from_secs(5);
        let after_7_seconds = now + Duration::from_secs(7);
        let after_13_seconds = now + Duration::from_secs(13);
        assert_eq!(Duration::from_secs(0), stupid.timeout_delay(now));
        assert_eq!(
            Duration::from_secs(5),
            stupid.timeout_delay(after_5_seconds)
        );
        assert_eq!(
            Duration::from_secs(13),
            stupid.timeout_delay(after_7_seconds)
        );
        assert_eq!(
            Duration::from_secs(17),
            stupid.timeout_delay(after_13_seconds)
        );
    }
}