use serde::Deserialize;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApiError {
pub status_code: Option<u16>,
pub name: String,
pub message: String,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.status_code {
Some(status) => write!(f, "{} ({}): {}", self.name, status, self.message),
None => write!(f, "{}: {}", self.name, self.message),
}
}
}
#[derive(Deserialize)]
struct RawApiError {
#[serde(rename = "statusCode")]
status_code: Option<u16>,
name: Option<String>,
message: Option<String>,
}
impl ApiError {
pub(crate) fn parse(status: u16, body: &[u8]) -> Self {
if let Ok(raw) = serde_json::from_slice::<RawApiError>(body) {
return ApiError {
status_code: raw.status_code.or(Some(status)),
name: raw.name.unwrap_or_else(|| "application_error".to_string()),
message: raw
.message
.unwrap_or_else(|| format!("Request failed with status {status}")),
};
}
ApiError {
status_code: Some(status),
name: "application_error".to_string(),
message: format!("Request failed with status {status}"),
}
}
}
#[derive(Debug)]
pub enum Error {
Http(reqwest::Error),
Api(ApiError),
Parse(serde_json::Error),
}
impl Error {
pub fn status_code(&self) -> Option<u16> {
match self {
Error::Api(err) => err.status_code,
_ => None,
}
}
pub fn name(&self) -> Option<&str> {
match self {
Error::Api(err) => Some(&err.name),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Http(err) => write!(f, "http error: {err}"),
Error::Api(err) => write!(f, "api error: {err}"),
Error::Parse(err) => write!(f, "parse error: {err}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Http(err) => Some(err),
Error::Parse(err) => Some(err),
Error::Api(_) => None,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;