use crate::cache::SliceCache; use crate::errors::ActorResolverError; use atproto_identity::{ plc::query as plc_query, resolve::{InputType, parse_input}, web::query as web_query, }; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::Mutex; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ActorData { pub did: String, pub handle: Option, pub pds: String, } pub async fn resolve_actor_data( client: &Client, did: &str, ) -> Result { resolve_actor_data_cached(client, did, None).await } pub async fn resolve_actor_data_cached( client: &Client, did: &str, cache: Option>>, ) -> Result { // Try cache first if provided if let Some(cache) = &cache { let cached_result = { let mut cache_lock = cache.lock().await; cache_lock.get_cached_did_resolution(did).await }; if let Ok(Some(actor_data_value)) = cached_result && let Ok(actor_data) = serde_json::from_value::(actor_data_value) { return Ok(actor_data); } } // Cache miss - resolve from PLC/web let actor_data = resolve_actor_data_impl(client, did).await?; // Cache the result if cache is provided if let Some(cache) = &cache && let Ok(actor_data_value) = serde_json::to_value(&actor_data) { let mut cache_lock = cache.lock().await; let _ = cache_lock .cache_did_resolution(did, &actor_data_value) .await; } Ok(actor_data) } pub async fn resolve_actor_data_with_retry( client: &Client, did: &str, cache: Option>>, invalidate_cache_on_retry: bool, ) -> Result { match resolve_actor_data_cached(client, did, cache.clone()).await { Ok(actor_data) => Ok(actor_data), Err(e) => { // If we should invalidate cache on retry and we have a cache if invalidate_cache_on_retry { if let Some(cache) = &cache { let mut cache_lock = cache.lock().await; let _ = cache_lock.invalidate_did_resolution(did).await; } // Retry once with fresh resolution resolve_actor_data_cached(client, did, cache).await } else { Err(e) } } } } async fn resolve_actor_data_impl( client: &Client, did: &str, ) -> Result { let (pds_url, handle) = match parse_input(did) { Ok(InputType::Plc(did_str)) => match plc_query(client, "plc.directory", &did_str).await { Ok(did_doc) => { let pds = did_doc .service .iter() .find(|service| service.r#type.contains("AtprotoPersonalDataServer")) .map(|service| service.service_endpoint.clone()) .map(|url| url.to_string()) .unwrap_or_else(|| "https://bsky.social".to_string()); let handle = did_doc .also_known_as .iter() .find(|aka| aka.starts_with("at://")) .map(|aka| aka.strip_prefix("at://").unwrap_or(aka).to_string()); (pds, handle) } Err(e) => { return Err(ActorResolverError::ResolveFailed(format!( "Failed to query PLC for {}: {:?}", did, e ))); } }, Ok(InputType::Web(did_str)) => match web_query(client, &did_str).await { Ok(did_doc) => { let pds = did_doc .service .iter() .find(|service| service.r#type.contains("AtprotoPersonalDataServer")) .map(|service| service.service_endpoint.clone()) .map(|url| url.to_string()) .unwrap_or_else(|| "https://bsky.social".to_string()); let handle = did_doc .also_known_as .iter() .find(|aka| aka.starts_with("at://")) .map(|aka| aka.strip_prefix("at://").unwrap_or(aka).to_string()); (pds, handle) } Err(e) => { return Err(ActorResolverError::ResolveFailed(format!( "Failed to query web DID for {}: {:?}", did, e ))); } }, Ok(InputType::Handle(_)) => { return Err(ActorResolverError::InvalidSubject); } Err(e) => { return Err(ActorResolverError::ParseFailed(format!( "Failed to parse DID {}: {:?}", did, e ))); } }; Ok(ActorData { did: did.to_string(), handle, pds: pds_url, }) }