use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("authentication failed: {message}")]
Authentication { message: String, body: String },
#[error("plan access denied: {message}")]
PlanAccess { message: String, body: String },
#[error("not found: {message}")]
NotFound { message: String, body: String },
#[error("rate limited: {message}")]
RateLimit { message: String, body: String },
#[error("api error (HTTP {status}): {message}")]
Api {
status: u16,
message: String,
body: String,
},
#[error("transport error: {0}")]
Transport(#[from] reqwest::Error),
#[error("decode error: {0}")]
Decode(#[from] serde_json::Error),
}
impl Error {
pub fn status(&self) -> Option<u16> {
match self {
Error::Authentication { .. } => Some(401),
Error::PlanAccess { .. } => Some(403),
Error::NotFound { .. } => Some(404),
Error::RateLimit { .. } => Some(429),
Error::Api { status, .. } => Some(*status),
_ => None,
}
}
}
pub(crate) fn from_response(status: u16, body: String) -> Error {
let message = extract_message(&body).unwrap_or_else(|| format!("HTTP {status}"));
match status {
401 => Error::Authentication { message, body },
403 => Error::PlanAccess { message, body },
404 => Error::NotFound { message, body },
429 => Error::RateLimit { message, body },
_ => Error::Api { status, message, body },
}
}
fn extract_message(body: &str) -> Option<String> {
#[derive(serde::Deserialize)]
struct Err {
error: Option<String>,
}
serde_json::from_str::<Err>(body).ok().and_then(|e| e.error)
}