A better Rust ATProto crate
1[![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) 2 3# Jacquard 4 5A suite of Rust crates intended to make it much easier to get started with atproto development, without sacrificing flexibility or performance. 6 7[Jacquard is simpler](https://whtwnd.com/nonbinary.computer/3m33efvsylz2s) because it is designed in a way which makes things simple that almost every other atproto library seems to make difficult. 8 9It is also designed around zero-copy/borrowed deserialization: types like [`Post<'_>`](https://tangled.org/@nonbinary.computer/jacquard/blob/main/crates/jacquard-api/src/app_bsky/feed/post.rs) can borrow data (via the [`CowStr<'_>`](https://docs.rs/jacquard/latest/jacquard/cowstr/enum.CowStr.html) type and a host of other types built on top of it) directly from the response buffer instead of allocating owned copies. Owned versions are themselves mostly inlined or reference-counted pointers and are therefore still quite efficient. The `IntoStatic` trait (which is derivable) makes it easy to get an owned version and avoid worrying about lifetimes. 10 11## Goals and Features 12 13- Validated, spec-compliant, easy to work with, and performant baseline types 14- Batteries-included, but easily replaceable batteries. 15 - Easy to extend with custom lexicons using code generation or handwritten api types 16 - Straightforward OAuth 17 - Stateless options (or options where you handle the state) for rolling your own 18 - All the building blocks of the convenient abstractions are available 19 - Server-side convenience features 20- Lexicon Data value type for working with unknown atproto data (dag-cbor or json) 21- An order of magnitude less boilerplate than some existing crates 22- Use as much or as little from the crates as you need 23 24 25## Example 26 27Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline. 28 29```rust 30// Note: this requires the `loopback` feature enabled (it is currently by default) 31use clap::Parser; 32use jacquard::CowStr; 33use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 34use jacquard::client::{Agent, FileAuthStore}; 35use jacquard::oauth::client::OAuthClient; 36use jacquard::oauth::loopback::LoopbackConfig; 37use jacquard::types::xrpc::XrpcClient; 38use miette::IntoDiagnostic; 39 40#[derive(Parser, Debug)] 41#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")] 42struct Args { 43 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 44 input: CowStr<'static>, 45 46 /// Path to auth store file (will be created if missing) 47 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 48 store: String, 49} 50 51#[tokio::main] 52async fn main() -> miette::Result<()> { 53 let args = Args::parse(); 54 55 // Build an OAuth client with file-backed auth store and default localhost config 56 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store)); 57 // Authenticate with a PDS, using a loopback server to handle the callback flow 58 let session = oauth 59 .login_with_local_server( 60 args.input.clone(), 61 Default::default(), 62 LoopbackConfig::default(), 63 ) 64 .await?; 65 // Wrap in Agent and fetch the timeline 66 let agent: Agent<_> = Agent::from(session); 67 let timeline = agent 68 .send(&GetTimeline::new().limit(5).build()) 69 .await? 70 .into_output()?; 71 for (i, post) in timeline.feed.iter().enumerate() { 72 println!("\n{}. by {}", i + 1, post.post.author.handle); 73 println!( 74 " {}", 75 serde_json::to_string_pretty(&post.post.record).into_diagnostic()? 76 ); 77 } 78 79 Ok(()) 80} 81 82``` 83 84If you have `just` installed, you can run the [examples](https://tangled.org/@nonbinary.computer/jacquard/tree/main/examples) using `just example {example-name} {ARGS}` or `just examples` to see what's available. 85 86## Component crates 87 88Jacquard is broken up into several crates for modularity. The correct one to use is generally `jacquard` itself, as it re-exports most of the others. 89- `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) 90- `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) 91- `jacquard-axum`: Axum extractor and other helpers [![Crates.io](https://img.shields.io/crates/v/jacquard-axum.svg)](https://crates.io/crates/jacquard-axum) [![Documentation](https://docs.rs/jacquard-axum/badge.svg)](https://docs.rs/jacquard-axum) 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`: 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## Changelog 99 100[CHANGELOG.md](./CHANGELOG.md) 101 102Highlights: 103 104- better value type deserialization helpers 105- service auth implementation 106- XrpcRequest derive Macros 107- more builders in generated api to make constructing things easier (lmk if compile time is awful) 108- `AgentSessionExt` trait with a host of convenience methods for working with records and preferences 109- Improvements to the `Collection` trait, code generation, and addition of the `VecUpdate` trait to enable that 110- A bunch of examples, both in the docs and in the repository 111- More lexicons in the generated API bindings. 112 113## Development 114 115This repo uses [Flakes](https://nixos.asia/en/flakes) from the get-go. 116 117```bash 118# Dev shell 119nix develop 120 121# or run via cargo 122nix develop -c cargo run 123 124# build 125nix build 126``` 127 128There'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. 129 130[![License](https://img.shields.io/crates/l/jacquard.svg)](./LICENSE)