use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde_json::Value;
use super::types::{Json, ResponseData};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ErrorKind {
Api,
Network,
Timeout,
Validation,
Authentication,
InsufficientBalance,
Permission,
NotFound,
RateLimit,
Server,
}
impl ErrorKind {
pub const fn is_network(self) -> bool {
matches!(self, ErrorKind::Network | ErrorKind::Timeout)
}
pub const fn as_str(self) -> &'static str {
match self {
ErrorKind::Api => "api",
ErrorKind::Network => "network",
ErrorKind::Timeout => "timeout",
ErrorKind::Validation => "validation",
ErrorKind::Authentication => "authentication",
ErrorKind::InsufficientBalance => "insufficient_balance",
ErrorKind::Permission => "permission",
ErrorKind::NotFound => "not_found",
ErrorKind::RateLimit => "rate_limit",
ErrorKind::Server => "server",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone)]
pub struct Error {
pub kind: ErrorKind,
pub message: String,
pub status: Option<u16>,
pub code: Option<String>,
pub response: Option<Json>,
pub retry_after: Option<Duration>,
source: Option<Arc<dyn StdError + Send + Sync>>,
}
impl Error {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
status: None,
code: None,
response: None,
retry_after: None,
source: None,
}
}
pub fn network(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Network, message)
}
pub fn timeout(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Timeout, message)
}
pub fn validation(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Validation, message)
}
pub fn authentication(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Authentication, message)
}
pub fn with_source(mut self, source: impl StdError + Send + Sync + 'static) -> Self {
self.source = Some(Arc::new(source));
self
}
pub fn with_response(mut self, response: Json) -> Self {
self.response = Some(response);
self
}
pub const fn kind(&self) -> ErrorKind {
self.kind
}
pub fn is_api(&self) -> bool {
self.kind == ErrorKind::Api
}
pub fn is_network(&self) -> bool {
self.kind.is_network()
}
pub fn is_timeout(&self) -> bool {
self.kind == ErrorKind::Timeout
}
pub fn is_validation(&self) -> bool {
self.kind == ErrorKind::Validation
}
pub fn is_authentication(&self) -> bool {
self.kind == ErrorKind::Authentication
}
pub fn is_insufficient_balance(&self) -> bool {
self.kind == ErrorKind::InsufficientBalance
}
pub fn is_permission(&self) -> bool {
self.kind == ErrorKind::Permission
}
pub fn is_not_found(&self) -> bool {
self.kind == ErrorKind::NotFound
}
pub fn is_rate_limit(&self) -> bool {
self.kind == ErrorKind::RateLimit
}
pub fn is_server(&self) -> bool {
self.kind == ErrorKind::Server
}
pub fn from_api(status: u16, data: &ResponseData, headers: &HashMap<String, String>) -> Self {
let body = data.to_value();
Self {
kind: kind_for_status(status),
message: extract_message(status, &body),
status: Some(status),
code: extract_code(&body),
response: Some(body),
retry_after: if kind_for_status(status) == ErrorKind::RateLimit {
parse_retry_after(headers, SystemTime::now())
} else {
None
},
source: None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)?;
if let Some(status) = self.status {
write!(f, " (HTTP {status})")?;
}
if let Some(code) = &self.code {
write!(f, " [{code}]")?;
}
Ok(())
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Error")
.field("kind", &self.kind)
.field("message", &self.message)
.field("status", &self.status)
.field("code", &self.code)
.field("retry_after", &self.retry_after)
.field("response", &self.response)
.finish()
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source
.as_ref()
.map(|source| &**source as &(dyn StdError + 'static))
}
}
fn kind_for_status(status: u16) -> ErrorKind {
match status {
400 | 422 => ErrorKind::Validation,
401 => ErrorKind::Authentication,
402 => ErrorKind::InsufficientBalance,
403 => ErrorKind::Permission,
404 | 410 => ErrorKind::NotFound,
429 => ErrorKind::RateLimit,
status if status >= 500 => ErrorKind::Server,
_ => ErrorKind::Api,
}
}
fn extract_message(status: u16, body: &Value) -> String {
for key in ["message", "error"] {
if let Some(message) = body.get(key).and_then(Value::as_str) {
if !message.is_empty() {
return message.to_string();
}
}
}
format!("A API respondeu com HTTP {status}.")
}
fn extract_code(body: &Value) -> Option<String> {
Some(body.get("code")?.as_str()?.to_string())
}
pub fn parse_retry_after(headers: &HashMap<String, String>, now: SystemTime) -> Option<Duration> {
let raw = header(headers, "retry-after")?.trim();
if raw.is_empty() {
return None;
}
if let Ok(seconds) = raw.parse::<f64>() {
if seconds <= 0.0 {
return None;
}
return Some(Duration::from_secs_f64(seconds));
}
let at = parse_http_date(raw)?;
let now = now.duration_since(UNIX_EPOCH).ok()?.as_secs() as i64;
let delta = at - now;
if delta > 0 {
Some(Duration::from_secs(delta as u64))
} else {
None
}
}
pub fn header<'a>(headers: &'a HashMap<String, String>, name: &str) -> Option<&'a str> {
headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
fn parse_http_date(raw: &str) -> Option<i64> {
let raw = raw.split_once(", ").map(|(_, rest)| rest).unwrap_or(raw);
let mut parts = raw.split_whitespace();
let day: i64 = parts.next()?.parse().ok()?;
let month = match parts.next()? {
"Jan" => 1,
"Feb" => 2,
"Mar" => 3,
"Apr" => 4,
"May" => 5,
"Jun" => 6,
"Jul" => 7,
"Aug" => 8,
"Sep" => 9,
"Oct" => 10,
"Nov" => 11,
"Dec" => 12,
_ => return None,
};
let year: i64 = parts.next()?.parse().ok()?;
let mut clock = parts.next()?.split(':');
let hour: i64 = clock.next()?.parse().ok()?;
let minute: i64 = clock.next()?.parse().ok()?;
let second: i64 = clock.next()?.parse().ok()?;
Some(days_from_civil(year, month, day) * 86_400 + hour * 3_600 + minute * 60 + second)
}
fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
let year = if month <= 2 { year - 1 } else { year };
let era = if year >= 0 { year } else { year - 399 } / 400;
let year_of_era = year - era * 400;
let day_of_year = (153 * (if month > 2 { month - 3 } else { month + 9 }) + 2) / 5 + day - 1;
let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
era * 146_097 + day_of_era - 719_468
}