1use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Error, Debug)]
13pub enum Error {
14 #[error("Invalid request: {0}")]
16 InvalidRequest(String),
17
18 #[error("Authentication failed: {0}")]
20 Authentication(String),
21
22 #[error("Rate limit exceeded: {0}")]
24 RateLimit(String),
25
26 #[error("HTTP error: {0}")]
28 Http(#[from] reqwest::Error),
29
30 #[error("JSON error: {0}")]
32 Json(#[from] serde_json::Error),
33
34 #[error("`OpenAI` API error (status {status}): {message}")]
36 Api {
37 status: u16,
39 message: String,
41 error_type: Option<String>,
43 error_code: Option<String>,
45 },
46
47 #[error("Stream connection error: {message}")]
49 StreamConnection {
50 message: String,
52 },
53
54 #[error("Stream parsing error: {message}, chunk: {chunk}")]
56 StreamParsing {
57 message: String,
59 chunk: String,
61 },
62
63 #[error("Stream buffer error: {message}")]
65 StreamBuffer {
66 message: String,
68 },
69
70 #[error("Stream error: {0}")]
72 Stream(String),
73
74 #[error("File error: {0}")]
76 File(#[from] std::io::Error),
77
78 #[error("Configuration error: {0}")]
80 Config(String),
81
82 #[error("Builder validation error: {0}")]
84 Builder(String),
85
86 #[error("Internal error: {0}")]
88 Internal(String),
89}
90
91impl Error {
92 pub fn api(status: u16, message: impl Into<String>) -> Self {
94 Self::Api {
95 status,
96 message: message.into(),
97 error_type: None,
98 error_code: None,
99 }
100 }
101
102 pub fn api_detailed(
104 status: u16,
105 message: impl Into<String>,
106 error_type: Option<String>,
107 error_code: Option<String>,
108 ) -> Self {
109 Self::Api {
110 status,
111 message: message.into(),
112 error_type,
113 error_code,
114 }
115 }
116
117 pub fn is_rate_limit(&self) -> bool {
119 matches!(self, Error::RateLimit(_)) || matches!(self, Error::Api { status: 429, .. })
120 }
121
122 pub fn is_auth_error(&self) -> bool {
124 matches!(self, Error::Authentication(_)) || matches!(self, Error::Api { status: 401, .. })
125 }
126
127 pub fn is_client_error(&self) -> bool {
129 match self {
130 Error::Api { status, .. } => (400..500).contains(status),
131 Error::Authentication(_) | Error::RateLimit(_) | Error::InvalidRequest(_) => true,
132 _ => false,
133 }
134 }
135
136 pub fn is_server_error(&self) -> bool {
138 match self {
139 Error::Api { status, .. } => (500..600).contains(status),
140 _ => false,
141 }
142 }
143
144 pub fn is_retryable(&self) -> bool {
146 self.is_rate_limit() || self.is_server_error()
147 }
148}
149
150pub mod chat {
152 use super::Error;
153
154 pub fn invalid_messages(msg: impl Into<String>) -> Error {
156 Error::InvalidRequest(format!("Invalid chat messages: {}", msg.into()))
157 }
158
159 pub fn unsupported_model(model: impl Into<String>) -> Error {
161 Error::InvalidRequest(format!("Unsupported model: {}", model.into()))
162 }
163}
164
165pub mod responses {
167 use super::Error;
168
169 pub fn invalid_tool(msg: impl Into<String>) -> Error {
171 Error::InvalidRequest(format!("Invalid tool definition: {}", msg.into()))
172 }
173
174 pub fn missing_response_format() -> Error {
176 Error::InvalidRequest("Response format is required for structured outputs".to_string())
177 }
178}
179
180pub mod files {
182 use super::Error;
183
184 pub fn upload_failed(msg: impl Into<String>) -> Error {
186 Error::File(std::io::Error::new(
187 std::io::ErrorKind::Other,
188 format!("File upload failed: {}", msg.into()),
189 ))
190 }
191
192 pub fn unsupported_type(file_type: impl Into<String>) -> Error {
194 Error::InvalidRequest(format!("Unsupported file type: {}", file_type.into()))
195 }
196}
197
198pub mod streaming {
200 use super::Error;
201
202 pub fn connection_failed(msg: impl Into<String>) -> Error {
204 Error::Stream(format!("Stream connection failed: {}", msg.into()))
205 }
206
207 pub fn parse_failed(msg: impl Into<String>) -> Error {
209 Error::Stream(format!("Stream parsing failed: {}", msg.into()))
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use std::io;
217
218 #[test]
219 fn test_error_display() {
220 let error = Error::InvalidRequest("test message".to_string());
221 assert_eq!(error.to_string(), "Invalid request: test message");
222
223 let error = Error::Authentication("invalid API key".to_string());
224 assert_eq!(error.to_string(), "Authentication failed: invalid API key");
225
226 let error = Error::RateLimit("rate limit exceeded".to_string());
227 assert_eq!(
228 error.to_string(),
229 "Rate limit exceeded: rate limit exceeded"
230 );
231 }
232
233 #[test]
234 fn test_api_error_constructors() {
235 let error = Error::api(400, "Bad request");
236 match error {
237 Error::Api {
238 status,
239 message,
240 error_type,
241 error_code,
242 } => {
243 assert_eq!(status, 400);
244 assert_eq!(message, "Bad request");
245 assert!(error_type.is_none());
246 assert!(error_code.is_none());
247 }
248 _ => panic!("Expected API error"),
249 }
250
251 let error = Error::api_detailed(
252 429,
253 "Rate limit exceeded",
254 Some("rate_limit_exceeded".to_string()),
255 Some("RL001".to_string()),
256 );
257 match error {
258 Error::Api {
259 status,
260 message,
261 error_type,
262 error_code,
263 } => {
264 assert_eq!(status, 429);
265 assert_eq!(message, "Rate limit exceeded");
266 assert_eq!(error_type, Some("rate_limit_exceeded".to_string()));
267 assert_eq!(error_code, Some("RL001".to_string()));
268 }
269 _ => panic!("Expected API error"),
270 }
271 }
272
273 #[test]
274 fn test_is_rate_limit() {
275 let error = Error::RateLimit("exceeded".to_string());
276 assert!(error.is_rate_limit());
277
278 let error = Error::api(429, "Too Many Requests");
279 assert!(error.is_rate_limit());
280
281 let error = Error::api(400, "Bad Request");
282 assert!(!error.is_rate_limit());
283
284 let error = Error::InvalidRequest("invalid".to_string());
285 assert!(!error.is_rate_limit());
286 }
287
288 #[test]
289 fn test_is_auth_error() {
290 let error = Error::Authentication("invalid key".to_string());
291 assert!(error.is_auth_error());
292
293 let error = Error::api(401, "Unauthorized");
294 assert!(error.is_auth_error());
295
296 let error = Error::api(403, "Forbidden");
297 assert!(!error.is_auth_error());
298
299 let error = Error::InvalidRequest("invalid".to_string());
300 assert!(!error.is_auth_error());
301 }
302
303 #[test]
304 fn test_is_client_error() {
305 let error = Error::api(400, "Bad Request");
306 assert!(error.is_client_error());
307
308 let error = Error::api(404, "Not Found");
309 assert!(error.is_client_error());
310
311 let error = Error::api(499, "Client Error");
312 assert!(error.is_client_error());
313
314 let error = Error::Authentication("invalid".to_string());
315 assert!(error.is_client_error());
316
317 let error = Error::RateLimit("exceeded".to_string());
318 assert!(error.is_client_error());
319
320 let error = Error::InvalidRequest("invalid".to_string());
321 assert!(error.is_client_error());
322
323 let error = Error::api(500, "Server Error");
324 assert!(!error.is_client_error());
325
326 let error = Error::Internal("internal".to_string());
327 assert!(!error.is_client_error());
328 }
329
330 #[test]
331 fn test_is_server_error() {
332 let error = Error::api(500, "Internal Server Error");
333 assert!(error.is_server_error());
334
335 let error = Error::api(502, "Bad Gateway");
336 assert!(error.is_server_error());
337
338 let error = Error::api(599, "Server Error");
339 assert!(error.is_server_error());
340
341 let error = Error::api(400, "Client Error");
342 assert!(!error.is_server_error());
343
344 let error = Error::Internal("internal".to_string());
345 assert!(!error.is_server_error());
346 }
347
348 #[test]
349 fn test_is_retryable() {
350 let error = Error::RateLimit("exceeded".to_string());
352 assert!(error.is_retryable());
353
354 let error = Error::api(429, "Too Many Requests");
355 assert!(error.is_retryable());
356
357 let error = Error::api(500, "Internal Server Error");
359 assert!(error.is_retryable());
360
361 let error = Error::api(502, "Bad Gateway");
362 assert!(error.is_retryable());
363
364 let error = Error::api(400, "Bad Request");
366 assert!(!error.is_retryable());
367
368 let error = Error::Authentication("invalid".to_string());
369 assert!(!error.is_retryable());
370
371 let error = Error::InvalidRequest("invalid".to_string());
372 assert!(!error.is_retryable());
373 }
374
375 #[test]
376 fn test_from_reqwest_error() {
377 #[allow(clippy::items_after_statements)]
379 fn _test_reqwest_error_conversion(reqwest_error: reqwest::Error) -> Error {
380 reqwest_error.into()
381 }
382
383 }
385
386 #[test]
387 fn test_from_serde_json_error() {
388 let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
389 let error: Error = json_error.into();
390 assert!(matches!(error, Error::Json(_)));
391 }
392
393 #[test]
394 fn test_from_io_error() {
395 let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
396 let error: Error = io_error.into();
397 assert!(matches!(error, Error::File(_)));
398 }
399
400 #[test]
401 fn test_stream_errors() {
402 let error = Error::StreamConnection {
403 message: "connection lost".to_string(),
404 };
405 assert_eq!(
406 error.to_string(),
407 "Stream connection error: connection lost"
408 );
409
410 let error = Error::StreamParsing {
411 message: "invalid data".to_string(),
412 chunk: "bad chunk".to_string(),
413 };
414 assert_eq!(
415 error.to_string(),
416 "Stream parsing error: invalid data, chunk: bad chunk"
417 );
418
419 let error = Error::StreamBuffer {
420 message: "buffer overflow".to_string(),
421 };
422 assert_eq!(error.to_string(), "Stream buffer error: buffer overflow");
423
424 let error = Error::Stream("generic stream error".to_string());
425 assert_eq!(error.to_string(), "Stream error: generic stream error");
426 }
427
428 #[test]
429 fn test_specialized_error_modules() {
430 let error = chat::invalid_messages("empty messages");
432 assert!(matches!(error, Error::InvalidRequest(_)));
433 assert!(error.to_string().contains("Invalid chat messages"));
434
435 let error = chat::unsupported_model("gpt-5");
436 assert!(matches!(error, Error::InvalidRequest(_)));
437 assert!(error.to_string().contains("Unsupported model"));
438
439 let error = responses::invalid_tool("missing name");
441 assert!(matches!(error, Error::InvalidRequest(_)));
442 assert!(error.to_string().contains("Invalid tool definition"));
443
444 let error = responses::missing_response_format();
445 assert!(matches!(error, Error::InvalidRequest(_)));
446 assert!(error.to_string().contains("Response format is required"));
447
448 let error = files::upload_failed("network error");
450 assert!(matches!(error, Error::File(_)));
451
452 let error = files::unsupported_type("txt");
453 assert!(matches!(error, Error::InvalidRequest(_)));
454 assert!(error.to_string().contains("Unsupported file type"));
455
456 let error = streaming::connection_failed("timeout");
458 assert!(matches!(error, Error::Stream(_)));
459 assert!(error.to_string().contains("Stream connection failed"));
460
461 let error = streaming::parse_failed("invalid JSON");
462 assert!(matches!(error, Error::Stream(_)));
463 assert!(error.to_string().contains("Stream parsing failed"));
464 }
465
466 #[test]
467 fn test_error_debug_format() {
468 let error = Error::InvalidRequest("test".to_string());
469 let debug_str = format!("{error:?}");
470 assert!(debug_str.contains("InvalidRequest"));
471 assert!(debug_str.contains("test"));
472 }
473
474 #[test]
475 fn test_error_chains() {
476 let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
478 let wrapped_error: Error = io_error.into();
479
480 match wrapped_error {
481 Error::File(ref err) => {
482 assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
483 }
484 _ => panic!("Expected File error"),
485 }
486 }
487
488 #[test]
489 fn test_config_error() {
490 let error = Error::Config("missing API key".to_string());
491 assert_eq!(error.to_string(), "Configuration error: missing API key");
492 }
493
494 #[test]
495 fn test_builder_error() {
496 let error = Error::Builder("validation failed".to_string());
497 assert_eq!(
498 error.to_string(),
499 "Builder validation error: validation failed"
500 );
501 }
502
503 #[test]
504 fn test_internal_error() {
505 let error = Error::Internal("unexpected state".to_string());
506 assert_eq!(error.to_string(), "Internal error: unexpected state");
507 }
508
509 #[test]
510 fn test_error_status_boundaries() {
511 let error = Error::api(399, "Client Error");
513 assert!(!error.is_client_error());
514
515 let error = Error::api(400, "Client Error");
516 assert!(error.is_client_error());
517
518 let error = Error::api(499, "Client Error");
519 assert!(error.is_client_error());
520
521 let error = Error::api(500, "Server Error");
522 assert!(!error.is_client_error());
523 assert!(error.is_server_error());
524
525 let error = Error::api(599, "Server Error");
526 assert!(error.is_server_error());
527
528 let error = Error::api(600, "Unknown");
529 assert!(!error.is_server_error());
530 }
531}