A better Rust ATProto crate
1[](https://crates.io/crates/jacquard) [](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## 0.7.0 Release Highlights:
12
13- **Bluesky-style rich text support**
14 - Parses from supplied text as well as explicit builder
15 - Sanitizes input text
16 - Also handles \[]() Markdown-style links
17 - Optionally pulls out candidates for link/record embedding
18 - Optionally fetches Opengraph link data for external links
19- **Moderation label application**
20 - Generic implementation of atproto moderation/labeling client-side filtering/tagging via traits
21 - Implementations for Bluesky and other types on best-effort basis
22 - Demonstration options for use while avoiding Bluesky namespace or AppView infrastructure
23- **Websocket Subscriber-sent message control traits/code**
24 - Primarily useful for Jetstream or other custom Websocket services
25- Fixed some `Data` value type deserialization issues
26
27> [!WARNING]
28> A lot of the streaming code is still pretty experimental. The examples work, though.\
29The modules are also less well-documented, and don't have code examples. There are also a lot of utility functions for conveniently working with the streams and transforming them which are lacking. Use [`n0-future`](https://docs.rs/n0-future/latest/n0_future/index.html) to work with them, that is what Jacquard uses internally as much as possible.
30
31### Changelog
32
33[CHANGELOG.md](./CHANGELOG.md)
34
35## Goals and Features
36
37- Validated, spec-compliant, easy to work with, and performant baseline types
38- Batteries-included, but easily replaceable batteries.
39 - Easy to extend with custom lexicons using code generation or handwritten api types
40 - Straightforward OAuth
41 - Stateless options (or options where you handle the state) for rolling your own
42 - All the building blocks of the convenient abstractions are available
43 - Server-side convenience features
44- Lexicon Data value type for working with unknown atproto data (dag-cbor or json)
45- An order of magnitude less boilerplate than some existing crates
46- Use as much or as little from the crates as you need
47
48## Example
49
50Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline.
51
52```rust
53// Note: this requires the `loopback` feature enabled (it is currently by default)
54use clap::Parser;
55use jacquard::CowStr;
56use jacquard::api::app_bsky::feed::get_timeline::GetTimeline;
57use jacquard::client::{Agent, FileAuthStore};
58use jacquard::oauth::client::OAuthClient;
59use jacquard::oauth::loopback::LoopbackConfig;
60use jacquard::types::xrpc::XrpcClient;
61use miette::IntoDiagnostic;
62
63#[derive(Parser, Debug)]
64#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")]
65struct Args {
66 /// Handle (e.g., alice.bsky.social), DID, or PDS URL
67 input: CowStr<'static>,
68
69 /// Path to auth store file (will be created if missing)
70 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")]
71 store: String,
72}
73
74#[tokio::main]
75async fn main() -> miette::Result<()> {
76 let args = Args::parse();
77
78 // Build an OAuth client with file-backed auth store and default localhost config
79 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
80 // Authenticate with a PDS, using a loopback server to handle the callback flow
81 let session = oauth
82 .login_with_local_server(
83 args.input.clone(),
84 Default::default(),
85 LoopbackConfig::default(),
86 )
87 .await?;
88 // Wrap in Agent and fetch the timeline
89 let agent: Agent<_> = Agent::from(session);
90 let timeline = agent
91 .send(&GetTimeline::new().limit(5).build())
92 .await?
93 .into_output()?;
94 for (i, post) in timeline.feed.iter().enumerate() {
95 println!("\n{}. by {}", i + 1, post.post.author.handle);
96 println!(
97 " {}",
98 serde_json::to_string_pretty(&post.post.record).into_diagnostic()?
99 );
100 }
101
102 Ok(())
103}
104
105```
106
107If 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.
108
109## Component crates
110
111Jacquard 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.
112
113| | | |
114| --- | --- | --- |
115| `jacquard` | Main crate | [](https://crates.io/crates/jacquard) [](https://docs.rs/jacquard) |
116|`jacquard-common` | Foundation crate | [](https://crates.io/crates/jacquard-common) [](https://docs.rs/jacquard-common)|
117| `jacquard-axum` | Axum extractor and other helpers | [](https://crates.io/crates/jacquard-axum) [](https://docs.rs/jacquard-axum) |
118| `jacquard-api` | Autogenerated API bindings | [](https://crates.io/crates/jacquard-api) [](https://docs.rs/jacquard-api) |
119| `jacquard-oauth` | atproto OAuth implementation | [](https://crates.io/crates/jacquard-oauth) [](https://docs.rs/jacquard-oauth) |
120| `jacquard-identity` | Identity resolution | [](https://crates.io/crates/jacquard-identity) [](https://docs.rs/jacquard-identity) |
121| `jacquard-lexicon` | Lexicon parsing and code generation | [](https://crates.io/crates/jacquard-lexicon) [](https://docs.rs/jacquard-lexicon) |
122| `jacquard-derive` | Macros for lexicon types | [](https://crates.io/crates/jacquard-derive) [](https://docs.rs/jacquard-derive) |
123
124## Development
125
126This repo uses [Flakes](https://nixos.asia/en/flakes)
127
128```bash
129# Dev shell
130nix develop
131
132# or run via cargo
133nix develop -c cargo run
134
135# build
136nix build
137```
138
139There'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.
140
141[](./LICENSE)