1use std::error::Error as StdError;
7use std::fmt;
8use std::time::Duration;
9
10#[derive(Debug, Clone)]
12pub enum AixError {
13 Transport {
15 source: String,
17 context: String,
19 },
20 Provider {
22 provider: String,
24 code: Option<String>,
26 message: String,
28 status: Option<u16>,
30 },
31 RateLimit {
33 provider: String,
35 retry_after: Option<Duration>,
37 message: String,
39 },
40 Serialization {
42 source: String,
44 context: String,
46 },
47 Config {
49 message: String,
51 },
52 Stream {
54 message: String,
56 source: Option<String>,
58 },
59 Safety {
61 provider: String,
63 category: String,
65 message: String,
67 },
68 Auth {
70 provider: String,
72 message: String,
74 },
75 Timeout {
77 operation: String,
79 duration: Duration,
81 },
82 Other {
84 message: String,
86 source: Option<String>,
88 },
89}
90
91impl AixError {
92 pub fn is_retryable(&self) -> bool {
96 match self {
97 AixError::Transport { .. } => true,
98 AixError::Provider { status, .. } => {
99 status.map_or(false, |s| s >= 500 || s == 429)
100 }
101 AixError::RateLimit { .. } => true,
102 AixError::Timeout { .. } => true,
103 AixError::Serialization { .. } => false,
104 AixError::Config { .. } => false,
105 AixError::Stream { .. } => false,
106 AixError::Safety { .. } => false,
107 AixError::Auth { .. } => false,
108 AixError::Other { .. } => false,
109 }
110 }
111
112 pub fn transport<S: Into<String>, C: Into<String>>(source: S, context: C) -> Self {
114 AixError::Transport {
115 source: source.into(),
116 context: context.into(),
117 }
118 }
119
120 pub fn provider<P: Into<String>, M: Into<String>>(
122 provider: P,
123 message: M,
124 ) -> Self {
125 AixError::Provider {
126 provider: provider.into(),
127 code: None,
128 message: message.into(),
129 status: None,
130 }
131 }
132
133 pub fn provider_with_details<P: Into<String>, M: Into<String>, C: Into<String>>(
135 provider: P,
136 message: M,
137 status: u16,
138 code: C,
139 ) -> Self {
140 AixError::Provider {
141 provider: provider.into(),
142 code: Some(code.into()),
143 message: message.into(),
144 status: Some(status),
145 }
146 }
147
148 pub fn rate_limit<P: Into<String>, M: Into<String>>(
150 provider: P,
151 message: M,
152 ) -> Self {
153 AixError::RateLimit {
154 provider: provider.into(),
155 retry_after: None,
156 message: message.into(),
157 }
158 }
159
160 pub fn rate_limit_with_retry<P: Into<String>, M: Into<String>>(
162 provider: P,
163 message: M,
164 retry_after: Duration,
165 ) -> Self {
166 AixError::RateLimit {
167 provider: provider.into(),
168 retry_after: Some(retry_after),
169 message: message.into(),
170 }
171 }
172
173 pub fn serialization<S: Into<String>, C: Into<String>>(source: S, context: C) -> Self {
175 AixError::Serialization {
176 source: source.into(),
177 context: context.into(),
178 }
179 }
180
181 pub fn config<M: Into<String>>(message: M) -> Self {
183 AixError::Config {
184 message: message.into(),
185 }
186 }
187
188 pub fn stream<M: Into<String>>(message: M) -> Self {
190 AixError::Stream {
191 message: message.into(),
192 source: None,
193 }
194 }
195
196 pub fn stream_with_source<M: Into<String>, S: Into<String>>(
198 message: M,
199 source: S,
200 ) -> Self {
201 AixError::Stream {
202 message: message.into(),
203 source: Some(source.into()),
204 }
205 }
206
207 pub fn safety<P: Into<String>, C: Into<String>, M: Into<String>>(
209 provider: P,
210 category: C,
211 message: M,
212 ) -> Self {
213 AixError::Safety {
214 provider: provider.into(),
215 category: category.into(),
216 message: message.into(),
217 }
218 }
219
220 pub fn auth<P: Into<String>, M: Into<String>>(provider: P, message: M) -> Self {
222 AixError::Auth {
223 provider: provider.into(),
224 message: message.into(),
225 }
226 }
227
228 pub fn timeout<O: Into<String>>(operation: O, duration: Duration) -> Self {
230 AixError::Timeout {
231 operation: operation.into(),
232 duration,
233 }
234 }
235
236 pub fn other<M: Into<String>>(message: M) -> Self {
238 AixError::Other {
239 message: message.into(),
240 source: None,
241 }
242 }
243
244 pub fn other_with_source<M: Into<String>, S: Into<String>>(
246 message: M,
247 source: S,
248 ) -> Self {
249 AixError::Other {
250 message: message.into(),
251 source: Some(source.into()),
252 }
253 }
254}
255
256impl fmt::Display for AixError {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match self {
259 AixError::Transport { source, context } => {
260 write!(f, "Transport error in {}: {}", context, source)
261 }
262 AixError::Provider {
263 provider,
264 code,
265 message,
266 status,
267 } => {
268 write!(f, "Provider error from {}", provider)?;
269 if let Some(status) = status {
270 write!(f, " (status {})", status)?;
271 }
272 if let Some(code) = code {
273 write!(f, " (code: {})", code)?;
274 }
275 write!(f, ": {}", message)
276 }
277 AixError::RateLimit {
278 provider,
279 retry_after,
280 message,
281 } => {
282 write!(f, "Rate limit error from {}: {}", provider, message)?;
283 if let Some(retry_after) = retry_after {
284 write!(f, " (retry after: {:?})", retry_after)?;
285 }
286 Ok(())
287 }
288 AixError::Serialization { source, context } => {
289 write!(f, "Serialization error in {}: {}", context, source)
290 }
291 AixError::Config { message } => {
292 write!(f, "Configuration error: {}", message)
293 }
294 AixError::Stream { message, source } => {
295 write!(f, "Stream error: {}", message)?;
296 if let Some(source) = source {
297 write!(f, " (source: {})", source)?;
298 }
299 Ok(())
300 }
301 AixError::Safety {
302 provider,
303 category,
304 message,
305 } => {
306 write!(
307 f,
308 "Safety violation from {} (category: {}): {}",
309 provider, category, message
310 )
311 }
312 AixError::Auth { provider, message } => {
313 write!(f, "Authentication error from {}: {}", provider, message)
314 }
315 AixError::Timeout { operation, duration } => {
316 write!(
317 f,
318 "Operation '{}' timed out after {:?}",
319 operation, duration
320 )
321 }
322 AixError::Other { message, source } => {
323 write!(f, "Error: {}", message)?;
324 if let Some(source) = source {
325 write!(f, " (source: {})", source)?;
326 }
327 Ok(())
328 }
329 }
330 }
331}
332
333impl StdError for AixError {}
334
335pub type AixResult<T> = Result<T, AixError>;
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_error_retryability() {
344 assert!(AixError::transport("network error", "request").is_retryable());
345 assert!(AixError::provider_with_details("openai", "error", 500, "internal_error").is_retryable());
346 assert!(AixError::provider_with_details("openai", "error", 429, "rate_limit").is_retryable());
347 assert!(AixError::rate_limit("openai", "too many requests").is_retryable());
348 assert!(AixError::timeout("chat", Duration::from_secs(30)).is_retryable());
349
350 assert!(!AixError::provider_with_details("openai", "error", 400, "bad_request").is_retryable());
351 assert!(!AixError::config("invalid api key").is_retryable());
352 assert!(!AixError::auth("openai", "unauthorized").is_retryable());
353 assert!(!AixError::safety("openai", "hate", "content flagged").is_retryable());
354 }
355
356 #[test]
357 fn test_error_display() {
358 let err = AixError::provider_with_details("openai", "invalid request", 400, "invalid_request");
359 assert_eq!(err.to_string(), "Provider error from openai (status 400) (code: invalid_request): invalid request");
360 }
361}