polyfill_rs/
errors.rs

1//! Error types for the Polymarket client
2//!
3//! This module defines all error types used throughout the client, designed
4//! for clear error handling in trading environments where fast error recovery
5//! is critical.
6
7use thiserror::Error;
8use std::time::Duration;
9
10/// Main error type for the Polymarket client
11#[derive(Error, Debug)]
12pub enum PolyfillError {
13    /// Network-related errors (retryable)
14    #[error("Network error: {message}")]
15    Network { 
16        message: String,
17        #[source]
18        source: Option<Box<dyn std::error::Error + Send + Sync>>,
19    },
20
21    /// API errors from Polymarket
22    #[error("API error ({status}): {message}")]
23    Api { 
24        status: u16,
25        message: String,
26        error_code: Option<String>,
27    },
28
29    /// Authentication/authorization errors
30    #[error("Auth error: {message}")]
31    Auth { 
32        message: String,
33        kind: AuthErrorKind,
34    },
35
36    /// Order-related errors
37    #[error("Order error: {message}")]
38    Order { 
39        message: String,
40        kind: OrderErrorKind,
41    },
42
43    /// Market data errors
44    #[error("Market data error: {message}")]
45    MarketData { 
46        message: String,
47        kind: MarketDataErrorKind,
48    },
49
50    /// Configuration errors
51    #[error("Config error: {message}")]
52    Config { 
53        message: String,
54    },
55
56    /// Parsing/serialization errors
57    #[error("Parse error: {message}")]
58    Parse { 
59        message: String,
60        #[source]
61        source: Option<Box<dyn std::error::Error + Send + Sync>>,
62    },
63
64    /// Timeout errors
65    #[error("Timeout error: operation timed out after {duration:?}")]
66    Timeout { 
67        duration: Duration,
68        operation: String,
69    },
70
71    /// Rate limiting errors
72    #[error("Rate limit exceeded: {message}")]
73    RateLimit { 
74        message: String,
75        retry_after: Option<Duration>,
76    },
77
78    /// WebSocket/streaming errors
79    #[error("Stream error: {message}")]
80    Stream { 
81        message: String,
82        kind: StreamErrorKind,
83    },
84
85    /// Validation errors
86    #[error("Validation error: {message}")]
87    Validation { 
88        message: String,
89        field: Option<String>,
90    },
91
92    /// Internal errors (bugs)
93    #[error("Internal error: {message}")]
94    Internal { 
95        message: String,
96        #[source]
97        source: Option<Box<dyn std::error::Error + Send + Sync>>,
98    },
99}
100
101/// Authentication error subcategories
102#[derive(Debug, Clone, PartialEq)]
103pub enum AuthErrorKind {
104    InvalidCredentials,
105    ExpiredCredentials,
106    InsufficientPermissions,
107    SignatureError,
108    NonceError,
109}
110
111/// Order error subcategories
112#[derive(Debug, Clone, PartialEq)]
113pub enum OrderErrorKind {
114    InvalidPrice,
115    InvalidSize,
116    InsufficientBalance,
117    MarketClosed,
118    DuplicateOrder,
119    OrderNotFound,
120    CancellationFailed,
121    ExecutionFailed,
122    SizeConstraint,
123    PriceConstraint,
124}
125
126/// Market data error subcategories
127#[derive(Debug, Clone, PartialEq)]
128pub enum MarketDataErrorKind {
129    TokenNotFound,
130    MarketNotFound,
131    StaleData,
132    IncompleteData,
133    BookUnavailable,
134}
135
136/// Streaming error subcategories
137#[derive(Debug, Clone, PartialEq)]
138pub enum StreamErrorKind {
139    ConnectionFailed,
140    ConnectionLost,
141    SubscriptionFailed,
142    MessageCorrupted,
143    Reconnecting,
144}
145
146impl PolyfillError {
147    /// Check if this error is retryable
148    pub fn is_retryable(&self) -> bool {
149        match self {
150            PolyfillError::Network { .. } => true,
151            PolyfillError::Api { status, .. } => {
152                // 5xx errors are typically retryable
153                *status >= 500 && *status < 600
154            },
155            PolyfillError::Timeout { .. } => true,
156            PolyfillError::RateLimit { .. } => true,
157            PolyfillError::Stream { kind, .. } => {
158                matches!(kind, StreamErrorKind::ConnectionLost | StreamErrorKind::Reconnecting)
159            },
160            _ => false,
161        }
162    }
163
164    /// Get suggested retry delay
165    pub fn retry_delay(&self) -> Option<Duration> {
166        match self {
167            PolyfillError::Network { .. } => Some(Duration::from_millis(100)),
168            PolyfillError::Api { status, .. } => {
169                if *status >= 500 {
170                    Some(Duration::from_millis(500))
171                } else {
172                    None
173                }
174            },
175            PolyfillError::Timeout { .. } => Some(Duration::from_millis(50)),
176            PolyfillError::RateLimit { retry_after, .. } => {
177                retry_after.or(Some(Duration::from_secs(1)))
178            },
179            PolyfillError::Stream { .. } => Some(Duration::from_millis(250)),
180            _ => None,
181        }
182    }
183
184    /// Check if this is a critical error that should stop trading
185    pub fn is_critical(&self) -> bool {
186        match self {
187            PolyfillError::Auth { .. } => true,
188            PolyfillError::Config { .. } => true,
189            PolyfillError::Internal { .. } => true,
190            PolyfillError::Order { kind, .. } => {
191                matches!(kind, OrderErrorKind::InsufficientBalance)
192            },
193            _ => false,
194        }
195    }
196
197    /// Get error category for metrics
198    pub fn category(&self) -> &'static str {
199        match self {
200            PolyfillError::Network { .. } => "network",
201            PolyfillError::Api { .. } => "api",
202            PolyfillError::Auth { .. } => "auth",
203            PolyfillError::Order { .. } => "order",
204            PolyfillError::MarketData { .. } => "market_data",
205            PolyfillError::Config { .. } => "config",
206            PolyfillError::Parse { .. } => "parse",
207            PolyfillError::Timeout { .. } => "timeout",
208            PolyfillError::RateLimit { .. } => "rate_limit",
209            PolyfillError::Stream { .. } => "stream",
210            PolyfillError::Validation { .. } => "validation",
211            PolyfillError::Internal { .. } => "internal",
212        }
213    }
214}
215
216// Convenience constructors
217impl PolyfillError {
218    pub fn network<E: std::error::Error + Send + Sync + 'static>(
219        message: impl Into<String>,
220        source: E,
221    ) -> Self {
222        Self::Network {
223            message: message.into(),
224            source: Some(Box::new(source)),
225        }
226    }
227
228    pub fn api(status: u16, message: impl Into<String>) -> Self {
229        Self::Api {
230            status,
231            message: message.into(),
232            error_code: None,
233        }
234    }
235
236    pub fn auth(message: impl Into<String>) -> Self {
237        Self::Auth {
238            message: message.into(),
239            kind: AuthErrorKind::SignatureError,
240        }
241    }
242
243    pub fn crypto(message: impl Into<String>) -> Self {
244        Self::Auth {
245            message: message.into(),
246            kind: AuthErrorKind::SignatureError,
247        }
248    }
249
250    pub fn order(message: impl Into<String>, kind: OrderErrorKind) -> Self {
251        Self::Order {
252            message: message.into(),
253            kind,
254        }
255    }
256
257    pub fn market_data(message: impl Into<String>, kind: MarketDataErrorKind) -> Self {
258        Self::MarketData {
259            message: message.into(),
260            kind,
261        }
262    }
263
264    pub fn config(message: impl Into<String>) -> Self {
265        Self::Config {
266            message: message.into(),
267        }
268    }
269
270    pub fn parse(message: impl Into<String>, source: Option<Box<dyn std::error::Error + Send + Sync>>) -> Self {
271        Self::Parse {
272            message: message.into(),
273            source,
274        }
275    }
276
277    pub fn timeout(duration: Duration, operation: impl Into<String>) -> Self {
278        Self::Timeout {
279            duration,
280            operation: operation.into(),
281        }
282    }
283
284    pub fn rate_limit(message: impl Into<String>) -> Self {
285        Self::RateLimit {
286            message: message.into(),
287            retry_after: None,
288        }
289    }
290
291    pub fn stream(message: impl Into<String>, kind: StreamErrorKind) -> Self {
292        Self::Stream {
293            message: message.into(),
294            kind,
295        }
296    }
297
298    pub fn validation(message: impl Into<String>) -> Self {
299        Self::Validation {
300            message: message.into(),
301            field: None,
302        }
303    }
304
305    pub fn internal<E: std::error::Error + Send + Sync + 'static>(
306        message: impl Into<String>,
307        source: E,
308    ) -> Self {
309        Self::Internal {
310            message: message.into(),
311            source: Some(Box::new(source)),
312        }
313    }
314
315    pub fn internal_simple(message: impl Into<String>) -> Self {
316        Self::Internal {
317            message: message.into(),
318            source: None,
319        }
320    }
321}
322
323// Implement From for common external error types
324impl From<reqwest::Error> for PolyfillError {
325    fn from(err: reqwest::Error) -> Self {
326        if err.is_timeout() {
327            PolyfillError::Timeout {
328                duration: Duration::from_secs(30), // default timeout
329                operation: "HTTP request".to_string(),
330            }
331        } else if err.is_connect() || err.is_request() {
332            PolyfillError::network("HTTP request failed", err)
333        } else {
334            PolyfillError::internal("Unexpected reqwest error", err)
335        }
336    }
337}
338
339impl From<serde_json::Error> for PolyfillError {
340    fn from(err: serde_json::Error) -> Self {
341        PolyfillError::Parse {
342            message: format!("JSON parsing failed: {}", err),
343            source: Some(Box::new(err)),
344        }
345    }
346}
347
348impl From<url::ParseError> for PolyfillError {
349    fn from(err: url::ParseError) -> Self {
350        PolyfillError::config(format!("Invalid URL: {}", err))
351    }
352}
353
354#[cfg(feature = "stream")]
355impl From<tokio_tungstenite::tungstenite::Error> for PolyfillError {
356    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
357        use tokio_tungstenite::tungstenite::Error as WsError;
358        
359        let kind = match &err {
360            WsError::ConnectionClosed | WsError::AlreadyClosed => StreamErrorKind::ConnectionLost,
361            WsError::Io(_) => StreamErrorKind::ConnectionFailed,
362            WsError::Protocol(_) => StreamErrorKind::MessageCorrupted,
363            _ => StreamErrorKind::ConnectionFailed,
364        };
365
366        PolyfillError::stream(format!("WebSocket error: {}", err), kind)
367    }
368}
369
370// Manual Clone implementation since Box<dyn Error> doesn't implement Clone
371impl Clone for PolyfillError {
372    fn clone(&self) -> Self {
373        match self {
374            PolyfillError::Network { message, source: _ } => {
375                PolyfillError::Network { 
376                    message: message.clone(), 
377                    source: None 
378                }
379            }
380            PolyfillError::Api { status, message, error_code } => {
381                PolyfillError::Api { 
382                    status: *status, 
383                    message: message.clone(), 
384                    error_code: error_code.clone() 
385                }
386            }
387            PolyfillError::Auth { message, kind } => {
388                PolyfillError::Auth { 
389                    message: message.clone(), 
390                    kind: kind.clone() 
391                }
392            }
393            PolyfillError::Order { message, kind } => {
394                PolyfillError::Order { 
395                    message: message.clone(), 
396                    kind: kind.clone() 
397                }
398            }
399            PolyfillError::MarketData { message, kind } => {
400                PolyfillError::MarketData { 
401                    message: message.clone(), 
402                    kind: kind.clone() 
403                }
404            }
405            PolyfillError::Config { message } => {
406                PolyfillError::Config { 
407                    message: message.clone() 
408                }
409            }
410            PolyfillError::Parse { message, source: _ } => {
411                PolyfillError::Parse { 
412                    message: message.clone(), 
413                    source: None 
414                }
415            }
416            PolyfillError::Timeout { duration, operation } => {
417                PolyfillError::Timeout { 
418                    duration: *duration, 
419                    operation: operation.clone() 
420                }
421            }
422            PolyfillError::RateLimit { message, retry_after } => {
423                PolyfillError::RateLimit { 
424                    message: message.clone(), 
425                    retry_after: *retry_after 
426                }
427            }
428            PolyfillError::Stream { message, kind } => {
429                PolyfillError::Stream { 
430                    message: message.clone(), 
431                    kind: kind.clone() 
432                }
433            }
434            PolyfillError::Validation { message, field } => {
435                PolyfillError::Validation { 
436                    message: message.clone(), 
437                    field: field.clone() 
438                }
439            }
440            PolyfillError::Internal { message, source: _ } => {
441                PolyfillError::Internal { 
442                    message: message.clone(), 
443                    source: None 
444                }
445            }
446        }
447    }
448}
449
450/// Result type alias for convenience
451pub type Result<T> = std::result::Result<T, PolyfillError>;