use std::result::Result as StdResult;
use std::error::Error as StdError;
use serde::{self, Serialize, Deserialize};
use serde_json::{self, Error as SerdeError, Value as JsonValue};
use url::ParseError as UrlError;
use reqwest::blocking::Response;
use reqwest::{
Error as ReqwestError, StatusCode, Response as AResponse,
header::{InvalidHeaderValue, HeaderMap}
};
use std::fmt::{Formatter, Display};
use crate::util::JsonMap;
pub type Result<T> = StdResult<T, Error>;
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
Json(SerdeError),
Url(UrlError),
Authorization(InvalidHeaderValue),
Request(ReqwestError),
Ratelimited {
limit: Option<usize>,
remaining: Option<usize>,
time_until_reset: Option<String>,
},
Status(StatusCode, Option<APIError>, Option<JsonValue>),
FetchFrom(String),
#[cfg(feature = "chrono")]
ParseTimeLike {
reason: String,
offender: Option<String>,
original_err: Option<::chrono::ParseError>,
},
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct APIError {
#[serde(default)]
pub reason: String,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
#[serde(rename = "type")]
pub err_type: Option<String>,
#[serde(default)]
pub detail: Option<JsonMap>,
}
impl Default for APIError {
fn default() -> APIError {
APIError {
reason: String::from(""),
message: None,
err_type: None,
detail: None,
}
}
}
impl From<SerdeError> for Error {
fn from(err: SerdeError) -> Error {
Error::Json(err)
}
}
impl From<ReqwestError> for Error {
fn from(err: ReqwestError) -> Error {
Error::Request(err)
}
}
impl From<UrlError> for Error {
fn from(err: UrlError) -> Error {
Error::Url(err)
}
}
#[cfg(feature = "chrono")]
impl From<::chrono::ParseError> for Error {
fn from(err: ::chrono::ParseError) -> Error {
Error::ParseTimeLike {
reason: err.to_string(),
offender: None,
original_err: Some(err),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> ::std::fmt::Result {
match *self {
Error::Json(ref e) => e.fmt(f),
Error::Request(ref e) => e.fmt(f),
_ => f.write_str(&*self.description()),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Error::Json(ref e) => Some(e),
Error::Url(ref e) => Some(e),
Error::Request(ref e) => Some(e),
#[cfg(feature = "chrono")]
Error::ParseTimeLike { ref original_err, .. } => match original_err {
Some(ref e) => Some(e),
None => None,
},
_ => None,
}
}
}
impl Error {
fn description(&self) -> String {
match *self {
Error::Json(ref e) => String::from(e.description()),
Error::Authorization(_) => String::from(
"Auth key was provided in an invalid format for a header."
),
Error::Url(_) => String::from("Invalid URL was given/built."),
Error::Ratelimited { limit, ref time_until_reset, .. } => {
let lim_part = match limit {
Some(lim) => format!(" Limit of {} requests/min.", lim),
None => String::from(""),
};
let time_part = match *time_until_reset {
Some(ref timeur) => format!(" Resets at timestamp {}.", timeur),
None => String::from(""),
};
let dot = {
if limit.is_none() && time_until_reset.is_none() {
"."
} else {
":"
}
};
format!("Ratelimited{}{}{}", dot, lim_part, time_part)
},
Error::Request(ref e) => String::from(e.description()),
Error::Status(ref status, _, _) => String::from(
status.canonical_reason().unwrap_or(
"Unknown HTTP status code error received"
)
),
Error::FetchFrom(ref string) => string.clone(),
#[cfg(feature = "chrono")]
Error::ParseTimeLike {
ref reason,
..
} => reason.clone(),
}
}
#[doc(hidden)]
pub(crate) fn from_response(response: Response, value: Option<JsonValue>) -> Error {
let status = response.status();
let headers: &HeaderMap = response.headers();
let headers = headers.clone();
let value: Option<JsonValue> = match value {
Some(val) => Some(val),
None => serde_json::from_reader(response).ok()
};
let reset_header = headers.get("x-ratelimit-reset");
if let Some(reset_header) = reset_header {
let reset_header = reset_header.to_str();
if let Ok(reset) = reset_header {
return Error::Ratelimited {
limit: match headers.get("x-ratelimit-limit") {
Some(lim_header) => lim_header.to_str().ok().and_then(
|s| { s.parse().ok() }
),
None => None,
},
remaining: match headers.get("x-ratelimit-remaining") {
Some(rem_header) => rem_header.to_str().ok().and_then(
|s| { s.parse().ok() }
),
None => None,
},
time_until_reset: Some(String::from(reset)),
}
}
}
let api_error: Option<APIError> = match value {
Some(ref val) => serde_json::from_value(val.clone()).ok(),
None => None,
};
Error::Status(status, api_error, value)
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub(crate) async fn a_from_response(response: AResponse, value: Option<JsonValue>) -> Error {
let status = response.status();
let headers: &HeaderMap = response.headers();
let headers = headers.clone();
let value: Option<JsonValue> = match value {
Some(val) => Some(val),
None => response.json().await.ok()
};
let reset_header = headers.get("x-ratelimit-reset");
if let Some(reset_header) = reset_header {
let reset_header = reset_header.to_str();
if let Ok(reset) = reset_header {
return Error::Ratelimited {
limit: match headers.get("x-ratelimit-limit") {
Some(lim_header) => lim_header.to_str().ok().and_then(
|s| { s.parse().ok() }
),
None => None,
},
remaining: match headers.get("x-ratelimit-remaining") {
Some(rem_header) => rem_header.to_str().ok().and_then(
|s| { s.parse().ok() }
),
None => None,
},
time_until_reset: Some(String::from(reset)),
}
}
}
let api_error: Option<APIError> = match value {
Some(ref val) => serde_json::from_value(val.clone()).ok(),
None => None,
};
Error::Status(status, api_error, value)
}
}