limtr 0.2.0

Implement rate limits for all sort of applications and scenarios.
Documentation
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, oneshot};
use crate::back::entity::{Blocks, IndexMapLimtr};
use crate::FeatureRaw;
use crate::utils::time;


pub(crate) type Callback<T> = oneshot::Sender<T>;

#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct Usage {
    calls: usize,
    expiration: u64,
    last_call: u64,
}

#[derive(Debug)]
pub(crate) enum Directive {
    SetLimit {
        id: String,
        feature: FeatureRaw,
        seconds: u32,
    },
    UpdateLimit {
        id: String,
        feature: FeatureRaw,
        max_calls: usize,
        seconds: u32,
        callback: Callback<u64>,
    },
    GetLimit {
        id: String,
        feature: FeatureRaw,
        callback: Callback<u64>,
    },
    Clear,
}

impl Directive {
    pub async fn handle(self, index_map: &mut IndexMapLimtr) { match self {
        Directive::SetLimit { id, feature, seconds } => {
            let blocks = get_or_create_blocks(index_map, id);
            tokio::spawn(async move {
                let calls = 0;
                let expiration = time::in_future(seconds);

                let mut lock = blocks.lock().await;

                if let Some(usage) = lock.get_mut(&feature) {
                    usage.calls = calls;
                    usage.expiration = expiration;
                    usage.last_call = time::now();
                } else {
                    lock.insert(feature, Usage {
                        calls,
                        expiration,
                        last_call: time::now(),
                    });
                }

                drop(lock);
            });
        },
        Directive::UpdateLimit { id, feature, max_calls, seconds, callback } => {
            let blocks = get_or_create_blocks(index_map, id);
            tokio::spawn(async move {
                let mut lock = blocks.lock().await;

                let mut exp = 0;
                let now = time::now();

                if let Some(usage) = lock.get_mut(&feature) {
                    if usage.calls >= max_calls {
                        usage.calls = 0;
                        usage.expiration = time::from_point(now, seconds);
                        usage.last_call = now;
                    } else if usage.expiration <= now && usage.expiration != 0 {
                        usage.calls = 1;
                        usage.expiration = 0;
                        usage.last_call = now;
                    } else if time::from_point(usage.last_call, seconds) <= now && usage.expiration == 0 {
                        usage.calls = 0;
                        usage.last_call = now;
                    } else {
                        if usage.expiration == 0 {
                            usage.calls += 1;
                            usage.last_call = now;
                        }
                    }

                    exp = usage.expiration;
                } else {
                    lock.insert(feature, Usage {
                        calls: 1,
                        expiration: exp,
                        last_call: now,
                    });
                }

                drop(lock);

                let _ = callback.send(exp);
            });
        },
        Directive::GetLimit { id, feature, callback } => {
            if let Some(blocks) = index_map.get(&id).map(Arc::clone) {
                tokio::spawn(async move {
                    let lock = blocks.lock().await;
                    let mut exp = 0;

                    if let Some(usage) = lock.get(&feature) {
                        if time::now() < usage.expiration {
                            exp = usage.expiration;
                        }
                    }

                    let _ = callback.send(exp);
                });
            } else {
                let _ = callback.send(0);
            }
        }
        Directive::Clear => {
            index_map.clear();
        }
    } }
}

unsafe impl Send for Directive {}
unsafe impl Sync for Directive {}

fn get_or_create_blocks(index_map: &mut IndexMapLimtr, id: String) -> Blocks {
    if let Some(blocks) = index_map.get(&id) {
        blocks.clone()
    } else {
        let blocks = Arc::new(
            Mutex::new(
                HashMap::<FeatureRaw, Usage>::new()
            )
        );

        index_map.insert(id, blocks.clone());
        blocks
    }
}