···11+use crate::ErrorResponseObject;
12use thiserror::Error;
2334#[derive(Debug, Error)]
···7576 MissingUpstreamCid,
7677 #[error("upstream CID was not valid: {0}")]
7778 BadUpstreamCid(String),
7979+ #[error("upstream atproto-looking bad request")]
8080+ UpstreamBadRequest(ErrorResponseObject),
8181+ #[error("upstream non-atproto bad request")]
8282+ UpstreamBadBadNotGoodRequest(reqwest::Error),
7883}
+1-1
slingshot/src/lib.rs
···88pub use consumer::consume;
99pub use firehose_cache::firehose_cache;
1010pub use identity::Identity;
1111-pub use record::{CachedRecord, Repo};
1111+pub use record::{CachedRecord, ErrorResponseObject, Repo};
1212pub use server::serve;
+51-20
slingshot/src/record.rs
···2233use crate::{Identity, error::RecordError};
44use atrium_api::types::string::{Cid, Did, Nsid, RecordKey};
55-use reqwest::Client;
55+use reqwest::{Client, StatusCode};
66use serde::{Deserialize, Serialize};
77use serde_json::value::RawValue;
88use std::str::FromStr;
···5656 value: Box<RawValue>,
5757}
58585959+#[derive(Debug, Deserialize)]
6060+pub struct ErrorResponseObject {
6161+ error: String,
6262+ #[allow(dead_code)]
6363+ message: String,
6464+}
6565+5966#[derive(Clone)]
6067pub struct Repo {
6168 identity: Identity,
···8794 return Err(RecordError::NotFound("could not get pds for DID"));
8895 };
89969090- // TODO: throttle by host probably, generally guard against outgoing requests
9797+ // cid gets set to None for a retry, if it's Some and we got NotFound
9898+ let mut cid = cid;
91999292- let mut params = vec![
9393- ("repo", did.to_string()),
9494- ("collection", collection.to_string()),
9595- ("rkey", rkey.to_string()),
9696- ];
9797- if let Some(cid) = cid {
9898- params.push(("cid", cid.as_ref().to_string()));
9999- }
100100- let mut url = Url::parse_with_params(&pds, ¶ms)?;
101101- url.set_path("/xrpc/com.atproto.repo.getRecord");
100100+ let res = loop {
101101+ // TODO: throttle outgoing requests by host probably, generally guard against outgoing requests
102102+ let mut params = vec![
103103+ ("repo", did.to_string()),
104104+ ("collection", collection.to_string()),
105105+ ("rkey", rkey.to_string()),
106106+ ];
107107+ if let Some(cid) = cid {
108108+ params.push(("cid", cid.as_ref().to_string()));
109109+ }
110110+ let mut url = Url::parse_with_params(&pds, ¶ms)?;
111111+ url.set_path("/xrpc/com.atproto.repo.getRecord");
112112+113113+ let res = self
114114+ .client
115115+ .get(url.clone())
116116+ .send()
117117+ .await
118118+ .map_err(RecordError::SendError)?;
119119+120120+ if res.status() == StatusCode::BAD_REQUEST {
121121+ // 1. if we're not able to parse json, it's not something we can handle
122122+ let err = res
123123+ .json::<ErrorResponseObject>()
124124+ .await
125125+ .map_err(RecordError::UpstreamBadBadNotGoodRequest)?;
126126+ // 2. if we are, is it a NotFound? and if so, did we try with a CID?
127127+ // if so, retry with no CID (api handler will reject for mismatch but
128128+ // with a nice error + warm cache)
129129+ if err.error == "NotFound" && cid.is_some() {
130130+ cid = &None;
131131+ continue;
132132+ } else {
133133+ return Err(RecordError::UpstreamBadRequest(err));
134134+ }
135135+ }
136136+ break res;
137137+ };
102138103103- let res = self
104104- .client
105105- .get(url)
106106- .send()
107107- .await
108108- .map_err(RecordError::SendError)?
139139+ let data = res
109140 .error_for_status()
110141 .map_err(RecordError::StatusError)? // TODO atproto error handling (think about handling not found)
111142 .json::<RecordResponseObject>()
112143 .await
113144 .map_err(RecordError::ParseJsonError)?; // todo...
114145115115- let Some(cid) = res.cid else {
146146+ let Some(cid) = data.cid else {
116147 return Err(RecordError::MissingUpstreamCid);
117148 };
118149 let cid = Cid::from_str(&cid).map_err(|e| RecordError::BadUpstreamCid(e.to_string()))?;
119150120151 Ok(CachedRecord::Found(RawRecord {
121152 cid,
122122- record: res.value.to_string(),
153153+ record: data.value.to_string(),
123154 }))
124155 }
125156}