use reqwest::header::HeaderMap;
use serde_json::Value;
#[derive(Debug, Clone, PartialEq)]
pub enum ErrorBody {
Json(Value),
Text(String),
}
impl ErrorBody {
pub fn as_json(&self) -> Option<&Value> {
match self {
ErrorBody::Json(v) => Some(v),
ErrorBody::Text(_) => None,
}
}
pub fn to_text(&self) -> String {
match self {
ErrorBody::Json(v) => v.to_string(),
ErrorBody::Text(s) => s.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct ApiError {
pub status: u16,
pub status_text: String,
pub body: ErrorBody,
pub headers: HeaderMap,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("truto API error: {} {}", .0.status, .0.status_text)]
Api(Box<ApiError>),
#[error("truto request timed out")]
Timeout,
#[error("truto transport error: {0}")]
Transport(#[source] reqwest::Error),
#[error("failed to decode truto response: {message}")]
Decode {
message: String,
#[source]
source: Option<serde_json::Error>,
},
#[error("invalid truto request: {0}")]
InvalidRequest(String),
}
impl Error {
pub fn status(&self) -> Option<u16> {
match self {
Error::Api(e) => Some(e.status),
Error::Timeout => Some(408),
_ => None,
}
}
pub fn api_error(&self) -> Option<&ApiError> {
match self {
Error::Api(e) => Some(e),
_ => None,
}
}
pub fn body(&self) -> Option<&ErrorBody> {
self.api_error().map(|e| &e.body)
}
pub fn is_not_found(&self) -> bool {
matches!(self, Error::Api(e) if e.status == 404)
}
pub fn is_rate_limited(&self) -> bool {
matches!(self, Error::Api(e) if e.status == 429)
}
pub fn is_unauthorized(&self) -> bool {
matches!(self, Error::Api(e) if e.status == 401)
}
pub fn is_timeout(&self) -> bool {
matches!(self, Error::Timeout)
}
pub(crate) fn decode(message: impl Into<String>, source: Option<serde_json::Error>) -> Self {
Error::Decode {
message: message.into(),
source,
}
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;