simple_agent_type/
error.rs1use std::time::Duration;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum SimpleAgentsError {
11 #[error("Provider error: {0}")]
13 Provider(#[from] ProviderError),
14
15 #[error("Healing error: {0}")]
17 Healing(#[from] HealingError),
18
19 #[error("Network error: {0}")]
21 Network(String),
22
23 #[error("Configuration error: {0}")]
25 Config(String),
26
27 #[error("Validation error: {0}")]
29 Validation(#[from] ValidationError),
30
31 #[error("Cache error: {0}")]
33 Cache(String),
34
35 #[error("Routing error: {0}")]
37 Routing(String),
38
39 #[error("Serialization error: {0}")]
41 Serialization(#[from] serde_json::Error),
42}
43
44pub type Result<T> = std::result::Result<T, SimpleAgentsError>;
46
47#[derive(Error, Debug, Clone)]
49pub enum ProviderError {
50 #[error("Rate limit exceeded (retry after {retry_after:?})")]
52 RateLimit {
53 retry_after: Option<Duration>,
55 },
56
57 #[error("Invalid API key")]
59 InvalidApiKey,
60
61 #[error("Model not found: {0}")]
63 ModelNotFound(String),
64
65 #[error("Timeout after {0:?}")]
67 Timeout(Duration),
68
69 #[error("Server error: {0}")]
71 ServerError(String),
72
73 #[error("Bad request: {0}")]
75 BadRequest(String),
76
77 #[error("Unsupported feature: {0}")]
79 UnsupportedFeature(String),
80
81 #[error("Invalid response format: {0}")]
83 InvalidResponse(String),
84}
85
86impl ProviderError {
87 pub fn is_retryable(&self) -> bool {
101 matches!(
102 self,
103 Self::RateLimit { .. } | Self::Timeout(_) | Self::ServerError(_)
104 )
105 }
106}
107
108#[derive(Error, Debug, Clone)]
110pub enum HealingError {
111 #[error("Failed to parse JSON: {error_message}")]
113 ParseFailed {
114 error_message: String,
116 input: String,
118 },
119
120 #[error("Type coercion failed: cannot convert {from} to {to}")]
122 CoercionFailed {
123 from: String,
125 to: String,
127 },
128
129 #[error("Missing required field: {field}")]
131 MissingField {
132 field: String,
134 },
135
136 #[error("Confidence {confidence} below threshold {threshold}")]
138 LowConfidence {
139 confidence: f32,
141 threshold: f32,
143 },
144
145 #[error("Invalid JSON structure: {0}")]
147 InvalidStructure(String),
148
149 #[error("Exceeded maximum healing attempts ({0})")]
151 MaxAttemptsExceeded(u32),
152
153 #[error("Coercion from {from} to {to} not allowed by configuration")]
155 CoercionNotAllowed {
156 from: String,
158 to: String,
160 },
161
162 #[error("Failed to parse '{input}' as {expected_type}")]
164 ParseError {
165 input: String,
167 expected_type: String,
169 },
170
171 #[error("Type mismatch: expected {expected}, found {found}")]
173 TypeMismatch {
174 expected: String,
176 found: String,
178 },
179
180 #[error("No matching variant in union for value: {value}")]
182 NoMatchingVariant {
183 value: serde_json::Value,
185 },
186}
187
188#[derive(Error, Debug, Clone)]
190pub enum ValidationError {
191 #[error("Field cannot be empty: {field}")]
193 Empty {
194 field: String,
196 },
197
198 #[error("Field too short: {field} (minimum: {min})")]
200 TooShort {
201 field: String,
203 min: usize,
205 },
206
207 #[error("Field too long: {field} (maximum: {max})")]
209 TooLong {
210 field: String,
212 max: usize,
214 },
215
216 #[error("Value out of range: {field} (must be {min}-{max})")]
218 OutOfRange {
219 field: String,
221 min: f32,
223 max: f32,
225 },
226
227 #[error("Invalid format: {field} ({reason})")]
229 InvalidFormat {
230 field: String,
232 reason: String,
234 },
235
236 #[error("{0}")]
238 Custom(String),
239}
240
241impl ValidationError {
242 pub fn new(msg: impl Into<String>) -> Self {
244 Self::Custom(msg.into())
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_provider_error_retryable() {
254 assert!(ProviderError::RateLimit { retry_after: None }.is_retryable());
255 assert!(ProviderError::Timeout(Duration::from_secs(30)).is_retryable());
256 assert!(ProviderError::ServerError("500".to_string()).is_retryable());
257
258 assert!(!ProviderError::InvalidApiKey.is_retryable());
259 assert!(!ProviderError::ModelNotFound("gpt-5".to_string()).is_retryable());
260 assert!(!ProviderError::BadRequest("invalid".to_string()).is_retryable());
261 }
262
263 #[test]
264 fn test_error_conversion() {
265 let validation_err = ValidationError::new("test");
266 let agents_err: SimpleAgentsError = validation_err.into();
267 assert!(matches!(agents_err, SimpleAgentsError::Validation(_)));
268
269 let provider_err = ProviderError::InvalidApiKey;
270 let agents_err: SimpleAgentsError = provider_err.into();
271 assert!(matches!(agents_err, SimpleAgentsError::Provider(_)));
272 }
273
274 #[test]
275 fn test_error_display() {
276 let err = ProviderError::RateLimit {
277 retry_after: Some(Duration::from_secs(60)),
278 };
279 let display = format!("{}", err);
280 assert!(display.contains("Rate limit"));
281 assert!(display.contains("60s"));
282
283 let err = ValidationError::Empty {
284 field: "model".to_string(),
285 };
286 let display = format!("{}", err);
287 assert!(display.contains("model"));
288 assert!(display.contains("empty"));
289 }
290
291 #[test]
292 fn test_healing_error_types() {
293 let err = HealingError::ParseFailed {
294 error_message: "unexpected token".to_string(),
295 input: "{invalid}".to_string(),
296 };
297 assert!(format!("{}", err).contains("parse"));
298
299 let err = HealingError::CoercionFailed {
300 from: "string".to_string(),
301 to: "number".to_string(),
302 };
303 assert!(format!("{}", err).contains("coercion"));
304 }
305}