1use crate::xrpc::EncodeError;
4use bytes::Bytes;
5
6#[derive(Debug, thiserror::Error, miette::Diagnostic)]
8pub enum ClientError {
9 #[error("HTTP transport error: {0}")]
11 Transport(
12 #[from]
13 #[diagnostic_source]
14 TransportError,
15 ),
16
17 #[error("{0}")]
19 Encode(
20 #[from]
21 #[diagnostic_source]
22 EncodeError,
23 ),
24
25 #[error("{0}")]
27 Decode(
28 #[from]
29 #[diagnostic_source]
30 DecodeError,
31 ),
32
33 #[error("HTTP {0}")]
35 Http(
36 #[from]
37 #[diagnostic_source]
38 HttpError,
39 ),
40
41 #[error("Authentication error: {0}")]
43 Auth(
44 #[from]
45 #[diagnostic_source]
46 AuthError,
47 ),
48}
49
50#[derive(Debug, thiserror::Error, miette::Diagnostic)]
52pub enum TransportError {
53 #[error("Connection error: {0}")]
55 Connect(String),
56
57 #[error("Request timeout")]
59 Timeout,
60
61 #[error("Invalid request: {0}")]
63 InvalidRequest(String),
64
65 #[error("Transport error: {0}")]
67 Other(Box<dyn std::error::Error + Send + Sync>),
68}
69
70#[derive(Debug, thiserror::Error, miette::Diagnostic)]
72pub enum DecodeError {
73 #[error("Failed to deserialize JSON: {0}")]
75 Json(
76 #[from]
77 #[source]
78 serde_json::Error,
79 ),
80 #[error("Failed to deserialize CBOR: {0}")]
82 CborLocal(
83 #[from]
84 #[source]
85 serde_ipld_dagcbor::DecodeError<std::io::Error>,
86 ),
87 #[error("Failed to deserialize CBOR: {0}")]
89 CborRemote(
90 #[from]
91 #[source]
92 serde_ipld_dagcbor::DecodeError<HttpError>,
93 ),
94 #[error("Failed to deserialize DAG-CBOR: {0}")]
96 DagCborInfallible(
97 #[from]
98 #[source]
99 serde_ipld_dagcbor::DecodeError<std::convert::Infallible>,
100 ),
101 #[cfg(feature = "websocket")]
103 #[error("Failed to deserialize cbor header: {0}")]
104 CborHeader(
105 #[from]
106 #[source]
107 ciborium::de::Error<std::io::Error>,
108 ),
109
110 #[cfg(feature = "websocket")]
112 #[error("Unknown event type: {0}")]
113 UnknownEventType(smol_str::SmolStr),
114}
115
116#[derive(Debug, thiserror::Error, miette::Diagnostic)]
118pub struct HttpError {
119 pub status: http::StatusCode,
121 pub body: Option<Bytes>,
123}
124
125impl std::fmt::Display for HttpError {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(f, "HTTP {}", self.status)?;
128 if let Some(body) = &self.body {
129 if let Ok(s) = std::str::from_utf8(body) {
130 write!(f, ":\n{}", s)?;
131 }
132 }
133 Ok(())
134 }
135}
136
137pub type XrpcResult<T> = std::result::Result<T, ClientError>;
139
140#[cfg(feature = "reqwest-client")]
141impl From<reqwest::Error> for TransportError {
142 #[cfg(not(target_arch = "wasm32"))]
143 fn from(e: reqwest::Error) -> Self {
144 if e.is_timeout() {
145 Self::Timeout
146 } else if e.is_connect() {
147 Self::Connect(e.to_string())
148 } else if e.is_builder() || e.is_request() {
149 Self::InvalidRequest(e.to_string())
150 } else {
151 Self::Other(Box::new(e))
152 }
153 }
154 #[cfg(target_arch = "wasm32")]
155 fn from(e: reqwest::Error) -> Self {
156 if e.is_timeout() {
157 Self::Timeout
158 } else if e.is_builder() || e.is_request() {
159 Self::InvalidRequest(e.to_string())
160 } else {
161 Self::Other(Box::new(e))
162 }
163 }
164}
165
166#[derive(Debug, thiserror::Error, miette::Diagnostic)]
168pub enum AuthError {
169 #[error("Access token expired")]
171 TokenExpired,
172
173 #[error("Invalid access token")]
175 InvalidToken,
176
177 #[error("Token refresh failed")]
179 RefreshFailed,
180
181 #[error("No authentication provided, but endpoint requires auth")]
183 NotAuthenticated,
184
185 #[error("Authentication error: {0:?}")]
187 Other(http::HeaderValue),
188}
189
190impl crate::IntoStatic for AuthError {
191 type Output = AuthError;
192
193 fn into_static(self) -> Self::Output {
194 match self {
195 AuthError::TokenExpired => AuthError::TokenExpired,
196 AuthError::InvalidToken => AuthError::InvalidToken,
197 AuthError::RefreshFailed => AuthError::RefreshFailed,
198 AuthError::NotAuthenticated => AuthError::NotAuthenticated,
199 AuthError::Other(header) => AuthError::Other(header),
200 }
201 }
202}