use anyhow::Result; use async_trait::async_trait; use at_shared::did::Did; /// Resolve a human-readable handle (`alice.bsky.social`) to its [`Did`]. /// /// Distinct from [`DidHandleResolver`], which is the inverse — it resolves /// a DID back to its current handle. Both live in the same module so the /// AppView's handle-sync worker can plug in a stub for tests. #[async_trait] pub trait HandleResolver: Send + Sync { async fn resolve(&self, handle: &str) -> Result>; } /// Resolve a DID (e.g. `did:plc:...`) to its current handle, if known. /// /// Returns `Ok(None)` — never `Err` — when the handle can't be determined /// for legitimate reasons (e.g. unknown DID or unsupported method such as /// `did:web:`). `Err(_)` is reserved for genuine network / protocol /// failures so the worker can distinguish "nothing to do" from "try again /// next pass". #[async_trait] pub trait DidHandleResolver: Send + Sync { async fn resolve_handle(&self, did: &str) -> Result>; } pub struct WellKnownResolver { pub client: reqwest::Client, pub dns_zone: String, } impl WellKnownResolver { pub fn new(dns_zone: String) -> Self { Self { client: reqwest::Client::builder() .timeout(std::time::Duration::from_secs(5)) .build() .unwrap(), dns_zone, } } } #[async_trait] impl HandleResolver for WellKnownResolver { async fn resolve(&self, handle: &str) -> Result> { if let Some(zone) = handle.strip_prefix('@') { if zone.ends_with(&self.dns_zone.trim_start_matches('.')) { let user = handle.trim_start_matches('@').trim_end_matches(&self.dns_zone); if let Some(did) = self.lookup_local(user).await? { return Ok(Some(did)); } } } if let Ok(resp) = self .client .get(format!("https://{}/.well-known/atproto-did", handle)) .send() .await { if resp.status().is_success() { let body = resp.text().await?; let did: Did = body.trim().parse()?; return Ok(Some(did)); } } Ok(None) } } impl WellKnownResolver { async fn lookup_local(&self, user: &str) -> Result> { let _ = user; Ok(None) } } pub async fn resolve_handle(handle: &str, resolver: &dyn HandleResolver) -> Result> { resolver.resolve(handle).await }