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