A better Rust ATProto crate
at oauth 4.1 kB view raw
1//! Error types for XRPC client operations 2 3use bytes::Bytes; 4 5use crate::types::xrpc::EncodeError; 6 7/// Client error type wrapping all possible error conditions 8#[derive(Debug, thiserror::Error, miette::Diagnostic)] 9pub enum ClientError { 10 /// HTTP transport error 11 #[error("HTTP transport error: {0}")] 12 Transport( 13 #[from] 14 #[diagnostic_source] 15 TransportError, 16 ), 17 18 /// Request serialization failed 19 #[error("{0}")] 20 Encode( 21 #[from] 22 #[diagnostic_source] 23 EncodeError, 24 ), 25 26 /// Response deserialization failed 27 #[error("{0}")] 28 Decode( 29 #[from] 30 #[diagnostic_source] 31 DecodeError, 32 ), 33 34 /// HTTP error response 35 #[error("HTTP {0}")] 36 Http( 37 #[from] 38 #[diagnostic_source] 39 HttpError, 40 ), 41 42 /// Authentication error 43 #[error("Authentication error: {0}")] 44 Auth( 45 #[from] 46 #[diagnostic_source] 47 AuthError, 48 ), 49} 50 51/// Transport-level errors that occur during HTTP communication 52#[derive(Debug, thiserror::Error, miette::Diagnostic)] 53pub enum TransportError { 54 /// Failed to establish connection to server 55 #[error("Connection error: {0}")] 56 Connect(String), 57 58 /// Request timed out 59 #[error("Request timeout")] 60 Timeout, 61 62 /// Request construction failed (malformed URI, headers, etc.) 63 #[error("Invalid request: {0}")] 64 InvalidRequest(String), 65 66 /// Other transport error 67 #[error("Transport error: {0}")] 68 Other(Box<dyn std::error::Error + Send + Sync>), 69} 70 71/// Response deserialization errors 72#[derive(Debug, thiserror::Error, miette::Diagnostic)] 73pub enum DecodeError { 74 /// JSON deserialization failed 75 #[error("Failed to deserialize JSON: {0}")] 76 Json( 77 #[from] 78 #[source] 79 serde_json::Error, 80 ), 81 /// CBOR deserialization failed (local I/O) 82 #[error("Failed to deserialize CBOR: {0}")] 83 CborLocal( 84 #[from] 85 #[source] 86 serde_ipld_dagcbor::DecodeError<std::io::Error>, 87 ), 88 /// CBOR deserialization failed (remote/reqwest) 89 #[error("Failed to deserialize CBOR: {0}")] 90 CborRemote( 91 #[from] 92 #[source] 93 serde_ipld_dagcbor::DecodeError<HttpError>, 94 ), 95} 96 97/// HTTP error response (non-200 status codes outside of XRPC error handling) 98#[derive(Debug, thiserror::Error, miette::Diagnostic)] 99pub struct HttpError { 100 /// HTTP status code 101 pub status: http::StatusCode, 102 /// Response body if available 103 pub body: Option<Bytes>, 104} 105 106impl std::fmt::Display for HttpError { 107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 108 write!(f, "HTTP {}", self.status)?; 109 if let Some(body) = &self.body { 110 if let Ok(s) = std::str::from_utf8(body) { 111 write!(f, ":\n{}", s)?; 112 } 113 } 114 Ok(()) 115 } 116} 117 118/// Result type for client operations 119pub type XrpcResult<T> = std::result::Result<T, ClientError>; 120 121#[cfg(feature = "reqwest-client")] 122impl From<reqwest::Error> for TransportError { 123 fn from(e: reqwest::Error) -> Self { 124 if e.is_timeout() { 125 Self::Timeout 126 } else if e.is_connect() { 127 Self::Connect(e.to_string()) 128 } else if e.is_builder() || e.is_request() { 129 Self::InvalidRequest(e.to_string()) 130 } else { 131 Self::Other(Box::new(e)) 132 } 133 } 134} 135 136/// Authentication and authorization errors 137#[derive(Debug, thiserror::Error, miette::Diagnostic)] 138pub enum AuthError { 139 /// Access token has expired (use refresh token to get a new one) 140 #[error("Access token expired")] 141 TokenExpired, 142 143 /// Access token is invalid or malformed 144 #[error("Invalid access token")] 145 InvalidToken, 146 147 /// Token refresh request failed 148 #[error("Token refresh failed")] 149 RefreshFailed, 150 151 /// Request requires authentication but none was provided 152 #[error("No authentication provided")] 153 NotAuthenticated, 154 155 /// Other authentication error 156 #[error("Authentication error: {0:?}")] 157 Other(http::HeaderValue), 158}