1use std::sync::{Arc, RwLock};
20
21use didbot_lookup::{Answer, ForeignRecords, RecordRef, MAX_NAMED_ACCOUNTS};
22use didbot_pds::policy::{LookupRefusal, OwnedFound, OwnedLookup, WriteLookups};
23use didbot_pds::Registry;
24use didbot_policy_records::records::{Accounts, Lookup};
25use didbot_policy_records::Lookups;
26use serde_json::Value;
27use tracing::debug;
28
29pub struct DeclaredLookups(RwLock<Arc<Lookups>>);
37
38impl DeclaredLookups {
39 #[must_use]
41 pub fn new() -> Self {
42 Self(RwLock::new(Arc::new(Lookups::of(
43 &didbot_policy_records::builtin_policies(),
44 &std::collections::HashMap::new(),
45 &std::collections::HashMap::new(),
46 ))))
47 }
48
49 pub fn publish(&self, lookups: Lookups) {
51 *self.0.write().unwrap_or_else(|p| p.into_inner()) = Arc::new(lookups);
52 }
53
54 #[must_use]
56 pub fn current(&self) -> Arc<Lookups> {
57 self.0.read().unwrap_or_else(|p| p.into_inner()).clone()
58 }
59}
60
61impl Default for DeclaredLookups {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67pub struct Reader {
69 declared: Arc<DeclaredLookups>,
70 foreign: Arc<ForeignRecords>,
71 registry: Arc<dyn Registry>,
72}
73
74impl Reader {
75 #[must_use]
79 pub fn new(
80 declared: Arc<DeclaredLookups>,
81 foreign: Arc<ForeignRecords>,
82 registry: Arc<dyn Registry>,
83 ) -> Self {
84 Self {
85 declared,
86 foreign,
87 registry,
88 }
89 }
90
91 fn wanted(&self, collection: &str, after: Option<&Value>) -> Result<Wanted, LookupRefusal> {
99 let declared = self.declared.current();
100 let for_write = declared.for_write(collection);
101 if for_write.is_empty() {
102 return Ok(Vec::new());
103 }
104 let named: Vec<String> = after
105 .map(didbot_policy_records::named_accounts)
106 .unwrap_or_default()
107 .into_iter()
108 .filter(|did| self.registry.account(did).is_none())
109 .collect();
110 let checked = MAX_NAMED_ACCOUNTS.min(self.foreign.limits().per_write / for_write.len());
111 if named.len() > checked {
112 return Err(LookupRefusal(format!(
113 "this record names more than {checked} accounts, which is more than this server \
114 checks for a declared AI preference; name fewer"
115 )));
116 }
117 let mut wanted = Vec::new();
118 for lookup in for_write {
119 let accounts: &[String] = match lookup.accounts {
120 Accounts::Named => &named,
121 Accounts::Other(_) => &[],
124 };
125 for account in accounts {
126 let reference = match &lookup.rkey {
127 Some(rkey) => RecordRef::new(account, &lookup.collection, rkey),
128 None => RecordRef::collection(account, &lookup.collection),
129 };
130 wanted.push((lookup.clone(), account.clone(), reference));
131 }
132 }
133 Ok(wanted)
134 }
135
136 pub async fn prefetch(&self, collection: &str, after: Option<&Value>) {
141 let Ok(wanted) = self.wanted(collection, after) else {
142 return;
143 };
144 if wanted.is_empty() {
145 return;
146 }
147 let references: Vec<RecordRef> = wanted.into_iter().map(|(_, _, r)| r).collect();
148 self.foreign.prefetch(&references).await;
149 }
150}
151
152type Wanted = Vec<(Lookup, String, RecordRef)>;
155
156impl WriteLookups for Reader {
157 fn answers(
158 &self,
159 _account: &str,
160 collection: &str,
161 after: Option<&Value>,
162 ) -> Result<Vec<OwnedLookup>, LookupRefusal> {
163 let handles = after
164 .map(didbot_policy_records::named_handles)
165 .unwrap_or_default();
166 Ok(self
167 .wanted(collection, after)?
168 .into_iter()
169 .map(|(lookup, account, reference)| {
170 let uri = reference.at_uri();
171 let found = match self.foreign.read(&reference) {
172 Answer::Present(records) => OwnedFound::Records((*records).clone()),
173 Answer::Absent => OwnedFound::Nothing,
174 Answer::Unknown(unknown) => OwnedFound::Unknown(unknown.as_str().to_owned()),
175 };
176 debug!(
177 lookup = %lookup.name,
178 names = %account,
179 uri = %uri,
180 found = match &found {
181 OwnedFound::Records(_) => "records",
182 OwnedFound::Nothing => "nothing",
183 OwnedFound::Unknown(why) => why.as_str(),
184 },
185 "read what a policy declared it reads about an account this write names"
186 );
187 let handle = handles
188 .iter()
189 .find(|(did, _)| *did == account)
190 .map(|(_, handle)| handle.clone());
191 OwnedLookup {
192 name: lookup.name.clone(),
193 account,
194 handle,
195 uri,
196 found,
197 }
198 })
199 .collect())
200 }
201}