llm_connector/
error.rs

1//! Error types for llm-connector
2
3/// Error types for llm-connector operations
4#[derive(thiserror::Error, Debug)]
5pub enum LlmConnectorError {
6    /// Authentication failed with the provider
7    #[error("Authentication failed: {0}")]
8    AuthenticationError(String),
9
10    /// Rate limit exceeded
11    #[error("Rate limit exceeded: {0}")]
12    RateLimitError(String),
13
14    /// Network-related error
15    #[error("Network error: {0}")]
16    NetworkError(String),
17
18    /// Invalid request format or parameters
19    #[error("Invalid request: {0}")]
20    InvalidRequest(String),
21
22    /// Model not supported by any provider
23    #[error("Unsupported model: {0}")]
24    UnsupportedModel(String),
25
26    /// Provider-specific error
27    #[error("Provider error: {0}")]
28    ProviderError(String),
29
30    /// Permission denied error
31    #[error("Permission denied: {0}")]
32    PermissionError(String),
33
34    /// Resource not found error
35    #[error("Not found: {0}")]
36    NotFoundError(String),
37
38    /// Server error (5xx)
39    #[error("Server error: {0}")]
40    ServerError(String),
41
42    /// Connection timeout error
43    #[error("Timeout error: {0}")]
44    TimeoutError(String),
45
46    /// Connection error
47    #[error("Connection error: {0}")]
48    ConnectionError(String),
49
50    /// Maximum retries exceeded
51    #[error("Max retries exceeded: {0}")]
52    MaxRetriesExceeded(String),
53
54    /// Parse error
55    #[error("Parse error: {0}")]
56    ParseError(String),
57
58    /// Configuration error
59    #[error("Configuration error: {0}")]
60    ConfigError(String),
61
62    /// Streaming-related error
63    #[error("Streaming error: {0}")]
64    StreamingError(String),
65
66    /// Streaming not supported by provider/model
67    #[error("Streaming not supported: {0}")]
68    StreamingNotSupported(String),
69
70    /// Unsupported operation error
71    #[error("Unsupported operation: {0}")]
72    UnsupportedOperation(String),
73
74    /// Generic API error returned by provider
75    #[error("API error: {0}")]
76    ApiError(String),
77
78    /// JSON parsing error
79    #[error("JSON parsing error: {0}")]
80    JsonError(#[from] serde_json::Error),
81
82    /// HTTP request error
83    #[cfg(feature = "reqwest")]
84    #[error("HTTP error: {0}")]
85    HttpError(#[from] reqwest::Error),
86}
87
88impl LlmConnectorError {
89    /// Check if the error is retryable
90    pub fn is_retryable(&self) -> bool {
91        matches!(
92            self,
93            LlmConnectorError::NetworkError(_)
94                | LlmConnectorError::RateLimitError(_)
95                | LlmConnectorError::ProviderError(_)
96        )
97    }
98
99    /// Get the HTTP status code for this error
100    pub fn status_code(&self) -> u16 {
101        match self {
102            LlmConnectorError::AuthenticationError(_) => 401,
103            LlmConnectorError::RateLimitError(_) => 429,
104            LlmConnectorError::InvalidRequest(_) => 400,
105            LlmConnectorError::UnsupportedModel(_) => 400,
106            LlmConnectorError::ConfigError(_) => 500,
107            LlmConnectorError::JsonError(_) => 400,
108            LlmConnectorError::NetworkError(_) => 502,
109            LlmConnectorError::ProviderError(_) => 502,
110            LlmConnectorError::StreamingError(_) => 500,
111            LlmConnectorError::StreamingNotSupported(_) => 501,
112            LlmConnectorError::PermissionError(_) => 403,
113            LlmConnectorError::NotFoundError(_) => 404,
114            LlmConnectorError::ServerError(_) => 500,
115            LlmConnectorError::TimeoutError(_) => 408,
116            LlmConnectorError::ConnectionError(_) => 502,
117            LlmConnectorError::ParseError(_) => 400,
118            LlmConnectorError::UnsupportedOperation(_) => 501,
119            LlmConnectorError::ApiError(_) => 500,
120            LlmConnectorError::HttpError(_) => 502,
121            LlmConnectorError::MaxRetriesExceeded(_) => 503,
122        }
123    }
124
125    /// Create error from HTTP status code
126    pub fn from_status_code(status: u16, message: String) -> Self {
127        match status {
128            401 | 403 => LlmConnectorError::AuthenticationError(message),
129            429 => LlmConnectorError::RateLimitError(message),
130            400 => LlmConnectorError::InvalidRequest(message),
131            _ if status >= 500 => LlmConnectorError::ProviderError(message),
132            _ => LlmConnectorError::NetworkError(message),
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::LlmConnectorError;
140
141    #[test]
142    fn test_status_codes() {
143        assert_eq!(
144            LlmConnectorError::AuthenticationError("test".to_string()).status_code(),
145            401
146        );
147        assert_eq!(
148            LlmConnectorError::RateLimitError("test".to_string()).status_code(),
149            429
150        );
151        assert_eq!(
152            LlmConnectorError::InvalidRequest("test".to_string()).status_code(),
153            400
154        );
155        assert_eq!(
156            LlmConnectorError::UnsupportedModel("test".to_string()).status_code(),
157            400
158        );
159        assert_eq!(
160            LlmConnectorError::NetworkError("test".to_string()).status_code(),
161            502
162        );
163    }
164
165    #[test]
166    fn test_retryable() {
167        assert!(LlmConnectorError::NetworkError("test".to_string()).is_retryable());
168        assert!(LlmConnectorError::RateLimitError("test".to_string()).is_retryable());
169        assert!(LlmConnectorError::ProviderError("test".to_string()).is_retryable());
170
171        assert!(!LlmConnectorError::AuthenticationError("test".to_string()).is_retryable());
172        assert!(!LlmConnectorError::InvalidRequest("test".to_string()).is_retryable());
173        assert!(!LlmConnectorError::UnsupportedModel("test".to_string()).is_retryable());
174    }
175
176    #[test]
177    fn test_from_status_code() {
178        assert!(matches!(
179            LlmConnectorError::from_status_code(401, "test".to_string()),
180            LlmConnectorError::AuthenticationError(_)
181        ));
182        assert!(matches!(
183            LlmConnectorError::from_status_code(429, "test".to_string()),
184            LlmConnectorError::RateLimitError(_)
185        ));
186        assert!(matches!(
187            LlmConnectorError::from_status_code(400, "test".to_string()),
188            LlmConnectorError::InvalidRequest(_)
189        ));
190    }
191}