A better Rust ATProto crate
1# Jacquard 2 3A suite of Rust crates for the AT Protocol. [Docs](https://docs.rs/jacquard/latest/jacquard/) 4 5## Goals and Features 6 7- Validated, spec-compliant, easy to work with, and performant baseline types 8- Batteries-included, but easily replaceable batteries. 9 - Easy to extend with custom lexicons using code generation or handwritten api types 10 - Straightforward OAuth 11 - stateless options (or options where you handle the state) for rolling your own 12 - all the building blocks of the convenient abstractions are available 13- lexicon Value type for working with unknown atproto data (dag-cbor or json) 14- order of magnitude less boilerplate than some existing crates 15- use as much or as little from the crates as you need 16 17## Example 18 19Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline. 20 21```rust 22// Note: this requires the `loopback` feature enabled (it is currently by default) 23use clap::Parser; 24use jacquard::CowStr; 25use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 26use jacquard::client::{Agent, FileAuthStore}; 27use jacquard::oauth::atproto::AtprotoClientMetadata; 28use jacquard::oauth::client::OAuthClient; 29use jacquard::oauth::loopback::LoopbackConfig; 30use jacquard::oauth::scopes::Scope; 31use jacquard::types::xrpc::XrpcClient; 32use miette::IntoDiagnostic; 33 34#[derive(Parser, Debug)] 35#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")] 36struct Args { 37 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 38 input: CowStr<'static>, 39 40 /// Path to auth store file (will be created if missing) 41 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 42 store: String, 43} 44 45#[tokio::main] 46async fn main() -> miette::Result<()> { 47 let args = Args::parse(); 48 49 // File-backed auth store for testing 50 let store = FileAuthStore::new(&args.store); 51 let client_data = jacquard_oauth::session::ClientData { 52 keyset: None, 53 // Default sets normal localhost redirect URIs and "atproto transition:generic" scopes. 54 // The localhost helper will ensure you have at least "atproto" and will fix urls 55 config: AtprotoClientMetadata::default_localhost() 56 }; 57 58 // Build an OAuth client 59 let oauth = OAuthClient::new(store, client_data); 60 // Authenticate with a PDS, using a loopback server to handle the callback flow 61 let session = oauth 62 .login_with_local_server( 63 args.input.clone(), 64 Default::default(), 65 LoopbackConfig::default(), 66 ) 67 .await?; 68 // Wrap in Agent and fetch the timeline 69 let agent: Agent<_> = Agent::from(session); 70 let timeline = agent 71 .send(&GetTimeline::new().limit(5).build()) 72 .await? 73 .into_output()?; 74 for (i, post) in timeline.feed.iter().enumerate() { 75 println!("\n{}. by {}", i + 1, post.post.author.handle); 76 println!( 77 " {}", 78 serde_json::to_string_pretty(&post.post.record).into_diagnostic()? 79 ); 80 } 81 82 Ok(()) 83} 84 85``` 86 87## Component crates 88 89Jacquard is broken up into several crates for modularity. The correct one to use is generally `jacquard` itself, as it re-exports the others. 90- `jacquard`: Main crate [![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) 91- `jacquard-common`: Foundation crate [![Crates.io](https://img.shields.io/crates/v/jacquard-common.svg)](https://crates.io/crates/jacquard-common) [![Documentation](https://docs.rs/jacquard-common/badge.svg)](https://docs.rs/jacquard-common) 92- `jacquard-api`: Autogenerated API bindings [![Crates.io](https://img.shields.io/crates/v/jacquard-api.svg)](https://crates.io/crates/jacquard-api) [![Documentation](https://docs.rs/jacquard-api/badge.svg)](https://docs.rs/jacquard-api) 93- `jacquard-oauth`: atproto OAuth implementation [![Crates.io](https://img.shields.io/crates/v/jacquard-oauth.svg)](https://crates.io/crates/jacquard-oauth) [![Documentation](https://docs.rs/jacquard-oauth/badge.svg)](https://docs.rs/jacquard-oauth) 94- `jacquard-identity`: Identity resolution [![Crates.io](https://img.shields.io/crates/v/jacquard-identity.svg)](https://crates.io/crates/jacquard-identity) [![Documentation](https://docs.rs/jacquard-identity/badge.svg)](https://docs.rs/jacquard-identity) 95- `jacquard-lexicon`: Lexicon parsing and code generation [![Crates.io](https://img.shields.io/crates/v/jacquard-lexicon.svg)](https://crates.io/crates/jacquard-lexicon) [![Documentation](https://docs.rs/jacquard-lexicon/badge.svg)](https://docs.rs/jacquard-lexicon) 96- `jacquard-derive`: Derive macros for lexicon types [![Crates.io](https://img.shields.io/crates/v/jacquard-derive.svg)](https://crates.io/crates/jacquard-derive) [![Documentation](https://docs.rs/jacquard-derive/badge.svg)](https://docs.rs/jacquard-derive) 97 98## Development 99 100This repo uses [Flakes](https://nixos.asia/en/flakes) from the get-go. 101 102```bash 103# Dev shell 104nix develop 105 106# or run via cargo 107nix develop -c cargo run 108 109# build 110nix build 111``` 112 113There'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. 114 115[![License](https://img.shields.io/crates/l/jacquard.svg)](./LICENSE)