gw2lib 3.0.2

A simple to use Gw2 API wrapper library
Documentation
use std::{
    any::{Any, TypeId},
    collections::hash_map::RandomState,
    hash::{BuildHasher, Hash, Hasher},
};

use async_trait::async_trait;
use chrono::{NaiveDateTime, Utc};
use dashmap::{mapref::entry::Entry, DashMap};
use gw2lib_model::{Endpoint, Language};

use crate::cache::Cache;

pub struct InMemoryCache {
    statics: DashMap<(TypeId, u64), (NaiveDateTime, Box<dyn Any + Send + Sync>)>,
    authenticated: DashMap<(TypeId, u64), (NaiveDateTime, Box<dyn Any + Send + Sync>)>,
}

impl Default for InMemoryCache {
    fn default() -> Self {
        let hasher = RandomState::new();
        Self {
            statics: DashMap::with_hasher(hasher.clone()),
            authenticated: DashMap::with_hasher(hasher),
        }
    }
}

#[async_trait]
impl Cache for InMemoryCache {
    async fn insert<T, I, E, A>(
        &self,
        id: &I,
        endpoint: &T,
        expiring: NaiveDateTime,
        lang: Language,
        auth: &Option<A>,
    ) where
        T: Clone + Send + Sync + 'static,
        I: Hash + Sync + 'static + ?Sized,
        E: Endpoint,
        A: Hash + Sync + 'static,
    {
        let hash = hash::<_, T, I, A>(self.statics.hasher(), id, E::LOCALE.then_some(lang), auth);
        let map = if E::AUTHENTICATED {
            &self.authenticated
        } else {
            &self.statics
        };
        map.insert(hash, (expiring, Box::new(endpoint.clone())));
    }

    async fn get<T, I, E, A>(&self, id: &I, lang: Language, auth: &Option<A>) -> Option<T>
    where
        T: Clone + Send + Sync + 'static,
        I: Hash + Sync + 'static + ?Sized,
        E: Endpoint,
        A: Hash + Sync + 'static,
    {
        let hash = hash::<_, T, I, A>(self.statics.hasher(), id, E::LOCALE.then_some(lang), auth);
        let map = if E::AUTHENTICATED {
            &self.authenticated
        } else {
            &self.statics
        };
        let entry = map.entry(hash);
        match entry {
            Entry::Occupied(entry) => {
                let (expiring, any) = entry.get();
                let now = Utc::now().naive_utc();
                if now < *expiring {
                    any.downcast_ref().cloned()
                } else {
                    entry.remove();
                    None
                }
            }
            Entry::Vacant(_) => None,
        }
    }

    async fn cleanup(&self) {
        let now = Utc::now().naive_utc();
        self.statics.retain(|_, (time, _)| *time < now);
        self.authenticated.retain(|_, (time, _)| *time < now);
    }

    async fn wipe_static(&self) {
        self.statics.clear();
    }

    async fn wipe_authenticated(&self) {
        self.authenticated.clear();
    }
}

#[inline]
pub(crate) fn hash<H: BuildHasher, T: 'static, I: 'static + Hash + ?Sized, A: 'static + Hash>(
    hasher: &H,
    id: &I,
    lang: Option<Language>,
    auth: &Option<A>,
) -> (TypeId, u64) {
    let type_id = TypeId::of::<T>();
    let hash = {
        let mut hasher = hasher.build_hasher();
        id.hash(&mut hasher);
        lang.hash(&mut hasher);
        auth.hash(&mut hasher);
        hasher.finish()
    };

    (type_id, hash)
}