use std::{error::Error as StdError, fmt, time::Duration};
#[derive(Debug, serde::Deserialize)]
#[non_exhaustive]
pub struct ApiError {
#[serde(skip)]
pub status: u16,
#[serde(default)]
pub error: String,
#[serde(default)]
pub code: String,
pub doc_url: Option<String>,
#[serde(skip)]
pub retry_after: Option<Duration>,
#[serde(flatten)]
pub details: serde_json::Map<String, serde_json::Value>,
}
impl ApiError {
pub fn is_retryable(&self) -> bool {
crate::client::retryable_status(self.status)
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RoxyAPI {} {}: {}", self.status, self.code, self.error)
}
}
impl StdError for ApiError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Configuration(&'static str),
Http(reqwest::Error),
Api(Box<ApiError>),
#[non_exhaustive]
Decode {
status: u16,
body: Vec<u8>,
source: serde_json::Error,
},
Timeout,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Configuration(message) => write!(f, "RoxyAPI: {message}"),
Self::Http(_) => f.write_str("RoxyAPI transport error"),
Self::Api(error) => error.fmt(f),
Self::Decode { status, .. } => {
write!(f, "RoxyAPI {status} response could not be decoded")
}
Self::Timeout => f.write_str("RoxyAPI request deadline exceeded"),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Http(error) => Some(error),
Self::Decode { source, .. } => Some(source),
_ => None,
}
}
}
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Self::Http(error.without_url())
}
}
impl ApiError {
pub fn error_code(&self) -> crate::ErrorCode {
crate::ErrorCode::from(self.code.as_str())
}
}