rusty_common/
error_utils.rs

1//! Error handling utilities for consistent error management across exchanges
2//!
3//! This module provides common error patterns and conversion utilities
4//! to reduce duplication across exchange implementations.
5
6use crate::{SmartString, error::CommonError};
7use anyhow::Result as AnyhowResult;
8
9/// Extension trait for anyhow Result type to provide common error conversions
10pub trait AnyhowErrorExt<T> {
11    /// Convert WebSocket connection errors to CommonError
12    fn websocket_connection_error(self, exchange: &str) -> Result<T, CommonError>;
13
14    /// Convert JSON parsing errors to CommonError
15    fn json_parse_error(self, context: &str) -> Result<T, CommonError>;
16
17    /// Convert API errors to CommonError
18    fn api_error(self, endpoint: &str) -> Result<T, CommonError>;
19}
20
21impl<T> AnyhowErrorExt<T> for AnyhowResult<T> {
22    #[inline]
23    fn websocket_connection_error(self, exchange: &str) -> Result<T, CommonError> {
24        self.map_err(|e| {
25            CommonError::Websocket(format!("Failed to connect to {exchange} WebSocket: {e}").into())
26        })
27    }
28
29    #[inline]
30    fn json_parse_error(self, context: &str) -> Result<T, CommonError> {
31        self.map_err(|e| CommonError::Json(format!("Failed to parse {context}: {e}").into()))
32    }
33
34    #[inline]
35    fn api_error(self, endpoint: &str) -> Result<T, CommonError> {
36        self.map_err(|e| CommonError::Api(format!("API error at {endpoint}: {e}").into()))
37    }
38}
39
40/// Common error messages builder for consistent error reporting
41pub struct ErrorMessages;
42
43impl ErrorMessages {
44    /// Create WebSocket connection error message
45    #[inline]
46    pub fn websocket_connect(exchange: &str) -> SmartString {
47        format!("Failed to connect to {exchange} WebSocket").into()
48    }
49
50    /// Create WebSocket timeout error message
51    #[inline]
52    pub fn websocket_timeout(exchange: &str) -> SmartString {
53        format!("{exchange} WebSocket message timeout").into()
54    }
55
56    /// Create JSON parsing error message
57    #[inline]
58    pub fn json_parse(message_type: &str) -> SmartString {
59        format!("Failed to parse {message_type} message").into()
60    }
61
62    /// Create authentication required error message
63    #[inline]
64    pub fn auth_required(endpoint: &str) -> SmartString {
65        format!("Authentication required for {endpoint}").into()
66    }
67
68    /// Create API response error message
69    #[inline]
70    pub fn api_response(status: u16, endpoint: &str) -> SmartString {
71        format!("API error {status}: {endpoint}").into()
72    }
73}
74
75/// Macro for consistent WebSocket error logging
76#[macro_export]
77macro_rules! log_websocket_error {
78    ($exchange:expr, $error:expr) => {
79        log::error!("Failed to connect to {} WebSocket: {}", $exchange, $error);
80    };
81}
82
83/// Macro for consistent JSON parsing error logging
84#[macro_export]
85macro_rules! log_parse_error {
86    ($message_type:expr, $error:expr) => {
87        log::error!("Failed to parse {} message: {}", $message_type, $error);
88    };
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use anyhow::anyhow;
95
96    #[test]
97    fn test_websocket_connection_error() {
98        let result: AnyhowResult<()> = Err(anyhow!("Connection refused"));
99        let error = result.websocket_connection_error("Binance").unwrap_err();
100
101        match error {
102            CommonError::Websocket(msg) => {
103                assert!(msg.contains("Failed to connect to Binance WebSocket"));
104                assert!(msg.contains("Connection refused"));
105            }
106            _ => panic!("Expected WebSocket error"),
107        }
108    }
109
110    #[test]
111    fn test_json_parse_error() {
112        let result: AnyhowResult<()> = Err(anyhow!("Invalid JSON"));
113        let error = result.json_parse_error("trade").unwrap_err();
114
115        match error {
116            CommonError::Json(msg) => {
117                assert!(msg.contains("Failed to parse trade"));
118                assert!(msg.contains("Invalid JSON"));
119            }
120            _ => panic!("Expected JSON error"),
121        }
122    }
123
124    #[test]
125    fn test_error_messages() {
126        let msg = ErrorMessages::websocket_connect("Upbit");
127        assert_eq!(msg.as_str(), "Failed to connect to Upbit WebSocket");
128
129        let msg = ErrorMessages::json_parse("orderbook");
130        assert_eq!(msg.as_str(), "Failed to parse orderbook message");
131
132        let msg = ErrorMessages::auth_required("private endpoint");
133        assert_eq!(msg.as_str(), "Authentication required for private endpoint");
134    }
135}