1use thiserror::Error;
7
8pub type RsllmResult<T> = Result<T, RsllmError>;
10
11#[derive(Error, Debug)]
13pub enum RsllmError {
14 #[error("Configuration error: {message}")]
16 Configuration {
17 message: String,
18 #[source]
19 source: Option<Box<dyn std::error::Error + Send + Sync>>,
20 },
21
22 #[error("Provider error ({provider}): {message}")]
24 Provider {
25 provider: String,
26 message: String,
27 #[source]
28 source: Option<Box<dyn std::error::Error + Send + Sync>>,
29 },
30
31 #[error("Network error: {message}")]
33 Network {
34 message: String,
35 status_code: Option<u16>,
36 #[source]
37 source: Option<Box<dyn std::error::Error + Send + Sync>>,
38 },
39
40 #[error("Authentication error: {message}")]
42 Authentication { message: String },
43
44 #[error("Rate limit exceeded: {message}")]
46 RateLimit {
47 message: String,
48 retry_after: Option<std::time::Duration>,
49 },
50
51 #[error("API error ({provider}): {message} (code: {code})")]
53 Api {
54 provider: String,
55 message: String,
56 code: String,
57 #[source]
58 source: Option<Box<dyn std::error::Error + Send + Sync>>,
59 },
60
61 #[error("Serialization error: {message}")]
63 Serialization {
64 message: String,
65 #[source]
66 source: Option<Box<dyn std::error::Error + Send + Sync>>,
67 },
68
69 #[error("Streaming error: {message}")]
71 Streaming {
72 message: String,
73 #[source]
74 source: Option<Box<dyn std::error::Error + Send + Sync>>,
75 },
76
77 #[error("Operation timed out after {timeout_ms}ms: {operation}")]
79 Timeout { operation: String, timeout_ms: u64 },
80
81 #[error("Validation error: {field} - {message}")]
83 Validation { field: String, message: String },
84
85 #[error("Resource not found: {resource}")]
87 NotFound { resource: String },
88
89 #[error("Invalid state: {message}")]
91 InvalidState { message: String },
92
93 #[error("Tool error: {message}")]
95 Tool {
96 message: String,
97 #[source]
98 source: Option<Box<dyn std::error::Error + Send + Sync>>,
99 },
100}
101
102impl RsllmError {
103 pub fn configuration(message: impl Into<String>) -> Self {
105 Self::Configuration {
106 message: message.into(),
107 source: None,
108 }
109 }
110
111 pub fn configuration_with_source(
113 message: impl Into<String>,
114 source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
115 ) -> Self {
116 Self::Configuration {
117 message: message.into(),
118 source: Some(source.into()),
119 }
120 }
121
122 pub fn provider(provider: impl Into<String>, message: impl Into<String>) -> Self {
124 Self::Provider {
125 provider: provider.into(),
126 message: message.into(),
127 source: None,
128 }
129 }
130
131 pub fn provider_with_source(
133 provider: impl Into<String>,
134 message: impl Into<String>,
135 source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
136 ) -> Self {
137 Self::Provider {
138 provider: provider.into(),
139 message: message.into(),
140 source: Some(source.into()),
141 }
142 }
143
144 pub fn network(message: impl Into<String>) -> Self {
146 Self::Network {
147 message: message.into(),
148 status_code: None,
149 source: None,
150 }
151 }
152
153 pub fn network_with_status(message: impl Into<String>, status_code: u16) -> Self {
155 Self::Network {
156 message: message.into(),
157 status_code: Some(status_code),
158 source: None,
159 }
160 }
161
162 pub fn authentication(message: impl Into<String>) -> Self {
164 Self::Authentication {
165 message: message.into(),
166 }
167 }
168
169 pub fn rate_limit(
171 message: impl Into<String>,
172 retry_after: Option<std::time::Duration>,
173 ) -> Self {
174 Self::RateLimit {
175 message: message.into(),
176 retry_after,
177 }
178 }
179
180 pub fn api(
182 provider: impl Into<String>,
183 message: impl Into<String>,
184 code: impl Into<String>,
185 ) -> Self {
186 Self::Api {
187 provider: provider.into(),
188 message: message.into(),
189 code: code.into(),
190 source: None,
191 }
192 }
193
194 pub fn serialization(message: impl Into<String>) -> Self {
196 Self::Serialization {
197 message: message.into(),
198 source: None,
199 }
200 }
201
202 pub fn streaming(message: impl Into<String>) -> Self {
204 Self::Streaming {
205 message: message.into(),
206 source: None,
207 }
208 }
209
210 pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
212 Self::Timeout {
213 operation: operation.into(),
214 timeout_ms,
215 }
216 }
217
218 pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
220 Self::Validation {
221 field: field.into(),
222 message: message.into(),
223 }
224 }
225
226 pub fn not_found(resource: impl Into<String>) -> Self {
228 Self::NotFound {
229 resource: resource.into(),
230 }
231 }
232
233 pub fn invalid_state(message: impl Into<String>) -> Self {
235 Self::InvalidState {
236 message: message.into(),
237 }
238 }
239
240 pub fn category(&self) -> &'static str {
242 match self {
243 Self::Configuration { .. } => "configuration",
244 Self::Provider { .. } => "provider",
245 Self::Network { .. } => "network",
246 Self::Authentication { .. } => "authentication",
247 Self::RateLimit { .. } => "rate_limit",
248 Self::Api { .. } => "api",
249 Self::Serialization { .. } => "serialization",
250 Self::Streaming { .. } => "streaming",
251 Self::Timeout { .. } => "timeout",
252 Self::Validation { .. } => "validation",
253 Self::NotFound { .. } => "not_found",
254 Self::InvalidState { .. } => "invalid_state",
255 Self::Tool { .. } => "tool",
256 }
257 }
258
259 pub fn is_retryable(&self) -> bool {
261 match self {
262 Self::Network { .. } => true,
263 Self::RateLimit { .. } => true,
264 Self::Timeout { .. } => true,
265 Self::Provider { .. } => false, Self::Api { .. } => false, _ => false,
268 }
269 }
270
271 pub fn retry_delay(&self) -> Option<std::time::Duration> {
273 match self {
274 Self::RateLimit { retry_after, .. } => *retry_after,
275 Self::Network { .. } => Some(std::time::Duration::from_secs(1)),
276 Self::Timeout { .. } => Some(std::time::Duration::from_secs(2)),
277 _ => None,
278 }
279 }
280}
281
282impl From<serde_json::Error> for RsllmError {
284 fn from(err: serde_json::Error) -> Self {
285 Self::serialization(format!("JSON error: {}", err))
286 }
287}
288
289impl From<url::ParseError> for RsllmError {
290 fn from(err: url::ParseError) -> Self {
291 Self::configuration(format!("Invalid URL: {}", err))
292 }
293}
294
295#[cfg(feature = "openai")]
296impl From<reqwest::Error> for RsllmError {
297 fn from(err: reqwest::Error) -> Self {
298 if err.is_timeout() {
299 Self::timeout("HTTP request", 30000) } else if err.is_connect() {
301 Self::network(format!("Connection error: {}", err))
302 } else if let Some(status) = err.status() {
303 Self::network_with_status(format!("HTTP error: {}", err), status.as_u16())
304 } else {
305 Self::network(format!("Request error: {}", err))
306 }
307 }
308}
309
310impl From<tokio::time::error::Elapsed> for RsllmError {
311 fn from(_err: tokio::time::error::Elapsed) -> Self {
312 Self::timeout("operation", 0)
313 }
314}
315
316impl From<crate::tools::ToolRegistryError> for RsllmError {
317 fn from(err: crate::tools::ToolRegistryError) -> Self {
318 Self::Tool {
319 message: err.to_string(),
320 source: Some(Box::new(err)),
321 }
322 }
323}