edgequake_llm/providers/vscode/
error.rs1use thiserror::Error;
48
49pub type Result<T> = std::result::Result<T, VsCodeError>;
50
51#[derive(Error, Debug)]
53pub enum VsCodeError {
54 #[error("Failed to initialize client: {0}")]
56 ClientInit(String),
57
58 #[error("Proxy unavailable: {0}. Is copilot-api running on localhost:4141?")]
60 ProxyUnavailable(String),
61
62 #[error("Network error: {0}")]
64 Network(String),
65
66 #[error("Authentication failed: {0}")]
68 Authentication(String),
69
70 #[error("Rate limited. Try again later.")]
72 RateLimited,
73
74 #[error("Invalid request: {0}")]
76 InvalidRequest(String),
77
78 #[error("Service unavailable")]
80 ServiceUnavailable,
81
82 #[error("API error: {0}")]
84 ApiError(String),
85
86 #[error("Failed to decode response: {0}")]
88 Decode(String),
89
90 #[error("Stream error: {0}")]
92 Stream(String),
93}
94
95impl VsCodeError {
96 pub fn is_retryable(&self) -> bool {
131 matches!(
132 self,
133 VsCodeError::Network(_) | VsCodeError::RateLimited | VsCodeError::ServiceUnavailable
134 )
135 }
136}
137
138impl From<VsCodeError> for crate::error::LlmError {
140 fn from(err: VsCodeError) -> Self {
141 match err {
142 VsCodeError::ClientInit(msg) => Self::ConfigError(msg),
143 VsCodeError::ProxyUnavailable(msg) => Self::NetworkError(msg),
144 VsCodeError::Network(msg) => Self::NetworkError(msg),
145 VsCodeError::Authentication(msg) => Self::AuthError(msg),
146 VsCodeError::RateLimited => Self::RateLimited("Rate limit exceeded".to_string()),
147 VsCodeError::InvalidRequest(msg) => Self::InvalidRequest(msg),
148 VsCodeError::ServiceUnavailable => {
149 Self::NetworkError("Service unavailable".to_string())
150 }
151 VsCodeError::ApiError(msg) => Self::ApiError(msg),
152 VsCodeError::Decode(msg) => Self::ApiError(format!("Decode: {}", msg)),
153 VsCodeError::Stream(msg) => Self::ApiError(format!("Stream: {}", msg)),
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::error::LlmError;
162
163 #[test]
168 fn test_vscode_error_display_client_init() {
169 let err = VsCodeError::ClientInit("TLS handshake failed".to_string());
170 let msg = err.to_string();
171 assert!(msg.contains("Failed to initialize client"));
172 assert!(msg.contains("TLS handshake failed"));
173 }
174
175 #[test]
176 fn test_vscode_error_display_proxy_unavailable() {
177 let err = VsCodeError::ProxyUnavailable("connection refused".to_string());
178 let msg = err.to_string();
179 assert!(msg.contains("Proxy unavailable"));
180 assert!(msg.contains("connection refused"));
181 assert!(msg.contains("localhost:4141")); }
183
184 #[test]
185 fn test_vscode_error_display_network() {
186 let err = VsCodeError::Network("timeout after 30s".to_string());
187 assert_eq!(err.to_string(), "Network error: timeout after 30s");
188 }
189
190 #[test]
191 fn test_vscode_error_display_authentication() {
192 let err = VsCodeError::Authentication("token expired".to_string());
193 assert_eq!(err.to_string(), "Authentication failed: token expired");
194 }
195
196 #[test]
197 fn test_vscode_error_display_rate_limited() {
198 let err = VsCodeError::RateLimited;
199 assert_eq!(err.to_string(), "Rate limited. Try again later.");
200 }
201
202 #[test]
203 fn test_vscode_error_display_service_unavailable() {
204 let err = VsCodeError::ServiceUnavailable;
205 assert_eq!(err.to_string(), "Service unavailable");
206 }
207
208 #[test]
213 fn test_conversion_client_init_to_config_error() {
214 let vscode_err = VsCodeError::ClientInit("init failed".to_string());
215 let llm_err: LlmError = vscode_err.into();
216
217 match llm_err {
218 LlmError::ConfigError(msg) => assert_eq!(msg, "init failed"),
219 other => panic!("Expected ConfigError, got {:?}", other),
220 }
221 }
222
223 #[test]
224 fn test_conversion_proxy_unavailable_to_network_error() {
225 let vscode_err = VsCodeError::ProxyUnavailable("refused".to_string());
226 let llm_err: LlmError = vscode_err.into();
227
228 match llm_err {
229 LlmError::NetworkError(msg) => assert_eq!(msg, "refused"),
230 other => panic!("Expected NetworkError, got {:?}", other),
231 }
232 }
233
234 #[test]
235 fn test_conversion_network_to_network_error() {
236 let vscode_err = VsCodeError::Network("dns lookup failed".to_string());
237 let llm_err: LlmError = vscode_err.into();
238
239 match llm_err {
240 LlmError::NetworkError(msg) => assert_eq!(msg, "dns lookup failed"),
241 other => panic!("Expected NetworkError, got {:?}", other),
242 }
243 }
244
245 #[test]
246 fn test_conversion_authentication_to_auth_error() {
247 let vscode_err = VsCodeError::Authentication("invalid token".to_string());
248 let llm_err: LlmError = vscode_err.into();
249
250 match llm_err {
251 LlmError::AuthError(msg) => assert_eq!(msg, "invalid token"),
252 other => panic!("Expected AuthError, got {:?}", other),
253 }
254 }
255
256 #[test]
257 fn test_conversion_rate_limited() {
258 let vscode_err = VsCodeError::RateLimited;
259 let llm_err: LlmError = vscode_err.into();
260
261 match llm_err {
262 LlmError::RateLimited(msg) => assert!(msg.contains("Rate limit")),
263 other => panic!("Expected RateLimited, got {:?}", other),
264 }
265 }
266
267 #[test]
268 fn test_conversion_invalid_request() {
269 let vscode_err = VsCodeError::InvalidRequest("missing model".to_string());
270 let llm_err: LlmError = vscode_err.into();
271
272 match llm_err {
273 LlmError::InvalidRequest(msg) => assert_eq!(msg, "missing model"),
274 other => panic!("Expected InvalidRequest, got {:?}", other),
275 }
276 }
277
278 #[test]
279 fn test_conversion_service_unavailable() {
280 let vscode_err = VsCodeError::ServiceUnavailable;
281 let llm_err: LlmError = vscode_err.into();
282
283 match llm_err {
284 LlmError::NetworkError(msg) => assert!(msg.contains("unavailable")),
285 other => panic!("Expected NetworkError, got {:?}", other),
286 }
287 }
288
289 #[test]
290 fn test_conversion_api_error() {
291 let vscode_err = VsCodeError::ApiError("internal server error".to_string());
292 let llm_err: LlmError = vscode_err.into();
293
294 match llm_err {
295 LlmError::ApiError(msg) => assert_eq!(msg, "internal server error"),
296 other => panic!("Expected ApiError, got {:?}", other),
297 }
298 }
299
300 #[test]
301 fn test_conversion_decode_error() {
302 let vscode_err = VsCodeError::Decode("invalid JSON".to_string());
303 let llm_err: LlmError = vscode_err.into();
304
305 match llm_err {
306 LlmError::ApiError(msg) => {
307 assert!(msg.contains("Decode"));
308 assert!(msg.contains("invalid JSON"));
309 }
310 other => panic!("Expected ApiError, got {:?}", other),
311 }
312 }
313
314 #[test]
315 fn test_conversion_stream_error() {
316 let vscode_err = VsCodeError::Stream("connection reset".to_string());
317 let llm_err: LlmError = vscode_err.into();
318
319 match llm_err {
320 LlmError::ApiError(msg) => {
321 assert!(msg.contains("Stream"));
322 assert!(msg.contains("connection reset"));
323 }
324 other => panic!("Expected ApiError, got {:?}", other),
325 }
326 }
327
328 #[test]
334 fn test_is_retryable_network_error() {
335 let err = VsCodeError::Network("connection timeout".to_string());
337 assert!(err.is_retryable(), "Network errors should be retryable");
338 }
339
340 #[test]
341 fn test_is_retryable_rate_limited() {
342 let err = VsCodeError::RateLimited;
344 assert!(
345 err.is_retryable(),
346 "Rate limited errors should be retryable"
347 );
348 }
349
350 #[test]
351 fn test_is_retryable_service_unavailable() {
352 let err = VsCodeError::ServiceUnavailable;
354 assert!(
355 err.is_retryable(),
356 "Service unavailable should be retryable"
357 );
358 }
359
360 #[test]
361 fn test_is_not_retryable_auth_error() {
362 let err = VsCodeError::Authentication("token expired".to_string());
364 assert!(!err.is_retryable(), "Auth errors should not be retryable");
365 }
366
367 #[test]
368 fn test_is_not_retryable_invalid_request() {
369 let err = VsCodeError::InvalidRequest("missing model".to_string());
371 assert!(
372 !err.is_retryable(),
373 "Invalid request should not be retryable"
374 );
375 }
376
377 #[test]
378 fn test_is_not_retryable_client_init() {
379 let err = VsCodeError::ClientInit("TLS failed".to_string());
381 assert!(
382 !err.is_retryable(),
383 "Client init errors should not be retryable"
384 );
385 }
386
387 #[test]
388 fn test_is_not_retryable_api_error() {
389 let err = VsCodeError::ApiError("internal error".to_string());
391 assert!(!err.is_retryable(), "API errors should not be retryable");
392 }
393
394 #[test]
395 fn test_is_not_retryable_decode_error() {
396 let err = VsCodeError::Decode("invalid JSON".to_string());
398 assert!(!err.is_retryable(), "Decode errors should not be retryable");
399 }
400}