A better Rust ATProto crate

ported minimal changes from trait branch

Orual 61b91cf5 b5cc9b35

Changed files
+251 -6
crates
jacquard
src
jacquard-common
src
types
+3 -6
crates/jacquard-common/src/types/cid.rs
··· 1 - use crate::{CowStr, IntoStatic}; 1 + use crate::{CowStr, IntoStatic, cowstr::ToCowStr}; 2 2 pub use cid::Cid as IpldCid; 3 3 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; 4 4 use smol_str::ToSmolStr; ··· 379 379 where 380 380 E: serde::de::Error, 381 381 { 382 - if let Ok(cid) = IpldCid::try_from(v.as_bytes()) { 383 - Ok(CidLink(Cid::ipld(cid))) 384 - } else { 385 - Err(E::custom("invalid CID string")) 386 - } 382 + // TODO: currently overly permissive, should fix 383 + Ok(CidLink::cow_str(v.to_cowstr()).into_static()) 387 384 } 388 385 389 386 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+248
crates/jacquard/src/client.rs
··· 47 47 use jacquard_common::types::string::AtUri; 48 48 #[cfg(feature = "api")] 49 49 use jacquard_common::types::uri::RecordUri; 50 + #[cfg(not(target_arch = "wasm32"))] 51 + use jacquard_common::xrpc::XrpcResponse; 50 52 use jacquard_common::xrpc::{ 51 53 CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, 52 54 }; ··· 58 60 use jacquard_identity::resolver::{ 59 61 DidDocResponse, IdentityError, IdentityResolver, ResolverOptions, 60 62 }; 63 + use jacquard_identity::{JacquardResolver, slingshot_resolver_default}; 61 64 use jacquard_oauth::authstore::ClientAuthStore; 62 65 use jacquard_oauth::client::OAuthSession; 63 66 use jacquard_oauth::dpop::DpopExt; ··· 66 69 #[cfg(feature = "api")] 67 70 use std::marker::Send; 68 71 use std::option::Option; 72 + use std::sync::Arc; 69 73 pub use token::FileAuthStore; 74 + use tokio::sync::RwLock; 75 + use url::Url; 70 76 71 77 /// Identifies the active authentication mode for an agent/session. 72 78 #[derive(Debug, Clone, Copy, PartialEq, Eq)] ··· 142 148 impl Default for BasicClient { 143 149 fn default() -> Self { 144 150 Self::unauthenticated() 151 + } 152 + } 153 + pub struct UnauthenticatedSession<T> { 154 + resolver: Arc<T>, 155 + endpoint: Arc<RwLock<Option<Url>>>, 156 + options: Arc<RwLock<CallOptions<'static>>>, 157 + } 158 + 159 + impl Default for UnauthenticatedSession<JacquardResolver> { 160 + fn default() -> Self { 161 + Self::new_public() 162 + } 163 + } 164 + 165 + impl UnauthenticatedSession<JacquardResolver> { 166 + pub fn new_public() -> Self { 167 + let resolver = Arc::new(JacquardResolver::default()); 168 + let endpoint = Arc::new(RwLock::new(None)); 169 + let options = Arc::new(RwLock::new(CallOptions::default())); 170 + Self { 171 + resolver, 172 + endpoint, 173 + options, 174 + } 175 + } 176 + 177 + pub fn new_slingshot() -> Self { 178 + let resolver = Arc::new(slingshot_resolver_default()); 179 + let endpoint = Arc::new(RwLock::new(None)); 180 + let options = Arc::new(RwLock::new(CallOptions::default())); 181 + Self { 182 + resolver, 183 + endpoint, 184 + options, 185 + } 186 + } 187 + } 188 + 189 + impl<T: HttpClient + Sync> HttpClient for UnauthenticatedSession<T> { 190 + type Error = T::Error; 191 + 192 + #[cfg(not(target_arch = "wasm32"))] 193 + fn send_http( 194 + &self, 195 + request: http::Request<Vec<u8>>, 196 + ) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, T::Error>> + Send { 197 + self.resolver.send_http(request) 198 + } 199 + 200 + #[cfg(target_arch = "wasm32")] 201 + fn send_http( 202 + &self, 203 + request: http::Request<Vec<u8>>, 204 + ) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, T::Error>> { 205 + self.resolver.send_http(request) 206 + } 207 + } 208 + 209 + impl<T: HttpClient> XrpcClient for UnauthenticatedSession<T> 210 + where 211 + T: Sync + Send, 212 + { 213 + #[doc = " Get the base URI for the client."] 214 + fn base_uri(&self) -> impl Future<Output = Url> + Send { 215 + async move { 216 + self.endpoint.read().await.clone().unwrap_or( 217 + Url::parse("https://public.bsky.app").expect("public appview should be valid url"), 218 + ) 219 + } 220 + } 221 + 222 + #[doc = " Send an XRPC request and parse the response"] 223 + #[cfg(not(target_arch = "wasm32"))] 224 + fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> + Send 225 + where 226 + R: XrpcRequest + Send + Sync, 227 + <R as XrpcRequest>::Response: Send + Sync, 228 + Self: Sync, 229 + { 230 + async move { 231 + let opts = self.options.read().await.clone(); 232 + self.send_with_opts(request, opts).await 233 + } 234 + } 235 + 236 + #[doc = " Send an XRPC request and parse the response"] 237 + #[cfg(not(target_arch = "wasm32"))] 238 + fn send_with_opts<R>( 239 + &self, 240 + request: R, 241 + opts: CallOptions<'_>, 242 + ) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> + Send 243 + where 244 + R: XrpcRequest + Send + Sync, 245 + <R as XrpcRequest>::Response: Send + Sync, 246 + Self: Sync, 247 + { 248 + async move { 249 + let base_uri = self.base_uri().await; 250 + self.resolver 251 + .xrpc(base_uri.clone()) 252 + .with_options(opts.clone()) 253 + .send(&request) 254 + .await 255 + } 256 + } 257 + 258 + #[doc = " Send an XRPC request and parse the response"] 259 + #[cfg(target_arch = "wasm32")] 260 + fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> 261 + where 262 + R: XrpcRequest + Send + Sync, 263 + <R as XrpcRequest>::Response: Send + Sync, 264 + { 265 + async move { 266 + let opts = self.options.read().await.clone(); 267 + self.send_with_opts(request, opts).await 268 + } 269 + } 270 + 271 + #[doc = " Send an XRPC request and parse the response"] 272 + #[cfg(target_arch = "wasm32")] 273 + fn send_with_opts<R>( 274 + &self, 275 + request: R, 276 + opts: CallOptions<'_>, 277 + ) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> 278 + where 279 + R: XrpcRequest + Send + Sync, 280 + <R as XrpcRequest>::Response: Send + Sync, 281 + { 282 + async move { 283 + let base_uri = self.base_uri().await; 284 + self.resolver 285 + .xrpc(base_uri.clone()) 286 + .with_options(opts.clone()) 287 + .send(&request) 288 + .await 289 + } 290 + } 291 + 292 + #[doc = " Set the base URI for the client."] 293 + fn set_base_uri(&self, url: Url) -> impl Future<Output = ()> + Send { 294 + async move { 295 + let mut guard = self.endpoint.write().await; 296 + *guard = Some(url); 297 + } 298 + } 299 + 300 + #[doc = " Get the call options for the client."] 301 + fn opts(&self) -> impl Future<Output = CallOptions<'_>> + Send { 302 + async move { self.options.read().await.clone() } 303 + } 304 + 305 + #[doc = " Set the call options for the client."] 306 + fn set_opts(&self, opts: CallOptions<'_>) -> impl Future<Output = ()> + Send { 307 + async move { 308 + *self.options.write().await = opts.into_static(); 309 + } 310 + } 311 + } 312 + 313 + impl<T: IdentityResolver + HttpClient> AgentSession for UnauthenticatedSession<T> 314 + where 315 + T: Sync + Send, 316 + { 317 + fn session_kind(&self) -> AgentKind { 318 + AgentKind::AppPassword 319 + } 320 + 321 + fn session_info( 322 + &self, 323 + ) -> impl Future<Output = Option<(Did<'static>, Option<CowStr<'static>>)>> { 324 + async { None } // no session 325 + } 326 + 327 + fn endpoint(&self) -> impl Future<Output = Url> { 328 + async { self.base_uri().await } 329 + } 330 + 331 + #[doc = " Override per-session call options."] 332 + fn set_options<'a>(&'a self, opts: CallOptions<'a>) -> impl Future<Output = ()> + Send { 333 + async move { 334 + *self.options.write().await = opts.into_static(); 335 + } 336 + } 337 + 338 + #[doc = " Refresh the session and return a fresh AuthorizationToken."] 339 + fn refresh(&self) -> impl Future<Output = ClientResult<AuthorizationToken<'static>>> + Send { 340 + async { 341 + Err(ClientError::auth( 342 + jacquard_common::error::AuthError::NotAuthenticated, 343 + )) 344 + } 345 + } 346 + } 347 + 348 + impl<T: IdentityResolver + Sync> IdentityResolver for UnauthenticatedSession<T> { 349 + #[doc = " Access options for validation decisions in default methods"] 350 + fn options(&self) -> &ResolverOptions { 351 + self.resolver.options() 352 + } 353 + 354 + #[doc = " Resolve handle"] 355 + #[cfg(not(target_arch = "wasm32"))] 356 + fn resolve_handle( 357 + &self, 358 + handle: &Handle<'_>, 359 + ) -> impl Future<Output = std::result::Result<Did<'static>, IdentityError>> + Send 360 + where 361 + Self: Sync, 362 + { 363 + self.resolver.resolve_handle(handle) 364 + } 365 + 366 + #[doc = " Resolve DID document"] 367 + #[cfg(not(target_arch = "wasm32"))] 368 + fn resolve_did_doc( 369 + &self, 370 + did: &Did<'_>, 371 + ) -> impl Future<Output = std::result::Result<DidDocResponse, IdentityError>> + Send 372 + where 373 + Self: Sync, 374 + { 375 + self.resolver.resolve_did_doc(did) 376 + } 377 + #[doc = " Resolve handle"] 378 + #[cfg(target_arch = "wasm32")] 379 + fn resolve_handle( 380 + &self, 381 + handle: &Handle<'_>, 382 + ) -> impl Future<Output = std::result::Result<Did<'static>, IdentityError>> { 383 + self.resolver.resolve_handle(handle) 384 + } 385 + 386 + #[doc = " Resolve DID document"] 387 + #[cfg(target_arch = "wasm32")] 388 + fn resolve_did_doc( 389 + &self, 390 + did: &Did<'_>, 391 + ) -> impl Future<Output = std::result::Result<DidDocResponse, IdentityError>> { 392 + self.resolver.resolve_did_doc(did) 145 393 } 146 394 } 147 395