1#[cfg(not(target_os = "wasi"))]
2use crate::response::StatusCode;
3use crate::{api_error::ApiError, json::JsonError};
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) kind: ErrorType,
13 pub(super) source: Option<Box<dyn StdError + Send + Sync>>,
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::CreatingHeader { name, .. } => {
55 f.write_str("parsing the value for header ")?;
56 f.write_str(name)?;
57
58 f.write_str(" failed")
59 }
60 ErrorType::Json => f.write_str("given value couldn't be serialized"),
61 ErrorType::Parsing { body, .. } => {
62 f.write_str("response body couldn't be deserialized: ")?;
63
64 if let Ok(body) = str::from_utf8(body) {
65 f.write_str(body)
66 } else {
67 Debug::fmt(body, f)
68 }
69 }
70 ErrorType::RequestCanceled => {
71 f.write_str("request was canceled either before or while being sent")
72 }
73 ErrorType::RequestError => f.write_str("parsing or receiving the response failed"),
74 ErrorType::RequestTimedOut => f.write_str("request timed out"),
75 #[cfg(not(target_os = "wasi"))]
76 ErrorType::Response { body, status, .. } => {
77 f.write_str("response error: status code ")?;
78 Display::fmt(status, f)?;
79 f.write_str(", error: ")?;
80
81 f.write_str(&String::from_utf8_lossy(body))
82 }
83 ErrorType::Unauthorized => {
84 f.write_str("token in use is invalid, expired, or is revoked")
85 }
86 ErrorType::Validation => f.write_str("request fields have invalid values"),
87 }
88 }
89}
90
91impl StdError for Error {
92 fn source(&self) -> Option<&(dyn StdError + 'static)> {
93 self.source
94 .as_ref()
95 .map(|source| &**source as &(dyn StdError + 'static))
96 }
97}
98
99#[non_exhaustive]
101pub enum ErrorType {
102 BuildingRequest,
103 CreatingHeader {
104 name: String,
105 },
106 Json,
107 Parsing {
108 body: Vec<u8>,
109 },
110 RequestCanceled,
111 RequestError,
112 RequestTimedOut,
113 #[cfg(not(target_os = "wasi"))]
114 Response {
115 body: Vec<u8>,
116 error: ApiError,
117 status: StatusCode,
118 },
119 Unauthorized,
124 Validation,
181}
182
183impl Debug for ErrorType {
184 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
185 match self {
186 Self::BuildingRequest => f.write_str("BuildingRequest"),
187 Self::CreatingHeader { name } => f
188 .debug_struct("CreatingHeader")
189 .field("name", name)
190 .finish(),
191 Self::Json => f.write_str("Json"),
192 Self::Parsing { body } => {
193 let mut debug = f.debug_struct("Parsing");
194
195 if let Ok(body_string) = str::from_utf8(body) {
196 debug.field("body_string", &body_string);
197 }
198
199 debug.field("body", body).finish()
200 }
201 Self::RequestCanceled => f.write_str("RequestCanceled"),
202 Self::RequestError => f.write_str("RequestError"),
203 Self::RequestTimedOut => f.write_str("RequestTimedOut"),
204 #[cfg(not(target_os = "wasi"))]
205 Self::Response {
206 body,
207 error,
208 status,
209 } => {
210 let mut debug = f.debug_struct("Response");
211
212 if let Ok(body_string) = str::from_utf8(body) {
213 debug.field("body_string", &body_string);
214 }
215
216 debug
217 .field("body", body)
218 .field("error", error)
219 .field("status", status)
220 .finish()
221 }
222 Self::Unauthorized => f.write_str("Unauthorized"),
223 Self::Validation => f.write_str("Validation"),
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::ErrorType;
231 use crate::{
232 api_error::{ApiError, GeneralApiError},
233 response::StatusCode,
234 };
235
236 #[test]
238 fn parsing_variant_debug() {
239 let body = br#"{"message": "aaa"#.to_vec();
240
241 let error = ErrorType::Parsing { body };
242
243 assert_eq!(
244 "Parsing {
245 body_string: \"{\\\"message\\\": \\\"aaa\",
246 body: [
247 123,
248 34,
249 109,
250 101,
251 115,
252 115,
253 97,
254 103,
255 101,
256 34,
257 58,
258 32,
259 34,
260 97,
261 97,
262 97,
263 ],
264}",
265 format!("{error:#?}"),
266 );
267 }
268
269 #[test]
270 fn response_variant_debug() {
271 let body = br#"{"message": "aaa"}"#.to_vec();
272
273 let error = ErrorType::Response {
274 body,
275 error: ApiError::General(GeneralApiError {
276 code: 0,
277 message: "401: Unauthorized".to_owned(),
278 }),
279 status: StatusCode::new(401),
280 };
281
282 assert_eq!(
283 "Response {
284 body_string: \"{\\\"message\\\": \\\"aaa\\\"}\",
285 body: [
286 123,
287 34,
288 109,
289 101,
290 115,
291 115,
292 97,
293 103,
294 101,
295 34,
296 58,
297 32,
298 34,
299 97,
300 97,
301 97,
302 34,
303 125,
304 ],
305 error: General(
306 GeneralApiError {
307 code: 0,
308 message: \"401: Unauthorized\",
309 },
310 ),
311 status: StatusCode(
312 401,
313 ),
314}",
315 format!("{error:#?}"),
316 );
317 }
318}