A better Rust ATProto crate
102
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs updates, improved explanation of lifetimes and patterns, linked to docs.rs

Orual c33ec212 c27a050b

+98 -42
+1 -1
crates/jacquard-axum/src/service_auth.rs
··· 249 249 #[error("missing Authorization header")] 250 250 MissingAuthHeader, 251 251 252 - /// Authorization header is malformed (not "Bearer <token>") 252 + /// Authorization header is malformed (not "Bearer `token`") 253 253 #[error("invalid Authorization header format")] 254 254 InvalidAuthHeader, 255 255
+3 -12
crates/jacquard-common/src/cowstr.rs
··· 1 - use serde::{Deserialize, Deserializer, Serialize}; 1 + use serde::{Deserialize, Serialize}; 2 2 use smol_str::SmolStr; 3 3 use std::{ 4 4 borrow::Cow, ··· 9 9 10 10 use crate::IntoStatic; 11 11 12 - /// Shamelessly copied from [](https://github.com/bearcove/merde) 13 12 /// A copy-on-write immutable string type that uses [`SmolStr`] for 14 13 /// the "owned" variant. 15 14 /// 16 15 /// The standard [`Cow`] type cannot be used, since 17 16 /// `<str as ToOwned>::Owned` is `String`, and not `SmolStr`. 17 + /// 18 + /// Shamelessly ported from [merde](https://github.com/bearcove/merde) 18 19 #[derive(Clone)] 19 20 pub enum CowStr<'s> { 20 21 /// &str varaiant ··· 330 331 { 331 332 deserializer.deserialize_str(CowStrVisitor) 332 333 } 333 - } 334 - 335 - /// Serde helper for deserializing stuff when you want an owned version 336 - pub fn deserialize_owned<'de, T, D>(deserializer: D) -> Result<<T as IntoStatic>::Output, D::Error> 337 - where 338 - T: Deserialize<'de> + IntoStatic, 339 - D: Deserializer<'de>, 340 - { 341 - let value = T::deserialize(deserializer)?; 342 - Ok(value.into_static()) 343 334 } 344 335 345 336 /// Convert to a CowStr.
+2 -1
crates/jacquard-common/src/into_static.rs
··· 7 7 use std::hash::Hash; 8 8 use std::sync::Arc; 9 9 10 - /// Shamelessly copied from [](https://github.com/bearcove/merde) 11 10 /// Allow turning a value into an "owned" variant, which can then be 12 11 /// returned, moved, etc. 13 12 /// 14 13 /// This usually involves allocating buffers for `Cow<'a, str>`, etc. 14 + /// 15 + /// Shamelessly copied from [merde](https://github.com/bearcove/merde) 15 16 pub trait IntoStatic: Sized { 16 17 /// The "owned" variant of the type. For `Cow<'a, str>`, this is `Cow<'static, str>`, for example. 17 18 type Output: 'static;
+12 -2
crates/jacquard-common/src/lib.rs
··· 211 211 /// HTTP client abstraction used by jacquard crates. 212 212 pub mod http_client; 213 213 pub mod macros; 214 - /// Generic session storage traits and utilities. 215 - pub mod session; 216 214 /// Service authentication JWT parsing and verification. 217 215 #[cfg(feature = "service-auth")] 218 216 pub mod service_auth; 217 + /// Generic session storage traits and utilities. 218 + pub mod session; 219 219 /// Baseline fundamental AT Protocol data types. 220 220 pub mod types; 221 221 // XRPC protocol types and traits ··· 239 239 } 240 240 } 241 241 } 242 + 243 + /// Serde helper for deserializing stuff when you want an owned version 244 + pub fn deserialize_owned<'de, T, D>(deserializer: D) -> Result<<T as IntoStatic>::Output, D::Error> 245 + where 246 + T: serde::Deserialize<'de> + IntoStatic, 247 + D: serde::Deserializer<'de>, 248 + { 249 + let value = T::deserialize(deserializer)?; 250 + Ok(value.into_static()) 251 + }
+1 -1
crates/jacquard-common/src/types/collection.rs
··· 21 21 const NSID: &'static str; 22 22 23 23 /// A marker type implementing [`XrpcResp`] that allows typed deserialization of records 24 - /// from this collection. Used by [`Agent::get_record`] to return properly typed responses. 24 + /// from this collection. Used by [`AgentSessionExt::get_record`](https://docs.rs/jacquard/latest/jacquard/client/trait.AgentSessionExt.html) to return properly typed responses. 25 25 type Record: XrpcResp; 26 26 27 27 /// Returns the [`Nsid`] for the Lexicon that defines the schema of records in this
+79 -25
crates/jacquard/src/lib.rs
··· 1 1 //! # Jacquard 2 2 //! 3 - //! A suite of Rust crates for the AT Protocol. 3 + //! A suite of Rust crates intended to make it much easier to get started with atproto development, 4 + //! without sacrificing flexibility or performance. 5 + //! 6 + //! [Jacquard is simpler](https://whtwnd.com/nonbinary.computer/3m33efvsylz2s) because it is 7 + //! designed in a way which makes things simple that almost every other atproto library seems to make difficult. 8 + //! 9 + //! It is also designed around zero-copy/borrowed deserialization: types like [`Post<'_>`](https://docs.rs/jacquard-api/latest/jacquard_api/app_bsky/feed/post/struct.Post.html) 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. 4 10 //! 5 11 //! 6 12 //! ## Goals and Features 7 13 //! 8 14 //! - Validated, spec-compliant, easy to work with, and performant baseline types 9 15 //! - Batteries-included, but easily replaceable batteries. 10 - //! - Easy to extend with custom lexicons 11 - //! - Straightforward OAuth 12 - //! - stateless options (or options where you handle the state) for rolling your own 13 - //! - all the building blocks of the convenient abstractions are available 14 - //! - lexicon Value type for working with unknown atproto data (dag-cbor or json) 15 - //! - order of magnitude less boilerplate than some existing crates 16 - //! - use as much or as little from the crates as you need 16 + //! - Easy to extend with custom lexicons using code generation or handwritten api types 17 + //! - Straightforward OAuth 18 + //! - Stateless options (or options where you handle the state) for rolling your own 19 + //! - All the building blocks of the convenient abstractions are available 20 + //! - Server-side convenience features 21 + //! - Lexicon Data value type for working with unknown atproto data (dag-cbor or json) 22 + //! - An order of magnitude less boilerplate than some existing crates 23 + //! - Use as much or as little from the crates as you need 24 + //! 17 25 //! 18 26 //! 19 27 //! ··· 77 85 //!} 78 86 //! ``` 79 87 //! 80 - //! ## Client options: 88 + //! 89 + //! ## Component crates 90 + //! 91 + //! Jacquard is split into several crates for modularity. The main `jacquard` crate 92 + //! re-exports most of the others, so you typically only need to depend on it directly. 93 + //! 94 + //! - [`jacquard-common`](https://docs.rs/jacquard-common/latest/jacquard_common/index.html) - AT Protocol types (DIDs, handles, at-URIs, NSIDs, TIDs, CIDs, etc.) 95 + //! - [`jacquard-api`](https://docs.rs/jacquard-api/latest/jacquard_api/index.html) - Generated API bindings from 646+ lexicon schemas 96 + //! - [`jacquard-axum`](https://docs.rs/jacquard-axum/latest/jacquard_axum/index.html) - Server-side XRPC handler extractors for Axum framework (not re-exported, depends on jacquard) 97 + //! - [`jacquard-oauth`](https://docs.rs/jacquard-oauth/latest/jacquard_oauth/index.html) - OAuth/DPoP flow implementation with session management 98 + //! - [`jacquard-identity`](https://docs.rs/jacquard-identity/latest/jacquard_identity/index.html) - Identity resolution (handle → DID, DID → Doc, OAuth metadata) 99 + //! - [`jacquard-lexicon`](https://docs.rs/jacquard-lexicon/latest/jacquard_lexicon/index.html) - Lexicon resolution, fetching, parsing and Rust code generation from schemas 100 + //! - [`jacquard-derive`](https://docs.rs/jacquard-derive/latest/jacquard_derive/index.html) - Macros (`#[lexicon]`, `#[open_union]`, `#[derive(IntoStatic)]`) 101 + //! 102 + //! 103 + //! ### A note on lifetimes 104 + //! 105 + //! You'll notice a bunch of lifetimes all over Jacquard types, examples, and so on. If you're newer 106 + //! to Rust or have simply avoided them, they're part of how Rust knows how long to keep something 107 + //! around before cleaning it up. They're not unique to Rust (C and C++ have the same concept 108 + //! internally) but Rust is perhaps the one language that makes them explicit, because they're part 109 + //! of how it validates that things are memory-safe, and being able to give information to the compiler 110 + //! about how long it can expect something to stick around lets the compiler reason out much more 111 + //! sophisticated things. [The Rust book](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) has a section on them if you want a refresher. 112 + //! 113 + //! > On Jacquard types like [`CowStr`], a `'static` lifetime parameter is used to refer to the owned 114 + //! version of a type, in the same way `String` is the owned version of `&str`. 115 + //! 116 + //! This is somewhat in tension with the 'make things simpler' goal of the crate, but it is honestly 117 + //! pretty straightforward once you know the deal, and Jacquard provides a number of escape hatches 118 + //! and easy ways to work. 119 + //! 120 + //! Because explicit lifetimes are somewhat unique to Rust and are not something you may be used to 121 + //! thinking about, they can seem a bit scary to work with. Normally the compiler is pretty good at 122 + //! them, but Jacquard is [built around borrowed deserialization](https://docs.rs/jacquard-common/latest/jacquard_common/#working-with-lifetimes-and-zero-copy-deserialization) and types. This is for reasons of 123 + //! speed and efficiency, because borrowing from your source buffer saves copying the data around. 124 + //! 125 + //! However, it does mean that any Jacquard type that can borrow (not all of them do) is annotated 126 + //! with a lifetime, to confirm that all the borrowed bits are ["covariant"](https://doc.rust-lang.org/nomicon/subtyping.html), i.e. that they all live 127 + //! at least the same amount of time, and that lifetime matches or exceeds the lifetime of the data 128 + //! structure. This also imposes certain restrictions on deserialization. Namely the [`DeserializeOwned`](https://serde.rs/lifetimes.html) 129 + //! bound does not apply to almost any types in Jacquard. There is a [`deserialize_owned`] function 130 + //! which you can use in a serde `deserialize_with` attribute to help, but the general pattern is 131 + //! to do borrowed deserialization and then call [`.into_static()`] if you need ownership. 132 + //! 133 + //! ### Easy mode 134 + //! 135 + //! Easy mode for jacquard is to mostly just use `'static` for your lifetime params and derive/use 136 + //! [`.into_static()`] as needed. When writing, first see if you can get away with `Thing<'_>` 137 + //! and let the compiler infer. second-easiest after that is `Thing<'static>`, third-easiest is giving 138 + //! everything one lifetime, e.g. `fn foo<'a>(&'a self, thing: Thing<'a>) -> /* thing with lifetime 'a */`. 139 + //! 140 + //! When parsing the output of atproto API calls, you can call `.into_output()` on the `Response<R>` 141 + //! struct to get an owned version with a `'static` lifetime. When deserializing, do not use 142 + //! `from_writer()` type deserialization functions, or features like Axum's `Json` extractor, as they 143 + //! have DeserializeOwned bounds and cannot borrow from their buffer. Either use Jacquard's features 144 + //! to get an owned version or follow the same [patterns](https://whtwnd.com/nonbinary.computer/3m33efvsylz2s) it uses in your own code. 145 + //! 146 + //! ## Client options 81 147 //! 82 148 //! - Stateless XRPC: any `HttpClient` (e.g., `reqwest::Client`) implements `XrpcExt`, 83 149 //! which provides `xrpc(base: Url) -> XrpcCall` for per-request calls with ··· 112 178 //! base endpoint to the user's PDS on login/restore. 113 179 //! - Stateful client (OAuth): `OAuthClient<S, T>` and `OAuthSession<S, T>` where `S: ClientAuthStore` and 114 180 //! `T: OAuthResolver + HttpClient`. The client is used to authenticate, returning a session which handles authentication and token refresh internally. 115 - //! - `Agent<A: AgentSession>` Session abstracts over the above two options. Currently it is a thin wrapper, but this will be the thing that gets all the convenience helpers. 181 + //! - `Agent<A: AgentSession>` Session abstracts over the above two options and provides some useful convenience features via the [`AgentSessionExt`] trait. 116 182 //! 117 183 //! Per-request overrides (stateless) 118 184 //! ```no_run ··· 145 211 //! } 146 212 //! ``` 147 213 //! 148 - //! ## Component Crates 149 - //! 150 - //! Jacquard is split into several crates for modularity. The main `jacquard` crate 151 - //! re-exports most of the others, so you typically only need to depend on it directly. 152 - //! 153 - //! - [`jacquard-common`] - AT Protocol types (DIDs, handles, at-URIs, NSIDs, TIDs, CIDs, etc.) 154 - //! - [`jacquard-api`] - Generated API bindings from 646+ lexicon schemas 155 - //! - [`jacquard-axum`] - Server-side XRPC handler extractors for Axum framework (not re-exported, depends on jacquard) 156 - //! - [`jacquard-oauth`] - OAuth/DPoP flow implementation with session management 157 - //! - [`jacquard-identity`] - Identity resolution (handle→DID, DID→Doc, OAuth metadata) 158 - //! - [`jacquard-lexicon`] - Lexicon resolution, fetching, parsing and Rust code generation from schemas 159 - //! - [`jacquard-derive`] - Macros (`#[lexicon]`, `#[open_union]`, `#[derive(IntoStatic)]`) 214 + //! [`deserialize_owned`]: crate::deserialize_owned 215 + //! [`AgentSessionExt`]: crate::client::AgentSessionExt 216 + //! [`.into_static()`]: IntoStatic 160 217 161 218 #![warn(missing_docs)] 162 219 ··· 164 221 165 222 pub use common::*; 166 223 #[cfg(feature = "api")] 167 - /// If enabled, re-export the generated api crate 168 224 pub use jacquard_api as api; 169 225 pub use jacquard_common as common; 170 226 171 227 #[cfg(feature = "derive")] 172 - /// if enabled, reexport the attribute macros 173 228 pub use jacquard_derive::*; 174 229 175 230 pub use jacquard_identity as identity; 176 231 177 - /// OAuth usage helpers (discovery, PAR, token exchange) 178 232 pub use jacquard_oauth as oauth;