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 [](https://crates.io/crates/jacquard) [](https://docs.rs/jacquard)
91- `jacquard-common`: Foundation crate [](https://crates.io/crates/jacquard-common) [](https://docs.rs/jacquard-common)
92- `jacquard-api`: Autogenerated API bindings [](https://crates.io/crates/jacquard-api) [](https://docs.rs/jacquard-api)
93- `jacquard-oauth`: atproto OAuth implementation [](https://crates.io/crates/jacquard-oauth) [](https://docs.rs/jacquard-oauth)
94- `jacquard-identity`: Identity resolution [](https://crates.io/crates/jacquard-identity) [](https://docs.rs/jacquard-identity)
95- `jacquard-lexicon`: Lexicon parsing and code generation [](https://crates.io/crates/jacquard-lexicon) [](https://docs.rs/jacquard-lexicon)
96- `jacquard-derive`: Derive macros for lexicon types [](https://crates.io/crates/jacquard-derive) [](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)