use std::fmt;
#[derive(Debug)]
pub enum ScaniiError {
Config(String),
Auth {
message: String,
request_id: Option<String>,
},
RateLimit {
retry_after: Option<u64>,
message: String,
request_id: Option<String>,
},
Http {
status: u16,
message: String,
request_id: Option<String>,
},
Transport(String),
Serde(String),
Io(std::io::Error),
}
impl fmt::Display for ScaniiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScaniiError::Config(m) => write!(f, "configuration error: {m}"),
ScaniiError::Auth { message, .. } => write!(f, "authentication failed: {message}"),
ScaniiError::RateLimit {
retry_after,
message,
..
} => match retry_after {
Some(s) => write!(f, "rate limited (retry after {s}s): {message}"),
None => write!(f, "rate limited: {message}"),
},
ScaniiError::Http {
status, message, ..
} => write!(f, "HTTP {status}: {message}"),
ScaniiError::Transport(m) => write!(f, "transport error: {m}"),
ScaniiError::Serde(m) => write!(f, "deserialization error: {m}"),
ScaniiError::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for ScaniiError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ScaniiError::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for ScaniiError {
fn from(e: std::io::Error) -> Self {
ScaniiError::Io(e)
}
}
impl From<serde_json::Error> for ScaniiError {
fn from(e: serde_json::Error) -> Self {
ScaniiError::Serde(e.to_string())
}
}
impl From<ureq::Error> for ScaniiError {
fn from(e: ureq::Error) -> Self {
match e {
ureq::Error::Status(status, response) => {
let request_id = response.header("x-scanii-request-id").map(|s| s.to_owned());
let retry_after = response
.header("retry-after")
.and_then(|s| s.parse::<u64>().ok());
let body = response.into_string().unwrap_or_default();
let message =
parse_error_message(&body).unwrap_or_else(|| format!("HTTP {status}"));
match status {
401 | 403 => ScaniiError::Auth {
message,
request_id,
},
429 => ScaniiError::RateLimit {
retry_after,
message,
request_id,
},
_ => ScaniiError::Http {
status,
message,
request_id,
},
}
}
ureq::Error::Transport(t) => ScaniiError::Transport(t.to_string()),
}
}
}
fn parse_error_message(body: &str) -> Option<String> {
if body.is_empty() {
return None;
}
if let Ok(serde_json::Value::Object(map)) = serde_json::from_str::<serde_json::Value>(body) {
if let Some(serde_json::Value::String(err)) = map.get("error") {
return Some(err.clone());
}
}
Some(body.to_owned())
}