use serde::Deserialize;
pub type Result<T> = std::result::Result<T, ApiError>;
#[derive(Debug, thiserror::Error)]
pub enum ApiError {
#[error("HTTP {status}: {message}")]
Http {
status: u16,
body: String,
code: Option<String>,
message: String,
request_id: Option<String>,
},
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
Url(#[from] url::ParseError),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error("environment variable error: {0}")]
EnvVar(String),
}
impl ApiError {
pub fn status(&self) -> Option<u16> {
match self {
ApiError::Http { status, .. } => Some(*status),
ApiError::Reqwest(e) => e.status().map(|s| s.as_u16()),
_ => None,
}
}
pub fn is_retryable(&self) -> bool {
match self {
ApiError::Http { status, .. } => {
matches!(*status, 408 | 429) || (*status >= 500 && *status < 600)
}
ApiError::Reqwest(e) => e.is_timeout() || e.is_connect() || e.is_request(),
_ => false,
}
}
}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct ErrorEnvelope {
#[serde(default, alias = "error_code", alias = "errorCode")]
pub code: Option<String>,
#[serde(default, alias = "msg", alias = "error", alias = "error_message")]
pub message: Option<String>,
#[serde(default, alias = "requestId", alias = "request-id")]
pub request_id: Option<String>,
}
impl ErrorEnvelope {
pub fn parse(body: &str) -> Self {
serde_json::from_str(body).unwrap_or_default()
}
}