use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::capability::Capability;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)]
#[error("{kind}: {message}")]
pub struct BackendError {
pub kind: BackendErrorKind,
pub message: String,
pub retryable: bool,
}
impl BackendError {
#[must_use]
pub fn new(kind: BackendErrorKind, message: impl Into<String>) -> Self {
let retryable = kind.is_retryable();
Self {
kind,
message: message.into(),
retryable,
}
}
#[must_use]
pub fn with_retryable(
kind: BackendErrorKind,
message: impl Into<String>,
retryable: bool,
) -> Self {
Self {
kind,
message: message.into(),
retryable,
}
}
#[must_use]
pub fn is_retryable(&self) -> bool {
self.retryable
}
#[must_use]
pub fn auth(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::Authentication, message)
}
#[must_use]
pub fn rate_limit(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::RateLimit, message)
}
#[must_use]
pub fn invalid_request(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::InvalidRequest, message)
}
#[must_use]
pub fn unavailable(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::Unavailable, message)
}
#[must_use]
pub fn network(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::Network, message)
}
#[must_use]
pub fn backend(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::BackendError, message)
}
#[must_use]
pub fn parse(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::ParseError, message)
}
#[must_use]
pub fn timeout(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::Timeout, message)
}
#[must_use]
pub fn unsupported(capability: &Capability) -> Self {
Self::new(
BackendErrorKind::UnsupportedCapability,
format!("capability not supported: {capability}"),
)
}
#[must_use]
pub fn resource_exhausted(message: impl Into<String>) -> Self {
Self::new(BackendErrorKind::ResourceExhausted, message)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BackendErrorKind {
Authentication,
RateLimit,
InvalidRequest,
Unavailable,
Network,
BackendError,
ParseError,
Timeout,
UnsupportedCapability,
ResourceExhausted,
Configuration,
}
impl BackendErrorKind {
#[must_use]
pub fn is_retryable(self) -> bool {
matches!(
self,
Self::RateLimit | Self::Unavailable | Self::Network | Self::Timeout
)
}
}
impl std::fmt::Display for BackendErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Authentication => write!(f, "authentication"),
Self::RateLimit => write!(f, "rate_limit"),
Self::InvalidRequest => write!(f, "invalid_request"),
Self::Unavailable => write!(f, "unavailable"),
Self::Network => write!(f, "network"),
Self::BackendError => write!(f, "backend_error"),
Self::ParseError => write!(f, "parse_error"),
Self::Timeout => write!(f, "timeout"),
Self::UnsupportedCapability => write!(f, "unsupported_capability"),
Self::ResourceExhausted => write!(f, "resource_exhausted"),
Self::Configuration => write!(f, "configuration"),
}
}
}