+1
-1
crates/jacquard-axum/src/service_auth.rs
+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
+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
+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
+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
+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
+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;