use serde::Deserialize;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ShardError {
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("unauthorized: {0}")]
Unauthorized(String),
#[error("forbidden: {0}")]
Forbidden(String),
#[error("not found: {0}")]
NotFound(String),
#[error("insufficient funds: balance={balance}, available={available_balance}")]
InsufficientFunds {
balance: i64,
available_balance: i64,
limit: i64,
},
#[error("payment required")]
PaymentRequired,
#[error("service unavailable: {0}")]
ServiceUnavailable(String),
#[error("request timed out")]
RequestTimeout,
#[error("decode error: {0}")]
Decode(String),
#[error("network error: {0}")]
Network(String),
}
impl ShardError {
pub fn is_retryable(&self) -> bool {
matches!(
self,
ShardError::ServiceUnavailable(_) | ShardError::RequestTimeout | ShardError::Network(_)
)
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct GatewayErrorBody {
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub balance: Option<i64>,
#[serde(default)]
pub available_balance: Option<i64>,
#[serde(default)]
pub limit: Option<i64>,
}
impl GatewayErrorBody {
pub fn message_text(&self) -> String {
self.error
.clone()
.or_else(|| self.message.clone())
.unwrap_or_else(|| "unknown error".to_string())
}
}
pub(crate) fn from_status(status: u16, body: Option<GatewayErrorBody>) -> ShardError {
let text = body
.as_ref()
.map(|b| b.message_text())
.unwrap_or_else(|| format!("HTTP {status}"));
match status {
400 => ShardError::InvalidInput(text),
401 => ShardError::Unauthorized(text),
402 => ShardError::PaymentRequired,
403 => ShardError::Forbidden(text),
404 => ShardError::NotFound(text),
422 => {
let b = body.unwrap_or(GatewayErrorBody {
error: None,
message: None,
balance: None,
available_balance: None,
limit: None,
});
ShardError::InsufficientFunds {
balance: b.balance.unwrap_or(0),
available_balance: b.available_balance.unwrap_or(0),
limit: b.limit.unwrap_or(0),
}
}
503 | 504 => ShardError::ServiceUnavailable(text),
_ => ShardError::Decode(format!("unexpected HTTP {status}: {text}")),
}
}