1use std::error::Error;
7use std::time::Duration;
8use thiserror::Error;
9
10pub trait ProviderError: Error + Send + Sync + 'static {
16 fn error_code(&self) -> Option<&str>;
18
19 fn is_retryable(&self) -> bool;
23
24 fn is_rate_limited(&self) -> bool;
28
29 fn is_auth_error(&self) -> bool;
33
34 fn retry_after(&self) -> Option<Duration>;
38
39 fn is_invalid_input(&self) -> bool {
43 false
44 }
45
46 fn is_service_unavailable(&self) -> bool {
50 false
51 }
52
53 fn is_content_filtered(&self) -> bool {
57 false
58 }
59}
60
61#[derive(Debug, Error)]
63pub enum ConfigError {
64 #[error("Missing required configuration: {field}")]
66 MissingField { field: String },
67
68 #[error("Invalid configuration value for {field}: {message}")]
70 InvalidValue { field: String, message: String },
71
72 #[error("Invalid URL: {url}")]
74 InvalidUrl { url: String },
75
76 #[error("Invalid API key format")]
78 InvalidApiKey,
79
80 #[error("Configuration validation failed: {message}")]
82 ValidationFailed { message: String },
83}
84
85#[derive(Debug, Error)]
87pub enum RequestError {
88 #[error("Invalid request: {message}")]
90 InvalidRequest { message: String },
91
92 #[error("Request too large: {size} bytes exceeds limit of {limit} bytes")]
94 RequestTooLarge { size: usize, limit: usize },
95
96 #[error("Unsupported feature: {feature}")]
98 UnsupportedFeature { feature: String },
99
100 #[error("Invalid message format: {message}")]
102 InvalidMessage { message: String },
103
104 #[error("Invalid tool definition: {message}")]
106 InvalidTool { message: String },
107}
108
109#[derive(Debug, Error)]
111pub enum ResponseError {
112 #[error("Failed to parse response: {message}")]
114 ParseError { message: String },
115
116 #[error("Unexpected response format: expected {expected}, got {actual}")]
118 UnexpectedFormat { expected: String, actual: String },
119
120 #[error("Missing required response field: {field}")]
122 MissingField { field: String },
123
124 #[error("Invalid response data: {message}")]
126 InvalidData { message: String },
127}
128
129#[derive(Debug, Error)]
131pub enum NetworkError {
132 #[error("HTTP request failed: {status}")]
134 HttpError { status: u16, message: String },
135
136 #[error("Connection timeout after {timeout:?}")]
138 Timeout { timeout: Duration },
139
140 #[error("Connection failed: {message}")]
142 ConnectionFailed { message: String },
143
144 #[error("DNS resolution failed: {host}")]
146 DnsError { host: String },
147
148 #[error("TLS error: {message}")]
150 TlsError { message: String },
151}
152
153#[derive(Debug, Error)]
155pub enum LlmError<E: ProviderError> {
156 #[error("Provider error: {0}")]
158 Provider(E),
159
160 #[error("Configuration error: {0}")]
162 Config(#[from] ConfigError),
163
164 #[error("Request error: {0}")]
166 Request(#[from] RequestError),
167
168 #[error("Response error: {0}")]
170 Response(#[from] ResponseError),
171
172 #[error("Network error: {0}")]
174 Network(#[from] NetworkError),
175
176 #[error("Memory error: {message}")]
178 Memory { message: String },
179
180 #[error("Tool execution error: {message}")]
182 ToolExecution { message: String },
183
184 #[error("Error: {message}")]
186 Other { message: String },
187}
188
189impl<E: ProviderError> ProviderError for LlmError<E> {
190 fn error_code(&self) -> Option<&str> {
191 match self {
192 Self::Provider(e) => e.error_code(),
193 Self::Config(_) => Some("config_error"),
194 Self::Request(_) => Some("request_error"),
195 Self::Response(_) => Some("response_error"),
196 Self::Network(_) => Some("network_error"),
197 Self::Memory { .. } => Some("memory_error"),
198 Self::ToolExecution { .. } => Some("tool_error"),
199 Self::Other { .. } => Some("other_error"),
200 }
201 }
202
203 fn is_retryable(&self) -> bool {
204 match self {
205 Self::Provider(e) => e.is_retryable(),
206 Self::Network(NetworkError::Timeout { .. }) => true,
207 Self::Network(NetworkError::ConnectionFailed { .. }) => true,
208 Self::Network(NetworkError::HttpError { status, .. }) => {
209 *status >= 500 || *status == 429 || *status == 408
211 }
212 _ => false,
213 }
214 }
215
216 fn is_rate_limited(&self) -> bool {
217 match self {
218 Self::Provider(e) => e.is_rate_limited(),
219 Self::Network(NetworkError::HttpError { status, .. }) => *status == 429,
220 _ => false,
221 }
222 }
223
224 fn is_auth_error(&self) -> bool {
225 match self {
226 Self::Provider(e) => e.is_auth_error(),
227 Self::Config(ConfigError::InvalidApiKey) => true,
228 Self::Network(NetworkError::HttpError { status, .. }) => {
229 *status == 401 || *status == 403
230 }
231 _ => false,
232 }
233 }
234
235 fn retry_after(&self) -> Option<Duration> {
236 match self {
237 Self::Provider(e) => e.retry_after(),
238 Self::Network(NetworkError::HttpError { status, .. }) if *status == 429 => {
239 Some(Duration::from_secs(60))
241 }
242 _ => None,
243 }
244 }
245
246 fn is_invalid_input(&self) -> bool {
247 match self {
248 Self::Provider(e) => e.is_invalid_input(),
249 Self::Request(_) => true,
250 Self::Config(_) => true,
251 Self::Network(NetworkError::HttpError { status, .. }) => *status == 400,
252 _ => false,
253 }
254 }
255
256 fn is_service_unavailable(&self) -> bool {
257 match self {
258 Self::Provider(e) => e.is_service_unavailable(),
259 Self::Network(NetworkError::HttpError { status, .. }) => {
260 *status == 503 || *status == 502 || *status == 504
261 }
262 Self::Network(NetworkError::ConnectionFailed { .. }) => true,
263 _ => false,
264 }
265 }
266
267 fn is_content_filtered(&self) -> bool {
268 match self {
269 Self::Provider(e) => e.is_content_filtered(),
270 _ => false,
271 }
272 }
273}
274
275pub type ProviderResult<T, E> = Result<T, E>;
277
278pub type LlmResult<T, E> = Result<T, LlmError<E>>;
280
281impl ConfigError {
283 pub fn missing_field(field: impl Into<String>) -> Self {
285 Self::MissingField {
286 field: field.into(),
287 }
288 }
289
290 pub fn invalid_value(field: impl Into<String>, message: impl Into<String>) -> Self {
292 Self::InvalidValue {
293 field: field.into(),
294 message: message.into(),
295 }
296 }
297
298 pub fn invalid_url(url: impl Into<String>) -> Self {
300 Self::InvalidUrl { url: url.into() }
301 }
302
303 pub fn validation_failed(message: impl Into<String>) -> Self {
305 Self::ValidationFailed {
306 message: message.into(),
307 }
308 }
309}
310
311impl RequestError {
312 pub fn invalid_request(message: impl Into<String>) -> Self {
314 Self::InvalidRequest {
315 message: message.into(),
316 }
317 }
318
319 pub fn request_too_large(size: usize, limit: usize) -> Self {
321 Self::RequestTooLarge { size, limit }
322 }
323
324 pub fn unsupported_feature(feature: impl Into<String>) -> Self {
326 Self::UnsupportedFeature {
327 feature: feature.into(),
328 }
329 }
330
331 pub fn invalid_message(message: impl Into<String>) -> Self {
333 Self::InvalidMessage {
334 message: message.into(),
335 }
336 }
337
338 pub fn invalid_tool(message: impl Into<String>) -> Self {
340 Self::InvalidTool {
341 message: message.into(),
342 }
343 }
344}
345
346impl ResponseError {
347 pub fn parse_error(message: impl Into<String>) -> Self {
349 Self::ParseError {
350 message: message.into(),
351 }
352 }
353
354 pub fn unexpected_format(expected: impl Into<String>, actual: impl Into<String>) -> Self {
356 Self::UnexpectedFormat {
357 expected: expected.into(),
358 actual: actual.into(),
359 }
360 }
361
362 pub fn missing_field(field: impl Into<String>) -> Self {
364 Self::MissingField {
365 field: field.into(),
366 }
367 }
368
369 pub fn invalid_data(message: impl Into<String>) -> Self {
371 Self::InvalidData {
372 message: message.into(),
373 }
374 }
375}
376
377impl NetworkError {
378 pub fn http_error(status: u16, message: impl Into<String>) -> Self {
380 Self::HttpError {
381 status,
382 message: message.into(),
383 }
384 }
385
386 pub fn timeout(timeout: Duration) -> Self {
388 Self::Timeout { timeout }
389 }
390
391 pub fn connection_failed(message: impl Into<String>) -> Self {
393 Self::ConnectionFailed {
394 message: message.into(),
395 }
396 }
397
398 pub fn dns_error(host: impl Into<String>) -> Self {
400 Self::DnsError { host: host.into() }
401 }
402
403 pub fn tls_error(message: impl Into<String>) -> Self {
405 Self::TlsError {
406 message: message.into(),
407 }
408 }
409}