1use serde::Deserialize;
2use std::fmt;
3
4pub type Result<T> = std::result::Result<T, Error>;
6
7#[derive(Debug)]
9pub enum Error {
10 Api(ApiError),
12 Http(reqwest::Error),
14 Json(serde_json::Error),
16 WebSocket(tokio_tungstenite::tungstenite::Error),
18}
19
20impl fmt::Display for Error {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Error::Api(e) => write!(f, "{e}"),
24 Error::Http(e) => write!(f, "qai: http error: {e}"),
25 Error::Json(e) => write!(f, "qai: json error: {e}"),
26 Error::WebSocket(e) => write!(f, "qai: websocket error: {e}"),
27 }
28 }
29}
30
31impl std::error::Error for Error {
32 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
33 match self {
34 Error::Api(_) => None,
35 Error::Http(e) => Some(e),
36 Error::Json(e) => Some(e),
37 Error::WebSocket(e) => Some(e),
38 }
39 }
40}
41
42impl From<tokio_tungstenite::tungstenite::Error> for Error {
43 fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
44 Error::WebSocket(err)
45 }
46}
47
48impl From<reqwest::Error> for Error {
49 fn from(err: reqwest::Error) -> Self {
50 Error::Http(err)
51 }
52}
53
54impl From<serde_json::Error> for Error {
55 fn from(err: serde_json::Error) -> Self {
56 Error::Json(err)
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct ApiError {
63 pub status_code: u16,
65 pub code: String,
67 pub message: String,
69 pub request_id: String,
71}
72
73impl fmt::Display for ApiError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 if self.request_id.is_empty() {
76 write!(
77 f,
78 "qai: {} {}: {}",
79 self.status_code, self.code, self.message
80 )
81 } else {
82 write!(
83 f,
84 "qai: {} {}: {} (request_id={})",
85 self.status_code, self.code, self.message, self.request_id
86 )
87 }
88 }
89}
90
91impl std::error::Error for ApiError {}
92
93impl ApiError {
94 pub fn is_rate_limit(&self) -> bool {
96 self.status_code == 429
97 }
98
99 pub fn is_auth(&self) -> bool {
101 self.status_code == 401 || self.status_code == 403
102 }
103
104 pub fn is_not_found(&self) -> bool {
106 self.status_code == 404
107 }
108}
109
110pub fn is_rate_limit_error(err: &Error) -> bool {
112 matches!(err, Error::Api(e) if e.is_rate_limit())
113}
114
115pub fn is_auth_error(err: &Error) -> bool {
117 matches!(err, Error::Api(e) if e.is_auth())
118}
119
120pub fn is_not_found_error(err: &Error) -> bool {
122 matches!(err, Error::Api(e) if e.is_not_found())
123}
124
125#[derive(Deserialize)]
127pub(crate) struct ApiErrorBody {
128 pub error: ApiErrorInner,
129}
130
131#[derive(Deserialize)]
132pub(crate) struct ApiErrorInner {
133 #[serde(default)]
134 pub message: String,
135 #[serde(default)]
136 pub code: String,
137 #[serde(rename = "type", default)]
138 pub error_type: String,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
157#[non_exhaustive]
158pub enum ErrorCode {
159 AuthHeaderMissing,
161 AuthHeaderEmpty,
162 KeyBearerMalformed,
163 KeyNotFound,
164 KeyExpired,
165 KeyRevokedByAdmin,
166 KeyRevokedByOwner,
167 KeyFrozenByBudget,
172 KeyPartnerRejected,
173 SessionExpired,
174 EphemeralExpired,
175
176 ScopeEndpointDenied,
178 AdminRequired,
179 ServiceAccountRequired,
180
181 InsufficientBalance,
183 TrialExpired,
184 SubscriptionLapsed,
185 SpendCapExceeded,
186 BudgetFrozen,
189 PaymentNotConfigured,
190 BillingPortalNoHistory,
191
192 RateLimitedPerKey,
194 RateLimitedPerIP,
195 QuotaExceeded,
196
197 ProviderRateLimited,
199 ProviderUnavailable,
200 ProviderAuthFailed,
201 ProviderInvalidRequest,
202 ContentRejected,
205 ModelNotAvailable,
206
207 InvalidRequestBody,
209 MissingRequiredField,
210 FieldTooLong,
211 InvalidAttachment,
212 AttachmentTooLarge,
213 UnsupportedCapability,
214
215 InternalError,
217 ServiceUnavailable,
218 StripeApiError,
219 IdempotencyConflict,
220
221 RecipeBoxPaywall,
223
224 Unknown,
228}
229
230impl ErrorCode {
231 pub fn from_wire(code: &str) -> Self {
236 match code {
237 "AUTH_HEADER_MISSING" => Self::AuthHeaderMissing,
238 "AUTH_HEADER_EMPTY" => Self::AuthHeaderEmpty,
239 "KEY_BEARER_MALFORMED" => Self::KeyBearerMalformed,
240 "KEY_NOT_FOUND" => Self::KeyNotFound,
241 "KEY_EXPIRED" => Self::KeyExpired,
242 "KEY_REVOKED_BY_ADMIN" => Self::KeyRevokedByAdmin,
243 "KEY_REVOKED_BY_OWNER" => Self::KeyRevokedByOwner,
244 "KEY_FROZEN_BY_BUDGET" => Self::KeyFrozenByBudget,
245 "KEY_PARTNER_REJECTED" => Self::KeyPartnerRejected,
246 "SESSION_EXPIRED" => Self::SessionExpired,
247 "EPHEMERAL_EXPIRED" => Self::EphemeralExpired,
248 "SCOPE_ENDPOINT_DENIED" => Self::ScopeEndpointDenied,
249 "ADMIN_REQUIRED" => Self::AdminRequired,
250 "SERVICE_ACCOUNT_REQUIRED" => Self::ServiceAccountRequired,
251 "INSUFFICIENT_BALANCE" => Self::InsufficientBalance,
252 "TRIAL_EXPIRED" => Self::TrialExpired,
253 "SUBSCRIPTION_LAPSED" => Self::SubscriptionLapsed,
254 "SPEND_CAP_EXCEEDED" => Self::SpendCapExceeded,
255 "BUDGET_FROZEN" => Self::BudgetFrozen,
256 "PAYMENT_NOT_CONFIGURED" => Self::PaymentNotConfigured,
257 "BILLING_PORTAL_NO_HISTORY" => Self::BillingPortalNoHistory,
258 "RATE_LIMITED_PER_KEY" => Self::RateLimitedPerKey,
259 "RATE_LIMITED_PER_IP" => Self::RateLimitedPerIP,
260 "QUOTA_EXCEEDED" => Self::QuotaExceeded,
261 "PROVIDER_RATE_LIMITED" => Self::ProviderRateLimited,
262 "PROVIDER_UNAVAILABLE" => Self::ProviderUnavailable,
263 "PROVIDER_AUTH_FAILED" => Self::ProviderAuthFailed,
264 "PROVIDER_INVALID_REQUEST" => Self::ProviderInvalidRequest,
265 "CONTENT_REJECTED" => Self::ContentRejected,
266 "MODEL_NOT_AVAILABLE" => Self::ModelNotAvailable,
267 "INVALID_REQUEST_BODY" => Self::InvalidRequestBody,
268 "MISSING_REQUIRED_FIELD" => Self::MissingRequiredField,
269 "FIELD_TOO_LONG" => Self::FieldTooLong,
270 "INVALID_ATTACHMENT" => Self::InvalidAttachment,
271 "ATTACHMENT_TOO_LARGE" => Self::AttachmentTooLarge,
272 "UNSUPPORTED_CAPABILITY" => Self::UnsupportedCapability,
273 "INTERNAL_ERROR" => Self::InternalError,
274 "SERVICE_UNAVAILABLE" => Self::ServiceUnavailable,
275 "STRIPE_API_ERROR" => Self::StripeApiError,
276 "IDEMPOTENCY_CONFLICT" => Self::IdempotencyConflict,
277 "RECIPE_BOX_PAYWALL" => Self::RecipeBoxPaywall,
278 _ => Self::Unknown,
279 }
280 }
281}
282
283impl ApiError {
284 pub fn typed_code(&self) -> ErrorCode {
287 ErrorCode::from_wire(&self.code)
288 }
289}