A better Rust ATProto crate

pds param on credential session login, logic to handle emails, language tag deserialization bugfix.

Changed files
+21 -4
crates
jacquard
jacquard-common
src
types
+8 -2
crates/jacquard-common/src/types/language.rs
··· 1 + use langtag::InvalidLangTag; 1 2 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 2 3 use smol_str::{SmolStr, ToSmolStr}; 3 4 use std::fmt; ··· 36 37 Ok(Language(SmolStr::new_static(tag.as_str()))) 37 38 } 38 39 40 + fn new_owned(lang: SmolStr) -> Result<Self, SmolStr> { 41 + let tag = langtag::LangTag::new(&lang).map_err(|e| e.to_smolstr())?; 42 + Ok(Language(SmolStr::new(tag.as_str()))) 43 + } 44 + 39 45 /// Infallible constructor for when you *know* the string is a valid IETF language tag. 40 46 /// Will panic on invalid tag. If you're manually decoding atproto records 41 47 /// or API values you know are valid (rather than using serde), this is the one to use. ··· 75 81 where 76 82 D: Deserializer<'de>, 77 83 { 78 - let value: &str = Deserialize::deserialize(deserializer)?; 79 - Self::new(value).map_err(D::Error::custom) 84 + let value = Deserialize::deserialize(deserializer)?; 85 + Self::new_owned(value).map_err(D::Error::custom) 80 86 } 81 87 } 82 88
+2 -1
crates/jacquard/src/client.rs
··· 452 452 identifier: CowStr<'_>, 453 453 password: CowStr<'_>, 454 454 session_id: Option<CowStr<'_>>, 455 + pds: Option<Url>, 455 456 ) -> ClientResult<(Self, AtpSession)> { 456 457 let session = MemoryCredentialSession::unauthenticated(); 457 458 let auth = session 458 - .login(identifier, password, session_id, None, None) 459 + .login(identifier, password, session_id, None, None, pds) 459 460 .await?; 460 461 Ok((session, auth)) 461 462 }
+10 -1
crates/jacquard/src/client/credential_session.rs
··· 204 204 session_id: Option<CowStr<'_>>, 205 205 allow_takendown: Option<bool>, 206 206 auth_factor_token: Option<CowStr<'_>>, 207 + pds: Option<Url>, 207 208 ) -> std::result::Result<AtpSession, ClientError> 208 209 where 209 210 S: Any + 'static, ··· 213 214 tracing::info_span!("credential_session_login", identifier = %identifier).entered(); 214 215 215 216 // Resolve PDS base 216 - let pds = if identifier.as_ref().starts_with("http://") 217 + let pds = if let Some(pds) = pds { 218 + pds 219 + } else if identifier.as_ref().starts_with("http://") 217 220 || identifier.as_ref().starts_with("https://") 218 221 { 219 222 Url::parse(identifier.as_ref()).map_err(|e: url::ParseError| { ··· 231 234 resp.into_owned()?.pds_endpoint().ok_or_else(|| { 232 235 ClientError::invalid_request("missing PDS endpoint") 233 236 .with_help("DID document must include a PDS service endpoint") 237 + })? 238 + } else if identifier.as_ref().contains("@") && !identifier.as_ref().starts_with("@") { 239 + // we're going to assume its an email 240 + pds.ok_or_else(|| { 241 + ClientError::invalid_request("missing PDS endpoint") 242 + .with_help("When logging in with email, we need your PDS") 234 243 })? 235 244 } else { 236 245 // treat as handle
+1
crates/jacquard/tests/credential_session.rs
··· 179 179 Some(jacquard::CowStr::from("session")), 180 180 None, 181 181 None, 182 + None, 182 183 ) 183 184 .await 184 185 .expect("login ok");