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