Skip to main content

albert_api/
error.rs

1use std::env::VarError;
2use std::fmt::{Display, Formatter};
3use std::time::Duration;
4
5#[derive(Debug)]
6pub enum ApiError {
7    MissingApiKey,
8    ExpiredOAuthToken,
9    Auth(String),
10    InvalidApiKeyEnv(VarError),
11    Http(reqwest::Error),
12    Io(std::io::Error),
13    Json(serde_json::Error),
14    Api {
15        status: reqwest::StatusCode,
16        error_type: Option<String>,
17        message: Option<String>,
18        body: String,
19        retryable: bool,
20    },
21    RetriesExhausted {
22        attempts: u32,
23        last_error: Box<ApiError>,
24    },
25    InvalidSseFrame(&'static str),
26    BackoffOverflow {
27        attempt: u32,
28        base_delay: Duration,
29    },
30    Config(String),
31    ProviderError {
32        status: reqwest::StatusCode,
33        body: String,
34    },
35}
36
37impl ApiError {
38    #[must_use]
39    pub fn is_retryable(&self) -> bool {
40        match self {
41            Self::Http(error) => error.is_connect() || error.is_timeout() || error.is_request(),
42            Self::Api { retryable, .. } => *retryable,
43            Self::RetriesExhausted { last_error, .. } => last_error.is_retryable(),
44            Self::ProviderError { status, .. } => status.is_server_error(),
45            Self::MissingApiKey
46            | Self::ExpiredOAuthToken
47            | Self::Auth(_)
48            | Self::InvalidApiKeyEnv(_)
49            | Self::Io(_)
50            | Self::Json(_)
51            | Self::InvalidSseFrame(_)
52            | Self::Config(_)
53            | Self::BackoffOverflow { .. } => false,
54        }
55    }
56}
57
58impl Display for ApiError {
59    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60        match self {
61            Self::Config(message) => write!(f, "configuration error: {message}"),
62            Self::ProviderError { status, body } => write!(f, "provider returned {status}: {body}"),
63            Self::MissingApiKey => {
64                write!(
65                    f,
66                    "TERNLANG_AUTH_TOKEN or TERNLANG_API_KEY is not set; export one before calling the Ternlang API"
67                )
68            }
69            Self::ExpiredOAuthToken => {
70                write!(
71                    f,
72                    "saved OAuth token is expired and no refresh token is available"
73                )
74            }
75            Self::Auth(message) => write!(f, "auth error: {message}"),
76            Self::InvalidApiKeyEnv(error) => {
77                write!(
78                    f,
79                    "failed to read TERNLANG_AUTH_TOKEN / TERNLANG_API_KEY: {error}"
80                )
81            }
82            Self::Http(error) => write!(f, "http error: {error}"),
83            Self::Io(error) => write!(f, "io error: {error}"),
84            Self::Json(error) => write!(f, "json error: {error}"),
85            Self::Api {
86                status,
87                error_type,
88                message,
89                body,
90                ..
91            } => match (error_type, message) {
92                (Some(error_type), Some(message)) => {
93                    write!(
94                        f,
95                        "ternlang api returned {status} ({error_type}): {message}"
96                    )
97                }
98                _ => write!(f, "ternlang api returned {status}: {body}"),
99            },
100            Self::RetriesExhausted {
101                attempts,
102                last_error,
103            } => write!(
104                f,
105                "ternlang api failed after {attempts} attempts: {last_error}"
106            ),
107            Self::InvalidSseFrame(message) => write!(f, "invalid sse frame: {message}"),
108            Self::BackoffOverflow {
109                attempt,
110                base_delay,
111            } => write!(
112                f,
113                "retry backoff overflowed on attempt {attempt} with base delay {base_delay:?}"
114            ),
115        }
116    }
117}
118
119impl std::error::Error for ApiError {}
120
121impl From<reqwest::Error> for ApiError {
122    fn from(value: reqwest::Error) -> Self {
123        Self::Http(value)
124    }
125}
126
127impl From<std::io::Error> for ApiError {
128    fn from(value: std::io::Error) -> Self {
129        Self::Io(value)
130    }
131}
132
133impl From<serde_json::Error> for ApiError {
134    fn from(value: serde_json::Error) -> Self {
135        Self::Json(value)
136    }
137}
138
139impl From<VarError> for ApiError {
140    fn from(value: VarError) -> Self {
141        Self::InvalidApiKeyEnv(value)
142    }
143}