A better Rust ATProto crate

version bump, need to do proper tests before release

Orual 8f04c5e5 d02398d1

Changed files
+76 -60
crates
jacquard
jacquard-api
jacquard-common
jacquard-identity
jacquard-oauth
-13
Cargo.lock
···
]
[[package]]
-
name = "enum_dispatch"
-
version = "0.3.13"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
-
dependencies = [
-
"once_cell",
-
"proc-macro2",
-
"quote",
-
"syn 2.0.106",
-
]
-
-
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"chrono",
"cid",
"ed25519-dalek",
-
"enum_dispatch",
"http",
"ipld-core",
"k256",
+18 -1
Cargo.toml
···
[workspace.package]
edition = "2024"
-
version = "0.2.0"
+
version = "0.3.0"
authors = ["Orual <orual@nonbinary.computer>"]
repository = "https://tangled.org/@nonbinary.computer/jacquard"
keywords = ["atproto", "at", "bluesky", "api", "client"]
···
# HTTP
http = "1.3"
reqwest = { version = "0.12", default-features = false }
+
+
# Async and runtimes
+
async-trait = "0.1"
+
tokio = "1"
+
+
# Encoding and crypto building blocks
+
base64 = "0.22"
+
percent-encoding = "2.3"
+
urlencoding = "2.1.3"
+
rand_core = "0.6"
+
+
# Time
+
chrono = "0.4"
+
+
# Crypto curves and JOSE
+
p256 = "0.13"
+
jose-jwk = "0.1"
+1 -1
crates/jacquard-api/Cargo.toml
···
tools_ozone = []
[dependencies]
-
bon = "3"
+
bon.workspace = true
bytes = { workspace = true, features = ["serde"] }
jacquard-common = { version = "0.2.0", path = "../jacquard-common" }
jacquard-derive = { version = "0.2.0", path = "../jacquard-derive" }
+6 -8
crates/jacquard-common/Cargo.toml
···
[dependencies]
-
bon = "3"
-
base64 = "0.22.1"
+
bon.workspace = true
+
base64.workspace = true
bytes.workspace = true
-
chrono = "0.4.42"
+
chrono.workspace = true
cid = { version = "0.11.1", features = ["serde", "std"] }
-
enum_dispatch = "0.3.13"
ipld-core = { version = "0.4.2", features = ["serde"] }
langtag = { version = "0.4.0", features = ["serde"] }
miette.workspace = true
···
thiserror.workspace = true
url.workspace = true
http.workspace = true
-
async-trait = "0.1"
-
tokio = { version = "1", features = ["sync"] }
+
async-trait.workspace = true
+
tokio = { workspace = true, features = ["sync"] }
reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
serde_ipld_dagcbor.workspace = true
trait-variant.workspace = true
···
features = ["arithmetic"]
[dependencies.p256]
-
version = "0.13"
+
workspace = true
optional = true
-
default-features = false
features = ["arithmetic"]
[package.metadata.docs.rs]
+4 -4
crates/jacquard-identity/Cargo.toml
···
dns = ["dep:hickory-resolver"]
[dependencies]
-
async-trait = "0.1.89"
+
async-trait.workspace = true
bon.workspace = true
bytes.workspace = true
jacquard-common = { version = "0.2", path = "../jacquard-common" }
-
percent-encoding = "2.3.2"
+
percent-encoding.workspace = true
reqwest.workspace = true
url.workspace = true
-
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
+
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] }
hickory-resolver = { optional = true, version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"]}
serde.workspace = true
serde_json.workspace = true
···
http.workspace = true
jacquard-api = { version = "0.2.0", path = "../jacquard-api" }
serde_html_form.workspace = true
-
urlencoding = "2.1.3"
+
urlencoding.workspace = true
+9 -9
crates/jacquard-oauth/Cargo.toml
···
[package]
name = "jacquard-oauth"
-
version = "0.1.0"
+
version = "0.3.0"
edition = "2024"
description = "AT Protocol OAuth 2.1 core types and helpers for Jacquard"
license = "MPL-2.0"
[dependencies]
-
jacquard-common = { version = "0.2.0", path = "../jacquard-common" }
+
jacquard-common = { version = "0.3.0", path = "../jacquard-common" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
url = { workspace = true }
smol_str = { workspace = true }
-
base64 = { version = "0.22" }
+
base64.workspace = true
sha2 = { version = "0.10" }
thiserror = { workspace = true }
serde_html_form = { workspace = true }
miette = { workspace = true }
uuid = { version = "1", features = ["v4","std"] }
-
p256 = { version = "0.13", features = ["ecdsa"] }
+
p256 = { workspace = true, features = ["ecdsa"] }
signature = "2"
-
rand_core = "0.6"
+
rand_core.workspace = true
jose-jwa = "0.1"
-
jose-jwk = { version = "0.1", features = ["p256"] }
-
chrono = "0.4"
+
jose-jwk = { workspace = true, features = ["p256"] }
+
chrono.workspace = true
elliptic-curve = "0.13.8"
http.workspace = true
bytes.workspace = true
rand = { version = "0.8.5", features = ["small_rng"] }
-
async-trait = "0.1.89"
+
async-trait.workspace = true
dashmap = "6.1.0"
-
tokio = { version = "1.47.1", features = ["sync"] }
+
tokio = { workspace = true, features = ["sync"] }
reqwest.workspace = true
trait-variant.workspace = true
+25 -11
crates/jacquard-oauth/src/request.rs
···
#[derive(Error, Debug, miette::Diagnostic)]
pub enum RequestError {
#[error("no {0} endpoint available")]
-
#[diagnostic(code(jacquard_oauth::request::no_endpoint), help("server does not advertise this endpoint"))]
+
#[diagnostic(
+
code(jacquard_oauth::request::no_endpoint),
+
help("server does not advertise this endpoint")
+
)]
NoEndpoint(CowStr<'static>),
#[error("token response verification failed")]
#[diagnostic(code(jacquard_oauth::request::token_verification))]
···
#[error("unsupported authentication method")]
#[diagnostic(
code(jacquard_oauth::request::unsupported_auth_method),
-
help("server must support `private_key_jwt` or `none`; configure client metadata accordingly")
+
help(
+
"server must support `private_key_jwt` or `none`; configure client metadata accordingly"
+
)
)]
UnsupportedAuthMethod,
#[error("no refresh token available")]
···
#[diagnostic(code(jacquard_oauth::request::http_build))]
Http(#[from] http::Error),
#[error("http status: {0}")]
-
#[diagnostic(code(jacquard_oauth::request::http_status), help("see server response for details"))]
+
#[diagnostic(
+
code(jacquard_oauth::request::http_status),
+
help("see server response for details")
+
)]
HttpStatus(StatusCode),
#[error("http status: {0}, body: {1:?}")]
-
#[diagnostic(code(jacquard_oauth::request::http_status_body), help("server returned error JSON; inspect fields like `error`, `error_description`"))]
+
#[diagnostic(
+
code(jacquard_oauth::request::http_status_body),
+
help("server returned error JSON; inspect fields like `error`, `error_description`")
+
)]
HttpStatusWithBody(StatusCode, Value),
#[error(transparent)]
#[diagnostic(code(jacquard_oauth::request::identity))]
···
mod tests {
use super::*;
use crate::types::{OAuthAuthorizationServerMetadata, OAuthClientMetadata};
-
use http::{Response as HttpResponse, StatusCode};
use bytes::Bytes;
+
use http::{Response as HttpResponse, StatusCode};
use jacquard_common::http_client::HttpClient;
use jacquard_identity::resolver::IdentityResolver;
use std::sync::Arc;
···
async fn refresh_no_refresh_token() {
let client = MockClient::default();
let meta = base_metadata();
-
let mut session = ClientSessionData {
+
let session = ClientSessionData {
account_did: jacquard_common::types::string::Did::new_static("did:plc:alice").unwrap(),
session_id: CowStr::from("state"),
host_url: url::Url::parse("https://pds").unwrap(),
···
*client.resp.lock().await = Some(
HttpResponse::builder()
.status(StatusCode::OK)
-
.body(serde_json::to_vec(&serde_json::json!({
-
"access_token":"tok",
-
"token_type":"DPoP",
-
"expires_in": 3600
-
})).unwrap())
+
.body(
+
serde_json::to_vec(&serde_json::json!({
+
"access_token":"tok",
+
"token_type":"DPoP",
+
"expires_in": 3600
+
}))
+
.unwrap(),
+
)
.unwrap(),
);
let meta = base_metadata();
+12 -12
crates/jacquard/Cargo.toml
···
name = "jacquard"
description.workspace = true
edition.workspace = true
-
version = "0.2.1"
+
version = "0.3.0"
authors.workspace = true
repository.workspace = true
keywords.workspace = true
···
[dependencies]
-
bon = "3"
-
async-trait = "0.1"
+
bon.workspace = true
+
async-trait.workspace = true
bytes.workspace = true
clap.workspace = true
http.workspace = true
-
jacquard-api = { version = "0.2.0", path = "../jacquard-api" }
-
jacquard-common = { version = "0.2.0", path = "../jacquard-common", features = ["reqwest-client"] }
-
jacquard-oauth = { version = "0.1.0", path = "../jacquard-oauth" }
+
jacquard-api = { version = "0.3.0", path = "../jacquard-api" }
+
jacquard-common = { version = "0.3.0", path = "../jacquard-common", features = ["reqwest-client"] }
+
jacquard-oauth = { version = "0.3.0", path = "../jacquard-oauth" }
jacquard-derive = { version = "0.2.0", path = "../jacquard-derive", optional = true }
miette = { workspace = true }
reqwest = { workspace = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
···
serde_ipld_dagcbor.workspace = true
serde_json.workspace = true
thiserror.workspace = true
-
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
+
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] }
url.workspace = true
smol_str.workspace = true
-
percent-encoding = "2"
-
urlencoding = "2"
-
jose-jwk = { version = "0.1", features = ["p256"] }
-
p256 = { version = "0.13", features = ["ecdsa"] }
-
rand_core = "0.6"
+
percent-encoding.workspace = true
+
urlencoding.workspace = true
+
jose-jwk = { workspace = true, features = ["p256"] }
+
p256 = { workspace = true, features = ["ecdsa"] }
+
rand_core.workspace = true
rouille = { version = "3.6.2", optional = true }
jacquard-identity = { version = "0.2.0", path = "../jacquard-identity" }
+1 -1
crates/jacquard/tests/oauth_flow.rs
···
keyset: None,
};
let login_hint = identity.map(|_| jacquard::CowStr::from("alice.bsky.social"));
-
let mut auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata)
+
let auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata)
.await
.unwrap();
// Construct authorization URL as OAuthClient::start_auth would do