A better Rust ATProto crate

cleaning up some ungodly type strings

+127 -137
+33 -36
crates/jacquard-common/src/xrpc.rs
··· 232 233 impl<T: HttpClient> XrpcExt for T {} 234 235 /// Stateful XRPC call trait 236 #[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))] 237 pub trait XrpcClient: HttpClient { ··· 245 246 /// Send an XRPC request and parse the response 247 #[cfg(not(target_arch = "wasm32"))] 248 - fn send<R>( 249 - &self, 250 - request: R, 251 - ) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>> 252 where 253 R: XrpcRequest + Send + Sync, 254 <R as XrpcRequest>::Response: Send + Sync, ··· 256 257 /// Send an XRPC request and parse the response 258 #[cfg(target_arch = "wasm32")] 259 - fn send<R>( 260 - &self, 261 - request: R, 262 - ) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>> 263 where 264 R: XrpcRequest + Send + Sync, 265 <R as XrpcRequest>::Response: Send + Sync; ··· 270 &self, 271 request: R, 272 opts: CallOptions<'_>, 273 - ) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>> 274 where 275 R: XrpcRequest + Send + Sync, 276 <R as XrpcRequest>::Response: Send + Sync, ··· 282 &self, 283 request: R, 284 opts: CallOptions<'_>, 285 - ) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>> 286 where 287 R: XrpcRequest + Send + Sync, 288 <R as XrpcRequest>::Response: Send + Sync; ··· 517 status: StatusCode, 518 } 519 520 - impl<Resp> Response<Resp> 521 where 522 - Resp: XrpcResp, 523 { 524 /// Create a new response from a buffer and status code 525 pub fn new(buffer: Bytes, status: StatusCode) -> Self { ··· 541 } 542 543 /// Parse the response, borrowing from the internal buffer 544 - pub fn parse<'s>( 545 - &'s self, 546 - ) -> Result<<Resp as XrpcResp>::Output<'s>, XrpcError<<Resp as XrpcResp>::Err<'s>>> { 547 // 200: parse as output 548 if self.status.is_success() { 549 match serde_json::from_slice::<_>(&self.buffer) { ··· 558 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 559 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 560 Ok(mut generic) => { 561 - generic.nsid = Resp::NSID; 562 generic.method = ""; // method info only available on request 563 generic.http_status = self.status; 564 // Map auth-related errors to AuthError ··· 576 } else { 577 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 578 Ok(mut generic) => { 579 - generic.nsid = Resp::NSID; 580 generic.method = ""; // method info only available on request 581 generic.http_status = self.status; 582 match generic.error.as_str() { ··· 593 /// Parse this as validated, loosely typed atproto data. 594 /// 595 /// NOTE: If the response is an error, it will still parse as the matching error type for the request. 596 - pub fn parse_data<'s>(&'s self) -> Result<Data<'s>, XrpcError<<Resp as XrpcResp>::Err<'s>>> { 597 // 200: parse as output 598 if self.status.is_success() { 599 match serde_json::from_slice::<_>(&self.buffer) { ··· 608 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 609 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 610 Ok(mut generic) => { 611 - generic.nsid = Resp::NSID; 612 generic.method = ""; // method info only available on request 613 generic.http_status = self.status; 614 // Map auth-related errors to AuthError ··· 626 } else { 627 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 628 Ok(mut generic) => { 629 - generic.nsid = Resp::NSID; 630 generic.method = ""; // method info only available on request 631 generic.http_status = self.status; 632 match generic.error.as_str() { ··· 643 /// Parse this as raw atproto data with minimal validation. 644 /// 645 /// NOTE: If the response is an error, it will still parse as the matching error type for the request. 646 - pub fn parse_raw<'s>(&'s self) -> Result<RawData<'s>, XrpcError<<Resp as XrpcResp>::Err<'s>>> { 647 // 200: parse as output 648 if self.status.is_success() { 649 match serde_json::from_slice::<_>(&self.buffer) { ··· 658 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 659 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 660 Ok(mut generic) => { 661 - generic.nsid = Resp::NSID; 662 generic.method = ""; // method info only available on request 663 generic.http_status = self.status; 664 // Map auth-related errors to AuthError ··· 676 } else { 677 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 678 Ok(mut generic) => { 679 - generic.nsid = Resp::NSID; 680 generic.method = ""; // method info only available on request 681 generic.http_status = self.status; 682 match generic.error.as_str() { ··· 710 } 711 } 712 713 - impl<Resp> Response<Resp> 714 where 715 - Resp: XrpcResp, 716 { 717 /// Parse the response into an owned output 718 - pub fn into_output( 719 - self, 720 - ) -> Result<<Resp as XrpcResp>::Output<'static>, XrpcError<<Resp as XrpcResp>::Err<'static>>> 721 where 722 - for<'a> <Resp as XrpcResp>::Output<'a>: 723 - IntoStatic<Output = <Resp as XrpcResp>::Output<'static>>, 724 - for<'a> <Resp as XrpcResp>::Err<'a>: IntoStatic<Output = <Resp as XrpcResp>::Err<'static>>, 725 { 726 // Use a helper to make lifetime inference work 727 fn parse_output<'b, R: XrpcResp>( ··· 736 737 // 200: parse as output 738 if self.status.is_success() { 739 - match parse_output::<Resp>(&self.buffer) { 740 Ok(output) => { 741 return Ok(output.into_static()); 742 } ··· 744 } 745 // 400: try typed XRPC error, fallback to generic error 746 } else if self.status.as_u16() == 400 { 747 - let error = match parse_error::<Resp>(&self.buffer) { 748 Ok(error) => XrpcError::Xrpc(error), 749 Err(_) => { 750 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 751 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 752 Ok(mut generic) => { 753 - generic.nsid = Resp::NSID; 754 generic.method = ""; // method info only available on request 755 generic.http_status = self.status; 756 // Map auth-related errors to AuthError ··· 767 Err(error.into_static()) 768 // 401: always auth error 769 } else { 770 - let error: XrpcError<<Resp as XrpcResp>::Err<'_>> = 771 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 772 Ok(mut generic) => { 773 let status = self.status; 774 - generic.nsid = Resp::NSID; 775 generic.method = ""; // method info only available on request 776 generic.http_status = status; 777 match generic.error.as_ref() {
··· 232 233 impl<T: HttpClient> XrpcExt for T {} 234 235 + /// Nicer alias for Xrpc response type 236 + pub type XrpcResponse<R> = Response<<R as XrpcRequest>::Response>; 237 + 238 /// Stateful XRPC call trait 239 #[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))] 240 pub trait XrpcClient: HttpClient { ··· 248 249 /// Send an XRPC request and parse the response 250 #[cfg(not(target_arch = "wasm32"))] 251 + fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> 252 where 253 R: XrpcRequest + Send + Sync, 254 <R as XrpcRequest>::Response: Send + Sync, ··· 256 257 /// Send an XRPC request and parse the response 258 #[cfg(target_arch = "wasm32")] 259 + fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> 260 where 261 R: XrpcRequest + Send + Sync, 262 <R as XrpcRequest>::Response: Send + Sync; ··· 267 &self, 268 request: R, 269 opts: CallOptions<'_>, 270 + ) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> 271 where 272 R: XrpcRequest + Send + Sync, 273 <R as XrpcRequest>::Response: Send + Sync, ··· 279 &self, 280 request: R, 281 opts: CallOptions<'_>, 282 + ) -> impl Future<Output = XrpcResult<XrpcResponse<R>>> 283 where 284 R: XrpcRequest + Send + Sync, 285 <R as XrpcRequest>::Response: Send + Sync; ··· 514 status: StatusCode, 515 } 516 517 + impl<R> Response<R> 518 where 519 + R: XrpcResp, 520 { 521 /// Create a new response from a buffer and status code 522 pub fn new(buffer: Bytes, status: StatusCode) -> Self { ··· 538 } 539 540 /// Parse the response, borrowing from the internal buffer 541 + pub fn parse<'s>(&'s self) -> Result<RespOutput<'s, R>, XrpcError<RespErr<'s, R>>> { 542 // 200: parse as output 543 if self.status.is_success() { 544 match serde_json::from_slice::<_>(&self.buffer) { ··· 553 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 554 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 555 Ok(mut generic) => { 556 + generic.nsid = R::NSID; 557 generic.method = ""; // method info only available on request 558 generic.http_status = self.status; 559 // Map auth-related errors to AuthError ··· 571 } else { 572 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 573 Ok(mut generic) => { 574 + generic.nsid = R::NSID; 575 generic.method = ""; // method info only available on request 576 generic.http_status = self.status; 577 match generic.error.as_str() { ··· 588 /// Parse this as validated, loosely typed atproto data. 589 /// 590 /// NOTE: If the response is an error, it will still parse as the matching error type for the request. 591 + pub fn parse_data<'s>(&'s self) -> Result<Data<'s>, XrpcError<RespErr<'s, R>>> { 592 // 200: parse as output 593 if self.status.is_success() { 594 match serde_json::from_slice::<_>(&self.buffer) { ··· 603 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 604 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 605 Ok(mut generic) => { 606 + generic.nsid = R::NSID; 607 generic.method = ""; // method info only available on request 608 generic.http_status = self.status; 609 // Map auth-related errors to AuthError ··· 621 } else { 622 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 623 Ok(mut generic) => { 624 + generic.nsid = R::NSID; 625 generic.method = ""; // method info only available on request 626 generic.http_status = self.status; 627 match generic.error.as_str() { ··· 638 /// Parse this as raw atproto data with minimal validation. 639 /// 640 /// NOTE: If the response is an error, it will still parse as the matching error type for the request. 641 + pub fn parse_raw<'s>(&'s self) -> Result<RawData<'s>, XrpcError<RespErr<'s, R>>> { 642 // 200: parse as output 643 if self.status.is_success() { 644 match serde_json::from_slice::<_>(&self.buffer) { ··· 653 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 654 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 655 Ok(mut generic) => { 656 + generic.nsid = R::NSID; 657 generic.method = ""; // method info only available on request 658 generic.http_status = self.status; 659 // Map auth-related errors to AuthError ··· 671 } else { 672 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 673 Ok(mut generic) => { 674 + generic.nsid = R::NSID; 675 generic.method = ""; // method info only available on request 676 generic.http_status = self.status; 677 match generic.error.as_str() { ··· 705 } 706 } 707 708 + /// doc 709 + pub type RespOutput<'a, Resp> = <Resp as XrpcResp>::Output<'a>; 710 + /// doc 711 + pub type RespErr<'a, Resp> = <Resp as XrpcResp>::Err<'a>; 712 + 713 + impl<R> Response<R> 714 where 715 + R: XrpcResp, 716 { 717 /// Parse the response into an owned output 718 + pub fn into_output(self) -> Result<RespOutput<'static, R>, XrpcError<RespErr<'static, R>>> 719 where 720 + for<'a> RespOutput<'a, R>: IntoStatic<Output = RespOutput<'static, R>>, 721 + for<'a> RespErr<'a, R>: IntoStatic<Output = RespErr<'static, R>>, 722 { 723 // Use a helper to make lifetime inference work 724 fn parse_output<'b, R: XrpcResp>( ··· 733 734 // 200: parse as output 735 if self.status.is_success() { 736 + match parse_output::<R>(&self.buffer) { 737 Ok(output) => { 738 return Ok(output.into_static()); 739 } ··· 741 } 742 // 400: try typed XRPC error, fallback to generic error 743 } else if self.status.as_u16() == 400 { 744 + let error = match parse_error::<R>(&self.buffer) { 745 Ok(error) => XrpcError::Xrpc(error), 746 Err(_) => { 747 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 748 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 749 Ok(mut generic) => { 750 + generic.nsid = R::NSID; 751 generic.method = ""; // method info only available on request 752 generic.http_status = self.status; 753 // Map auth-related errors to AuthError ··· 764 Err(error.into_static()) 765 // 401: always auth error 766 } else { 767 + let error: XrpcError<<R as XrpcResp>::Err<'_>> = 768 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { 769 Ok(mut generic) => { 770 let status = self.status; 771 + generic.nsid = R::NSID; 772 generic.method = ""; // method info only available on request 773 generic.http_status = status; 774 match generic.error.as_ref() {
+12 -16
crates/jacquard-oauth/src/client.rs
··· 15 http_client::HttpClient, 16 types::{did::Did, string::Handle}, 17 xrpc::{ 18 - CallOptions, Response, XrpcClient, XrpcExt, XrpcRequest, XrpcResp, build_http_request, 19 - process_response, 20 }, 21 }; 22 - use jacquard_identity::{JacquardResolver, resolver::IdentityResolver}; 23 use jose_jwk::JwkSet; 24 use std::sync::Arc; 25 use tokio::sync::RwLock; ··· 434 self.options.read().await.clone() 435 } 436 437 - async fn send<R>(&self, request: R) -> XrpcResult<Response<<R as XrpcRequest>::Response>> 438 where 439 R: XrpcRequest + Send + Sync, 440 <R as XrpcRequest>::Response: Send + Sync, ··· 447 &self, 448 request: R, 449 mut opts: CallOptions<'_>, 450 - ) -> XrpcResult<Response<<R as XrpcRequest>::Response>> 451 where 452 R: XrpcRequest + Send + Sync, 453 <R as XrpcRequest>::Response: Send + Sync, ··· 492 .to_str() 493 .is_ok_and(|s| s.starts_with("DPoP ") && s.contains("error=\"invalid_token\"")), 494 Ok(resp) => match resp.parse() { 495 - Err(jacquard_common::xrpc::XrpcError::Auth(AuthError::InvalidToken)) => true, 496 _ => false, 497 }, 498 _ => false, ··· 504 S: ClientAuthStore + Send + Sync + 'static, 505 T: OAuthResolver + IdentityResolver + XrpcExt + Send + Sync + 'static, 506 { 507 - fn options(&self) -> &jacquard_identity::resolver::ResolverOptions { 508 self.client.options() 509 } 510 511 fn resolve_handle( 512 &self, 513 handle: &Handle<'_>, 514 - ) -> impl Future< 515 - Output = std::result::Result<Did<'static>, jacquard_identity::resolver::IdentityError>, 516 - > { 517 async { self.client.resolve_handle(handle).await } 518 } 519 520 fn resolve_did_doc( 521 &self, 522 did: &Did<'_>, 523 - ) -> impl Future< 524 - Output = std::result::Result< 525 - jacquard_identity::resolver::DidDocResponse, 526 - jacquard_identity::resolver::IdentityError, 527 - >, 528 - > { 529 async { self.client.resolve_did_doc(did).await } 530 } 531 }
··· 15 http_client::HttpClient, 16 types::{did::Did, string::Handle}, 17 xrpc::{ 18 + CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, XrpcResponse, 19 + build_http_request, process_response, 20 }, 21 }; 22 + use jacquard_identity::{ 23 + JacquardResolver, 24 + resolver::{DidDocResponse, IdentityError, IdentityResolver, ResolverOptions}, 25 + }; 26 use jose_jwk::JwkSet; 27 use std::sync::Arc; 28 use tokio::sync::RwLock; ··· 437 self.options.read().await.clone() 438 } 439 440 + async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>> 441 where 442 R: XrpcRequest + Send + Sync, 443 <R as XrpcRequest>::Response: Send + Sync, ··· 450 &self, 451 request: R, 452 mut opts: CallOptions<'_>, 453 + ) -> XrpcResult<XrpcResponse<R>> 454 where 455 R: XrpcRequest + Send + Sync, 456 <R as XrpcRequest>::Response: Send + Sync, ··· 495 .to_str() 496 .is_ok_and(|s| s.starts_with("DPoP ") && s.contains("error=\"invalid_token\"")), 497 Ok(resp) => match resp.parse() { 498 + Err(XrpcError::Auth(AuthError::InvalidToken)) => true, 499 _ => false, 500 }, 501 _ => false, ··· 507 S: ClientAuthStore + Send + Sync + 'static, 508 T: OAuthResolver + IdentityResolver + XrpcExt + Send + Sync + 'static, 509 { 510 + fn options(&self) -> &ResolverOptions { 511 self.client.options() 512 } 513 514 fn resolve_handle( 515 &self, 516 handle: &Handle<'_>, 517 + ) -> impl Future<Output = std::result::Result<Did<'static>, IdentityError>> { 518 async { self.client.resolve_handle(handle).await } 519 } 520 521 fn resolve_did_doc( 522 &self, 523 did: &Did<'_>, 524 + ) -> impl Future<Output = std::result::Result<DidDocResponse, IdentityError>> { 525 async { self.client.resolve_did_doc(did).await } 526 } 527 }
+14 -3
crates/jacquard-oauth/src/dpop.rs
··· 1 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 2 use chrono::Utc; 3 use http::{Request, Response, header::InvalidHeaderValue}; ··· 43 44 #[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))] 45 pub trait DpopClient: HttpClient { 46 - fn dpop_server(&self, request: Request<Vec<u8>>) -> impl std::future::Future<Output = Result<Response<Vec<u8>>>>; 47 - fn dpop_client(&self, request: Request<Vec<u8>>) -> impl std::future::Future<Output = Result<Response<Vec<u8>>>>; 48 - fn wrap_request(&self, request: Request<Vec<u8>>) -> impl std::future::Future<Output = Result<Response<Vec<u8>>>>; 49 } 50 51 pub trait DpopExt: HttpClient {
··· 1 + use std::future::Future; 2 + 3 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4 use chrono::Utc; 5 use http::{Request, Response, header::InvalidHeaderValue}; ··· 45 46 #[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))] 47 pub trait DpopClient: HttpClient { 48 + fn dpop_server( 49 + &self, 50 + request: Request<Vec<u8>>, 51 + ) -> impl Future<Output = Result<Response<Vec<u8>>>>; 52 + fn dpop_client( 53 + &self, 54 + request: Request<Vec<u8>>, 55 + ) -> impl Future<Output = Result<Response<Vec<u8>>>>; 56 + fn wrap_request( 57 + &self, 58 + request: Request<Vec<u8>>, 59 + ) -> impl Future<Output = Result<Response<Vec<u8>>>>; 60 } 61 62 pub trait DpopExt: HttpClient {
+26 -29
crates/jacquard-oauth/src/resolver.rs
··· 1 use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata}; 2 use http::{Request, StatusCode}; 3 use jacquard_common::CowStr; ··· 400 &self, 401 server_metadata: &OAuthAuthorizationServerMetadata<'_>, 402 sub: &Did<'_>, 403 - ) -> impl std::future::Future<Output = Result<Url, ResolverError>> + Send 404 where 405 Self: Sync, 406 { ··· 412 &self, 413 server_metadata: &OAuthAuthorizationServerMetadata<'_>, 414 sub: &Did<'_>, 415 - ) -> impl std::future::Future<Output = Result<Url, ResolverError>> { 416 verify_issuer_impl(self, server_metadata, sub) 417 } 418 ··· 420 fn resolve_oauth( 421 &self, 422 input: &str, 423 - ) -> impl std::future::Future< 424 Output = Result< 425 ( 426 OAuthAuthorizationServerMetadata<'static>, ··· 439 fn resolve_oauth( 440 &self, 441 input: &str, 442 - ) -> impl std::future::Future< 443 Output = Result< 444 ( 445 OAuthAuthorizationServerMetadata<'static>, ··· 455 fn resolve_from_service( 456 &self, 457 input: &Url, 458 - ) -> impl std::future::Future< 459 - Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>, 460 - > + Send 461 where 462 Self: Sync, 463 { ··· 468 fn resolve_from_service( 469 &self, 470 input: &Url, 471 - ) -> impl std::future::Future< 472 - Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>, 473 - > { 474 resolve_from_service_impl(self, input) 475 } 476 ··· 478 fn resolve_from_identity( 479 &self, 480 input: &str, 481 - ) -> impl std::future::Future< 482 Output = Result< 483 ( 484 OAuthAuthorizationServerMetadata<'static>, ··· 497 fn resolve_from_identity( 498 &self, 499 input: &str, 500 - ) -> impl std::future::Future< 501 Output = Result< 502 ( 503 OAuthAuthorizationServerMetadata<'static>, ··· 513 fn get_authorization_server_metadata( 514 &self, 515 issuer: &Url, 516 - ) -> impl std::future::Future< 517 - Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>, 518 - > + Send 519 where 520 Self: Sync, 521 { ··· 526 fn get_authorization_server_metadata( 527 &self, 528 issuer: &Url, 529 - ) -> impl std::future::Future< 530 - Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>, 531 - > { 532 get_authorization_server_metadata_impl(self, issuer) 533 } 534 ··· 536 fn get_resource_server_metadata( 537 &self, 538 pds: &Url, 539 - ) -> impl std::future::Future< 540 - Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>, 541 - > + Send 542 where 543 Self: Sync, 544 { ··· 549 fn get_resource_server_metadata( 550 &self, 551 pds: &Url, 552 - ) -> impl std::future::Future< 553 - Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>, 554 - > { 555 get_resource_server_metadata_impl(self, pds) 556 } 557 } ··· 630 631 #[cfg(test)] 632 mod tests { 633 use super::*; 634 use http::{Request as HttpRequest, Response as HttpResponse, StatusCode}; 635 use jacquard_common::http_client::HttpClient; 636 637 #[derive(Default, Clone)] 638 struct MockHttp { 639 - next: std::sync::Arc<tokio::sync::Mutex<Option<HttpResponse<Vec<u8>>>>>, 640 } 641 642 impl HttpClient for MockHttp { 643 - type Error = std::convert::Infallible; 644 fn send_http( 645 &self, 646 _request: HttpRequest<Vec<u8>>, 647 - ) -> impl core::future::Future< 648 - Output = core::result::Result<HttpResponse<Vec<u8>>, Self::Error>, 649 - > + Send { 650 let next = self.next.clone(); 651 async move { Ok(next.lock().await.take().unwrap()) } 652 }
··· 1 + #[cfg(not(target_arch = "wasm32"))] 2 + use std::future::Future; 3 + 4 use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata}; 5 use http::{Request, StatusCode}; 6 use jacquard_common::CowStr; ··· 403 &self, 404 server_metadata: &OAuthAuthorizationServerMetadata<'_>, 405 sub: &Did<'_>, 406 + ) -> impl Future<Output = Result<Url, ResolverError>> + Send 407 where 408 Self: Sync, 409 { ··· 415 &self, 416 server_metadata: &OAuthAuthorizationServerMetadata<'_>, 417 sub: &Did<'_>, 418 + ) -> impl Future<Output = Result<Url, ResolverError>> { 419 verify_issuer_impl(self, server_metadata, sub) 420 } 421 ··· 423 fn resolve_oauth( 424 &self, 425 input: &str, 426 + ) -> impl Future< 427 Output = Result< 428 ( 429 OAuthAuthorizationServerMetadata<'static>, ··· 442 fn resolve_oauth( 443 &self, 444 input: &str, 445 + ) -> impl Future< 446 Output = Result< 447 ( 448 OAuthAuthorizationServerMetadata<'static>, ··· 458 fn resolve_from_service( 459 &self, 460 input: &Url, 461 + ) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> + Send 462 where 463 Self: Sync, 464 { ··· 469 fn resolve_from_service( 470 &self, 471 input: &Url, 472 + ) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> 473 + { 474 resolve_from_service_impl(self, input) 475 } 476 ··· 478 fn resolve_from_identity( 479 &self, 480 input: &str, 481 + ) -> impl Future< 482 Output = Result< 483 ( 484 OAuthAuthorizationServerMetadata<'static>, ··· 497 fn resolve_from_identity( 498 &self, 499 input: &str, 500 + ) -> impl Future< 501 Output = Result< 502 ( 503 OAuthAuthorizationServerMetadata<'static>, ··· 513 fn get_authorization_server_metadata( 514 &self, 515 issuer: &Url, 516 + ) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> + Send 517 where 518 Self: Sync, 519 { ··· 524 fn get_authorization_server_metadata( 525 &self, 526 issuer: &Url, 527 + ) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> 528 + { 529 get_authorization_server_metadata_impl(self, issuer) 530 } 531 ··· 533 fn get_resource_server_metadata( 534 &self, 535 pds: &Url, 536 + ) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> + Send 537 where 538 Self: Sync, 539 { ··· 544 fn get_resource_server_metadata( 545 &self, 546 pds: &Url, 547 + ) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> 548 + { 549 get_resource_server_metadata_impl(self, pds) 550 } 551 } ··· 624 625 #[cfg(test)] 626 mod tests { 627 + use core::future::Future; 628 + use std::{convert::Infallible, sync::Arc}; 629 + 630 use super::*; 631 use http::{Request as HttpRequest, Response as HttpResponse, StatusCode}; 632 use jacquard_common::http_client::HttpClient; 633 + use tokio::sync::Mutex; 634 635 #[derive(Default, Clone)] 636 struct MockHttp { 637 + next: Arc<Mutex<Option<HttpResponse<Vec<u8>>>>>, 638 } 639 640 impl HttpClient for MockHttp { 641 + type Error = Infallible; 642 fn send_http( 643 &self, 644 _request: HttpRequest<Vec<u8>>, 645 + ) -> impl Future<Output = core::result::Result<HttpResponse<Vec<u8>>, Self::Error>> + Send 646 + { 647 let next = self.next.clone(); 648 async move { Ok(next.lock().await.take().unwrap()) } 649 }
+31 -40
crates/jacquard/src/client.rs
··· 40 CowStr, IntoStatic, 41 types::string::{Did, Handle}, 42 }; 43 - use jacquard_identity::resolver::IdentityResolver; 44 use jacquard_oauth::authstore::ClientAuthStore; 45 use jacquard_oauth::client::OAuthSession; 46 use jacquard_oauth::dpop::DpopExt; ··· 325 server::{create_session::CreateSessionOutput, refresh_session::RefreshSessionOutput}, 326 }; 327 328 /// Extension trait providing convenience methods for common repository operations. 329 /// 330 /// This trait is automatically implemented for any type that implements both ··· 406 &self, 407 record: R, 408 rkey: Option<RecordKey<Rkey<'_>>>, 409 - ) -> impl std::future::Future<Output = Result<CreateRecordOutput<'static>, AgentError>> 410 where 411 R: Collection + serde::Serialize, 412 { ··· 474 fn get_record<R>( 475 &self, 476 uri: AtUri<'_>, 477 - ) -> impl std::future::Future<Output = Result<Response<R::Record>, ClientError>> 478 where 479 R: Collection, 480 { ··· 550 &self, 551 record_type: R, 552 uri: AtUri<'_>, 553 - ) -> impl std::future::Future< 554 - Output = Result<<<R as Collection>::Record as XrpcResp>::Output<'static>, ClientError>, 555 - > 556 where 557 R: Collection, 558 - for<'a> <<R as Collection>::Record as XrpcResp>::Output<'a>: 559 - IntoStatic<Output = <<R as Collection>::Record as XrpcResp>::Output<'static>>, 560 - for<'a> <<R as Collection>::Record as XrpcResp>::Err<'a>: 561 - IntoStatic<Output = <<R as Collection>::Record as XrpcResp>::Err<'static>>, 562 { 563 let _ = record_type; 564 async move { ··· 602 &self, 603 uri: AtUri<'_>, 604 f: impl FnOnce(&mut R), 605 - ) -> impl std::future::Future<Output = Result<PutRecordOutput<'static>, AgentError>> 606 where 607 R: Collection + Serialize, 608 - R: for<'a> From<<<R as Collection>::Record as XrpcResp>::Output<'a>>, 609 { 610 async move { 611 #[cfg(feature = "tracing")] ··· 652 fn delete_record<R>( 653 &self, 654 rkey: RecordKey<Rkey<'_>>, 655 - ) -> impl std::future::Future<Output = Result<DeleteRecordOutput<'static>, AgentError>> 656 where 657 R: Collection, 658 { ··· 692 &self, 693 rkey: RecordKey<Rkey<'static>>, 694 record: R, 695 - ) -> impl std::future::Future<Output = Result<PutRecordOutput<'static>, AgentError>> 696 where 697 R: Collection + serde::Serialize, 698 { ··· 755 &self, 756 data: impl Into<bytes::Bytes>, 757 mime_type: MimeType<'_>, 758 - ) -> impl std::future::Future<Output = Result<Blob<'static>, AgentError>> { 759 async move { 760 #[cfg(feature = "tracing")] 761 let _span = tracing::debug_span!("upload_blob", mime_type = %mime_type).entered(); ··· 808 fn update_vec<U>( 809 &self, 810 modify: impl FnOnce(&mut Vec<<U as VecUpdate>::Item>), 811 - ) -> impl std::future::Future< 812 - Output = Result< 813 - xrpc::Response<<<U as VecUpdate>::PutRequest as XrpcRequest>::Response>, 814 - AgentError, 815 - >, 816 - > 817 where 818 U: VecUpdate, 819 <U as VecUpdate>::PutRequest: Send + Sync, 820 <U as VecUpdate>::GetRequest: Send + Sync, 821 - <<U as VecUpdate>::GetRequest as XrpcRequest>::Response: Send + Sync, 822 - <<U as VecUpdate>::PutRequest as XrpcRequest>::Response: Send + Sync, 823 { 824 async { 825 // Fetch current data ··· 863 fn update_vec_item<U>( 864 &self, 865 item: <U as VecUpdate>::Item, 866 - ) -> impl std::future::Future< 867 - Output = Result< 868 - xrpc::Response<<<U as VecUpdate>::PutRequest as XrpcRequest>::Response>, 869 - AgentError, 870 - >, 871 - > 872 where 873 U: VecUpdate, 874 <U as VecUpdate>::PutRequest: Send + Sync, 875 <U as VecUpdate>::GetRequest: Send + Sync, 876 - <<U as VecUpdate>::GetRequest as XrpcRequest>::Response: Send + Sync, 877 - <<U as VecUpdate>::PutRequest as XrpcRequest>::Response: Send + Sync, 878 { 879 async { 880 self.update_vec::<U>(|vec| { ··· 944 } 945 946 impl<A: AgentSession + IdentityResolver> IdentityResolver for Agent<A> { 947 - fn options(&self) -> &jacquard_identity::resolver::ResolverOptions { 948 self.inner.options() 949 } 950 951 fn resolve_handle( 952 &self, 953 handle: &Handle<'_>, 954 - ) -> impl Future<Output = Result<Did<'static>, jacquard_identity::resolver::IdentityError>> 955 - { 956 async { self.inner.resolve_handle(handle).await } 957 } 958 959 fn resolve_did_doc( 960 &self, 961 did: &Did<'_>, 962 - ) -> impl Future< 963 - Output = Result< 964 - jacquard_identity::resolver::DidDocResponse, 965 - jacquard_identity::resolver::IdentityError, 966 - >, 967 - > { 968 async { self.inner.resolve_did_doc(did).await } 969 } 970 }
··· 40 CowStr, IntoStatic, 41 types::string::{Did, Handle}, 42 }; 43 + use jacquard_identity::resolver::{ 44 + DidDocResponse, IdentityError, IdentityResolver, ResolverOptions, 45 + }; 46 use jacquard_oauth::authstore::ClientAuthStore; 47 use jacquard_oauth::client::OAuthSession; 48 use jacquard_oauth::dpop::DpopExt; ··· 327 server::{create_session::CreateSessionOutput, refresh_session::RefreshSessionOutput}, 328 }; 329 330 + /// doc 331 + pub type CollectionOutput<'a, R> = <<R as Collection>::Record as XrpcResp>::Output<'a>; 332 + /// doc 333 + pub type CollectionErr<'a, R> = <<R as Collection>::Record as XrpcResp>::Err<'a>; 334 + /// doc 335 + pub type VecGetResponse<U> = <<U as VecUpdate>::GetRequest as XrpcRequest>::Response; 336 + /// doc 337 + pub type VecPutResponse<U> = <<U as VecUpdate>::PutRequest as XrpcRequest>::Response; 338 + 339 /// Extension trait providing convenience methods for common repository operations. 340 /// 341 /// This trait is automatically implemented for any type that implements both ··· 417 &self, 418 record: R, 419 rkey: Option<RecordKey<Rkey<'_>>>, 420 + ) -> impl Future<Output = Result<CreateRecordOutput<'static>, AgentError>> 421 where 422 R: Collection + serde::Serialize, 423 { ··· 485 fn get_record<R>( 486 &self, 487 uri: AtUri<'_>, 488 + ) -> impl Future<Output = Result<Response<R::Record>, ClientError>> 489 where 490 R: Collection, 491 { ··· 561 &self, 562 record_type: R, 563 uri: AtUri<'_>, 564 + ) -> impl Future<Output = Result<CollectionOutput<'static, R>, ClientError>> 565 where 566 R: Collection, 567 + for<'a> CollectionOutput<'a, R>: IntoStatic<Output = CollectionOutput<'static, R>>, 568 + for<'a> CollectionErr<'a, R>: IntoStatic<Output = CollectionErr<'static, R>>, 569 { 570 let _ = record_type; 571 async move { ··· 609 &self, 610 uri: AtUri<'_>, 611 f: impl FnOnce(&mut R), 612 + ) -> impl Future<Output = Result<PutRecordOutput<'static>, AgentError>> 613 where 614 R: Collection + Serialize, 615 + R: for<'a> From<CollectionOutput<'a, R>>, 616 { 617 async move { 618 #[cfg(feature = "tracing")] ··· 659 fn delete_record<R>( 660 &self, 661 rkey: RecordKey<Rkey<'_>>, 662 + ) -> impl Future<Output = Result<DeleteRecordOutput<'static>, AgentError>> 663 where 664 R: Collection, 665 { ··· 699 &self, 700 rkey: RecordKey<Rkey<'static>>, 701 record: R, 702 + ) -> impl Future<Output = Result<PutRecordOutput<'static>, AgentError>> 703 where 704 R: Collection + serde::Serialize, 705 { ··· 762 &self, 763 data: impl Into<bytes::Bytes>, 764 mime_type: MimeType<'_>, 765 + ) -> impl Future<Output = Result<Blob<'static>, AgentError>> { 766 async move { 767 #[cfg(feature = "tracing")] 768 let _span = tracing::debug_span!("upload_blob", mime_type = %mime_type).entered(); ··· 815 fn update_vec<U>( 816 &self, 817 modify: impl FnOnce(&mut Vec<<U as VecUpdate>::Item>), 818 + ) -> impl Future<Output = Result<xrpc::Response<VecPutResponse<U>>, AgentError>> 819 where 820 U: VecUpdate, 821 <U as VecUpdate>::PutRequest: Send + Sync, 822 <U as VecUpdate>::GetRequest: Send + Sync, 823 + VecGetResponse<U>: Send + Sync, 824 + VecPutResponse<U>: Send + Sync, 825 { 826 async { 827 // Fetch current data ··· 865 fn update_vec_item<U>( 866 &self, 867 item: <U as VecUpdate>::Item, 868 + ) -> impl Future<Output = Result<xrpc::Response<VecPutResponse<U>>, AgentError>> 869 where 870 U: VecUpdate, 871 <U as VecUpdate>::PutRequest: Send + Sync, 872 <U as VecUpdate>::GetRequest: Send + Sync, 873 + VecGetResponse<U>: Send + Sync, 874 + VecPutResponse<U>: Send + Sync, 875 { 876 async { 877 self.update_vec::<U>(|vec| { ··· 941 } 942 943 impl<A: AgentSession + IdentityResolver> IdentityResolver for Agent<A> { 944 + fn options(&self) -> &ResolverOptions { 945 self.inner.options() 946 } 947 948 fn resolve_handle( 949 &self, 950 handle: &Handle<'_>, 951 + ) -> impl Future<Output = Result<Did<'static>, IdentityError>> { 952 async { self.inner.resolve_handle(handle).await } 953 } 954 955 fn resolve_did_doc( 956 &self, 957 did: &Did<'_>, 958 + ) -> impl Future<Output = Result<DidDocResponse, IdentityError>> { 959 async { self.inner.resolve_did_doc(did).await } 960 } 961 }
+11 -13
crates/jacquard/src/client/credential_session.rs
··· 9 http_client::HttpClient, 10 session::SessionStore, 11 types::{did::Did, string::Handle}, 12 - xrpc::{CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp}, 13 }; 14 use tokio::sync::RwLock; 15 use url::Url; 16 17 use crate::client::AtpSession; 18 - use jacquard_identity::resolver::IdentityResolver; 19 use std::any::Any; 20 21 /// Storage key for app‑password sessions: `(account DID, session id)`. ··· 426 } 427 } 428 429 - async fn send<R>(&self, request: R) -> XrpcResult<Response<<R as XrpcRequest>::Response>> 430 where 431 R: XrpcRequest + Send + Sync, 432 <R as XrpcRequest>::Response: Send + Sync, ··· 439 &self, 440 request: R, 441 mut opts: CallOptions<'_>, 442 - ) -> XrpcResult<Response<<R as XrpcRequest>::Response>> 443 where 444 R: XrpcRequest + Send + Sync, 445 <R as XrpcRequest>::Response: Send + Sync, ··· 485 S: SessionStore<SessionKey, AtpSession> + Send + Sync + 'static, 486 T: HttpClient + IdentityResolver + Send + Sync + 'static, 487 { 488 - fn options(&self) -> &jacquard_identity::resolver::ResolverOptions { 489 self.client.options() 490 } 491 492 fn resolve_handle( 493 &self, 494 handle: &Handle<'_>, 495 - ) -> impl Future<Output = Result<Did<'static>, jacquard_identity::resolver::IdentityError>> 496 - { 497 async { self.client.resolve_handle(handle).await } 498 } 499 500 fn resolve_did_doc( 501 &self, 502 did: &Did<'_>, 503 - ) -> impl Future< 504 - Output = Result< 505 - jacquard_identity::resolver::DidDocResponse, 506 - jacquard_identity::resolver::IdentityError, 507 - >, 508 - > { 509 async { self.client.resolve_did_doc(did).await } 510 } 511 }
··· 9 http_client::HttpClient, 10 session::SessionStore, 11 types::{did::Did, string::Handle}, 12 + xrpc::{ 13 + CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, XrpcResponse, 14 + }, 15 }; 16 use tokio::sync::RwLock; 17 use url::Url; 18 19 use crate::client::AtpSession; 20 + use jacquard_identity::resolver::{ 21 + DidDocResponse, IdentityError, IdentityResolver, ResolverOptions, 22 + }; 23 use std::any::Any; 24 25 /// Storage key for app‑password sessions: `(account DID, session id)`. ··· 430 } 431 } 432 433 + async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>> 434 where 435 R: XrpcRequest + Send + Sync, 436 <R as XrpcRequest>::Response: Send + Sync, ··· 443 &self, 444 request: R, 445 mut opts: CallOptions<'_>, 446 + ) -> XrpcResult<XrpcResponse<R>> 447 where 448 R: XrpcRequest + Send + Sync, 449 <R as XrpcRequest>::Response: Send + Sync, ··· 489 S: SessionStore<SessionKey, AtpSession> + Send + Sync + 'static, 490 T: HttpClient + IdentityResolver + Send + Sync + 'static, 491 { 492 + fn options(&self) -> &ResolverOptions { 493 self.client.options() 494 } 495 496 fn resolve_handle( 497 &self, 498 handle: &Handle<'_>, 499 + ) -> impl Future<Output = Result<Did<'static>, IdentityError>> { 500 async { self.client.resolve_handle(handle).await } 501 } 502 503 fn resolve_did_doc( 504 &self, 505 did: &Did<'_>, 506 + ) -> impl Future<Output = Result<DidDocResponse, IdentityError>> { 507 async { self.client.resolve_did_doc(did).await } 508 } 509 }