use serde::Deserialize;
use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Api(ApiError),
Http(reqwest::Error),
Json(serde_json::Error),
WebSocket(tokio_tungstenite::tungstenite::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Api(e) => write!(f, "{e}"),
Error::Http(e) => write!(f, "qai: http error: {e}"),
Error::Json(e) => write!(f, "qai: json error: {e}"),
Error::WebSocket(e) => write!(f, "qai: websocket error: {e}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Api(_) => None,
Error::Http(e) => Some(e),
Error::Json(e) => Some(e),
Error::WebSocket(e) => Some(e),
}
}
}
impl From<tokio_tungstenite::tungstenite::Error> for Error {
fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
Error::WebSocket(err)
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::Http(err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Json(err)
}
}
#[derive(Debug, Clone)]
pub struct ApiError {
pub status_code: u16,
pub code: String,
pub message: String,
pub request_id: String,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.request_id.is_empty() {
write!(
f,
"qai: {} {}: {}",
self.status_code, self.code, self.message
)
} else {
write!(
f,
"qai: {} {}: {} (request_id={})",
self.status_code, self.code, self.message, self.request_id
)
}
}
}
impl std::error::Error for ApiError {}
impl ApiError {
pub fn is_rate_limit(&self) -> bool {
self.status_code == 429
}
pub fn is_auth(&self) -> bool {
self.status_code == 401 || self.status_code == 403
}
pub fn is_not_found(&self) -> bool {
self.status_code == 404
}
}
pub fn is_rate_limit_error(err: &Error) -> bool {
matches!(err, Error::Api(e) if e.is_rate_limit())
}
pub fn is_auth_error(err: &Error) -> bool {
matches!(err, Error::Api(e) if e.is_auth())
}
pub fn is_not_found_error(err: &Error) -> bool {
matches!(err, Error::Api(e) if e.is_not_found())
}
#[derive(Deserialize)]
pub(crate) struct ApiErrorBody {
pub error: ApiErrorInner,
}
#[derive(Deserialize)]
pub(crate) struct ApiErrorInner {
#[serde(default)]
pub message: String,
#[serde(default)]
pub code: String,
#[serde(rename = "type", default)]
pub error_type: String,
}