use std::sync::Mutex;
use async_trait::async_trait;
use crate::request::TokenSourceFetchOptions;
use crate::response::{TokenSourceResponse, TokenSourceResult};
use crate::token_source::TokenSourceConfigurable;
#[async_trait]
pub trait TokenSourceStore: Send + Sync {
async fn store(&self, options: TokenSourceFetchOptions, response: TokenSourceResponse);
async fn retrieve(&self) -> Option<(TokenSourceFetchOptions, TokenSourceResponse)>;
async fn clear(&self);
}
#[derive(Default)]
pub struct TokenSourceInMemoryStore {
cached: Mutex<Option<(TokenSourceFetchOptions, TokenSourceResponse)>>,
}
#[async_trait]
impl TokenSourceStore for TokenSourceInMemoryStore {
async fn store(&self, options: TokenSourceFetchOptions, response: TokenSourceResponse) {
*self.cached.lock().unwrap_or_else(|e| e.into_inner()) = Some((options, response));
}
async fn retrieve(&self) -> Option<(TokenSourceFetchOptions, TokenSourceResponse)> {
self.cached.lock().unwrap_or_else(|e| e.into_inner()).clone()
}
async fn clear(&self) {
*self.cached.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
}
type Validator = Box<dyn Fn(&TokenSourceFetchOptions, &TokenSourceResponse) -> bool + Send + Sync>;
pub struct TokenSourceCached<S> {
source: S,
store: Box<dyn TokenSourceStore>,
validator: Validator,
}
impl<S> TokenSourceCached<S> {
pub(crate) fn new(source: S) -> Self {
Self {
source,
store: Box::new(TokenSourceInMemoryStore::default()),
validator: Box::new(|_, response| response.has_valid_token()),
}
}
pub fn with_store(mut self, store: impl TokenSourceStore + 'static) -> Self {
self.store = Box::new(store);
self
}
pub fn with_validator<F>(mut self, validator: F) -> Self
where
F: Fn(&TokenSourceFetchOptions, &TokenSourceResponse) -> bool + Send + Sync + 'static,
{
self.validator = Box::new(validator);
self
}
pub async fn invalidate(&self) {
self.store.clear().await;
}
pub async fn cached_response(&self) -> Option<TokenSourceResponse> {
self.store.retrieve().await.map(|(_, response)| response)
}
}
#[async_trait]
impl<S: TokenSourceConfigurable + Send + Sync> TokenSourceConfigurable for TokenSourceCached<S> {
async fn fetch(
&self,
options: &TokenSourceFetchOptions,
) -> TokenSourceResult<TokenSourceResponse> {
if let Some((cached_options, cached_response)) = self.store.retrieve().await {
if cached_options == *options && (self.validator)(&cached_options, &cached_response) {
return Ok(cached_response);
}
}
let response = self.source.fetch(options).await?;
self.store.store(options.clone(), response.clone()).await;
Ok(response)
}
}