1use crate::transport::TransportError;
9use thiserror::Error;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ErrorSeverity {
13 Transient,
15 Client,
17 Server,
19 Fatal,
21}
22
23#[derive(Error, Debug, Clone)]
24pub enum AiLibError {
25 #[error("Provider error: {0}")]
26 ProviderError(String),
27
28 #[error("Transport error: {0}")]
29 TransportError(#[from] TransportError),
30
31 #[error("Invalid request: {0}")]
32 InvalidRequest(String),
33
34 #[error("Rate limit exceeded: {0}")]
35 RateLimitExceeded(String),
36
37 #[error("Authentication failed: {0}")]
38 AuthenticationError(String),
39
40 #[error("Configuration error: {0}")]
41 ConfigurationError(String),
42
43 #[error("Network error: {0}")]
44 NetworkError(String),
45
46 #[error("Timeout error: {0}")]
47 TimeoutError(String),
48
49 #[error("Retry exhausted: {0}")]
50 RetryExhausted(String),
51
52 #[error("Serialization error: {0}")]
53 SerializationError(String),
54
55 #[error("Deserialization error: {0}")]
56 DeserializationError(String),
57
58 #[error("File operation error: {0}")]
59 FileError(String),
60
61 #[error("Unsupported feature: {0}")]
62 UnsupportedFeature(String),
63
64 #[error("Model not found: {0}")]
65 ModelNotFound(String),
66
67 #[error("Invalid model response: {0}")]
68 InvalidModelResponse(String),
69
70 #[error("Context length exceeded: {0}")]
71 ContextLengthExceeded(String),
72}
73
74impl AiLibError {
75 pub fn severity(&self) -> ErrorSeverity {
77 match self {
78 AiLibError::NetworkError(_)
79 | AiLibError::TimeoutError(_)
80 | AiLibError::RateLimitExceeded(_) => ErrorSeverity::Transient,
81 AiLibError::InvalidRequest(_)
82 | AiLibError::ConfigurationError(_)
83 | AiLibError::ContextLengthExceeded(_)
84 | AiLibError::SerializationError(_)
85 | AiLibError::DeserializationError(_)
86 | AiLibError::FileError(_)
87 | AiLibError::ModelNotFound(_) => ErrorSeverity::Client,
88 AiLibError::AuthenticationError(_)
89 | AiLibError::UnsupportedFeature(_)
90 | AiLibError::RetryExhausted(_) => ErrorSeverity::Fatal,
91 AiLibError::ProviderError(_) | AiLibError::InvalidModelResponse(_) => {
92 ErrorSeverity::Server
93 }
94 AiLibError::TransportError(err) => match err {
95 TransportError::Timeout(_) | TransportError::RateLimitExceeded => {
96 ErrorSeverity::Transient
97 }
98 TransportError::InvalidUrl(_)
99 | TransportError::JsonError(_)
100 | TransportError::ClientError { .. } => ErrorSeverity::Client,
101 TransportError::AuthenticationError(_) => ErrorSeverity::Fatal,
102 TransportError::ServerError { .. } | TransportError::HttpError(_) => {
103 ErrorSeverity::Server
104 }
105 },
106 }
107 }
108
109 pub fn error_code(&self) -> &'static str {
111 match self {
112 AiLibError::ProviderError(_) => "PROVIDER_ERROR",
113 AiLibError::TransportError(_) => "TRANSPORT_ERROR",
114 AiLibError::InvalidRequest(_) => "INVALID_REQUEST",
115 AiLibError::RateLimitExceeded(_) => "RATE_LIMIT",
116 AiLibError::AuthenticationError(_) => "AUTH_FAILED",
117 AiLibError::ConfigurationError(_) => "CONFIG_ERROR",
118 AiLibError::NetworkError(_) => "NETWORK_ERROR",
119 AiLibError::TimeoutError(_) => "TIMEOUT",
120 AiLibError::RetryExhausted(_) => "RETRY_EXHAUSTED",
121 AiLibError::SerializationError(_) => "SERIALIZATION_ERROR",
122 AiLibError::DeserializationError(_) => "DESERIALIZATION_ERROR",
123 AiLibError::FileError(_) => "FILE_ERROR",
124 AiLibError::UnsupportedFeature(_) => "UNSUPPORTED_FEATURE",
125 AiLibError::ModelNotFound(_) => "MODEL_NOT_FOUND",
126 AiLibError::InvalidModelResponse(_) => "INVALID_RESPONSE",
127 AiLibError::ContextLengthExceeded(_) => "CONTEXT_TOO_LONG",
128 }
129 }
130
131 pub fn error_code_with_severity(&self) -> String {
133 format!("{:?}_{}", self.severity(), self.error_code()).to_uppercase()
134 }
135
136 pub fn with_context(self, context: impl Into<String>) -> Self {
138 let ctx = context.into();
139 match self {
140 AiLibError::ProviderError(msg) => AiLibError::ProviderError(prepend_context(&ctx, msg)),
141 AiLibError::InvalidRequest(msg) => {
142 AiLibError::InvalidRequest(prepend_context(&ctx, msg))
143 }
144 AiLibError::RateLimitExceeded(msg) => {
145 AiLibError::RateLimitExceeded(prepend_context(&ctx, msg))
146 }
147 AiLibError::AuthenticationError(msg) => {
148 AiLibError::AuthenticationError(prepend_context(&ctx, msg))
149 }
150 AiLibError::ConfigurationError(msg) => {
151 AiLibError::ConfigurationError(prepend_context(&ctx, msg))
152 }
153 AiLibError::NetworkError(msg) => AiLibError::NetworkError(prepend_context(&ctx, msg)),
154 AiLibError::TimeoutError(msg) => AiLibError::TimeoutError(prepend_context(&ctx, msg)),
155 AiLibError::RetryExhausted(msg) => {
156 AiLibError::RetryExhausted(prepend_context(&ctx, msg))
157 }
158 AiLibError::SerializationError(msg) => {
159 AiLibError::SerializationError(prepend_context(&ctx, msg))
160 }
161 AiLibError::DeserializationError(msg) => {
162 AiLibError::DeserializationError(prepend_context(&ctx, msg))
163 }
164 AiLibError::FileError(msg) => AiLibError::FileError(prepend_context(&ctx, msg)),
165 AiLibError::UnsupportedFeature(msg) => {
166 AiLibError::UnsupportedFeature(prepend_context(&ctx, msg))
167 }
168 AiLibError::ModelNotFound(msg) => AiLibError::ModelNotFound(prepend_context(&ctx, msg)),
169 AiLibError::InvalidModelResponse(msg) => {
170 AiLibError::InvalidModelResponse(prepend_context(&ctx, msg))
171 }
172 AiLibError::ContextLengthExceeded(msg) => {
173 AiLibError::ContextLengthExceeded(prepend_context(&ctx, msg))
174 }
175 AiLibError::TransportError(err) => {
176 AiLibError::TransportError(transport_with_context(err, &ctx))
177 }
178 }
179 }
180
181 pub fn is_retryable(&self) -> bool {
183 match self {
184 AiLibError::NetworkError(_) => true,
185 AiLibError::TimeoutError(_) => true,
186 AiLibError::RateLimitExceeded(_) => true,
187 AiLibError::TransportError(transport_err) => {
188 transport_err.to_string().contains("timeout")
190 || transport_err.to_string().contains("connection")
191 || transport_err.to_string().contains("temporary")
192 }
193 _ => false,
194 }
195 }
196
197 pub fn retry_delay_ms(&self) -> u64 {
199 match self {
200 AiLibError::RateLimitExceeded(_) => 60000, AiLibError::NetworkError(_) => 1000, AiLibError::TimeoutError(_) => 2000, _ => 1000,
204 }
205 }
206
207 pub fn context(&self) -> &str {
209 match self {
210 AiLibError::ProviderError(_) => "Provider API call failed",
211 AiLibError::TransportError(_) => "Network transport layer error",
212 AiLibError::InvalidRequest(_) => "Invalid request parameters",
213 AiLibError::RateLimitExceeded(_) => "API rate limit exceeded",
214 AiLibError::AuthenticationError(_) => "Authentication failed",
215 AiLibError::ConfigurationError(_) => "Configuration validation failed",
216 AiLibError::NetworkError(_) => "Network connectivity issue",
217 AiLibError::TimeoutError(_) => "Request timed out",
218 AiLibError::RetryExhausted(_) => "All retry attempts failed",
219 AiLibError::SerializationError(_) => "Request serialization failed",
220 AiLibError::DeserializationError(_) => "Response deserialization failed",
221 AiLibError::FileError(_) => "File operation failed",
222 AiLibError::UnsupportedFeature(_) => "Feature not supported by provider",
223 AiLibError::ModelNotFound(_) => "Specified model not found",
224 AiLibError::InvalidModelResponse(_) => "Invalid response from model",
225 AiLibError::ContextLengthExceeded(_) => "Context length limit exceeded",
226 }
227 }
228
229 pub fn is_auth_error(&self) -> bool {
231 match self {
232 AiLibError::AuthenticationError(_) => true,
233 AiLibError::TransportError(TransportError::AuthenticationError(_)) => true,
234 AiLibError::TransportError(TransportError::ClientError { status, .. }) => {
235 *status == 401 || *status == 403
236 }
237 _ => false,
238 }
239 }
240
241 pub fn is_config_error(&self) -> bool {
243 matches!(self, AiLibError::ConfigurationError(_))
244 }
245
246 pub fn is_request_error(&self) -> bool {
248 matches!(
249 self,
250 AiLibError::InvalidRequest(_)
251 | AiLibError::ContextLengthExceeded(_)
252 | AiLibError::UnsupportedFeature(_)
253 )
254 }
255}
256
257fn prepend_context(context: &str, message: String) -> String {
258 if message.is_empty() {
259 context.to_string()
260 } else {
261 format!("{context}: {message}")
262 }
263}
264
265fn transport_with_context(err: TransportError, context: &str) -> TransportError {
266 match err {
267 TransportError::HttpError(msg) => TransportError::HttpError(prepend_context(context, msg)),
268 TransportError::JsonError(msg) => TransportError::JsonError(prepend_context(context, msg)),
269 TransportError::InvalidUrl(msg) => {
270 TransportError::InvalidUrl(prepend_context(context, msg))
271 }
272 TransportError::AuthenticationError(msg) => {
273 TransportError::AuthenticationError(prepend_context(context, msg))
274 }
275 TransportError::RateLimitExceeded => TransportError::RateLimitExceeded,
276 TransportError::ServerError { status, message } => TransportError::ServerError {
277 status,
278 message: prepend_context(context, message),
279 },
280 TransportError::ClientError { status, message } => TransportError::ClientError {
281 status,
282 message: prepend_context(context, message),
283 },
284 TransportError::Timeout(msg) => TransportError::Timeout(prepend_context(context, msg)),
285 }
286}