| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- use std::env::VarError;
- use std::fmt::{Display, Formatter};
- use std::time::Duration;
- #[derive(Debug)]
- pub enum ApiError {
- MissingCredentials {
- provider: &'static str,
- env_vars: &'static [&'static str],
- },
- ExpiredOAuthToken,
- Auth(String),
- InvalidApiKeyEnv(VarError),
- Http(reqwest::Error),
- Io(std::io::Error),
- Json(serde_json::Error),
- Api {
- status: reqwest::StatusCode,
- error_type: Option<String>,
- message: Option<String>,
- body: String,
- retryable: bool,
- },
- RetriesExhausted {
- attempts: u32,
- last_error: Box<ApiError>,
- },
- InvalidSseFrame(&'static str),
- BackoffOverflow {
- attempt: u32,
- base_delay: Duration,
- },
- }
- impl ApiError {
- #[must_use]
- pub const fn missing_credentials(
- provider: &'static str,
- env_vars: &'static [&'static str],
- ) -> Self {
- Self::MissingCredentials { provider, env_vars }
- }
- #[must_use]
- pub fn is_retryable(&self) -> bool {
- match self {
- Self::Http(error) => error.is_connect() || error.is_timeout() || error.is_request(),
- Self::Api { retryable, .. } => *retryable,
- Self::RetriesExhausted { last_error, .. } => last_error.is_retryable(),
- Self::MissingCredentials { .. }
- | Self::ExpiredOAuthToken
- | Self::Auth(_)
- | Self::InvalidApiKeyEnv(_)
- | Self::Io(_)
- | Self::Json(_)
- | Self::InvalidSseFrame(_)
- | Self::BackoffOverflow { .. } => false,
- }
- }
- }
- impl Display for ApiError {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::MissingCredentials { provider, env_vars } => write!(
- f,
- "missing {provider} credentials; export {} before calling the {provider} API",
- env_vars.join(" or ")
- ),
- Self::ExpiredOAuthToken => {
- write!(
- f,
- "saved OAuth token is expired and no refresh token is available"
- )
- }
- Self::Auth(message) => write!(f, "auth error: {message}"),
- Self::InvalidApiKeyEnv(error) => {
- write!(f, "failed to read credential environment variable: {error}")
- }
- Self::Http(error) => write!(f, "http error: {error}"),
- Self::Io(error) => write!(f, "io error: {error}"),
- Self::Json(error) => write!(f, "json error: {error}"),
- Self::Api {
- status,
- error_type,
- message,
- body,
- ..
- } => match (error_type, message) {
- (Some(error_type), Some(message)) => {
- write!(f, "api returned {status} ({error_type}): {message}")
- }
- _ => write!(f, "api returned {status}: {body}"),
- },
- Self::RetriesExhausted {
- attempts,
- last_error,
- } => write!(f, "api failed after {attempts} attempts: {last_error}"),
- Self::InvalidSseFrame(message) => write!(f, "invalid sse frame: {message}"),
- Self::BackoffOverflow {
- attempt,
- base_delay,
- } => write!(
- f,
- "retry backoff overflowed on attempt {attempt} with base delay {base_delay:?}"
- ),
- }
- }
- }
- impl std::error::Error for ApiError {}
- impl From<reqwest::Error> for ApiError {
- fn from(value: reqwest::Error) -> Self {
- Self::Http(value)
- }
- }
- impl From<std::io::Error> for ApiError {
- fn from(value: std::io::Error) -> Self {
- Self::Io(value)
- }
- }
- impl From<serde_json::Error> for ApiError {
- fn from(value: serde_json::Error) -> Self {
- Self::Json(value)
- }
- }
- impl From<VarError> for ApiError {
- fn from(value: VarError) -> Self {
- Self::InvalidApiKeyEnv(value)
- }
- }
|