1use thiserror::Error;
8use std::time::Duration;
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 {
53 message: String,
54 },
55
56 #[error("Parse error: {message}")]
58 Parse {
59 message: String,
60 #[source]
61 source: Option<Box<dyn std::error::Error + Send + Sync>>,
62 },
63
64 #[error("Timeout error: operation timed out after {duration:?}")]
66 Timeout {
67 duration: Duration,
68 operation: String,
69 },
70
71 #[error("Rate limit exceeded: {message}")]
73 RateLimit {
74 message: String,
75 retry_after: Option<Duration>,
76 },
77
78 #[error("Stream error: {message}")]
80 Stream {
81 message: String,
82 kind: StreamErrorKind,
83 },
84
85 #[error("Validation error: {message}")]
87 Validation {
88 message: String,
89 field: Option<String>,
90 },
91
92 #[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#[derive(Debug, Clone, PartialEq)]
103pub enum AuthErrorKind {
104 InvalidCredentials,
105 ExpiredCredentials,
106 InsufficientPermissions,
107 SignatureError,
108 NonceError,
109}
110
111#[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#[derive(Debug, Clone, PartialEq)]
128pub enum MarketDataErrorKind {
129 TokenNotFound,
130 MarketNotFound,
131 StaleData,
132 IncompleteData,
133 BookUnavailable,
134}
135
136#[derive(Debug, Clone, PartialEq)]
138pub enum StreamErrorKind {
139 ConnectionFailed,
140 ConnectionLost,
141 SubscriptionFailed,
142 MessageCorrupted,
143 Reconnecting,
144}
145
146impl PolyfillError {
147 pub fn is_retryable(&self) -> bool {
149 match self {
150 PolyfillError::Network { .. } => true,
151 PolyfillError::Api { status, .. } => {
152 *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 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 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 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
216impl 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
323impl 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), 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
370impl 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
450pub type Result<T> = std::result::Result<T, PolyfillError>;