use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use GazelleError::*;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[allow(clippy::absolute_paths)]
pub enum GazelleError {
Request(String),
Response(String),
Deserialization(String),
Upload(String),
BadRequest,
Unauthorized,
NotFound,
TooManyRequests,
Unexpected(u16, String),
Empty(u16),
}
#[allow(clippy::absolute_paths)]
impl GazelleError {
pub(crate) fn request(error: reqwest::Error) -> Self {
Request(error.to_string())
}
pub(crate) fn response(error: reqwest::Error) -> Self {
Response(error.to_string())
}
pub(crate) fn deserialization(error: serde_json::Error) -> Self {
Request(error.to_string())
}
pub(crate) fn upload(error: std::io::Error) -> Self {
Upload(error.to_string())
}
pub(crate) fn unexpected(status_code: StatusCode, error: String) -> Self {
Unexpected(status_code.as_u16(), error)
}
pub(crate) fn empty(status_code: StatusCode) -> Self {
Empty(status_code.as_u16())
}
pub(crate) fn match_client_error(status_code: StatusCode) -> Option<Self> {
match status_code {
StatusCode::BAD_REQUEST => Some(BadRequest),
StatusCode::UNAUTHORIZED => Some(Unauthorized),
StatusCode::NOT_FOUND => Some(NotFound),
StatusCode::TOO_MANY_REQUESTS => Some(TooManyRequests),
_ => None,
}
}
pub(crate) fn match_response_error(value: &str) -> Option<Self> {
match value {
"bad id parameter" | "bad parameters" => Some(BadRequest),
"This page is limited to API key usage only." | "This page requires an api token" => {
Some(Unauthorized)
}
"endpoint not found" | "failure" => Some(NotFound),
"Rate limit exceeded" => Some(TooManyRequests),
_ => None,
}
}
}
impl Display for GazelleError {
#[allow(clippy::absolute_paths)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let message = match self {
Request(message) => format!("Failed to send API request: {message}"),
Response(message) => {
format!("Failed to read API response: {message}")
}
Deserialization(message) => {
format!("Failed to deserialize API response: {message}")
}
Upload(message) => {
format!("Failed to upload torrent file: {message}")
}
BadRequest => "Invalid parameters".to_owned(),
Unauthorized => "Invalid API key".to_owned(),
NotFound => "Resource does not exist".to_owned(),
TooManyRequests => "Exceeded rate limit".to_owned(),
Unexpected(code, message) => {
format!(
"Unexpected API response ({}): {message}",
status_code_and_reason(*code)
)
}
Empty(code) => format!(
"Unexpected API response without error message ({})",
status_code_and_reason(*code)
),
};
message.fmt(formatter)
}
}
fn status_code_and_reason(code: u16) -> String {
StatusCode::from_u16(code)
.ok()
.and_then(|code| code.canonical_reason())
.map(|reason| format!("{code} {reason}"))
.unwrap_or(code.to_string())
}