use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("config error: {0}")]
Config(String),
#[error("provider error (HTTP {status}): {body}")]
Provider {
status: u16,
body: String,
retry_after: Option<Duration>,
},
#[error("network error: {0}")]
Network(String),
#[error("decode error: {0}")]
Decode(String),
#[error("encode error: {0}")]
Encode(String),
#[error("invalid request: {0}")]
Invalid(String),
#[error("unsupported: {0}")]
Unsupported(String),
#[error("routing error: {0}")]
Routing(String),
#[error("request timed out")]
Timeout,
#[error("internal error: {0}")]
Internal(String),
}
impl Error {
pub fn is_transient(&self) -> bool {
match self {
Error::Provider { status, .. } => matches!(status, 429 | 500 | 502 | 503 | 504),
Error::Network(_) | Error::Timeout => true,
_ => false,
}
}
pub fn retry_after(&self) -> Option<Duration> {
match self {
Error::Provider { retry_after, .. } => *retry_after,
_ => None,
}
}
pub fn http_status(&self) -> u16 {
match self {
Error::Provider { status, .. } => *status,
Error::Network(_) | Error::Decode(_) => 502,
Error::Unsupported(_) => 501,
Error::Routing(_) => 404,
Error::Invalid(_) => 400,
Error::Timeout => 504,
Error::Config(_) | Error::Encode(_) | Error::Internal(_) => 500,
}
}
pub fn kind(&self) -> &'static str {
match self {
Error::Provider { .. } => "upstream_error",
Error::Network(_) => "network_error",
Error::Decode(_) => "decode_error",
Error::Routing(_) | Error::Invalid(_) => "invalid_request_error",
Error::Unsupported(_) => "unsupported_error",
Error::Timeout => "timeout_error",
Error::Config(_) | Error::Encode(_) | Error::Internal(_) => "server_error",
}
}
pub fn not_implemented(method: &str) -> Self {
Error::Unsupported(format!("provider method '{method}' not implemented"))
}
}
#[cfg(feature = "gateway")]
impl From<toml::de::Error> for Error {
fn from(e: toml::de::Error) -> Self {
Error::Config(e.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ApiError {
pub error: ApiErrorBody,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ApiErrorBody {
pub message: String,
#[serde(rename = "type")]
pub kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub param: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
}
impl ApiError {
pub fn new(message: impl Into<String>, kind: impl Into<String>) -> Self {
ApiError {
error: ApiErrorBody {
message: message.into(),
kind: kind.into(),
param: None,
code: None,
},
}
}
}