use std::sync::Arc;
use crate::syntax::Did;
use crate::sync::{DownloadedRepo, RepoEntry, SyncError};
pub struct SyncClient {
xrpc: crate::xrpc::Client,
#[allow(dead_code)]
identity: Option<Arc<crate::identity::Directory>>,
}
impl SyncClient {
pub fn new(xrpc: crate::xrpc::Client) -> Self {
SyncClient {
xrpc,
identity: None,
}
}
pub fn with_identity(xrpc: crate::xrpc::Client, dir: Arc<crate::identity::Directory>) -> Self {
SyncClient {
xrpc,
identity: Some(dir),
}
}
pub async fn get_repo(&self, did: &Did) -> Result<DownloadedRepo, SyncError> {
let params = serde_json::json!({ "did": did.as_str() });
let car_bytes = self
.xrpc
.query_raw("com.atproto.sync.getRepo", ¶ms)
.await?;
let (roots, blocks) = crate::car::read_all(&car_bytes[..])?;
let root_cid = roots
.first()
.ok_or_else(|| SyncError::Sync("CAR has no roots".into()))?;
let root_block = blocks
.iter()
.find(|b| b.cid == *root_cid)
.ok_or_else(|| SyncError::Sync("root block not found in CAR".into()))?;
let commit = crate::repo::Commit::from_cbor(&root_block.data)?;
Ok(DownloadedRepo {
did: commit.did.clone(),
commit,
blocks,
})
}
#[allow(unused_variables)]
pub async fn list_repos(
&self,
cursor: Option<&str>,
) -> Result<(Vec<RepoEntry>, Option<String>), SyncError> {
Err(SyncError::Sync("list_repos not yet implemented".into()))
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable
)]
mod tests {
use super::*;
use crate::cbor::{Cid, Codec};
use crate::crypto::P256SigningKey;
use crate::syntax::{Did, Nsid, RecordKey, TidClock};
fn make_test_commit() -> crate::repo::Commit {
let sk = P256SigningKey::generate();
let did = Did::try_from("did:plc:test123456789abcdefghij").unwrap();
let clock = TidClock::new(0).unwrap();
let mut repo = crate::repo::Repo::new(did, clock);
let col = Nsid::try_from("app.bsky.feed.post").unwrap();
repo.create(&col, &RecordKey::try_from("a").unwrap(), b"\xa0")
.unwrap();
repo.commit(&sk).unwrap()
}
#[test]
fn sync_client_construction() {
let client = SyncClient::new(crate::xrpc::Client::new("https://bsky.social"));
let _ = client;
}
#[test]
fn sync_client_with_identity_construction() {
let dir = Arc::new(crate::identity::Directory::new());
let client =
SyncClient::with_identity(crate::xrpc::Client::new("https://bsky.social"), dir);
let _ = client;
}
#[test]
fn sync_client_with_custom_plc_url() {
let dir = Arc::new(crate::identity::Directory::with_plc_url(
"https://custom-plc.example.com",
));
let client =
SyncClient::with_identity(crate::xrpc::Client::new("https://pds.example.com"), dir);
let _ = client;
}
#[test]
fn sync_client_with_default_directory() {
let dir = Arc::new(crate::identity::Directory::default());
let client =
SyncClient::with_identity(crate::xrpc::Client::new("https://bsky.social"), dir);
let _ = client;
}
#[test]
fn downloaded_repo_fields_accessible() {
let commit = make_test_commit();
let did = Did::try_from("did:plc:test123456789abcdefghij").unwrap();
let data = b"test block data".to_vec();
let cid = Cid::compute(Codec::Raw, &data);
let blocks = vec![crate::car::Block { cid, data }];
let repo = DownloadedRepo {
did: did.clone(),
commit,
blocks,
};
assert_eq!(repo.did.as_str(), "did:plc:test123456789abcdefghij");
assert_eq!(repo.blocks.len(), 1);
assert_eq!(repo.blocks[0].cid, cid);
assert_eq!(repo.blocks[0].data, b"test block data");
assert_eq!(repo.commit.did.as_str(), "did:plc:test123456789abcdefghij");
}
#[test]
fn downloaded_repo_empty_blocks() {
let commit = make_test_commit();
let did = Did::try_from("did:plc:test123456789abcdefghij").unwrap();
let repo = DownloadedRepo {
did,
commit,
blocks: vec![],
};
assert!(repo.blocks.is_empty());
}
#[test]
fn record_field_access() {
let collection = Nsid::try_from("app.bsky.feed.post").unwrap();
let rkey = RecordKey::try_from("3jwdwj2ctlk26").unwrap();
let data = b"record payload".to_vec();
let cid = Cid::compute(Codec::Drisl, &data);
let record = crate::sync::Record {
collection: collection.clone(),
rkey: rkey.clone(),
cid,
data: data.clone(),
};
assert_eq!(record.collection.as_str(), "app.bsky.feed.post");
assert_eq!(record.rkey.as_str(), "3jwdwj2ctlk26");
assert_eq!(record.cid, cid);
assert_eq!(record.data, data);
}
#[test]
fn repo_entry_field_access() {
let did = Did::try_from("did:plc:test123456789abcdefghij").unwrap();
let head = Cid::compute(Codec::Drisl, b"head block");
let entry = crate::sync::RepoEntry {
did: did.clone(),
head,
};
assert_eq!(entry.did.as_str(), "did:plc:test123456789abcdefghij");
assert_eq!(entry.head, head);
}
}