APIs for links and references in the ATmosphere

retry upstream getRecord without CID if NotFound

Changed files
+57 -21
slingshot
+5
slingshot/src/error.rs
··· 1 + use crate::ErrorResponseObject; 1 2 use thiserror::Error; 2 3 3 4 #[derive(Debug, Error)] ··· 75 76 MissingUpstreamCid, 76 77 #[error("upstream CID was not valid: {0}")] 77 78 BadUpstreamCid(String), 79 + #[error("upstream atproto-looking bad request")] 80 + UpstreamBadRequest(ErrorResponseObject), 81 + #[error("upstream non-atproto bad request")] 82 + UpstreamBadBadNotGoodRequest(reqwest::Error), 78 83 }
+1 -1
slingshot/src/lib.rs
··· 8 8 pub use consumer::consume; 9 9 pub use firehose_cache::firehose_cache; 10 10 pub use identity::Identity; 11 - pub use record::{CachedRecord, Repo}; 11 + pub use record::{CachedRecord, ErrorResponseObject, Repo}; 12 12 pub use server::serve;
+51 -20
slingshot/src/record.rs
··· 2 2 3 3 use crate::{Identity, error::RecordError}; 4 4 use atrium_api::types::string::{Cid, Did, Nsid, RecordKey}; 5 - use reqwest::Client; 5 + use reqwest::{Client, StatusCode}; 6 6 use serde::{Deserialize, Serialize}; 7 7 use serde_json::value::RawValue; 8 8 use std::str::FromStr; ··· 56 56 value: Box<RawValue>, 57 57 } 58 58 59 + #[derive(Debug, Deserialize)] 60 + pub struct ErrorResponseObject { 61 + error: String, 62 + #[allow(dead_code)] 63 + message: String, 64 + } 65 + 59 66 #[derive(Clone)] 60 67 pub struct Repo { 61 68 identity: Identity, ··· 87 94 return Err(RecordError::NotFound("could not get pds for DID")); 88 95 }; 89 96 90 - // TODO: throttle by host probably, generally guard against outgoing requests 97 + // cid gets set to None for a retry, if it's Some and we got NotFound 98 + let mut cid = cid; 91 99 92 - let mut params = vec![ 93 - ("repo", did.to_string()), 94 - ("collection", collection.to_string()), 95 - ("rkey", rkey.to_string()), 96 - ]; 97 - if let Some(cid) = cid { 98 - params.push(("cid", cid.as_ref().to_string())); 99 - } 100 - let mut url = Url::parse_with_params(&pds, &params)?; 101 - url.set_path("/xrpc/com.atproto.repo.getRecord"); 100 + let res = loop { 101 + // TODO: throttle outgoing requests by host probably, generally guard against outgoing requests 102 + let mut params = vec![ 103 + ("repo", did.to_string()), 104 + ("collection", collection.to_string()), 105 + ("rkey", rkey.to_string()), 106 + ]; 107 + if let Some(cid) = cid { 108 + params.push(("cid", cid.as_ref().to_string())); 109 + } 110 + let mut url = Url::parse_with_params(&pds, &params)?; 111 + url.set_path("/xrpc/com.atproto.repo.getRecord"); 112 + 113 + let res = self 114 + .client 115 + .get(url.clone()) 116 + .send() 117 + .await 118 + .map_err(RecordError::SendError)?; 119 + 120 + if res.status() == StatusCode::BAD_REQUEST { 121 + // 1. if we're not able to parse json, it's not something we can handle 122 + let err = res 123 + .json::<ErrorResponseObject>() 124 + .await 125 + .map_err(RecordError::UpstreamBadBadNotGoodRequest)?; 126 + // 2. if we are, is it a NotFound? and if so, did we try with a CID? 127 + // if so, retry with no CID (api handler will reject for mismatch but 128 + // with a nice error + warm cache) 129 + if err.error == "NotFound" && cid.is_some() { 130 + cid = &None; 131 + continue; 132 + } else { 133 + return Err(RecordError::UpstreamBadRequest(err)); 134 + } 135 + } 136 + break res; 137 + }; 102 138 103 - let res = self 104 - .client 105 - .get(url) 106 - .send() 107 - .await 108 - .map_err(RecordError::SendError)? 139 + let data = res 109 140 .error_for_status() 110 141 .map_err(RecordError::StatusError)? // TODO atproto error handling (think about handling not found) 111 142 .json::<RecordResponseObject>() 112 143 .await 113 144 .map_err(RecordError::ParseJsonError)?; // todo... 114 145 115 - let Some(cid) = res.cid else { 146 + let Some(cid) = data.cid else { 116 147 return Err(RecordError::MissingUpstreamCid); 117 148 }; 118 149 let cid = Cid::from_str(&cid).map_err(|e| RecordError::BadUpstreamCid(e.to_string()))?; 119 150 120 151 Ok(CachedRecord::Found(RawRecord { 121 152 cid, 122 - record: res.value.to_string(), 153 + record: data.value.to_string(), 123 154 })) 124 155 } 125 156 }