1use std::result::Result as StdResult;
4use std::error::Error as StdError;
5
6use serde::{self, Serialize, Deserialize};
7use serde_json::{self, Error as SerdeError, Value as JsonValue};
8use url::ParseError as UrlError;
9use reqwest::blocking::Response;
10use reqwest::{
11 Error as ReqwestError, StatusCode, Response as AResponse,
12 header::{InvalidHeaderValue, HeaderMap}
13};
14use std::fmt::{Formatter, Display};
15use crate::util::JsonMap;
16
17
18pub type Result<T> = StdResult<T, Error>;
20
21#[non_exhaustive]
24#[derive(Debug)]
25pub enum Error {
26 Json(SerdeError),
29
30 Url(UrlError),
32
33 Authorization(InvalidHeaderValue),
39
40 Request(ReqwestError),
43
44 Ratelimited {
46 limit: Option<usize>,
48
49 remaining: Option<usize>,
51
52 time_until_reset: Option<String>,
56 },
57
58Status(StatusCode, Option<APIError>, Option<JsonValue>),
72
73 FetchFrom(String),
81
82 #[cfg(feature = "chrono")]
87 ParseTimeLike {
88 reason: String,
90
91 offender: Option<String>,
93
94 original_err: Option<::chrono::ParseError>,
99 },
100}
101
102#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
104pub struct APIError {
105 #[serde(default)]
107 pub reason: String,
108
109 #[serde(default)]
111 pub message: Option<String>,
112
113 #[serde(default)]
115 #[serde(rename = "type")]
116 pub err_type: Option<String>,
117
118 #[serde(default)]
120 pub detail: Option<JsonMap>,
121}
122
123impl Default for APIError {
124 fn default() -> APIError {
125 APIError {
126 reason: String::from(""),
127 message: None,
128 err_type: None,
129 detail: None,
130 }
131 }
132}
133
134impl From<SerdeError> for Error {
135 fn from(err: SerdeError) -> Error {
136 Error::Json(err)
137 }
138}
139
140impl From<ReqwestError> for Error {
141 fn from(err: ReqwestError) -> Error {
142 Error::Request(err)
143 }
144}
145
146impl From<UrlError> for Error {
147 fn from(err: UrlError) -> Error {
148 Error::Url(err)
149 }
150}
151
152#[cfg(feature = "chrono")]
153impl From<::chrono::ParseError> for Error {
154 fn from(err: ::chrono::ParseError) -> Error {
155 Error::ParseTimeLike {
156 reason: err.to_string(),
157 offender: None,
158 original_err: Some(err),
159 }
160 }
161}
162
163
164impl Display for Error {
165 fn fmt(&self, f: &mut Formatter<'_>) -> ::std::fmt::Result {
166 match *self {
167 Error::Json(ref e) => e.fmt(f),
168 Error::Request(ref e) => e.fmt(f),
169 _ => f.write_str(&*self.description()),
170 }
172 }
173}
174
175
176
177impl StdError for Error {
178
179 fn source(&self) -> Option<&(dyn StdError + 'static)> {
180 match *self {
181 Error::Json(ref e) => Some(e),
182 Error::Url(ref e) => Some(e),
183 Error::Request(ref e) => Some(e),
184
185 #[cfg(feature = "chrono")]
186 Error::ParseTimeLike { ref original_err, .. } => match original_err {
187 Some(ref e) => Some(e),
188 None => None,
189 },
190
191 _ => None,
192 }
193 }
194}
195
196impl Error {
197
198 fn description(&self) -> String {
199 match *self {
200 Error::Json(ref e) => String::from(e.description()),
201
202 Error::Authorization(_) => String::from(
203 "Auth key was provided in an invalid format for a header."
204 ),
205
206 Error::Url(_) => String::from("Invalid URL was given/built."),
207
208 Error::Ratelimited { limit, ref time_until_reset, .. } => {
209 let lim_part = match limit {
210 Some(lim) => format!(" Limit of {} requests/min.", lim),
211 None => String::from(""),
212 };
213
214 let time_part = match *time_until_reset {
215 Some(ref timeur) => format!(" Resets at timestamp {}.", timeur),
216 None => String::from(""),
217 };
218
219 let dot = {
220 if limit.is_none() && time_until_reset.is_none() {
221 "."
222 } else {
223 ":"
224 }
225 };
226
227 format!("Ratelimited{}{}{}", dot, lim_part, time_part)
228 },
229
230 Error::Request(ref e) => String::from(e.description()),
231
232Error::Status(ref status, _, _) => String::from(
235 status.canonical_reason().unwrap_or(
236 "Unknown HTTP status code error received"
237 )
238 ),
239
240 Error::FetchFrom(ref string) => string.clone(),
241
242 #[cfg(feature = "chrono")]
243 Error::ParseTimeLike {
244 ref reason,
245 ..
246 } => reason.clone(),
247 }
248 }
249
250 #[doc(hidden)]
253 pub(crate) fn from_response(response: Response, value: Option<JsonValue>) -> Error {
254 let status = response.status();
255
256 let headers: &HeaderMap = response.headers();
257 let headers = headers.clone();
258
259 let value: Option<JsonValue> = match value {
260 Some(val) => Some(val),
261 None => serde_json::from_reader(response).ok()
262 };
263
264 let reset_header = headers.get("x-ratelimit-reset");
265 if let Some(reset_header) = reset_header { let reset_header = reset_header.to_str();
267 if let Ok(reset) = reset_header {
268 return Error::Ratelimited {
269 limit: match headers.get("x-ratelimit-limit") {
270 Some(lim_header) => lim_header.to_str().ok().and_then(
271 |s| { s.parse().ok() }
272 ),
273 None => None,
274 },
275
276 remaining: match headers.get("x-ratelimit-remaining") {
277 Some(rem_header) => rem_header.to_str().ok().and_then(
278 |s| { s.parse().ok() }
279 ),
280 None => None,
281 },
282
283 time_until_reset: Some(String::from(reset)),
284 }
285 }
286 }
287
288 let api_error: Option<APIError> = match value {
289 Some(ref val) => serde_json::from_value(val.clone()).ok(),
290 None => None,
291 };
292
293 Error::Status(status, api_error, value)
294 }
295
296 #[doc(hidden)]
299 #[cfg(feature = "async")]
300 pub(crate) async fn a_from_response(response: AResponse, value: Option<JsonValue>) -> Error {
301 let status = response.status();
302 let headers: &HeaderMap = response.headers();
303 let headers = headers.clone();
304
305 let value: Option<JsonValue> = match value {
306 Some(val) => Some(val),
307 None => response.json().await.ok()
308 };
309
310 let reset_header = headers.get("x-ratelimit-reset");
311 if let Some(reset_header) = reset_header { let reset_header = reset_header.to_str();
313 if let Ok(reset) = reset_header {
314 return Error::Ratelimited {
315 limit: match headers.get("x-ratelimit-limit") {
316 Some(lim_header) => lim_header.to_str().ok().and_then(
317 |s| { s.parse().ok() }
318 ),
319 None => None,
320 },
321
322 remaining: match headers.get("x-ratelimit-remaining") {
323 Some(rem_header) => rem_header.to_str().ok().and_then(
324 |s| { s.parse().ok() }
325 ),
326 None => None,
327 },
328
329 time_until_reset: Some(String::from(reset)),
330 }
331 }
332 }
333
334 let api_error: Option<APIError> = match value {
335 Some(ref val) => serde_json::from_value(val.clone()).ok(),
336 None => None,
337 };
338
339 Error::Status(status, api_error, value)
340 }
341
342
343}