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
··· 844 844 ] 845 845 846 846 [[package]] 847 - name = "enum_dispatch" 848 - version = "0.3.13" 849 - source = "registry+https://github.com/rust-lang/crates.io-index" 850 - checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" 851 - dependencies = [ 852 - "once_cell", 853 - "proc-macro2", 854 - "quote", 855 - "syn 2.0.106", 856 - ] 857 - 858 - [[package]] 859 847 name = "equivalent" 860 848 version = "1.0.2" 861 849 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1587 1575 "chrono", 1588 1576 "cid", 1589 1577 "ed25519-dalek", 1590 - "enum_dispatch", 1591 1578 "http", 1592 1579 "ipld-core", 1593 1580 "k256",
+18 -1
Cargo.toml
··· 5 5 6 6 [workspace.package] 7 7 edition = "2024" 8 - version = "0.2.0" 8 + version = "0.3.0" 9 9 authors = ["Orual <orual@nonbinary.computer>"] 10 10 repository = "https://tangled.org/@nonbinary.computer/jacquard" 11 11 keywords = ["atproto", "at", "bluesky", "api", "client"] ··· 57 57 # HTTP 58 58 http = "1.3" 59 59 reqwest = { version = "0.12", default-features = false } 60 + 61 + # Async and runtimes 62 + async-trait = "0.1" 63 + tokio = "1" 64 + 65 + # Encoding and crypto building blocks 66 + base64 = "0.22" 67 + percent-encoding = "2.3" 68 + urlencoding = "2.1.3" 69 + rand_core = "0.6" 70 + 71 + # Time 72 + chrono = "0.4" 73 + 74 + # Crypto curves and JOSE 75 + p256 = "0.13" 76 + jose-jwk = "0.1"
+1 -1
crates/jacquard-api/Cargo.toml
··· 22 22 tools_ozone = [] 23 23 24 24 [dependencies] 25 - bon = "3" 25 + bon.workspace = true 26 26 bytes = { workspace = true, features = ["serde"] } 27 27 jacquard-common = { version = "0.2.0", path = "../jacquard-common" } 28 28 jacquard-derive = { version = "0.2.0", path = "../jacquard-derive" }
+6 -8
crates/jacquard-common/Cargo.toml
··· 13 13 14 14 15 15 [dependencies] 16 - bon = "3" 17 - base64 = "0.22.1" 16 + bon.workspace = true 17 + base64.workspace = true 18 18 bytes.workspace = true 19 - chrono = "0.4.42" 19 + chrono.workspace = true 20 20 cid = { version = "0.11.1", features = ["serde", "std"] } 21 - enum_dispatch = "0.3.13" 22 21 ipld-core = { version = "0.4.2", features = ["serde"] } 23 22 langtag = { version = "0.4.0", features = ["serde"] } 24 23 miette.workspace = true ··· 36 35 thiserror.workspace = true 37 36 url.workspace = true 38 37 http.workspace = true 39 - async-trait = "0.1" 40 - tokio = { version = "1", features = ["sync"] } 38 + async-trait.workspace = true 39 + tokio = { workspace = true, features = ["sync"] } 41 40 reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] } 42 41 serde_ipld_dagcbor.workspace = true 43 42 trait-variant.workspace = true ··· 63 62 features = ["arithmetic"] 64 63 65 64 [dependencies.p256] 66 - version = "0.13" 65 + workspace = true 67 66 optional = true 68 - default-features = false 69 67 features = ["arithmetic"] 70 68 71 69 [package.metadata.docs.rs]
+4 -4
crates/jacquard-identity/Cargo.toml
··· 16 16 dns = ["dep:hickory-resolver"] 17 17 18 18 [dependencies] 19 - async-trait = "0.1.89" 19 + async-trait.workspace = true 20 20 bon.workspace = true 21 21 bytes.workspace = true 22 22 jacquard-common = { version = "0.2", path = "../jacquard-common" } 23 - percent-encoding = "2.3.2" 23 + percent-encoding.workspace = true 24 24 reqwest.workspace = true 25 25 url.workspace = true 26 - tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] } 26 + tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] } 27 27 hickory-resolver = { optional = true, version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"]} 28 28 serde.workspace = true 29 29 serde_json.workspace = true ··· 32 32 http.workspace = true 33 33 jacquard-api = { version = "0.2.0", path = "../jacquard-api" } 34 34 serde_html_form.workspace = true 35 - urlencoding = "2.1.3" 35 + urlencoding.workspace = true
+9 -9
crates/jacquard-oauth/Cargo.toml
··· 1 1 [package] 2 2 name = "jacquard-oauth" 3 - version = "0.1.0" 3 + version = "0.3.0" 4 4 edition = "2024" 5 5 description = "AT Protocol OAuth 2.1 core types and helpers for Jacquard" 6 6 license = "MPL-2.0" 7 7 8 8 [dependencies] 9 - jacquard-common = { version = "0.2.0", path = "../jacquard-common" } 9 + jacquard-common = { version = "0.3.0", path = "../jacquard-common" } 10 10 serde = { workspace = true, features = ["derive"] } 11 11 serde_json = { workspace = true } 12 12 url = { workspace = true } 13 13 smol_str = { workspace = true } 14 - base64 = { version = "0.22" } 14 + base64.workspace = true 15 15 sha2 = { version = "0.10" } 16 16 thiserror = { workspace = true } 17 17 serde_html_form = { workspace = true } 18 18 miette = { workspace = true } 19 19 uuid = { version = "1", features = ["v4","std"] } 20 - p256 = { version = "0.13", features = ["ecdsa"] } 20 + p256 = { workspace = true, features = ["ecdsa"] } 21 21 signature = "2" 22 - rand_core = "0.6" 22 + rand_core.workspace = true 23 23 jose-jwa = "0.1" 24 - jose-jwk = { version = "0.1", features = ["p256"] } 25 - chrono = "0.4" 24 + jose-jwk = { workspace = true, features = ["p256"] } 25 + chrono.workspace = true 26 26 elliptic-curve = "0.13.8" 27 27 http.workspace = true 28 28 bytes.workspace = true 29 29 rand = { version = "0.8.5", features = ["small_rng"] } 30 - async-trait = "0.1.89" 30 + async-trait.workspace = true 31 31 dashmap = "6.1.0" 32 - tokio = { version = "1.47.1", features = ["sync"] } 32 + tokio = { workspace = true, features = ["sync"] } 33 33 34 34 reqwest.workspace = true 35 35 trait-variant.workspace = true
+25 -11
crates/jacquard-oauth/src/request.rs
··· 43 43 #[derive(Error, Debug, miette::Diagnostic)] 44 44 pub enum RequestError { 45 45 #[error("no {0} endpoint available")] 46 - #[diagnostic(code(jacquard_oauth::request::no_endpoint), help("server does not advertise this endpoint"))] 46 + #[diagnostic( 47 + code(jacquard_oauth::request::no_endpoint), 48 + help("server does not advertise this endpoint") 49 + )] 47 50 NoEndpoint(CowStr<'static>), 48 51 #[error("token response verification failed")] 49 52 #[diagnostic(code(jacquard_oauth::request::token_verification))] ··· 51 54 #[error("unsupported authentication method")] 52 55 #[diagnostic( 53 56 code(jacquard_oauth::request::unsupported_auth_method), 54 - help("server must support `private_key_jwt` or `none`; configure client metadata accordingly") 57 + help( 58 + "server must support `private_key_jwt` or `none`; configure client metadata accordingly" 59 + ) 55 60 )] 56 61 UnsupportedAuthMethod, 57 62 #[error("no refresh token available")] ··· 76 81 #[diagnostic(code(jacquard_oauth::request::http_build))] 77 82 Http(#[from] http::Error), 78 83 #[error("http status: {0}")] 79 - #[diagnostic(code(jacquard_oauth::request::http_status), help("see server response for details"))] 84 + #[diagnostic( 85 + code(jacquard_oauth::request::http_status), 86 + help("see server response for details") 87 + )] 80 88 HttpStatus(StatusCode), 81 89 #[error("http status: {0}, body: {1:?}")] 82 - #[diagnostic(code(jacquard_oauth::request::http_status_body), help("server returned error JSON; inspect fields like `error`, `error_description`"))] 90 + #[diagnostic( 91 + code(jacquard_oauth::request::http_status_body), 92 + help("server returned error JSON; inspect fields like `error`, `error_description`") 93 + )] 83 94 HttpStatusWithBody(StatusCode, Value), 84 95 #[error(transparent)] 85 96 #[diagnostic(code(jacquard_oauth::request::identity))] ··· 134 145 mod tests { 135 146 use super::*; 136 147 use crate::types::{OAuthAuthorizationServerMetadata, OAuthClientMetadata}; 137 - use http::{Response as HttpResponse, StatusCode}; 138 148 use bytes::Bytes; 149 + use http::{Response as HttpResponse, StatusCode}; 139 150 use jacquard_common::http_client::HttpClient; 140 151 use jacquard_identity::resolver::IdentityResolver; 141 152 use std::sync::Arc; ··· 249 260 async fn refresh_no_refresh_token() { 250 261 let client = MockClient::default(); 251 262 let meta = base_metadata(); 252 - let mut session = ClientSessionData { 263 + let session = ClientSessionData { 253 264 account_did: jacquard_common::types::string::Did::new_static("did:plc:alice").unwrap(), 254 265 session_id: CowStr::from("state"), 255 266 host_url: url::Url::parse("https://pds").unwrap(), ··· 284 295 *client.resp.lock().await = Some( 285 296 HttpResponse::builder() 286 297 .status(StatusCode::OK) 287 - .body(serde_json::to_vec(&serde_json::json!({ 288 - "access_token":"tok", 289 - "token_type":"DPoP", 290 - "expires_in": 3600 291 - })).unwrap()) 298 + .body( 299 + serde_json::to_vec(&serde_json::json!({ 300 + "access_token":"tok", 301 + "token_type":"DPoP", 302 + "expires_in": 3600 303 + })) 304 + .unwrap(), 305 + ) 292 306 .unwrap(), 293 307 ); 294 308 let meta = base_metadata();
+12 -12
crates/jacquard/Cargo.toml
··· 2 2 name = "jacquard" 3 3 description.workspace = true 4 4 edition.workspace = true 5 - version = "0.2.1" 5 + version = "0.3.0" 6 6 authors.workspace = true 7 7 repository.workspace = true 8 8 keywords.workspace = true ··· 30 30 31 31 32 32 [dependencies] 33 - bon = "3" 34 - async-trait = "0.1" 33 + bon.workspace = true 34 + async-trait.workspace = true 35 35 bytes.workspace = true 36 36 clap.workspace = true 37 37 http.workspace = true 38 - jacquard-api = { version = "0.2.0", path = "../jacquard-api" } 39 - jacquard-common = { version = "0.2.0", path = "../jacquard-common", features = ["reqwest-client"] } 40 - jacquard-oauth = { version = "0.1.0", path = "../jacquard-oauth" } 38 + jacquard-api = { version = "0.3.0", path = "../jacquard-api" } 39 + jacquard-common = { version = "0.3.0", path = "../jacquard-common", features = ["reqwest-client"] } 40 + jacquard-oauth = { version = "0.3.0", path = "../jacquard-oauth" } 41 41 jacquard-derive = { version = "0.2.0", path = "../jacquard-derive", optional = true } 42 42 miette = { workspace = true } 43 43 reqwest = { workspace = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] } ··· 46 46 serde_ipld_dagcbor.workspace = true 47 47 serde_json.workspace = true 48 48 thiserror.workspace = true 49 - tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] } 49 + tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] } 50 50 url.workspace = true 51 51 smol_str.workspace = true 52 - percent-encoding = "2" 53 - urlencoding = "2" 54 - jose-jwk = { version = "0.1", features = ["p256"] } 55 - p256 = { version = "0.13", features = ["ecdsa"] } 56 - rand_core = "0.6" 52 + percent-encoding.workspace = true 53 + urlencoding.workspace = true 54 + jose-jwk = { workspace = true, features = ["p256"] } 55 + p256 = { workspace = true, features = ["ecdsa"] } 56 + rand_core.workspace = true 57 57 rouille = { version = "3.6.2", optional = true } 58 58 jacquard-identity = { version = "0.2.0", path = "../jacquard-identity" }
+1 -1
crates/jacquard/tests/oauth_flow.rs
··· 239 239 keyset: None, 240 240 }; 241 241 let login_hint = identity.map(|_| jacquard::CowStr::from("alice.bsky.social")); 242 - let mut auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata) 242 + let auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata) 243 243 .await 244 244 .unwrap(); 245 245 // Construct authorization URL as OAuthClient::start_auth would do