A better Rust ATProto crate
1# Jacquard 2 3A suite of Rust crates for the AT Protocol. 4 5[![Crates.io](https://img.shields.io/crates/v/jacquard.svg)](https://crates.io/crates/jacquard) [![Documentation](https://docs.rs/jacquard/badge.svg)](https://docs.rs/jacquard) [![License](https://img.shields.io/crates/l/jacquard.svg)](./LICENSE) 6 7## Goals 8 9- Validated, spec-compliant, easy to work with, and performant baseline types (including typed at:// uris) 10- Batteries-included, but easily replaceable batteries. 11 - Easy to extend with custom lexicons 12- lexicon Value type for working with unknown atproto data (dag-cbor or json) 13- order of magnitude less boilerplate than some existing crates 14 - either the codegen produces code that's easy to work with, or there are good handwritten wrappers 15- didDoc type with helper methods for getting handles, multikey, and PDS endpoint 16- use as much or as little from the crates as you need 17 18 19## Example 20 21Dead simple API client. Logs in with an app password and prints the latest 5 posts from your timeline. 22 23```rust 24use std::sync::Arc; 25use clap::Parser; 26use jacquard::CowStr; 27use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 28use jacquard::client::credential_session::{CredentialSession, SessionKey}; 29use jacquard::client::{AtpSession, FileAuthStore, MemorySessionStore}; 30use jacquard::identity::PublicResolver as JacquardResolver; 31use miette::IntoDiagnostic; 32 33#[derive(Parser, Debug)] 34#[command(author, version, about = "Jacquard - AT Protocol client demo")] 35struct Args { 36 /// Username/handle (e.g., alice.bsky.social) or DID 37 #[arg(short, long)] 38 username: CowStr<'static>, 39 /// App password 40 #[arg(short, long)] 41 password: CowStr<'static>, 42} 43 44#[tokio::main] 45async fn main() -> miette::Result<()> { 46 let args = Args::parse(); 47 48 // Resolver + storage 49 let resolver = Arc::new(JacquardResolver::default()); 50 let store: Arc<MemorySessionStore<SessionKey, AtpSession>> = Arc::new(Default::default()); 51 let client = Arc::new(resolver.clone()); 52 let session = CredentialSession::new(store, client); 53 54 // Login (resolves PDS automatically) and persist as (did, "session") 55 session 56 .login(args.username.clone(), args.password.clone(), None, None, None) 57 .await 58 .into_diagnostic()?; 59 60 // Fetch timeline 61 let timeline = session 62 .clone() 63 .send(GetTimeline::new().limit(5).build()) 64 .await 65 .into_diagnostic()? 66 .into_output() 67 .into_diagnostic()?; 68 69 println!("\ntimeline ({} posts):", timeline.feed.len()); 70 for (i, post) in timeline.feed.iter().enumerate() { 71 println!("\n{}. by {}", i + 1, post.post.author.handle); 72 println!( 73 " {}", 74 serde_json::to_string_pretty(&post.post.record).into_diagnostic()? 75 ); 76 } 77 78 Ok(()) 79} 80``` 81 82## Development 83 84This repo uses [Flakes](https://nixos.asia/en/flakes) from the get-go. 85 86```bash 87# Dev shell 88nix develop 89 90# or run via cargo 91nix develop -c cargo run 92 93# build 94nix build 95``` 96 97There's also a [`justfile`](https://just.systems/) for Makefile-esque commands to be run inside of the devShell, and you can generally `cargo ...` or `just ...` whatever just fine if you don't want to use Nix and have the prerequisites installed.