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}