use jacquard_common::{
IntoStatic,
types::{
cid::Cid,
ident::AtIdentifier,
nsid::Nsid,
recordkey::{RecordKey, Rkey},
string::AtUri,
},
};
use super::get_record::GetRecordOutput;
use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
struct RecordLookup {
repo: AtIdentifier,
collection: Nsid,
rkey: RecordKey<Rkey>,
}
#[derive(Serialize, Deserialize)]
pub struct GetRecordByUriRequestData {
pub at_uri: AtUri,
#[serde(default)]
pub cid: Option<Cid>,
}
fn record_lookup_from_at_uri(at_uri: &AtUri) -> Result<RecordLookup, &'static str> {
let Some(collection) = at_uri.collection() else {
return Err("at-uri must include a collection");
};
let Some(rkey) = at_uri.rkey() else {
return Err("at-uri must include an rkey");
};
Ok(RecordLookup {
repo: at_uri.authority().into_static(),
collection: collection.into_static(),
rkey: RecordKey(rkey.into_static()),
})
}
fn matches_requested_cid<S1: jacquard_common::BosStr, S2: jacquard_common::BosStr>(
record_cid: &Cid<S1>,
requested_cid: Option<&Cid<S2>>,
) -> bool {
requested_cid.is_none_or(|cid| cid.as_str() == record_cid.as_str())
}
pub async fn handle(
State(hydrant): State<Hydrant>,
ExtractXrpc(req): ExtractXrpc<GetRecordByUriRequestData>,
) -> XrpcResult<Json<GetRecordOutput>> {
let lookup = record_lookup_from_at_uri(&req.at_uri).map_err(bad_request)?;
let repo = hydrant
.repos
.resolve(&lookup.repo)
.await
.map_err(internal_error)?;
if let Some(reason) = unavailable(&repo).await.map_err(internal_error)? {
return Err(record_unavailable(reason));
}
let record = repo
.get_record(lookup.collection.as_str(), lookup.rkey.as_ref())
.await
.map_err(internal_error)?;
let Some(record) = record else {
return Err(xrpc_error(StatusCode::NOT_FOUND, "RecordNotFound", None));
};
if !matches_requested_cid(&record.cid, req.cid.as_ref()) {
return Err(xrpc_error(StatusCode::NOT_FOUND, "RecordNotFound", None));
}
Ok(Json(GetRecordOutput {
uri: AtUri::from_parts_owned(
record.did.as_str(),
lookup.collection.as_str(),
lookup.rkey.as_ref(),
)
.unwrap(),
cid: Some(record.cid),
value: record.value,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_lookup_extracts_all_parts() {
let at_uri =
AtUri::new_static("at://bad-example.com/app.bsky.feed.post/3k4duaz5vfs2w").unwrap();
let lookup = record_lookup_from_at_uri(&at_uri).unwrap();
assert_eq!(lookup.repo.as_str(), "bad-example.com");
assert_eq!(lookup.collection.as_str(), "app.bsky.feed.post");
assert_eq!(lookup.rkey.as_ref(), "3k4duaz5vfs2w");
}
#[test]
fn record_lookup_rejects_missing_collection() {
let at_uri = AtUri::new_static("at://bad-example.com").unwrap();
let err = record_lookup_from_at_uri(&at_uri).unwrap_err();
assert_eq!(err, "at-uri must include a collection");
}
#[test]
fn record_lookup_rejects_missing_rkey() {
let at_uri = AtUri::new_static("at://bad-example.com/app.bsky.feed.post").unwrap();
let err = record_lookup_from_at_uri(&at_uri).unwrap_err();
assert_eq!(err, "at-uri must include an rkey");
}
#[test]
fn cid_matching_is_optional_and_exact() {
let actual = Cid::str("bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a");
let matching = Cid::str("bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a");
let different = Cid::str("bafyreib2rxk3rybk3aobmv5cjuql3bm2twh4jo5uxgf5kpqcsgz7soitae");
assert!(matches_requested_cid(&actual, None::<&Cid>));
assert!(matches_requested_cid(&actual, Some(&matching)));
assert!(!matches_requested_cid(&actual, Some(&different)));
}
}