1use crate::{api_error::ApiError, json::JsonError, response::StatusCode};
2use http::Response;
3use hyper::body::Incoming;
4use std::{
5 error::Error as StdError,
6 fmt::{Debug, Display, Formatter, Result as FmtResult},
7 str,
8};
9
10#[derive(Debug)]
11pub struct Error {
12 pub(super) source: Option<Box<dyn StdError + Send + Sync>>,
13 pub(super) kind: ErrorType,
14}
15
16impl Error {
17 #[must_use = "retrieving the type has no effect if left unused"]
19 pub const fn kind(&self) -> &ErrorType {
20 &self.kind
21 }
22
23 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
25 pub fn into_source(self) -> Option<Box<dyn StdError + Send + Sync>> {
26 self.source
27 }
28
29 #[must_use = "consuming the error into its parts has no effect if left unused"]
31 pub fn into_parts(self) -> (ErrorType, Option<Box<dyn StdError + Send + Sync>>) {
32 (self.kind, self.source)
33 }
34
35 pub(super) fn json(source: JsonError) -> Self {
36 Self {
37 kind: ErrorType::Json,
38 source: Some(Box::new(source)),
39 }
40 }
41
42 pub(super) fn validation(source: impl StdError + Send + Sync + 'static) -> Self {
43 Self {
44 kind: ErrorType::Validation,
45 source: Some(Box::new(source)),
46 }
47 }
48}
49
50impl Display for Error {
51 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
52 match &self.kind {
53 ErrorType::BuildingRequest => f.write_str("failed to build the request"),
54 ErrorType::ChunkingResponse => f.write_str("Chunking the response failed"),
55 ErrorType::CreatingHeader { name, .. } => {
56 f.write_str("Parsing the value for header {}")?;
57 f.write_str(name)?;
58
59 f.write_str(" failed")
60 }
61 ErrorType::Json => f.write_str("Given value couldn't be serialized"),
62 ErrorType::Parsing { body, .. } => {
63 f.write_str("Response body couldn't be deserialized: ")?;
64
65 if let Ok(body) = str::from_utf8(body) {
66 f.write_str(body)
67 } else {
68 Debug::fmt(body, f)
69 }
70 }
71 ErrorType::RatelimiterTicket => f.write_str("Failed to get ratelimiter ticket"),
72 ErrorType::RequestCanceled => {
73 f.write_str("Request was canceled either before or while being sent")
74 }
75 ErrorType::RequestError => f.write_str("Parsing or sending the response failed"),
76 ErrorType::RequestTimedOut => f.write_str("request timed out"),
77 ErrorType::Response { body, status, .. } => {
78 f.write_str("Response error: status code ")?;
79 Display::fmt(status, f)?;
80 f.write_str(", error: ")?;
81
82 f.write_str(&String::from_utf8_lossy(body))
83 }
84 ErrorType::ServiceUnavailable { .. } => {
85 f.write_str("api may be temporarily unavailable (received a 503)")
86 }
87 ErrorType::Unauthorized => {
88 f.write_str("token in use is invalid, expired, or is revoked")
89 }
90 ErrorType::Validation => f.write_str("request fields have invalid values"),
91 }
92 }
93}
94
95impl StdError for Error {
96 fn source(&self) -> Option<&(dyn StdError + 'static)> {
97 self.source
98 .as_ref()
99 .map(|source| &**source as &(dyn StdError + 'static))
100 }
101}
102
103#[non_exhaustive]
105pub enum ErrorType {
106 BuildingRequest,
107 ChunkingResponse,
108 CreatingHeader {
109 name: String,
110 },
111 Json,
112 Parsing {
113 body: Vec<u8>,
114 },
115 RatelimiterTicket,
116 RequestCanceled,
117 RequestError,
118 RequestTimedOut,
119 Response {
120 body: Vec<u8>,
121 error: ApiError,
122 status: StatusCode,
123 },
124 ServiceUnavailable {
129 response: Response<Incoming>,
130 },
131 Unauthorized,
136 Validation,
193}
194
195impl Debug for ErrorType {
196 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
197 match self {
198 Self::BuildingRequest => f.write_str("BuildingRequest"),
199 Self::ChunkingResponse => f.write_str("ChunkingResponse"),
200 Self::CreatingHeader { name } => f
201 .debug_struct("CreatingHeader")
202 .field("name", name)
203 .finish(),
204 Self::Json => f.write_str("Json"),
205 Self::Parsing { body } => {
206 let mut debug = f.debug_struct("Parsing");
207
208 if let Ok(body_string) = str::from_utf8(body) {
209 debug.field("body_string", &body_string);
210 }
211
212 debug.field("body", body).finish()
213 }
214 Self::RatelimiterTicket => f.write_str("RatelimiterTicket"),
215 Self::RequestCanceled => f.write_str("RequestCanceled"),
216 Self::RequestError => f.write_str("RequestError"),
217 Self::RequestTimedOut => f.write_str("RequestTimedOut"),
218 Self::Response {
219 body,
220 error,
221 status,
222 } => {
223 let mut debug = f.debug_struct("Response");
224
225 if let Ok(body_string) = str::from_utf8(body) {
226 debug.field("body_string", &body_string);
227 }
228
229 debug
230 .field("body", body)
231 .field("error", error)
232 .field("status", status)
233 .finish()
234 }
235 Self::ServiceUnavailable { response } => f
236 .debug_struct("ServiceUnavailable")
237 .field("response", response)
238 .finish(),
239 Self::Unauthorized => f.write_str("Unauthorized"),
240 Self::Validation => f.write_str("Validation"),
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::ErrorType;
248 use crate::{
249 api_error::{ApiError, GeneralApiError},
250 response::StatusCode,
251 };
252
253 #[test]
255 fn parsing_variant_debug() {
256 let body = br#"{"message": "aaa"#.to_vec();
257
258 let error = ErrorType::Parsing { body };
259
260 assert_eq!(
261 "Parsing {
262 body_string: \"{\\\"message\\\": \\\"aaa\",
263 body: [
264 123,
265 34,
266 109,
267 101,
268 115,
269 115,
270 97,
271 103,
272 101,
273 34,
274 58,
275 32,
276 34,
277 97,
278 97,
279 97,
280 ],
281}",
282 format!("{error:#?}"),
283 );
284 }
285
286 #[test]
287 fn response_variant_debug() {
288 let body = br#"{"message": "aaa"}"#.to_vec();
289
290 let error = ErrorType::Response {
291 body,
292 error: ApiError::General(GeneralApiError {
293 code: 0,
294 message: "401: Unauthorized".to_owned(),
295 }),
296 status: StatusCode::new(401),
297 };
298
299 assert_eq!(
300 "Response {
301 body_string: \"{\\\"message\\\": \\\"aaa\\\"}\",
302 body: [
303 123,
304 34,
305 109,
306 101,
307 115,
308 115,
309 97,
310 103,
311 101,
312 34,
313 58,
314 32,
315 34,
316 97,
317 97,
318 97,
319 34,
320 125,
321 ],
322 error: General(
323 GeneralApiError {
324 code: 0,
325 message: \"401: Unauthorized\",
326 },
327 ),
328 status: StatusCode(
329 401,
330 ),
331}",
332 format!("{error:#?}"),
333 );
334 }
335}