#[derive(thiserror::Error, Debug, PartialEq)]
pub enum CertificatesCacheError {
#[error("Cache is outdated")]
Outdated,
#[error("Cache is empty")]
Empty,
}
#[derive(Debug, Default)]
pub(crate) struct CertificatesCache {
inner: async_lock::RwLock<Option<CertificatesCacheStorage>>,
ttl: std::time::Duration,
}
impl CertificatesCache {
#[must_use]
pub(crate) fn new(ttl: std::time::Duration) -> Self {
Self {
ttl,
..Default::default()
}
}
pub(crate) async fn get(&self) -> Result<jsonwebtoken::jwk::JwkSet, CertificatesCacheError> {
let cache = self.inner.read().await;
if let Some(cache) = &*cache {
if cache.stored + self.ttl < chrono::Utc::now() {
Err(CertificatesCacheError::Outdated)
} else {
Ok(cache.jwks.clone())
}
} else {
Err(CertificatesCacheError::Empty)
}
}
pub(crate) async fn update(
&self,
jwks: jsonwebtoken::jwk::JwkSet,
) -> Result<jsonwebtoken::jwk::JwkSet, CertificatesCacheError> {
let mut cache = self.inner.write().await;
let new_cache = CertificatesCacheStorage {
jwks: jwks.clone(),
stored: chrono::Utc::now(),
};
*cache = Some(new_cache);
Ok(jwks)
}
}
#[derive(Debug)]
struct CertificatesCacheStorage {
stored: chrono::DateTime<chrono::Utc>,
jwks: jsonwebtoken::jwk::JwkSet,
}
#[cfg(test)]
mod test {
#[tokio::test]
async fn certificate_cache() {
use super::*;
let cache = CertificatesCache::new(std::time::Duration::from_secs(1));
assert_eq!(Err(CertificatesCacheError::Empty), cache.get().await);
let jwks = jsonwebtoken::jwk::JwkSet { keys: vec![] };
cache.update(jwks.clone()).await.unwrap();
assert_eq!(Ok(jwks), cache.get().await);
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
assert_eq!(Err(CertificatesCacheError::Outdated), cache.get().await);
}
}