async-priority-limiter 0.4.4

Throttles prioritised tasks by limiting the max concurrent tasks and minimum time between tasks, with up to two levels based on keys
Documentation
use std::{collections::HashMap, time::Duration};
use tokio::time::{Instant, sleep};

use crate::traits::Key;

#[derive(Debug)]
pub struct Blocks<K: Key> {
    default: Option<Instant>,
    by_key: HashMap<K, Instant>,
}

impl<K: Key> Default for Blocks<K> {
    fn default() -> Self {
        Self {
            default: None,
            by_key: HashMap::new(),
        }
    }
}

impl<K: Key> Blocks<K> {
    pub fn get(&self, key: Option<&K>) -> Option<Duration> {
        self.default
            .as_ref()
            .max(key.and_then(|key| self.by_key.get(key)))
            .map(|instant| instant.duration_since(Instant::now()))
            .filter(|duration| !duration.is_zero())
    }

    pub fn get_default(&self) -> Option<Duration> {
        self.get(None)
    }

    pub fn get_by_key(&self, key: &K) -> Option<Duration> {
        self.get(Some(key))
    }

    pub async fn wait(&self, key: Option<&K>) {
        if let Some(duration) = self.get(key) {
            sleep(duration).await;
        }
    }

    #[allow(unused)]
    pub async fn wait_default(&self) {
        self.wait(None).await;
    }

    #[allow(unused)]
    pub async fn wait_by_key(&self, key: &K) {
        self.wait(Some(key)).await;
    }

    pub fn set_default_at_least(&mut self, instant: Instant) {
        if self
            .default
            .as_ref()
            .is_none_or(|previous| previous < &instant)
        {
            self.default = Some(instant);
        }
    }

    pub fn set_at_least_by_key(&mut self, instant: Instant, key: K) {
        if self
            .by_key
            .get(&key)
            .is_none_or(|previous| previous < &instant)
        {
            self.by_key.insert(key, instant);
        }
    }

    pub fn set_default(&mut self, instant: Option<Instant>) {
        self.default = instant;
    }

    pub fn set_by_key(&mut self, instant: Option<Instant>, key: K) {
        if let Some(instant) = instant {
            self.by_key.insert(key, instant);
        } else {
            self.by_key.remove(&key);
        }
    }
}

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

    #[tokio::test]
    async fn default() {
        let mut blocks = Blocks::<String>::default();

        let start = Instant::now();

        blocks.set_default(Some(start + Duration::from_secs(1)));

        blocks.wait_default().await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 1;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);

        blocks.wait_default().await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 1;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);
    }

    #[tokio::test]
    async fn by_key() {
        let mut blocks = Blocks::default();

        let start = Instant::now();

        let key = "derp";

        blocks.set_by_key(Some(start + Duration::from_secs(1)), key);

        blocks.wait_by_key(&key).await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 1;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);

        blocks.wait_by_key(&key).await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 1;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);
    }

    #[tokio::test]
    async fn default_and_by_key() {
        let mut blocks = Blocks::default();

        let start = Instant::now();

        let key_a = "derp";
        let key_b = "herp";
        let key_c = "flerp";

        blocks.set_default(Some(start + Duration::from_secs(1)));
        blocks.set_by_key(Some(start + Duration::from_secs(2)), key_a);
        blocks.set_by_key(Some(start + Duration::from_secs(4)), key_b);
        blocks.set_by_key(Some(start + Duration::from_secs(3)), key_c);

        blocks.wait_by_key(&key_a).await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 2;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);

        blocks.wait_by_key(&key_b).await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 4;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);

        blocks.wait_by_key(&key_c).await;
        let passed = Instant::now().duration_since(start).as_secs();
        let expected = 4;
        println!("Passed: {passed}s Expected: {expected}s");
        assert_eq!(passed, expected);
    }
}