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}
31
32impl ApiError {
33    #[must_use]
34    pub fn is_retryable(&self) -> bool {
35        match self {
36            Self::Http(error) => error.is_connect() || error.is_timeout() || error.is_request(),
37            Self::Api { retryable, .. } => *retryable,
38            Self::RetriesExhausted { last_error, .. } => last_error.is_retryable(),
39            Self::MissingApiKey
40            | Self::ExpiredOAuthToken
41            | Self::Auth(_)
42            | Self::InvalidApiKeyEnv(_)
43            | Self::Io(_)
44            | Self::Json(_)
45            | Self::InvalidSseFrame(_)
46            | Self::BackoffOverflow { .. } => false,
47        }
48    }
49}
50
51impl Display for ApiError {
52    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::MissingApiKey => {
55                write!(
56                    f,
57                    "TERNLANG_AUTH_TOKEN or TERNLANG_API_KEY is not set; export one before calling the Ternlang API"
58                )
59            }
60            Self::ExpiredOAuthToken => {
61                write!(
62                    f,
63                    "saved OAuth token is expired and no refresh token is available"
64                )
65            }
66            Self::Auth(message) => write!(f, "auth error: {message}"),
67            Self::InvalidApiKeyEnv(error) => {
68                write!(
69                    f,
70                    "failed to read TERNLANG_AUTH_TOKEN / TERNLANG_API_KEY: {error}"
71                )
72            }
73            Self::Http(error) => write!(f, "http error: {error}"),
74            Self::Io(error) => write!(f, "io error: {error}"),
75            Self::Json(error) => write!(f, "json error: {error}"),
76            Self::Api {
77                status,
78                error_type,
79                message,
80                body,
81                ..
82            } => match (error_type, message) {
83                (Some(error_type), Some(message)) => {
84                    write!(
85                        f,
86                        "ternlang api returned {status} ({error_type}): {message}"
87                    )
88                }
89                _ => write!(f, "ternlang api returned {status}: {body}"),
90            },
91            Self::RetriesExhausted {
92                attempts,
93                last_error,
94            } => write!(
95                f,
96                "ternlang api failed after {attempts} attempts: {last_error}"
97            ),
98            Self::InvalidSseFrame(message) => write!(f, "invalid sse frame: {message}"),
99            Self::BackoffOverflow {
100                attempt,
101                base_delay,
102            } => write!(
103                f,
104                "retry backoff overflowed on attempt {attempt} with base delay {base_delay:?}"
105            ),
106        }
107    }
108}
109
110impl std::error::Error for ApiError {}
111
112impl From<reqwest::Error> for ApiError {
113    fn from(value: reqwest::Error) -> Self {
114        Self::Http(value)
115    }
116}
117
118impl From<std::io::Error> for ApiError {
119    fn from(value: std::io::Error) -> Self {
120        Self::Io(value)
121    }
122}
123
124impl From<serde_json::Error> for ApiError {
125    fn from(value: serde_json::Error) -> Self {
126        Self::Json(value)
127    }
128}
129
130impl From<VarError> for ApiError {
131    fn from(value: VarError) -> Self {
132        Self::InvalidApiKeyEnv(value)
133    }
134}