use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use chrono::{Duration, Utc};
use crate::CloudCredentials;
const EXPIRY_MARGIN_SECS: i64 = 60;
pub struct CredentialCache {
entries: Mutex<HashMap<String, Arc<CloudCredentials>>>,
}
impl Default for CredentialCache {
fn default() -> Self {
Self::new()
}
}
impl CredentialCache {
pub fn new() -> Self {
Self {
entries: Mutex::new(HashMap::new()),
}
}
pub fn get(&self, key: &str) -> Option<Arc<CloudCredentials>> {
let entries = self.entries.lock().unwrap();
if let Some(creds) = entries.get(key) {
let margin = Duration::seconds(EXPIRY_MARGIN_SECS);
if creds.expires_at > Utc::now() + margin {
return Some(creds.clone());
}
}
None
}
pub fn put(&self, key: String, creds: Arc<CloudCredentials>) {
let mut entries = self.entries.lock().unwrap();
entries.insert(key, creds);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_creds(expires_in_secs: i64) -> CloudCredentials {
CloudCredentials {
access_key_id: "AKID".into(),
secret_access_key: "secret".into(),
session_token: "token".into(),
expires_at: Utc::now() + Duration::seconds(expires_in_secs),
}
}
#[test]
fn cache_returns_valid_entry() {
let cache = CredentialCache::new();
let creds = Arc::new(make_creds(600));
cache.put("role-a".into(), creds.clone());
let got = cache.get("role-a");
assert!(got.is_some());
assert_eq!(got.unwrap().access_key_id, "AKID");
}
#[test]
fn cache_evicts_expired_entry() {
let cache = CredentialCache::new();
let creds = Arc::new(make_creds(30));
cache.put("role-b".into(), creds);
let got = cache.get("role-b");
assert!(got.is_none());
}
#[test]
fn cache_miss_for_unknown_key() {
let cache = CredentialCache::new();
assert!(cache.get("unknown").is_none());
}
}