use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use crate::syntax::Did;
use tokio::sync::Mutex;
use tokio::time::Instant;
use crate::identity::Identity;
use crate::identity::IdentityError;
use crate::identity::did_web::resolve_did_web;
use crate::identity::plc::PlcClient;
const DEFAULT_TTL: Duration = Duration::from_secs(300);
const DEFAULT_CAPACITY: usize = 1024;
struct CacheEntry {
identity: Arc<Identity>,
expires_at: Instant,
}
pub struct Directory {
plc: PlcClient,
http: reqwest::Client,
cache: Mutex<HashMap<Did, CacheEntry>>,
ttl: Duration,
capacity: usize,
}
impl Directory {
pub fn new() -> Self {
Self::with_plc_url("https://plc.directory")
}
pub fn with_plc_url(plc_url: &str) -> Self {
Directory {
plc: PlcClient::new(plc_url),
http: reqwest::Client::new(),
cache: Mutex::new(HashMap::new()),
ttl: DEFAULT_TTL,
capacity: DEFAULT_CAPACITY,
}
}
pub async fn lookup_did(&self, did: &Did) -> Result<Arc<Identity>, IdentityError> {
{
let cache = self.cache.lock().await;
if let Some(entry) = cache.get(did)
&& entry.expires_at > Instant::now()
{
return Ok(Arc::clone(&entry.identity));
}
}
let doc = match did.method() {
"plc" => self.plc.resolve(did).await?,
"web" => resolve_did_web(did, &self.http).await?,
method => {
return Err(IdentityError::NotFound(format!(
"unsupported DID method: {method}"
)));
}
};
let identity = Arc::new(Identity::from_document(doc)?);
let mut cache = self.cache.lock().await;
if cache.len() >= self.capacity && !cache.contains_key(did) {
let expired_key = cache
.iter()
.find(|(_, e)| e.expires_at <= Instant::now())
.map(|(k, _)| k.clone());
if let Some(k) = expired_key {
cache.remove(&k);
} else if let Some(k) = cache.keys().next().cloned() {
cache.remove(&k);
}
}
cache.insert(
did.clone(),
CacheEntry {
identity: Arc::clone(&identity),
expires_at: Instant::now() + self.ttl,
},
);
Ok(identity)
}
}
impl Default for Directory {
fn default() -> Self {
Self::new()
}
}