use futures::TryFutureExt;
use jacquard_common::types::{did::Did, nsid::Nsid, string::Handle, value::Data};
use crate::util::invalid_handle;
use super::*;
#[derive(Deserialize)]
pub struct AtprotoDescribeRepo {
pub repo: AtIdentifier,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DescribeRepoOutput {
pub collections: Vec<Nsid>,
pub did: Did,
pub did_doc: Data,
pub handle: Handle,
pub handle_is_correct: bool,
}
fn describe_repo_output(
did: Did,
handle: Handle,
handle_is_correct: bool,
did_doc: Data,
collections: Vec<Nsid>,
) -> DescribeRepoOutput {
DescribeRepoOutput {
did,
handle,
handle_is_correct,
did_doc,
collections,
}
}
pub async fn handle(
State(hydrant): State<Hydrant>,
ExtractXrpc(req): ExtractXrpc<AtprotoDescribeRepo>,
) -> XrpcResult<Json<DescribeRepoOutput>> {
let resolver = &hydrant.state.resolver;
let did = resolver
.resolve_did(&req.repo)
.await
.map_err(|e| bad_request(format!("could not resolve identifier: {e}")))?;
let repo = hydrant.repos.get(&did);
if let Some(reason) = unavailable(&repo).await.map_err(internal_error)? {
return Err(bad_request(reason));
}
let ((did_doc, handle, handle_is_correct), collections) = tokio::try_join!(
async {
let (doc, handle) = resolver
.resolve_raw_doc(&did)
.map_err(|e| upstream_error(format!("could not resolve DID document: {e}")))
.await?;
let (handle, handle_is_correct) = if let Some(h) = handle {
let is_correct = resolver
.verify_handle(&did, &h)
.map_err(|e| bad_request(format!("could not verify handle {h}: {e}")))
.await?;
(h, is_correct)
} else {
(invalid_handle(), false)
};
Ok((doc, handle, handle_is_correct))
},
repo.collections().map_err(internal_error),
)?;
Ok(Json(describe_repo_output(
did,
handle,
handle_is_correct,
did_doc,
collections.into_keys().collect(),
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn describe_repo_json_projection_contract() {
let output = describe_repo_output(
jacquard_common::types::did::Did::new_static("did:plc:ewvi7nxzyoun6zhxrhs64oiz")
.unwrap(),
jacquard_common::types::string::Handle::new_static("alice.test").unwrap(),
true,
serde_json::from_str(
r##"{
"id": "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
"service": [{
"id": "#atproto_pds",
"type": "AtprotoPersonalDataServer",
"serviceEndpoint": "https://pds.example"
}]
}"##,
)
.unwrap(),
vec![jacquard_common::types::nsid::Nsid::new_static("app.bsky.feed.post").unwrap()],
);
assert_eq!(
serde_json::to_value(output).unwrap(),
serde_json::json!({
"collections": ["app.bsky.feed.post"],
"did": "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
"didDoc": {
"id": "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
"service": [{
"id": "#atproto_pds",
"type": "AtprotoPersonalDataServer",
"serviceEndpoint": "https://pds.example",
}],
},
"handle": "alice.test",
"handleIsCorrect": true,
})
);
}
}