use std::collections::BTreeMap;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
use thiserror::Error;
use crate::protocol::ProviderProtocol;
pub type ErrorExtensions = BTreeMap<String, Value>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderError {
pub protocol: ProviderProtocol,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_after: Option<Duration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw_body: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub vendor_extensions: ErrorExtensions,
}
impl fmt::Display for ProviderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} error", self.protocol)?;
if let Some(status) = self.status {
write!(f, " ({status})")?;
}
write!(f, ": {}", self.message)
}
}
impl std::error::Error for ProviderError {}
#[derive(Debug, Error)]
pub enum GatewayError {
#[error("no healthy key with sufficient TPM capacity")]
NoAvailableKey,
#[error("budget limit exceeded")]
BudgetExceeded,
#[error("rate limited by local RPM window")]
RateLimited,
#[error("provider returned 401/403 — key is dead")]
Unauthorized,
#[error("request cancelled by upstream")]
Cancelled,
#[error(transparent)]
Provider(ProviderError),
#[error("protocol conversion error: {0}")]
Protocol(String),
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
}
#[derive(Debug)]
pub(crate) enum ApiError {
Unauthorized,
RateLimited { retry_after: Duration },
Provider(ProviderError),
Protocol(String),
Cancelled,
}