use futures_util::future::BoxFuture;
#[derive(Debug, Clone, PartialEq)]
pub struct KeyError(pub String);
impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for KeyError {}
pub trait KeySource: Send + Sync {
fn get(&self) -> BoxFuture<'_, Result<Option<String>, KeyError>>;
fn refresh(&self) -> BoxFuture<'_, Result<(), KeyError>>;
fn refreshable(&self) -> bool;
}
pub struct StaticKey(pub Option<String>);
impl KeySource for StaticKey {
fn get(&self) -> BoxFuture<'_, Result<Option<String>, KeyError>> {
let v = self.0.clone();
Box::pin(async move { Ok(v) })
}
fn refresh(&self) -> BoxFuture<'_, Result<(), KeyError>> {
Box::pin(async { Ok(()) })
}
fn refreshable(&self) -> bool {
false
}
}
#[derive(Default)]
pub struct AuthRetry {
refreshed: bool,
}
pub enum AuthAction {
RefreshAndRetry,
Surface,
}
impl AuthRetry {
pub fn on_auth_error(&mut self, source_refreshable: bool) -> AuthAction {
if source_refreshable && !self.refreshed {
self.refreshed = true;
AuthAction::RefreshAndRetry
} else {
AuthAction::Surface
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn block_on<F: std::future::Future>(f: F) -> F::Output {
tokio::runtime::Builder::new_current_thread()
.build()
.unwrap()
.block_on(f)
}
#[test]
fn static_key_returns_value_and_is_not_refreshable() {
let s = StaticKey(Some("sk-abc".into()));
assert_eq!(block_on(s.get()).unwrap(), Some("sk-abc".into()));
assert!(!s.refreshable());
block_on(s.refresh()).unwrap(); assert_eq!(block_on(s.get()).unwrap(), Some("sk-abc".into()));
}
#[test]
fn static_keyless_is_none() {
assert_eq!(block_on(StaticKey(None).get()).unwrap(), None);
}
#[test]
fn auth_retry_refreshes_once_then_surfaces() {
let mut g = AuthRetry::default();
assert!(matches!(g.on_auth_error(true), AuthAction::RefreshAndRetry));
assert!(matches!(g.on_auth_error(true), AuthAction::Surface));
}
#[test]
fn auth_retry_surfaces_immediately_for_static_sources() {
let mut g = AuthRetry::default();
assert!(matches!(g.on_auth_error(false), AuthAction::Surface));
}
}