rusty_ems/
error.rs

1use rusty_common::SmartString;
2use std::fmt::Write;
3use thiserror::Error;
4
5pub mod batch_errors;
6// Exchange-specific error parsing and rate limit handling
7pub mod exchange_errors;
8
9/// Errors that can occur in the Execution Management System
10#[derive(Error, Debug)]
11pub enum EMSError {
12    /// Connection errors with the exchange
13    #[error("Connection error: {0}")]
14    ConnectionError(SmartString),
15
16    /// Authentication errors
17    #[error("Authentication error: {0}")]
18    AuthenticationError(SmartString),
19
20    /// Order submission errors
21    #[error("Order submission error: {0}")]
22    OrderSubmissionError(SmartString),
23
24    /// Order cancellation errors
25    #[error("Order cancellation error: {0}")]
26    OrderCancellationError(SmartString),
27
28    /// Order modification errors
29    #[error("Order modification error: {0}")]
30    OrderModificationError(SmartString),
31
32    /// Rate limiting errors
33    #[error("Rate limit exceeded: {message}")]
34    RateLimitExceeded {
35        /// Error message describing the rate limit violation
36        message: SmartString,
37        /// Optional retry delay in milliseconds
38        retry_after_ms: Option<u64>,
39    },
40
41    /// Invalid order parameters
42    #[error("Invalid order parameters: {0}")]
43    InvalidOrderParameters(SmartString),
44
45    /// Insufficient balance errors
46    #[error("Insufficient balance: {0}")]
47    InsufficientBalance(SmartString),
48
49    /// Instrument not found errors
50    #[error("Instrument not found: {0}")]
51    InstrumentNotFound(SmartString),
52
53    /// Exchange API error with detailed information
54    #[error("Exchange API error ({exchange}): {code} - {message}")]
55    ExchangeApiError {
56        /// Name of the exchange that generated the error
57        exchange: SmartString,
58        /// Exchange-specific error code
59        code: i32,
60        /// Error message from the exchange
61        message: SmartString,
62        /// Optional additional error details
63        details: Option<SmartString>,
64    },
65
66    /// Operation not supported by exchange
67    #[error("{exchange} does not support {operation}")]
68    OperationNotSupported {
69        /// Name of the exchange that doesn't support the operation
70        exchange: SmartString,
71        /// Description of the unsupported operation
72        operation: SmartString,
73    },
74
75    /// Unknown errors
76    #[error("Unknown error: {0}")]
77    UnknownError(SmartString),
78
79    /// Error with JSON serialization/deserialization
80    #[error("JSON error: {0}")]
81    JsonError(#[from] simd_json::Error),
82
83    /// JSON parsing error with context
84    #[error("JSON parsing error: {context} - {message}")]
85    JsonParsingError {
86        /// Context where the JSON parsing error occurred
87        context: SmartString,
88        /// Detailed error message
89        message: SmartString,
90    },
91
92    /// General I/O errors
93    #[error("I/O error: {0}")]
94    IoError(#[from] std::io::Error),
95
96    /// Network/HTTP errors
97    #[error("Network error: {0}")]
98    NetworkError(SmartString),
99
100    /// Timeout errors
101    #[error("Timeout after {duration_ms}ms: {context}")]
102    Timeout {
103        /// Duration in milliseconds after which the timeout occurred
104        duration_ms: u64,
105        /// Context or operation that timed out
106        context: SmartString,
107    },
108
109    /// WebSocket errors
110    #[error("WebSocket error: {0}")]
111    WebSocketError(SmartString),
112
113    /// WebSocket frame errors from yawc
114    #[error("WebSocket frame error: {0}")]
115    WebSocketFrameError(String),
116}
117
118/// Result type alias for EMS operations
119pub type Result<T> = std::result::Result<T, EMSError>;
120
121/// Conversion from `flume::SendError`
122impl<T> From<flume::SendError<T>> for EMSError {
123    fn from(err: flume::SendError<T>) -> Self {
124        Self::channel_send_error(&err)
125    }
126}
127
128/// Helper trait for converting anyhow errors to `EMSError`
129pub trait AnyhowToEmsError<T> {
130    /// Convert an anyhow::Result to an EMSError Result
131    fn to_ems_error(self) -> Result<T>;
132}
133
134impl<T> AnyhowToEmsError<T> for anyhow::Result<T> {
135    fn to_ems_error(self) -> Result<T> {
136        self.map_err(|e| EMSError::UnknownError(e.to_string().into()))
137    }
138}
139
140/// Helper functions for common error scenarios
141impl EMSError {
142    /// Create a connection error with a descriptive message
143    pub fn connection<S: Into<SmartString>>(msg: S) -> Self {
144        Self::ConnectionError(msg.into())
145    }
146
147    /// Create an authentication error with a descriptive message
148    pub fn auth<S: Into<SmartString>>(msg: S) -> Self {
149        Self::AuthenticationError(msg.into())
150    }
151
152    /// Create an order submission error with a descriptive message
153    pub fn order_submission<S: Into<SmartString>>(msg: S) -> Self {
154        Self::OrderSubmissionError(msg.into())
155    }
156
157    /// Create an order cancellation error with a descriptive message
158    pub fn order_cancellation<S: Into<SmartString>>(msg: S) -> Self {
159        Self::OrderCancellationError(msg.into())
160    }
161
162    /// Create an order modification error with a descriptive message
163    pub fn order_modification<S: Into<SmartString>>(msg: S) -> Self {
164        Self::OrderModificationError(msg.into())
165    }
166
167    /// Create a rate limit error
168    pub fn rate_limit<S: Into<SmartString>>(msg: S, retry_after_ms: Option<u64>) -> Self {
169        Self::RateLimitExceeded {
170            message: msg.into(),
171            retry_after_ms,
172        }
173    }
174
175    /// Create an invalid order parameters error
176    pub fn invalid_params<S: Into<SmartString>>(msg: S) -> Self {
177        Self::InvalidOrderParameters(msg.into())
178    }
179
180    /// Create an insufficient balance error
181    pub fn insufficient_balance<S: Into<SmartString>>(msg: S) -> Self {
182        Self::InsufficientBalance(msg.into())
183    }
184
185    /// Create an instrument not found error
186    pub fn instrument_not_found<S: Into<SmartString>>(symbol: S) -> Self {
187        Self::InstrumentNotFound(symbol.into())
188    }
189
190    /// Create an exchange API error
191    pub fn exchange_api<S1, S2, S3>(
192        exchange: S1,
193        code: i32,
194        message: S2,
195        details: Option<S3>,
196    ) -> Self
197    where
198        S1: Into<SmartString>,
199        S2: Into<SmartString>,
200        S3: Into<SmartString>,
201    {
202        Self::ExchangeApiError {
203            exchange: exchange.into(),
204            code,
205            message: message.into(),
206            details: details.map(std::convert::Into::into),
207        }
208    }
209
210    /// Create an operation not supported error
211    pub fn not_supported<S1, S2>(exchange: S1, operation: S2) -> Self
212    where
213        S1: Into<SmartString>,
214        S2: Into<SmartString>,
215    {
216        Self::OperationNotSupported {
217            exchange: exchange.into(),
218            operation: operation.into(),
219        }
220    }
221
222    /// Create a WebSocket error
223    pub fn websocket<S: Into<SmartString>>(msg: S) -> Self {
224        Self::WebSocketError(msg.into())
225    }
226
227    /// Create a timeout error
228    pub fn timeout<S: Into<SmartString>>(duration_ms: u64, context: S) -> Self {
229        Self::Timeout {
230            duration_ms,
231            context: context.into(),
232        }
233    }
234
235    /// Create a JSON parsing error with context
236    pub fn json_parse<S1, S2>(context: S1, message: S2) -> Self
237    where
238        S1: Into<SmartString>,
239        S2: Into<SmartString>,
240    {
241        Self::JsonParsingError {
242            context: context.into(),
243            message: message.into(),
244        }
245    }
246
247    /// Create an internal error
248    pub fn internal<S: Into<SmartString>>(msg: S) -> Self {
249        Self::UnknownError(msg.into())
250    }
251
252    /// Optimized error creation helpers that avoid temporary String allocations
253    /// These use write! directly into `SmartString` for better performance
254    /// Create a channel send error with optimized formatting
255    pub fn channel_send_error(err: &impl std::fmt::Display) -> Self {
256        let mut msg = SmartString::new();
257        // Use write! with expect for better error message in case of failure
258        write!(msg, "Channel send error: {err}").expect("Failed to format channel send error");
259        Self::UnknownError(msg)
260    }
261
262    /// Create a JSON error with optimized formatting
263    pub fn json_error(err: &impl std::fmt::Display) -> Self {
264        let mut msg = SmartString::new();
265        write!(msg, "JSON error: {err}").expect("Failed to format JSON error");
266        Self::UnknownError(msg)
267    }
268
269    /// Create a connection failed error with optimized formatting
270    pub fn connection_failed(err: &impl std::fmt::Display) -> Self {
271        let mut msg = SmartString::new();
272        write!(msg, "Connection failed: {err}").expect("Failed to format connection error");
273        Self::ConnectionError(msg)
274    }
275
276    /// Create an HTTP status error with optimized formatting
277    #[must_use]
278    pub fn http_status_error(status: u16) -> SmartString {
279        let mut msg = SmartString::new();
280        write!(msg, "HTTP {status}").expect("Failed to format HTTP status error");
281        msg
282    }
283
284    /// Create a rate limit error with optimized formatting
285    #[must_use]
286    pub fn rate_limit_with_exchange(exchange: &str, msg: &str) -> SmartString {
287        let mut message = SmartString::new();
288        write!(message, "{exchange} rate limit exceeded: {msg}")
289            .expect("Failed to format rate limit error");
290        message
291    }
292
293    /// Create an authentication failed error with optimized formatting
294    #[must_use]
295    pub fn auth_failed_error(exchange: &str) -> Self {
296        let mut msg = SmartString::new();
297        write!(msg, "{exchange} authentication failed").expect("Failed to format auth error");
298        Self::AuthenticationError(msg)
299    }
300
301    /// Create an access forbidden error with optimized formatting
302    #[must_use]
303    pub fn access_forbidden_error(exchange: &str) -> Self {
304        let mut msg = SmartString::new();
305        write!(msg, "{exchange} access forbidden")
306            .expect("Failed to format access forbidden error");
307        Self::AuthenticationError(msg)
308    }
309
310    /// Create a rate limit exceeded error with optimized formatting
311    #[must_use]
312    pub fn rate_limit_exceeded_error(exchange: &str) -> SmartString {
313        let mut msg = SmartString::new();
314        write!(msg, "{exchange} rate limit exceeded")
315            .expect("Failed to format rate limit exceeded error");
316        msg
317    }
318
319    /// Create an internal server error with optimized formatting
320    #[must_use]
321    pub fn internal_server_error(exchange: &str) -> Self {
322        let mut msg = SmartString::new();
323        write!(msg, "{exchange} internal server error")
324            .expect("Failed to format internal server error");
325        Self::ConnectionError(msg)
326    }
327
328    /// Create a service unavailable error with optimized formatting
329    #[must_use]
330    pub fn service_unavailable_error(exchange: &str) -> Self {
331        let mut msg = SmartString::new();
332        write!(msg, "{exchange} service unavailable")
333            .expect("Failed to format service unavailable error");
334        Self::ConnectionError(msg)
335    }
336
337    /// Create a gateway timeout error with optimized formatting
338    #[must_use]
339    pub fn gateway_timeout_error(exchange: &str) -> SmartString {
340        let mut msg = SmartString::new();
341        write!(msg, "{exchange} gateway timeout").expect("Failed to format gateway timeout error");
342        msg
343    }
344
345    // Auth-specific error helpers
346    /// Create an auth error with optimized formatting for exchange auth failures
347    pub fn auth_exchange_failed(exchange: &str, err: &impl std::fmt::Display) -> Self {
348        let mut msg = SmartString::new();
349        write!(msg, "{exchange} auth failed: {err}").expect("Failed to format auth exchange error");
350        Self::AuthenticationError(msg)
351    }
352
353    /// Create an auth error with optimized formatting for header name errors
354    pub fn auth_invalid_header_name(err: &impl std::fmt::Display) -> Self {
355        let mut msg = SmartString::new();
356        write!(msg, "Invalid header name: {err}")
357            .expect("Failed to format invalid header name error");
358        Self::AuthenticationError(msg)
359    }
360
361    /// Create an auth error with optimized formatting for header value errors
362    pub fn auth_invalid_header_value(err: &impl std::fmt::Display) -> Self {
363        let mut msg = SmartString::new();
364        write!(msg, "Invalid header value: {err}")
365            .expect("Failed to format invalid header value error");
366        Self::AuthenticationError(msg)
367    }
368
369    /// Check if error is recoverable (e.g., temporary network issues)
370    #[must_use]
371    pub const fn is_recoverable(&self) -> bool {
372        matches!(
373            self,
374            Self::ConnectionError(_)
375                | Self::RateLimitExceeded { .. }
376                | Self::Timeout { .. }
377                | Self::NetworkError(_)
378                | Self::WebSocketError(_)
379        )
380    }
381
382    /// Get retry delay in milliseconds if applicable
383    #[must_use]
384    pub const fn retry_delay_ms(&self) -> Option<u64> {
385        match self {
386            Self::RateLimitExceeded { retry_after_ms, .. } => *retry_after_ms,
387            Self::Timeout { .. } => Some(5000), // Default 5 second retry
388            Self::ConnectionError(_) | Self::NetworkError(_) => Some(1000), // Default 1 second retry
389            _ => None,
390        }
391    }
392}
393
394/// Manual Clone implementation for `EMSError` (some inner types don't implement Clone)
395impl Clone for EMSError {
396    fn clone(&self) -> Self {
397        match self {
398            Self::ConnectionError(s) => Self::ConnectionError(s.clone()),
399            Self::AuthenticationError(s) => Self::AuthenticationError(s.clone()),
400            Self::OrderSubmissionError(s) => Self::OrderSubmissionError(s.clone()),
401            Self::OrderCancellationError(s) => Self::OrderCancellationError(s.clone()),
402            Self::OrderModificationError(s) => Self::OrderModificationError(s.clone()),
403            Self::RateLimitExceeded {
404                message,
405                retry_after_ms,
406            } => Self::RateLimitExceeded {
407                message: message.clone(),
408                retry_after_ms: *retry_after_ms,
409            },
410            Self::InvalidOrderParameters(s) => Self::InvalidOrderParameters(s.clone()),
411            Self::InsufficientBalance(s) => Self::InsufficientBalance(s.clone()),
412            Self::InstrumentNotFound(s) => Self::InstrumentNotFound(s.clone()),
413            Self::ExchangeApiError {
414                exchange,
415                code,
416                message,
417                details,
418            } => Self::ExchangeApiError {
419                exchange: exchange.clone(),
420                code: *code,
421                message: message.clone(),
422                details: details.clone(),
423            },
424            Self::OperationNotSupported {
425                exchange,
426                operation,
427            } => Self::OperationNotSupported {
428                exchange: exchange.clone(),
429                operation: operation.clone(),
430            },
431            Self::UnknownError(s) => Self::UnknownError(s.clone()),
432            Self::JsonError(e) => Self::json_error(e),
433            Self::JsonParsingError { context, message } => Self::JsonParsingError {
434                context: context.clone(),
435                message: message.clone(),
436            },
437            Self::IoError(e) => Self::IoError(std::io::Error::new(e.kind(), e.to_string())),
438            Self::NetworkError(s) => Self::NetworkError(s.clone()),
439            Self::Timeout {
440                duration_ms,
441                context,
442            } => Self::Timeout {
443                duration_ms: *duration_ms,
444                context: context.clone(),
445            },
446            Self::WebSocketError(s) => Self::WebSocketError(s.clone()),
447            Self::WebSocketFrameError(s) => Self::WebSocketFrameError(s.clone()),
448        }
449    }
450}
451
452/// Conversion from `CommonError`
453impl From<rusty_common::CommonError> for EMSError {
454    fn from(err: rusty_common::CommonError) -> Self {
455        Self::UnknownError(err.to_string().into())
456    }
457}
458
459/// Conversion from `anyhow::Error`
460impl From<anyhow::Error> for EMSError {
461    fn from(err: anyhow::Error) -> Self {
462        // Try to downcast to specific error types
463        if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
464            return Self::IoError(io_err.kind().into());
465        }
466
467        if let Some(req_err) = err.downcast_ref::<reqwest::Error>() {
468            if req_err.is_timeout() {
469                return Self::Timeout {
470                    duration_ms: 30000, // Default timeout
471                    context: "Request timed out".into(),
472                };
473            }
474            // For now, just return as string - reqwest::Error doesn't implement From<String>
475            return Self::NetworkError(req_err.to_string().into());
476        }
477
478        // Default to unknown error
479        Self::UnknownError(err.to_string().into())
480    }
481}
482
483/// Conversion from `reqwest::Error`
484impl From<reqwest::Error> for EMSError {
485    fn from(err: reqwest::Error) -> Self {
486        if err.is_timeout() {
487            Self::Timeout {
488                duration_ms: 30000, // Default timeout
489                context: "Request timed out".into(),
490            }
491        } else if err.is_connect() {
492            Self::connection_failed(&err)
493        } else if err.is_status() {
494            if let Some(status) = err.status() {
495                Self::ExchangeApiError {
496                    exchange: "Unknown".into(),
497                    code: i32::from(status.as_u16()),
498                    message: Self::http_status_error(status.as_u16()),
499                    details: Some(err.to_string().into()),
500                }
501            } else {
502                Self::NetworkError(err.to_string().into())
503            }
504        } else {
505            Self::NetworkError(err.to_string().into())
506        }
507    }
508}
509
510/// Helper macro for converting exchange-specific errors
511#[macro_export]
512macro_rules! exchange_error {
513    ($exchange:expr, $code:expr, $msg:expr) => {
514        EMSError::exchange_api($exchange, $code, $msg, None::<String>)
515    };
516    ($exchange:expr, $code:expr, $msg:expr, $details:expr) => {
517        EMSError::exchange_api($exchange, $code, $msg, Some($details))
518    };
519}
520
521/// Helper macro for operation not supported errors
522#[macro_export]
523macro_rules! not_supported {
524    ($exchange:expr, $operation:expr) => {
525        EMSError::not_supported($exchange, $operation)
526    };
527}