use thiserror::Error;
#[derive(Error, Debug)]
pub enum RelayError {
#[error("API error ({code}): {message}")]
Api {
code: String,
message: String,
status: u16,
},
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("URL error: {0}")]
Url(#[from] url::ParseError),
#[error("WebSocket error: {0}")]
WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
#[error("Invalid response: {0}")]
InvalidResponse(String),
#[error("WebSocket not connected. Call connect() first.")]
NotConnected,
}
impl RelayError {
pub fn api(code: impl Into<String>, message: impl Into<String>, status: u16) -> Self {
Self::Api {
code: code.into(),
message: message.into(),
status,
}
}
pub fn is_retryable(&self) -> bool {
match self {
Self::Api { status, .. } => *status >= 500 && *status <= 599,
Self::Http(e) => e.is_connect() || e.is_timeout(),
_ => false,
}
}
pub fn is_rate_limited(&self) -> bool {
matches!(self, Self::Api { status: 429, .. })
}
pub fn is_not_found(&self) -> bool {
matches!(self, Self::Api { status: 404, .. })
}
pub fn is_auth_rejection(&self) -> bool {
matches!(
self,
Self::Api {
status: 401 | 403,
..
}
)
}
pub fn is_conflict(&self) -> bool {
matches!(self, Self::Api { status: 409, .. })
}
pub fn status(&self) -> Option<u16> {
match self {
Self::Api { status, .. } => Some(*status),
_ => None,
}
}
pub fn code(&self) -> Option<&str> {
match self {
Self::Api { code, .. } => Some(code),
_ => None,
}
}
}
impl From<tokio_tungstenite::tungstenite::Error> for RelayError {
fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
Self::WebSocket(Box::new(err))
}
}
pub type Result<T> = std::result::Result<T, RelayError>;