Highly ambitious ATProtocol AppView service and sdks
1use crate::cache::SliceCache;
2use crate::errors::ActorResolverError;
3use atproto_identity::{
4 plc::query as plc_query,
5 resolve::{InputType, parse_input},
6 web::query as web_query,
7};
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use tokio::sync::Mutex;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ActorData {
15 pub did: String,
16 pub handle: Option<String>,
17 pub pds: String,
18}
19
20pub async fn resolve_actor_data(
21 client: &Client,
22 did: &str,
23) -> Result<ActorData, ActorResolverError> {
24 resolve_actor_data_cached(client, did, None).await
25}
26
27pub async fn resolve_actor_data_cached(
28 client: &Client,
29 did: &str,
30 cache: Option<Arc<Mutex<SliceCache>>>,
31) -> Result<ActorData, ActorResolverError> {
32 // Try cache first if provided
33 if let Some(cache) = &cache {
34 let cached_result = {
35 let mut cache_lock = cache.lock().await;
36 cache_lock.get_cached_did_resolution(did).await
37 };
38
39 if let Ok(Some(actor_data_value)) = cached_result
40 && let Ok(actor_data) = serde_json::from_value::<ActorData>(actor_data_value)
41 {
42 return Ok(actor_data);
43 }
44 }
45
46 // Cache miss - resolve from PLC/web
47 let actor_data = resolve_actor_data_impl(client, did).await?;
48
49 // Cache the result if cache is provided
50 if let Some(cache) = &cache
51 && let Ok(actor_data_value) = serde_json::to_value(&actor_data)
52 {
53 let mut cache_lock = cache.lock().await;
54 let _ = cache_lock
55 .cache_did_resolution(did, &actor_data_value)
56 .await;
57 }
58
59 Ok(actor_data)
60}
61
62pub async fn resolve_actor_data_with_retry(
63 client: &Client,
64 did: &str,
65 cache: Option<Arc<Mutex<SliceCache>>>,
66 invalidate_cache_on_retry: bool,
67) -> Result<ActorData, ActorResolverError> {
68 match resolve_actor_data_cached(client, did, cache.clone()).await {
69 Ok(actor_data) => Ok(actor_data),
70 Err(e) => {
71 // If we should invalidate cache on retry and we have a cache
72 if invalidate_cache_on_retry {
73 if let Some(cache) = &cache {
74 let mut cache_lock = cache.lock().await;
75 let _ = cache_lock.invalidate_did_resolution(did).await;
76 }
77
78 // Retry once with fresh resolution
79 resolve_actor_data_cached(client, did, cache).await
80 } else {
81 Err(e)
82 }
83 }
84 }
85}
86
87async fn resolve_actor_data_impl(
88 client: &Client,
89 did: &str,
90) -> Result<ActorData, ActorResolverError> {
91 let (pds_url, handle) = match parse_input(did) {
92 Ok(InputType::Plc(did_str)) => match plc_query(client, "plc.directory", &did_str).await {
93 Ok(did_doc) => {
94 let pds = did_doc
95 .service
96 .iter()
97 .find(|service| service.r#type.contains("AtprotoPersonalDataServer"))
98 .map(|service| service.service_endpoint.clone())
99 .map(|url| url.to_string())
100 .unwrap_or_else(|| "https://bsky.social".to_string());
101 let handle = did_doc
102 .also_known_as
103 .iter()
104 .find(|aka| aka.starts_with("at://"))
105 .map(|aka| aka.strip_prefix("at://").unwrap_or(aka).to_string());
106 (pds, handle)
107 }
108 Err(e) => {
109 return Err(ActorResolverError::ResolveFailed(format!(
110 "Failed to query PLC for {}: {:?}",
111 did, e
112 )));
113 }
114 },
115 Ok(InputType::Web(did_str)) => match web_query(client, &did_str).await {
116 Ok(did_doc) => {
117 let pds = did_doc
118 .service
119 .iter()
120 .find(|service| service.r#type.contains("AtprotoPersonalDataServer"))
121 .map(|service| service.service_endpoint.clone())
122 .map(|url| url.to_string())
123 .unwrap_or_else(|| "https://bsky.social".to_string());
124 let handle = did_doc
125 .also_known_as
126 .iter()
127 .find(|aka| aka.starts_with("at://"))
128 .map(|aka| aka.strip_prefix("at://").unwrap_or(aka).to_string());
129 (pds, handle)
130 }
131 Err(e) => {
132 return Err(ActorResolverError::ResolveFailed(format!(
133 "Failed to query web DID for {}: {:?}",
134 did, e
135 )));
136 }
137 },
138 Ok(InputType::Handle(_)) => {
139 return Err(ActorResolverError::InvalidSubject);
140 }
141 Err(e) => {
142 return Err(ActorResolverError::ParseFailed(format!(
143 "Failed to parse DID {}: {:?}",
144 did, e
145 )));
146 }
147 };
148
149 Ok(ActorData {
150 did: did.to_string(),
151 handle,
152 pds: pds_url,
153 })
154}