use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct RawVTError {
error: InnerVTError,
}
#[allow(dead_code)]
#[derive(Deserialize)]
struct InnerVTError {
code: VirusTotalError,
message: String,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum VirusTotalError {
#[serde(alias = "badrequesterror")]
BadRequestError,
#[serde(alias = "invalidargumenterror")]
InvalidArgumentError,
#[serde(alias = "notavailableyet")]
NotAvailableYet,
#[serde(alias = "unselectivecontentqueryerror")]
UnselectiveContentQueryError,
#[serde(alias = "unsupportedcontentqueryerror")]
UnsupportedContentQueryError,
#[serde(alias = "authenticationrequirederror")]
AuthenticationRequiredError,
#[serde(alias = "usernotactiveerror")]
UserNotActiveError,
#[serde(alias = "wrongcredentialserror")]
WrongCredentialsError,
#[serde(alias = "forbiddenerror")]
ForbiddenError,
#[serde(alias = "notfounderror")]
NotFoundError,
#[serde(alias = "alreadyexistserror")]
AlreadyExistsError,
#[serde(alias = "faileddependencyerror")]
FailedDependencyError,
#[serde(alias = "quotaexceedederror")]
QuotaExceededError,
#[serde(alias = "toomanyrequestserror")]
TooManyRequestsError,
#[serde(alias = "transienterror")]
TransientError,
#[serde(alias = "deadlineexceedederror")]
DeadlineExceededError,
NoURLReturned,
JsonError(String),
UTF8Error(Vec<u8>),
NetworkError(String),
IOError(String),
NonPaginatedResults,
UnknownError,
}
impl VirusTotalError {
#[inline]
pub(crate) fn parse_json<'a, T: Deserialize<'a>>(data: &'a str) -> Result<T, VirusTotalError> {
let result: serde_json::error::Result<T> = serde_json::from_str(data);
if let Ok(item) = result {
return Ok(item);
}
match serde_json::from_str::<RawVTError>(data) {
Ok(item) => Err(item.error.code),
Err(_e) => Err(VirusTotalError::JsonError(data.to_string())),
}
}
#[inline]
#[must_use]
pub fn message(&self) -> &'static str {
match self {
VirusTotalError::BadRequestError => "The API request is invalid or malformed.",
VirusTotalError::InvalidArgumentError => "Some of the provided arguments are incorrect.",
VirusTotalError::NotAvailableYet => "The resource is not available yet, but will become available later.",
VirusTotalError::UnselectiveContentQueryError => "Content search query is not selective enough.",
VirusTotalError::UnsupportedContentQueryError => "Unsupported content search query.",
VirusTotalError::AuthenticationRequiredError => "The operation requires an authenticated user. Verify that you have provided your API key.",
VirusTotalError::UserNotActiveError => "The user account is not active. Make sure you properly activated your account by following the link sent to your email.",
VirusTotalError::WrongCredentialsError => "The provided API key is incorrect.",
VirusTotalError::ForbiddenError => "You are not allowed to perform the requested operation.",
VirusTotalError::NotFoundError => "The requested resource was not found.",
VirusTotalError::AlreadyExistsError => "The resource already exists.",
VirusTotalError::FailedDependencyError => "The request depended on another request and that request failed.",
VirusTotalError::QuotaExceededError => "You have exceeded one of your quotas (minute, daily or monthly). Daily quotas are reset every day at 00:00 UTC. You may have run out of disk space and/or number of files on your VirusTotal Monitor account",
VirusTotalError::TooManyRequestsError => "Too many requests.",
VirusTotalError::TransientError => "Transient server error. Retry might work.",
VirusTotalError::DeadlineExceededError => "The operation took too long to complete.",
VirusTotalError::NoURLReturned => "If the custom upload endpoint URL request fails",
VirusTotalError::JsonError(_) => "Json decoding error",
VirusTotalError::UTF8Error(_) => "String UTF-8 decoding error",
VirusTotalError::NetworkError(_) => "Network error",
VirusTotalError::IOError(_) => "Error opening a file for submitting to VirusTotal",
VirusTotalError::NonPaginatedResults => "A search query didn't have an offset",
VirusTotalError::UnknownError => "Some other unknown or unforeseen error occurred",
}
}
#[must_use]
#[inline]
pub fn inner_string(&self) -> Option<String> {
match self {
VirusTotalError::IOError(s)
| VirusTotalError::JsonError(s)
| VirusTotalError::NetworkError(s) => Some(s.clone()),
_ => None,
}
}
}
impl std::fmt::Display for VirusTotalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message())
}
}
impl From<reqwest::Error> for VirusTotalError {
fn from(err: reqwest::Error) -> Self {
let url = if let Some(url) = err.url() {
format!(" loading {url}")
} else {
String::new()
};
VirusTotalError::NetworkError(format!("Http error{url}: {err}"))
}
}
impl From<reqwest::StatusCode> for VirusTotalError {
fn from(status: reqwest::StatusCode) -> Self {
match status {
reqwest::StatusCode::BAD_REQUEST => VirusTotalError::BadRequestError,
reqwest::StatusCode::UNAUTHORIZED => VirusTotalError::AuthenticationRequiredError,
reqwest::StatusCode::FORBIDDEN => VirusTotalError::ForbiddenError,
reqwest::StatusCode::NOT_FOUND => VirusTotalError::NotFoundError,
reqwest::StatusCode::CONFLICT => VirusTotalError::AlreadyExistsError,
reqwest::StatusCode::TOO_MANY_REQUESTS => VirusTotalError::TooManyRequestsError,
reqwest::StatusCode::INTERNAL_SERVER_ERROR => VirusTotalError::TransientError,
_ => VirusTotalError::UnknownError,
}
}
}
impl std::error::Error for VirusTotalError {}