···
use jacquard_api::com_atproto::repo::create_record::CreateRecordOutput;
use jacquard_api::com_atproto::repo::delete_record::DeleteRecordOutput;
30
+
use jacquard_api::com_atproto::repo::get_record::GetRecordResponse;
use jacquard_api::com_atproto::repo::put_record::PutRecordOutput;
32
+
use jacquard_api::com_atproto::repo::upload_blob::UploadBlobResponse;
use jacquard_api::com_atproto::server::create_session::CreateSessionOutput;
use jacquard_api::com_atproto::server::refresh_session::RefreshSessionOutput;
use jacquard_common::error::TransportError;
···
pub async fn refresh(&self) -> Result<AuthorizationToken<'static>, ClientError> {
self.inner.refresh().await
322
-
// Convenience methods for repository operations
325
+
/// Extension trait providing convenience methods for common repository operations.
327
+
/// This trait is automatically implemented for any type that implements both
328
+
/// [`AgentSession`] and [`IdentityResolver`]. It provides higher-level methods
329
+
/// that handle common patterns like fetch-modify-put, with automatic repo resolution
330
+
/// for at:// uris, and typed record operations.
332
+
/// # Available Operations
334
+
/// - **Basic CRUD**: [`create_record`](Self::create_record), [`get_record`](Self::get_record),
335
+
/// [`put_record`](Self::put_record), [`delete_record`](Self::delete_record)
336
+
/// - **Update patterns**: [`update_record`](Self::update_record) (fetch-modify-put for records),
337
+
/// [`update_vec`](Self::update_vec) and [`update_vec_item`](Self::update_vec_item) (for array endpoints)
338
+
/// - **Blob operations**: [`upload_blob`](Self::upload_blob)
343
+
/// # use jacquard::client::BasicClient;
344
+
/// # use jacquard_api::app_bsky::feed::post::Post;
345
+
/// # use jacquard_common::types::string::{AtUri, Datetime};
346
+
/// # use jacquard_common::CowStr;
347
+
/// use jacquard::client::AgentSessionExt;
348
+
/// # #[tokio::main]
349
+
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
350
+
/// # let agent: BasicClient = todo!();
351
+
/// // Create a post
352
+
/// let post = Post {
353
+
/// text: CowStr::from("Hello from Jacquard!"),
354
+
/// created_at: Datetime::now(),
355
+
/// # embed: None, entities: None, facets: None, labels: None,
356
+
/// # langs: None, reply: None, tags: None, extra_data: Default::default(),
358
+
/// let output = agent.create_record(post, None).await?;
360
+
/// // Read it back
361
+
/// let response = agent.get_record::<Post>(output.uri).await?;
362
+
/// let record = response.parse()?;
363
+
/// println!("Post: {}", record.value.text);
367
+
pub trait AgentSessionExt: AgentSession + IdentityResolver {
/// Create a new record in the repository.
/// The collection is inferred from the record type's `Collection::NSID`.
···
/// # use jacquard_api::app_bsky::feed::post::Post;
/// # use jacquard_common::types::string::Datetime;
/// # use jacquard_common::CowStr;
380
+
/// use jacquard::client::AgentSessionExt;
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let agent: BasicClient = todo!();
···
356
-
pub async fn create_record<R>(
401
+
fn create_record<R>(
rkey: Option<RecordKey<Rkey<'_>>>,
360
-
) -> Result<CreateRecordOutput<'static>, AgentError>
405
+
) -> impl std::future::Future<Output = Result<CreateRecordOutput<'static>, AgentError>>
R: Collection + serde::Serialize,
364
-
use jacquard_api::com_atproto::repo::create_record::CreateRecord;
365
-
use jacquard_common::types::ident::AtIdentifier;
366
-
use jacquard_common::types::value::to_data;
410
+
use jacquard_api::com_atproto::repo::create_record::CreateRecord;
411
+
use jacquard_common::types::ident::AtIdentifier;
412
+
use jacquard_common::types::value::to_data;
414
+
let (did, _) = self.session_info().await.ok_or(AgentError::NoSession)?;
416
+
let data = to_data(&record).map_err(|e| AgentError::SubOperation {
417
+
step: "serialize record",
418
+
error: Box::new(e),
421
+
let request = CreateRecord::new()
422
+
.repo(AtIdentifier::Did(did))
423
+
.collection(R::nsid())
428
+
let response = self.send(request).await?;
429
+
response.into_output().map_err(|e| match e {
430
+
XrpcError::Auth(auth) => AgentError::Auth(auth),
431
+
XrpcError::Generic(g) => AgentError::Generic(g),
432
+
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
433
+
XrpcError::Xrpc(typed) => AgentError::SubOperation {
434
+
step: "create record",
435
+
error: Box::new(typed),
441
+
/// Get a record from the repository using an at:// URI.
443
+
/// Returns a typed `Response` that deserializes directly to the record type.
444
+
/// Use `.parse()` to borrow from the response buffer, or `.into_output()` for owned data.
449
+
/// # use jacquard::client::BasicClient;
450
+
/// # use jacquard_api::app_bsky::feed::post::Post;
451
+
/// # use jacquard_common::types::string::AtUri;
452
+
/// # use jacquard_common::IntoStatic;
453
+
/// use jacquard::client::AgentSessionExt;
454
+
/// # #[tokio::main]
455
+
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
456
+
/// # let agent: BasicClient = todo!();
457
+
/// let uri = AtUri::new_static("at://did:plc:xyz/app.bsky.feed.post/3l5bqm7lepk2c").unwrap();
458
+
/// let response = agent.get_record::<Post>(uri).await?;
459
+
/// let output = response.parse()?; // PostGetRecordOutput<'_> borrowing from buffer
460
+
/// println!("Post text: {}", output.value.text);
462
+
/// // Or get owned data
463
+
/// let output_owned = response.into_output()?;
470
+
) -> impl std::future::Future<Output = Result<Response<R::Record>, ClientError>>
475
+
// Validate that URI's collection matches the expected type
476
+
if let Some(uri_collection) = uri.collection() {
477
+
if uri_collection.as_str() != R::nsid().as_str() {
478
+
return Err(ClientError::Transport(TransportError::Other(
480
+
"Collection mismatch: URI contains '{}' but type parameter expects '{}'",
489
+
let rkey = uri.rkey().ok_or_else(|| {
490
+
ClientError::Transport(TransportError::Other("AtUri missing rkey".into()))
493
+
// Resolve authority (DID or handle) to get DID and PDS
494
+
use jacquard_common::types::ident::AtIdentifier;
495
+
let (repo_did, pds_url) = match uri.authority() {
496
+
AtIdentifier::Did(did) => {
497
+
let pds = self.pds_for_did(did).await.map_err(|e| {
498
+
ClientError::Transport(TransportError::Other(
499
+
format!("Failed to resolve PDS for {}: {}", did, e).into(),
504
+
AtIdentifier::Handle(handle) => self.pds_for_handle(handle).await.map_err(|e| {
505
+
ClientError::Transport(TransportError::Other(
506
+
format!("Failed to resolve handle {}: {}", handle, e).into(),
511
+
// Make stateless XRPC call to that PDS (no auth required for public records)
512
+
use jacquard_api::com_atproto::repo::get_record::GetRecord;
513
+
let request = GetRecord::new()
514
+
.repo(AtIdentifier::Did(repo_did))
515
+
.collection(R::nsid())
516
+
.rkey(rkey.clone())
519
+
let response: Response<GetRecordResponse> = {
520
+
let http_request = xrpc::build_http_request(&pds_url, &request, &self.opts().await)
521
+
.map_err(|e| ClientError::Transport(TransportError::from(e)))?;
523
+
let http_response = self
524
+
.send_http(http_request)
526
+
.map_err(|e| ClientError::Transport(TransportError::Other(Box::new(e))))?;
528
+
xrpc::process_response(http_response)
530
+
Ok(response.transmute())
534
+
/// Update a record in-place with a fetch-modify-put pattern.
536
+
/// This fetches the record using an at:// URI, converts it to owned data, applies
537
+
/// the modification function, and puts it back. The modification function receives
538
+
/// a mutable reference to the record data.
543
+
/// # use jacquard::client::BasicClient;
544
+
/// # use jacquard_api::app_bsky::actor::profile::Profile;
545
+
/// # use jacquard_common::CowStr;
546
+
/// # use jacquard_common::types::string::AtUri;
547
+
/// use jacquard::client::AgentSessionExt;
548
+
/// # #[tokio::main]
549
+
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
550
+
/// # let agent: BasicClient = todo!();
551
+
/// let uri = AtUri::new_static("at://did:plc:xyz/app.bsky.actor.profile/self").unwrap();
552
+
/// // Update profile record in-place
553
+
/// agent.update_record::<Profile>(uri, |profile| {
554
+
/// profile.display_name = Some(CowStr::from("New Name"));
555
+
/// profile.description = Some(CowStr::from("Updated bio"));
560
+
fn update_record<R>(
563
+
f: impl FnOnce(&mut R),
564
+
) -> impl std::future::Future<Output = Result<PutRecordOutput<'static>, AgentError>>
566
+
R: Collection + Serialize,
567
+
R: for<'a> From<<<R as Collection>::Record as XrpcResp>::Output<'a>>,
570
+
// Fetch the record - Response<R::Record> where R::Record::Output<'de> = R<'de>
571
+
let response = self.get_record::<R>(uri.clone()).await?;
368
-
let (did, _) = self.info().await.ok_or(AgentError::NoSession)?;
573
+
// Parse to get R<'_> borrowing from response buffer
574
+
let record = response.parse().map_err(|e| match e {
575
+
XrpcError::Auth(auth) => AgentError::Auth(auth),
576
+
XrpcError::Generic(g) => AgentError::Generic(g),
577
+
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
578
+
XrpcError::Xrpc(typed) => AgentError::SubOperation {
579
+
step: "get record",
580
+
error: format!("{:?}", typed).into(),
370
-
let data = to_data(&record).map_err(|e| AgentError::SubOperation {
371
-
step: "serialize record",
372
-
error: Box::new(e),
584
+
// Convert to owned
585
+
let mut owned = R::from(record);
375
-
let request = CreateRecord::new()
376
-
.repo(AtIdentifier::Did(did))
377
-
.collection(R::nsid())
587
+
// Apply modification
382
-
let response = self.send(request).await?;
383
-
response.into_output().map_err(|e| match e {
384
-
XrpcError::Auth(auth) => AgentError::Auth(auth),
385
-
XrpcError::Generic(g) => AgentError::Generic(g),
386
-
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
387
-
XrpcError::Xrpc(typed) => AgentError::SubOperation {
388
-
step: "create record",
389
-
error: Box::new(typed),
593
+
.ok_or(AgentError::SubOperation {
594
+
step: "extract rkey",
595
+
error: "AtUri missing rkey".into(),
599
+
self.put_record::<R>(rkey, owned).await
/// Delete a record from the repository.
/// The collection is inferred from the type parameter.
/// The repo is automatically filled from the session info.
398
-
pub async fn delete_record<R>(
607
+
fn delete_record<R>(
rkey: RecordKey<Rkey<'_>>,
401
-
) -> Result<DeleteRecordOutput<'static>, AgentError>
610
+
) -> impl std::future::Future<Output = Result<DeleteRecordOutput<'static>, AgentError>>
405
-
use jacquard_api::com_atproto::repo::delete_record::DeleteRecord;
406
-
use jacquard_common::types::ident::AtIdentifier;
615
+
use jacquard_api::com_atproto::repo::delete_record::DeleteRecord;
616
+
use jacquard_common::types::ident::AtIdentifier;
408
-
let (did, _) = self.info().await.ok_or(AgentError::NoSession)?;
618
+
let (did, _) = self.session_info().await.ok_or(AgentError::NoSession)?;
410
-
let request = DeleteRecord::new()
411
-
.repo(AtIdentifier::Did(did))
412
-
.collection(R::nsid())
620
+
let request = DeleteRecord::new()
621
+
.repo(AtIdentifier::Did(did))
622
+
.collection(R::nsid())
416
-
let response = self.send(request).await?;
417
-
response.into_output().map_err(|e| match e {
418
-
XrpcError::Auth(auth) => AgentError::Auth(auth),
419
-
XrpcError::Generic(g) => AgentError::Generic(g),
420
-
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
421
-
XrpcError::Xrpc(typed) => AgentError::SubOperation {
422
-
step: "delete record",
423
-
error: Box::new(typed),
626
+
let response = self.send(request).await?;
627
+
response.into_output().map_err(|e| match e {
628
+
XrpcError::Auth(auth) => AgentError::Auth(auth),
629
+
XrpcError::Generic(g) => AgentError::Generic(g),
630
+
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
631
+
XrpcError::Xrpc(typed) => AgentError::SubOperation {
632
+
step: "delete record",
633
+
error: Box::new(typed),
/// Put (upsert) a record in the repository.
/// The collection is inferred from the record type's `Collection::NSID`.
/// The repo is automatically filled from the session info.
432
-
pub async fn put_record<R>(
rkey: RecordKey<Rkey<'static>>,
436
-
) -> Result<PutRecordOutput<'static>, AgentError>
647
+
) -> impl std::future::Future<Output = Result<PutRecordOutput<'static>, AgentError>>
R: Collection + serde::Serialize,
440
-
use jacquard_api::com_atproto::repo::put_record::PutRecord;
441
-
use jacquard_common::types::ident::AtIdentifier;
442
-
use jacquard_common::types::value::to_data;
652
+
use jacquard_api::com_atproto::repo::put_record::PutRecord;
653
+
use jacquard_common::types::ident::AtIdentifier;
654
+
use jacquard_common::types::value::to_data;
444
-
let (did, _) = self.info().await.ok_or(AgentError::NoSession)?;
656
+
let (did, _) = self.session_info().await.ok_or(AgentError::NoSession)?;
446
-
let data = to_data(&record).map_err(|e| AgentError::SubOperation {
447
-
step: "serialize record",
448
-
error: Box::new(e),
658
+
let data = to_data(&record).map_err(|e| AgentError::SubOperation {
659
+
step: "serialize record",
660
+
error: Box::new(e),
451
-
let request = PutRecord::new()
452
-
.repo(AtIdentifier::Did(did))
453
-
.collection(R::nsid())
663
+
let request = PutRecord::new()
664
+
.repo(AtIdentifier::Did(did))
665
+
.collection(R::nsid())
458
-
let response = self.send(request).await?;
459
-
response.into_output().map_err(|e| match e {
460
-
XrpcError::Auth(auth) => AgentError::Auth(auth),
461
-
XrpcError::Generic(g) => AgentError::Generic(g),
462
-
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
463
-
XrpcError::Xrpc(typed) => AgentError::SubOperation {
464
-
step: "put record",
465
-
error: Box::new(typed),
670
+
let response = self.send(request).await?;
671
+
response.into_output().map_err(|e| match e {
672
+
XrpcError::Auth(auth) => AgentError::Auth(auth),
673
+
XrpcError::Generic(g) => AgentError::Generic(g),
674
+
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
675
+
XrpcError::Xrpc(typed) => AgentError::SubOperation {
676
+
step: "put record",
677
+
error: Box::new(typed),
/// Upload a blob to the repository.
···
/// # use jacquard::client::BasicClient;
/// # use jacquard_common::types::blob::MimeType;
693
+
/// use jacquard::client::AgentSessionExt;
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let agent: BasicClient = todo!();
···
489
-
pub async fn upload_blob(
data: impl Into<bytes::Bytes>,
493
-
) -> Result<Blob<'static>, AgentError> {
494
-
use http::header::CONTENT_TYPE;
495
-
use jacquard_api::com_atproto::repo::upload_blob::UploadBlob;
707
+
) -> impl std::future::Future<Output = Result<Blob<'static>, AgentError>> {
709
+
use http::header::CONTENT_TYPE;
710
+
use jacquard_api::com_atproto::repo::upload_blob::UploadBlob;
712
+
let bytes = data.into();
713
+
let request = UploadBlob::new().body(bytes).build();
715
+
// Override Content-Type header with actual mime type instead of */*
716
+
let base = self.base_uri();
717
+
let mut opts = self.opts().await;
718
+
opts.extra_headers.push((
720
+
http::HeaderValue::from_str(mime_type.as_str()).map_err(|e| {
721
+
AgentError::SubOperation {
722
+
step: "set Content-Type header",
723
+
error: Box::new(e),
497
-
let bytes = data.into();
498
-
let request = UploadBlob::new().body(bytes).build();
728
+
let response: Response<UploadBlobResponse> = {
730
+
xrpc::build_http_request(&base, &request, &opts).map_err(|e| {
731
+
AgentError::Client(ClientError::Transport(TransportError::from(e)))
500
-
// Override Content-Type header with actual mime type instead of */*
501
-
let base = self.base_uri();
502
-
let mut opts = self.opts().await;
503
-
opts.extra_headers.push((
505
-
http::HeaderValue::from_str(mime_type.as_str()).map_err(|e| {
506
-
AgentError::SubOperation {
507
-
step: "set Content-Type header",
508
-
error: Box::new(e),
734
+
let http_response = self.send_http(http_request).await.map_err(|e| {
735
+
AgentError::Client(ClientError::Transport(TransportError::Other(Box::new(e))))
513
-
let response = self.xrpc(base).with_options(opts).send(&request).await?;
738
+
xrpc::process_response(http_response)
515
-
let output = response.into_output().map_err(|e| match e {
516
-
XrpcError::Auth(auth) => AgentError::Auth(auth),
517
-
XrpcError::Generic(g) => AgentError::Generic(g),
518
-
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
519
-
XrpcError::Xrpc(typed) => AgentError::SubOperation {
520
-
step: "upload blob",
521
-
error: Box::new(typed),
524
-
Ok(output.blob.into_static())
741
+
let output = response.into_output().map_err(|e| match e {
742
+
XrpcError::Auth(auth) => AgentError::Auth(auth),
743
+
XrpcError::Generic(g) => AgentError::Generic(g),
744
+
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
745
+
XrpcError::Xrpc(typed) => AgentError::SubOperation {
746
+
step: "upload blob",
747
+
error: Box::new(typed),
750
+
Ok(output.blob.into_static())
/// Update a vec-based data structure with a fetch-modify-put pattern.
···
/// prefs.retain(|p| !matches!(p, Preference::Hidden(_)));
540
-
pub async fn update_vec<'s, U>(
767
+
fn update_vec<'s, U>(
modify: impl FnOnce(&mut Vec<<U as vec_update::VecUpdate>::Item>),
543
-
) -> Result<xrpc::Response<<U::PutRequest<'s> as XrpcRequest<'s>>::Response>, AgentError>
770
+
) -> impl std::future::Future<
772
+
xrpc::Response<<U::PutRequest<'s> as XrpcRequest<'s>>::Response>,
U: vec_update::VecUpdate + 's,
547
-
// Fetch current data
548
-
let get_request = U::build_get();
549
-
let response = self.send(get_request).await?;
550
-
let output = response.parse().map_err(|e| match e {
551
-
XrpcError::Auth(auth) => AgentError::Auth(auth),
552
-
XrpcError::Generic(g) => AgentError::Generic(g),
553
-
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
554
-
XrpcError::Xrpc(_) => AgentError::SubOperation {
556
-
error: format!("{:?}", e).into(),
780
+
// Fetch current data
781
+
let get_request = U::build_get();
782
+
let response = self.send(get_request).await?;
783
+
let output = response.parse().map_err(|e| match e {
784
+
XrpcError::Auth(auth) => AgentError::Auth(auth),
785
+
XrpcError::Generic(g) => AgentError::Generic(g),
786
+
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
787
+
XrpcError::Xrpc(_) => AgentError::SubOperation {
789
+
error: format!("{:?}", e).into(),
560
-
// Extract vec (converts to owned via IntoStatic)
561
-
let mut items = U::extract_vec(output);
793
+
// Extract vec (converts to owned via IntoStatic)
794
+
let mut items = U::extract_vec(output);
563
-
// Apply modification
564
-
modify(&mut items);
796
+
// Apply modification
797
+
modify(&mut items);
566
-
// Build put request
567
-
let put_request = U::build_put(items);
799
+
// Build put request
800
+
let put_request = U::build_put(items);
570
-
Ok(self.send(put_request).await?)
803
+
Ok(self.send(put_request).await?)
/// Update a single item in a vec-based data structure.
···
/// let pref = AdultContentPref::new().enabled(true).build();
/// agent.update_vec_item::<PreferencesUpdate>(pref.into()).await?;
584
-
pub async fn update_vec_item<'s, U>(
818
+
fn update_vec_item<'s, U>(
item: <U as vec_update::VecUpdate>::Item,
587
-
) -> Result<xrpc::Response<<U::PutRequest<'s> as XrpcRequest<'s>>::Response>, AgentError>
821
+
) -> impl std::future::Future<
823
+
xrpc::Response<<U::PutRequest<'s> as XrpcRequest<'s>>::Response>,
U: vec_update::VecUpdate + 's,
591
-
self.update_vec::<U>(|vec| {
592
-
if let Some(pos) = vec.iter().position(|i| U::matches(i, &item)) {
602
-
impl<A: AgentSession + IdentityResolver> Agent<A> {
603
-
/// Get a record from the repository using an at:// URI.
605
-
/// Returns a typed `Response` that deserializes directly to the record type.
606
-
/// Use `.parse()` to borrow from the response buffer, or `.into_output()` for owned data.
611
-
/// # use jacquard::client::BasicClient;
612
-
/// # use jacquard_api::app_bsky::feed::post::Post;
613
-
/// # use jacquard_common::types::string::AtUri;
614
-
/// # use jacquard_common::IntoStatic;
615
-
/// # #[tokio::main]
616
-
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
617
-
/// # let agent: BasicClient = todo!();
618
-
/// let uri = AtUri::new_static("at://did:plc:xyz/app.bsky.feed.post/3l5bqm7lepk2c").unwrap();
619
-
/// let response = agent.get_record::<Post>(uri).await?;
620
-
/// let output = response.parse()?; // PostGetRecordOutput<'_> borrowing from buffer
621
-
/// println!("Post text: {}", output.value.text);
623
-
/// // Or get owned data
624
-
/// let output_owned = response.into_output()?;
628
-
pub async fn get_record<R>(&self, uri: AtUri<'_>) -> Result<Response<R::Record>, ClientError>
632
-
// Validate that URI's collection matches the expected type
633
-
if let Some(uri_collection) = uri.collection() {
634
-
if uri_collection.as_str() != R::nsid().as_str() {
635
-
return Err(ClientError::Transport(TransportError::Other(
637
-
"Collection mismatch: URI contains '{}' but type parameter expects '{}'",
831
+
self.update_vec::<U>(|vec| {
832
+
if let Some(pos) = vec.iter().position(|i| U::matches(i, &item)) {
646
-
let rkey = uri.rkey().ok_or_else(|| {
647
-
ClientError::Transport(TransportError::Other("AtUri missing rkey".into()))
650
-
// Resolve authority (DID or handle) to get DID and PDS
651
-
use jacquard_common::types::ident::AtIdentifier;
652
-
let (repo_did, pds_url) = match uri.authority() {
653
-
AtIdentifier::Did(did) => {
654
-
let pds = self.pds_for_did(did).await.map_err(|e| {
655
-
ClientError::Transport(TransportError::Other(
656
-
format!("Failed to resolve PDS for {}: {}", did, e).into(),
661
-
AtIdentifier::Handle(handle) => self.pds_for_handle(handle).await.map_err(|e| {
662
-
ClientError::Transport(TransportError::Other(
663
-
format!("Failed to resolve handle {}: {}", handle, e).into(),
668
-
// Make stateless XRPC call to that PDS (no auth required for public records)
669
-
use jacquard_api::com_atproto::repo::get_record::GetRecord;
670
-
let request = GetRecord::new()
671
-
.repo(AtIdentifier::Did(repo_did))
672
-
.collection(R::nsid())
673
-
.rkey(rkey.clone())
676
-
let response = self.xrpc(pds_url).send(&request).await?;
677
-
Ok(response.transmute())
680
-
/// Update a record in-place with a fetch-modify-put pattern.
682
-
/// This fetches the record using an at:// URI, converts it to owned data, applies
683
-
/// the modification function, and puts it back. The modification function receives
684
-
/// a mutable reference to the record data.
689
-
/// # use jacquard::client::BasicClient;
690
-
/// # use jacquard_api::app_bsky::actor::profile::Profile;
691
-
/// # use jacquard_common::CowStr;
692
-
/// # use jacquard_common::types::string::AtUri;
693
-
/// # #[tokio::main]
694
-
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
695
-
/// # let agent: BasicClient = todo!();
696
-
/// let uri = AtUri::new_static("at://did:plc:xyz/app.bsky.actor.profile/self").unwrap();
697
-
/// // Update profile record in-place
698
-
/// agent.update_record::<Profile>(uri, |profile| {
699
-
/// profile.display_name = Some(CowStr::from("New Name"));
700
-
/// profile.description = Some(CowStr::from("Updated bio"));
705
-
pub async fn update_record<R>(
708
-
f: impl FnOnce(&mut R),
709
-
) -> Result<PutRecordOutput<'static>, AgentError>
711
-
R: Collection + Serialize,
712
-
R: for<'a> From<<<R as Collection>::Record as XrpcResp>::Output<'a>>,
714
-
// Fetch the record - Response<R::Record> where R::Record::Output<'de> = R<'de>
715
-
let response = self.get_record::<R>(uri.clone()).await?;
717
-
// Parse to get R<'_> borrowing from response buffer
718
-
let record = response.parse().map_err(|e| match e {
719
-
XrpcError::Auth(auth) => AgentError::Auth(auth),
720
-
XrpcError::Generic(g) => AgentError::Generic(g),
721
-
XrpcError::Decode(e) => AgentError::Decode(DecodeError::Json(e)),
722
-
XrpcError::Xrpc(typed) => AgentError::SubOperation {
723
-
step: "get record",
724
-
error: format!("{:?}", typed).into(),
728
-
// Convert to owned
729
-
let mut owned = R::from(record);
731
-
// Apply modification
737
-
.ok_or(AgentError::SubOperation {
738
-
step: "extract rkey",
739
-
error: "AtUri missing rkey".into(),
743
-
self.put_record::<R>(rkey, owned).await
843
+
impl<T: AgentSession + IdentityResolver> AgentSessionExt for T {}
impl<A: AgentSession> HttpClient for Agent<A> {
type Error = <A as HttpClient>::Error;
···
901
+
impl<A: AgentSession> AgentSession for Agent<A> {
902
+
fn session_kind(&self) -> AgentKind {
908
+
) -> impl Future<Output = Option<(Did<'static>, Option<CowStr<'static>>)>> {
909
+
async { self.info().await }
912
+
fn endpoint(&self) -> impl Future<Output = url::Url> {
913
+
async { self.endpoint().await }
916
+
fn set_options<'a>(&'a self, opts: CallOptions<'a>) -> impl Future<Output = ()> {
917
+
async { self.set_options(opts).await }
920
+
fn refresh(&self) -> impl Future<Output = Result<AuthorizationToken<'static>, ClientError>> {
921
+
async { self.refresh().await }
impl<A: AgentSession> From<A> for Agent<A> {
fn from(inner: A) -> Self {
···
/// # use jacquard::client::BasicClient;
/// # use jacquard::types::string::AtUri;
/// # use jacquard_api::app_bsky::feed::post::Post;
956
+
/// use crate::jacquard::client::AgentSessionExt;
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = BasicClient::unauthenticated();