A better Rust ATProto crate

interim more resilient error parsing for auth errors

Changed files
+49 -11
crates
jacquard-common
src
+49 -11
crates/jacquard-common/src/xrpc.rs
··· 48 }; 49 use serde::{Deserialize, Serialize}; 50 use smol_str::SmolStr; 51 #[cfg(feature = "websocket")] 52 pub use subscription::{ 53 BasicSubscriptionClient, MessageEncoding, SubscriptionCall, SubscriptionClient, ··· 485 Resp: XrpcResp, 486 { 487 let status = http_response.status(); 488 // If the server returned 401 with a WWW-Authenticate header, expose it so higher layers 489 // (e.g., DPoP handling) can detect `error="invalid_token"` and trigger refresh. 490 #[allow(deprecated)] 491 if status.as_u16() == 401 { 492 if let Some(hv) = http_response.headers().get(http::header::WWW_AUTHENTICATE) { 493 - return Err(crate::error::ClientError::auth( 494 - crate::error::AuthError::Other(hv.clone()), 495 - ) 496 - .for_nsid(Resp::NSID)); 497 } 498 } 499 let buffer = Bytes::from(http_response.into_body()); ··· 670 } 671 // 400: try typed XRPC error, fallback to generic error 672 } else if self.status.as_u16() == 400 { 673 - match serde_json::from_slice::<_>(&self.buffer) { 674 - Ok(error) => Err(XrpcError::Xrpc(error)), 675 Err(_) => { 676 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 677 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { ··· 730 } 731 // 400: try typed XRPC error, fallback to generic error 732 } else if self.status.as_u16() == 400 { 733 - match serde_json::from_slice::<_>(&self.buffer) { 734 - Ok(error) => Err(XrpcError::Xrpc(error)), 735 Err(_) => { 736 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 737 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { ··· 790 } 791 // 400: try typed XRPC error, fallback to generic error 792 } else if self.status.as_u16() == 400 { 793 - match serde_json::from_slice::<_>(&self.buffer) { 794 - Ok(error) => Err(XrpcError::Xrpc(error)), 795 Err(_) => { 796 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 797 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { ··· 876 // 400: try typed XRPC error, fallback to generic error 877 } else if self.status.as_u16() == 400 { 878 let error = match parse_error::<R>(&self.buffer) { 879 - Ok(error) => XrpcError::Xrpc(error), 880 Err(_) => { 881 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 882 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
··· 48 }; 49 use serde::{Deserialize, Serialize}; 50 use smol_str::SmolStr; 51 + 52 #[cfg(feature = "websocket")] 53 pub use subscription::{ 54 BasicSubscriptionClient, MessageEncoding, SubscriptionCall, SubscriptionClient, ··· 486 Resp: XrpcResp, 487 { 488 let status = http_response.status(); 489 + 490 // If the server returned 401 with a WWW-Authenticate header, expose it so higher layers 491 // (e.g., DPoP handling) can detect `error="invalid_token"` and trigger refresh. 492 #[allow(deprecated)] 493 if status.as_u16() == 401 { 494 if let Some(hv) = http_response.headers().get(http::header::WWW_AUTHENTICATE) { 495 + return Err( 496 + crate::error::ClientError::auth(crate::error::AuthError::Other(hv.clone())) 497 + .for_nsid(Resp::NSID), 498 + ); 499 } 500 } 501 let buffer = Bytes::from(http_response.into_body()); ··· 672 } 673 // 400: try typed XRPC error, fallback to generic error 674 } else if self.status.as_u16() == 400 { 675 + match serde_json::from_slice::<R::Err<'_>>(&self.buffer) { 676 + Ok(error) => { 677 + use alloc::string::ToString; 678 + if error.to_string().contains("InvalidToken") { 679 + Err(XrpcError::Auth(AuthError::InvalidToken)) 680 + } else if error.to_string().contains("ExpiredToken") { 681 + Err(XrpcError::Auth(AuthError::TokenExpired)) 682 + } else { 683 + Err(XrpcError::Xrpc(error)) 684 + } 685 + } 686 Err(_) => { 687 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 688 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { ··· 741 } 742 // 400: try typed XRPC error, fallback to generic error 743 } else if self.status.as_u16() == 400 { 744 + match serde_json::from_slice::<R::Err<'_>>(&self.buffer) { 745 + Ok(error) => { 746 + use alloc::string::ToString; 747 + if error.to_string().contains("InvalidToken") { 748 + Err(XrpcError::Auth(AuthError::InvalidToken)) 749 + } else if error.to_string().contains("ExpiredToken") { 750 + Err(XrpcError::Auth(AuthError::TokenExpired)) 751 + } else { 752 + Err(XrpcError::Xrpc(error)) 753 + } 754 + } 755 Err(_) => { 756 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 757 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { ··· 810 } 811 // 400: try typed XRPC error, fallback to generic error 812 } else if self.status.as_u16() == 400 { 813 + match serde_json::from_slice::<R::Err<'_>>(&self.buffer) { 814 + Ok(error) => { 815 + use alloc::string::ToString; 816 + if error.to_string().contains("InvalidToken") { 817 + Err(XrpcError::Auth(AuthError::InvalidToken)) 818 + } else if error.to_string().contains("ExpiredToken") { 819 + Err(XrpcError::Auth(AuthError::TokenExpired)) 820 + } else { 821 + Err(XrpcError::Xrpc(error)) 822 + } 823 + } 824 Err(_) => { 825 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 826 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) { ··· 905 // 400: try typed XRPC error, fallback to generic error 906 } else if self.status.as_u16() == 400 { 907 let error = match parse_error::<R>(&self.buffer) { 908 + Ok(error) => { 909 + use alloc::string::ToString; 910 + if error.to_string().contains("InvalidToken") { 911 + XrpcError::Auth(AuthError::InvalidToken) 912 + } else if error.to_string().contains("ExpiredToken") { 913 + XrpcError::Auth(AuthError::TokenExpired) 914 + } else { 915 + XrpcError::Xrpc(error) 916 + } 917 + } 918 Err(_) => { 919 // Fallback to generic error (InvalidRequest, ExpiredToken, etc.) 920 match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {