A better Rust ATProto crate
103
fork

Configure Feed

Select the types of activity you want to include in your feed.

[jacquard-lexgen, jacquard-identity] remove jacquard-api dependency, use jacquard-common XRPC types

+79 -240
-2
Cargo.lock
··· 2545 2545 "bytes", 2546 2546 "hickory-resolver", 2547 2547 "http", 2548 - "jacquard-api", 2549 2548 "jacquard-common", 2550 2549 "jacquard-lexicon", 2551 2550 "miette", ··· 2570 2569 "clap_mangen", 2571 2570 "glob", 2572 2571 "inventory", 2573 - "jacquard-api", 2574 2572 "jacquard-common", 2575 2573 "jacquard-derive", 2576 2574 "jacquard-identity",
+40 -102
crates/jacquard-common/src/xrpc/atproto.rs
··· 4 4 //! between jacquard-lexgen/jacquard-identity and jacquard-api. They provide minimal 5 5 //! implementations sufficient for bootstrap code generation without builders or 6 6 //! validation helpers. 7 - 8 - use alloc::vec::Vec; 7 + //! 9 8 use crate::CowStr; 10 9 use crate::IntoStatic; 11 - use crate::types::string::{AtUri, Cid, Did, Handle, Nsid}; 12 10 use crate::types::ident::AtIdentifier; 11 + use crate::types::string::{AtUri, Cid, Did, Handle, Nsid}; 13 12 use crate::types::value::Data; 14 13 use crate::xrpc::{GenericError, XrpcMethod, XrpcRequest, XrpcResp}; 14 + 15 + use alloc::vec::Vec; 15 16 use core::error::Error; 16 17 use core::fmt::{self, Display}; 17 18 use serde::{Deserialize, Serialize}; ··· 21 22 // ============================================================================ 22 23 23 24 /// Request for com.atproto.repo.listRecords. 24 - #[derive( 25 - Serialize, 26 - Deserialize, 27 - Debug, 28 - Clone, 29 - PartialEq, 30 - Eq, 31 - )] 25 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 32 26 #[serde(rename_all = "camelCase")] 27 + #[allow(missing_docs)] 33 28 pub struct ListRecords<'a> { 34 29 #[serde(borrow)] 35 30 pub collection: Nsid<'a>, ··· 59 54 } 60 55 61 56 /// Output for com.atproto.repo.listRecords. 62 - #[derive( 63 - Serialize, 64 - Deserialize, 65 - Debug, 66 - Clone, 67 - PartialEq, 68 - Eq, 69 - )] 57 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 70 58 #[serde(rename_all = "camelCase")] 59 + #[allow(missing_docs)] 71 60 pub struct ListRecordsOutput<'a> { 72 61 #[serde(skip_serializing_if = "Option::is_none")] 73 62 #[serde(borrow)] ··· 88 77 } 89 78 90 79 /// A single record in a list response. 91 - #[derive( 92 - Serialize, 93 - Deserialize, 94 - Debug, 95 - Clone, 96 - PartialEq, 97 - Eq, 98 - )] 80 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 99 81 #[serde(rename_all = "camelCase")] 82 + #[allow(missing_docs)] 100 83 pub struct ListRecordsRecord<'a> { 101 84 #[serde(skip_serializing_if = "Option::is_none")] 102 85 #[serde(borrow)] ··· 140 123 // ============================================================================ 141 124 142 125 /// Request for com.atproto.repo.getRecord. 143 - #[derive( 144 - Serialize, 145 - Deserialize, 146 - Debug, 147 - Clone, 148 - PartialEq, 149 - Eq, 150 - )] 126 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 127 + #[allow(missing_docs)] 151 128 #[serde(rename_all = "camelCase")] 152 129 pub struct GetRecord<'a> { 153 130 #[serde(skip_serializing_if = "Option::is_none")] ··· 175 152 } 176 153 177 154 /// Output for com.atproto.repo.getRecord. 178 - #[derive( 179 - Serialize, 180 - Deserialize, 181 - Debug, 182 - Clone, 183 - PartialEq, 184 - Eq, 185 - )] 155 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 156 + #[allow(missing_docs)] 186 157 #[serde(rename_all = "camelCase")] 187 158 pub struct GetRecordOutput<'a> { 188 159 #[serde(skip_serializing_if = "Option::is_none")] ··· 207 178 } 208 179 209 180 /// Error type for com.atproto.repo.getRecord. 210 - #[derive( 211 - Serialize, 212 - Deserialize, 213 - Debug, 214 - Clone, 215 - PartialEq, 216 - Eq, 217 - )] 181 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 182 + #[allow(missing_docs)] 218 183 #[serde(tag = "error", content = "message")] 219 184 #[serde(bound(deserialize = "'de: 'a"))] 220 185 pub enum GetRecordError<'a> { ··· 269 234 // ============================================================================ 270 235 271 236 /// Request for com.atproto.identity.resolveHandle. 272 - #[derive( 273 - Serialize, 274 - Deserialize, 275 - Debug, 276 - Clone, 277 - PartialEq, 278 - Eq, 279 - )] 237 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 280 238 #[serde(rename_all = "camelCase")] 239 + #[allow(missing_docs)] 281 240 pub struct ResolveHandle<'a> { 282 241 #[serde(borrow)] 283 242 pub handle: Handle<'a>, ··· 294 253 } 295 254 296 255 /// Output for com.atproto.identity.resolveHandle. 297 - #[derive( 298 - Serialize, 299 - Deserialize, 300 - Debug, 301 - Clone, 302 - PartialEq, 303 - Eq, 304 - )] 256 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 305 257 #[serde(rename_all = "camelCase")] 258 + #[allow(missing_docs)] 306 259 pub struct ResolveHandleOutput<'a> { 307 260 #[serde(borrow)] 308 261 pub did: Did<'a>, ··· 319 272 } 320 273 321 274 /// Error type for com.atproto.identity.resolveHandle. 322 - #[derive( 323 - Serialize, 324 - Deserialize, 325 - Debug, 326 - Clone, 327 - PartialEq, 328 - Eq, 329 - )] 275 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 330 276 #[serde(tag = "error", content = "message")] 277 + #[allow(missing_docs)] 331 278 #[serde(bound(deserialize = "'de: 'a"))] 332 279 pub enum ResolveHandleError<'a> { 333 280 #[serde(rename = "HandleNotFound")] ··· 381 328 // ============================================================================ 382 329 383 330 /// Request for com.atproto.identity.resolveDid. 384 - #[derive( 385 - Serialize, 386 - Deserialize, 387 - Debug, 388 - Clone, 389 - PartialEq, 390 - Eq, 391 - )] 331 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 392 332 #[serde(rename_all = "camelCase")] 333 + #[allow(missing_docs)] 393 334 pub struct ResolveDid<'a> { 394 335 #[serde(borrow)] 395 336 pub did: Did<'a>, ··· 406 347 } 407 348 408 349 /// Output for com.atproto.identity.resolveDid. 409 - #[derive( 410 - Serialize, 411 - Deserialize, 412 - Debug, 413 - Clone, 414 - PartialEq, 415 - Eq, 416 - )] 350 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 417 351 #[serde(rename_all = "camelCase")] 352 + #[allow(missing_docs)] 418 353 pub struct ResolveDidOutput<'a> { 419 354 #[serde(borrow)] 420 355 pub did_doc: Data<'a>, ··· 431 366 } 432 367 433 368 /// Error type for com.atproto.identity.resolveDid. 434 - #[derive( 435 - Serialize, 436 - Deserialize, 437 - Debug, 438 - Clone, 439 - PartialEq, 440 - Eq, 441 - )] 369 + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 442 370 #[serde(tag = "error", content = "message")] 443 371 #[serde(bound(deserialize = "'de: 'a"))] 372 + #[allow(missing_docs)] 444 373 pub enum ResolveDidError<'a> { 445 374 #[serde(rename = "DidNotFound")] 446 375 DidNotFound(Option<CowStr<'a>>), ··· 510 439 #[test] 511 440 fn test_list_records_serializes() { 512 441 let req = ListRecords { 513 - repo: AtIdentifier::new("test.bsky.social").unwrap().into_static().into(), 442 + repo: AtIdentifier::new("test.bsky.social") 443 + .unwrap() 444 + .into_static() 445 + .into(), 514 446 collection: Nsid::new("app.bsky.feed.post").unwrap().into_static(), 515 447 cursor: None, 516 448 limit: Some(50), ··· 602 534 #[test] 603 535 fn test_types_implement_into_static() { 604 536 let list_records = ListRecords { 605 - repo: AtIdentifier::new("test.bsky.social").unwrap().into_static().into(), 537 + repo: AtIdentifier::new("test.bsky.social") 538 + .unwrap() 539 + .into_static() 540 + .into(), 606 541 collection: Nsid::new("app.bsky.feed.post").unwrap().into_static(), 607 542 cursor: None, 608 543 limit: Some(50), ··· 611 546 let _static = list_records.into_static(); 612 547 613 548 let get_record = GetRecord { 614 - repo: AtIdentifier::new("test.bsky.social").unwrap().into_static().into(), 549 + repo: AtIdentifier::new("test.bsky.social") 550 + .unwrap() 551 + .into_static() 552 + .into(), 615 553 collection: Nsid::new("app.bsky.feed.post").unwrap().into_static(), 616 554 rkey: CowStr::from("abc123").into_static(), 617 555 cid: None,
-1
crates/jacquard-identity/Cargo.toml
··· 23 23 bon.workspace = true 24 24 bytes.workspace = true 25 25 jacquard-common = { version = "0.10", path = "../jacquard-common", features = ["reqwest-client"] } 26 - jacquard-api = { version = "0.10", path = "../jacquard-api", default-features = false, features = ["minimal"] } 27 26 jacquard-lexicon = { version = "0.10", path = "../jacquard-lexicon", default-features = false } 28 27 reqwest.workspace = true 29 28 serde.workspace = true
+8 -104
crates/jacquard-identity/src/lexicon_resolver.rs
··· 8 8 9 9 use jacquard_common::{ 10 10 IntoStatic, 11 - deps::fluent_uri::Uri, 12 11 deps::smol_str, 13 - from_json_value, 14 - types::{aturi::AtUri, cid::Cid, did::Did, string::Nsid}, 12 + types::{cid::Cid, did::Did, string::Nsid}, 15 13 }; 16 - use jacquard_lexicon::lexicon::LexiconDoc; 17 14 use smol_str::SmolStr; 18 15 19 16 /// Resolve lexicon authority (NSID → authoritative DID) ··· 366 363 } 367 364 } 368 365 369 - #[allow(unused)] 370 - use jacquard_api::com_atproto::lexicon::resolve_lexicon::{ResolveLexicon, ResolveLexiconOutput}; 371 - 372 - impl crate::JacquardResolver { 373 - #[allow(dead_code)] 374 - async fn resolve_lexicon_xrpc( 375 - &self, 376 - nsid: &Nsid<'_>, 377 - ) -> std::result::Result<ResolvedLexiconSchema<'static>, LexiconResolutionError> { 378 - #[cfg(feature = "tracing")] 379 - tracing::debug!("resolving lexicon via XRPC: {}", nsid); 380 - 381 - let qs = serde_html_form::to_string(&ResolveLexicon::new().nsid(nsid.clone()).build()) 382 - .map_err(|e| LexiconResolutionError::fetch_failed(nsid.as_str(), e))?; 383 - 384 - let url_str = format!( 385 - "https://public.api.bsky.app/xrpc/com.atproto.lexicon.resolveLexicon?{}", 386 - qs 387 - ); 388 - let url = Uri::parse(url_str) 389 - .map(|u| u.to_owned()) 390 - .map_err(|(e, _)| LexiconResolutionError::fetch_failed(nsid.as_str(), e))?; 391 - 392 - #[cfg(feature = "tracing")] 393 - tracing::debug!("fetching from URL: {}", url); 394 - 395 - let (buf, status) = self 396 - .get_json_bytes(url.borrow()) 397 - .await 398 - .map_err(|e| LexiconResolutionError::fetch_failed(nsid.as_str(), e))?; 399 - 400 - #[cfg(feature = "tracing")] 401 - tracing::debug!("got response with status: {}", status); 402 - 403 - if !status.is_success() { 404 - return Err(LexiconResolutionError::http_error( 405 - nsid.as_str(), 406 - status.as_u16(), 407 - )); 408 - } 409 - 410 - let val = serde_json::from_slice::<serde_json::Value>(&buf) 411 - .map_err(|e| LexiconResolutionError::parse_failed(nsid.as_str(), e))?; 412 - 413 - #[cfg(feature = "tracing")] 414 - tracing::debug!("parsed JSON response"); 415 - 416 - let obj = val.as_object().ok_or_else(|| { 417 - LexiconResolutionError::resolution_failed(nsid.as_str(), "response not an object") 418 - })?; 419 - 420 - let schema_val = obj.get("schema").ok_or_else(|| { 421 - #[cfg(feature = "tracing")] 422 - tracing::error!( 423 - "response missing 'schema' field, got keys: {:?}", 424 - obj.keys().collect::<Vec<_>>() 425 - ); 426 - 427 - LexiconResolutionError::missing_response_field(nsid.as_str(), "schema") 428 - })?; 429 - 430 - #[cfg(feature = "tracing")] 431 - tracing::debug!("found schema field in response"); 432 - 433 - let schema = from_json_value::<LexiconDoc>(schema_val.clone()) 434 - .map_err(|e| LexiconResolutionError::parse_failed(nsid.as_str(), e))?; 435 - 436 - let uri_str = obj 437 - .get("uri") 438 - .and_then(|v| v.as_str()) 439 - .ok_or_else(|| LexiconResolutionError::missing_response_field(nsid.as_str(), "uri"))?; 440 - 441 - let cid_str = obj 442 - .get("cid") 443 - .and_then(|v| v.as_str()) 444 - .ok_or_else(|| LexiconResolutionError::missing_response_field(nsid.as_str(), "cid"))?; 445 - 446 - let uri = AtUri::new_owned(uri_str) 447 - .map_err(|e| LexiconResolutionError::parse_failed(nsid.as_str(), e))?; 448 - 449 - let cid = Cid::str(cid_str).into_static(); 450 - let repo = Did::raw(uri.authority().as_str()).into_static(); 451 - 452 - #[cfg(feature = "tracing")] 453 - tracing::debug!("successfully resolved lexicon schema for {}", nsid); 454 - 455 - Ok(ResolvedLexiconSchema { 456 - repo, 457 - cid, 458 - nsid: nsid.clone().into_static(), 459 - doc: schema.into_static(), 460 - }) 461 - } 462 - } 463 366 464 367 #[cfg(all(feature = "dns", not(target_family = "wasm")))] 465 368 impl LexiconAuthorityResolver for crate::JacquardResolver { ··· 585 488 &self, 586 489 nsid: &Nsid<'_>, 587 490 ) -> std::result::Result<ResolvedLexiconSchema<'static>, LexiconResolutionError> { 588 - use jacquard_api::com_atproto::repo::get_record::GetRecord; 491 + use jacquard_common::xrpc::atproto::GetRecord; 589 492 use jacquard_common::{IntoStatic, xrpc::XrpcExt}; 590 493 591 494 // Try cache first ··· 624 527 let collection = Nsid::new("com.atproto.lexicon.schema") 625 528 .map_err(|_| LexiconResolutionError::invalid_collection())?; 626 529 627 - let request = GetRecord::new() 628 - .repo(authority_did.clone()) 629 - .collection(collection.into_static()) 630 - .rkey(nsid.clone()) 631 - .build(); 530 + let request = GetRecord { 531 + repo: authority_did.clone().into(), 532 + collection: collection.into_static(), 533 + rkey: nsid.clone().into(), 534 + cid: None, 535 + }; 632 536 633 537 let response = self 634 538 .xrpc(pds)
+16 -11
crates/jacquard-identity/src/lib.rs
··· 75 75 ResolverOptions, 76 76 }; 77 77 use bytes::Bytes; 78 - use jacquard_api::com_atproto::identity::resolve_did; 79 - use jacquard_api::com_atproto::identity::resolve_handle::ResolveHandle; 78 + use jacquard_common::xrpc::atproto::{ResolveDid, ResolveHandle}; 80 79 #[cfg(feature = "streaming")] 81 80 use jacquard_common::ByteStream; 82 81 use jacquard_common::deps::fluent_uri::Uri; ··· 577 576 Some(u) => u.clone(), 578 577 None => return Err(IdentityError::no_pds_fallback()), 579 578 }; 580 - let req = ResolveHandle::new() 581 - .handle(handle.clone().into_static()) 582 - .build(); 579 + let req = ResolveHandle { 580 + handle: handle.clone().into_static(), 581 + }; 583 582 let resp = self.http.xrpc(pds).send(&req).await.map_err(|e| { 584 583 IdentityError::from(e).with_context(format!("resolving handle {}", handle)) 585 584 })?; ··· 608 607 Some(u) => u.clone(), 609 608 None => return Err(IdentityError::no_pds_fallback()), 610 609 }; 611 - let req = resolve_did::ResolveDid::new().did(did.clone()).build(); 610 + let req = ResolveDid { 611 + did: did.clone(), 612 + }; 612 613 let resp = self.http.xrpc(pds).send(&req).await.map_err(|e| { 613 614 IdentityError::from(e).with_context(format!("fetching DID doc for {}", did)) 614 615 })?; ··· 640 641 }; 641 642 // Build URL using string manipulation, then parse 642 643 let qs = serde_html_form::to_string( 643 - &resolve_did::ResolveDid::new() 644 - .did(did.clone().into_static()) 645 - .build(), 644 + &ResolveDid { 645 + did: did.clone().into_static(), 646 + }, 646 647 ) 647 648 .unwrap_or_default(); 648 649 let url_str = if qs.is_empty() { ··· 722 723 // Public unauth fallback 723 724 if self.opts.public_fallback_for_handle { 724 725 if let Ok(qs) = serde_html_form::to_string( 725 - &ResolveHandle::new().handle((*handle).clone()).build(), 726 + &ResolveHandle { 727 + handle: (*handle).clone(), 728 + }, 726 729 ) { 727 730 let url_str = format!( 728 731 "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?{}", ··· 756 759 // Non-auth path: if PlcSource is Slingshot, use its resolveHandle endpoint. 757 760 if let PlcSource::Slingshot { base } = &self.opts.plc_source { 758 761 let qs = serde_html_form::to_string( 759 - &ResolveHandle::new().handle((*handle).clone()).build(), 762 + &ResolveHandle { 763 + handle: (*handle).clone(), 764 + }, 760 765 ) 761 766 .unwrap_or_default(); 762 767 let url_str = if qs.is_empty() {
-1
crates/jacquard-lexgen/Cargo.toml
··· 32 32 clap.workspace = true 33 33 glob = "0.3" 34 34 inventory = "0.3" 35 - jacquard-api = { version = "0.10", path = "../jacquard-api", default-features = false, features = [ "minimal" ] } 36 35 jacquard-common = { version = "0.10", features = [ "reqwest-client" ], path = "../jacquard-common" } 37 36 jacquard-derive = { version = "0.10", path = "../jacquard-derive" } 38 37 jacquard-identity = { version = "0.10", path = "../jacquard-identity", features = ["dns"] }
+15 -19
crates/jacquard-lexgen/src/fetch/sources/atproto.rs
··· 1 1 use super::LexiconSource; 2 - use jacquard_api::com_atproto::repo::list_records::{ListRecords, Record}; 2 + use jacquard_common::xrpc::atproto::{ListRecords, ListRecordsRecord}; 3 3 use jacquard_common::types::ident::AtIdentifier; 4 4 use jacquard_common::types::string::Nsid; 5 5 use jacquard_common::xrpc::XrpcExt; ··· 35 35 Ok(lexicons) 36 36 } 37 37 38 - fn parse_lexicon_record(record_data: &Record<'_>) -> Option<LexiconDoc<'static>> { 38 + fn parse_lexicon_record(record_data: &ListRecordsRecord<'_>) -> Option<LexiconDoc<'static>> { 39 39 // // Extract the 'value' field from the record 40 40 // let value = match record_data { 41 41 // jacquard_common::types::value::Data::Object(map) => map.0.get("value")?, ··· 104 104 let mut lexicons = HashMap::new(); 105 105 106 106 // Try to fetch all records at once first 107 - let req = ListRecords::new() 108 - .repo(repo.clone().into_static()) 109 - .collection(collection.clone().into_static()) 110 - .build(); 107 + let req = ListRecords { 108 + repo: repo.clone().into_static().into(), 109 + collection: collection.clone().into_static(), 110 + cursor: None, 111 + limit: None, 112 + reverse: None, 113 + }; 111 114 112 115 let resp = resolver.xrpc(pds.clone()).send(&req).await?; 113 116 ··· 128 131 129 132 let mut cursor: Option<CowStr> = None; 130 133 loop { 131 - let req = if let Some(ref c) = cursor { 132 - ListRecords::new() 133 - .repo(repo.clone().into_static()) 134 - .collection(collection.clone().into_static()) 135 - .limit(1) 136 - .cursor(c.clone()) 137 - .build() 138 - } else { 139 - ListRecords::new() 140 - .repo(repo.clone().into_static()) 141 - .collection(collection.clone().into_static()) 142 - .limit(1) 143 - .build() 134 + let req = ListRecords { 135 + repo: repo.clone().into_static().into(), 136 + collection: collection.clone().into_static(), 137 + cursor: cursor.clone(), 138 + limit: Some(1), 139 + reverse: None, 144 140 }; 145 141 let resp = resolver.xrpc(pds.clone()).send(&req).await?; 146 142