Skip to main content

marketdata_core/rest/
error.rs

1//! Error conversion from ureq to MarketDataError
2
3use crate::errors::MarketDataError;
4
5impl From<ureq::Error> for MarketDataError {
6    fn from(error: ureq::Error) -> Self {
7        match error {
8            // Status errors (HTTP response received with error code)
9            ureq::Error::Status(status, response) => {
10                let status_code = status;
11                let message = response
12                    .into_string()
13                    .unwrap_or_else(|_| format!("HTTP {}", status_code));
14
15                match status_code {
16                    // Authentication errors
17                    401 | 403 => MarketDataError::AuthError { msg: message },
18                    // Rate limiting and server errors (retryable in ApiError context)
19                    429 | 500..=599 => MarketDataError::ApiError {
20                        status: status_code,
21                        message,
22                    },
23                    // Other client/server errors
24                    _ => MarketDataError::ApiError {
25                        status: status_code,
26                        message,
27                    },
28                }
29            }
30            // Transport errors (connection issues, no HTTP response)
31            ureq::Error::Transport(transport) => {
32                let error_msg = transport.to_string();
33
34                // Check for timeout errors
35                if error_msg.contains("timed out") || error_msg.contains("timeout") {
36                    MarketDataError::TimeoutError {
37                        operation: error_msg,
38                    }
39                } else {
40                    // Other connection errors
41                    MarketDataError::ConnectionError { msg: error_msg }
42                }
43            }
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn test_error_is_retryable() {
54        // Connection errors should be retryable
55        let err = MarketDataError::ConnectionError {
56            msg: "test".to_string(),
57        };
58        assert!(err.is_retryable());
59
60        // Timeout errors should be retryable
61        let err = MarketDataError::TimeoutError {
62            operation: "test".to_string(),
63        };
64        assert!(err.is_retryable());
65
66        // Auth errors should NOT be retryable
67        let err = MarketDataError::AuthError {
68            msg: "test".to_string(),
69        };
70        assert!(!err.is_retryable());
71
72        // API errors with 4xx should NOT be retryable
73        let err = MarketDataError::ApiError {
74            status: 400,
75            message: "test".to_string(),
76        };
77        assert!(!err.is_retryable());
78
79        // API errors with 429 SHOULD be retryable
80        let err = MarketDataError::ApiError {
81            status: 429,
82            message: "rate limit".to_string(),
83        };
84        assert!(err.is_retryable());
85
86        // API errors with 5xx SHOULD be retryable
87        let err = MarketDataError::ApiError {
88            status: 503,
89            message: "service unavailable".to_string(),
90        };
91        assert!(err.is_retryable());
92    }
93
94    #[test]
95    fn test_status_401_converts_to_auth_error() {
96        // Simulate 401 error
97        let err = MarketDataError::AuthError {
98            msg: "HTTP 401".to_string(),
99        };
100        assert!(!err.is_retryable());
101        assert!(matches!(err, MarketDataError::AuthError { .. }));
102    }
103
104    #[test]
105    fn test_status_403_converts_to_auth_error() {
106        // Simulate 403 error
107        let err = MarketDataError::AuthError {
108            msg: "HTTP 403".to_string(),
109        };
110        assert!(!err.is_retryable());
111        assert!(matches!(err, MarketDataError::AuthError { .. }));
112    }
113
114    #[test]
115    fn test_status_429_converts_to_api_error_retryable() {
116        // Simulate 429 error
117        let err = MarketDataError::ApiError {
118            status: 429,
119            message: "Too Many Requests".to_string(),
120        };
121        assert!(err.is_retryable());
122        assert!(matches!(err, MarketDataError::ApiError { status: 429, .. }));
123    }
124
125    #[test]
126    fn test_status_500_converts_to_api_error_retryable() {
127        // Simulate 500 error
128        let err = MarketDataError::ApiError {
129            status: 500,
130            message: "Internal Server Error".to_string(),
131        };
132        assert!(err.is_retryable());
133        assert!(matches!(err, MarketDataError::ApiError { status: 500, .. }));
134    }
135
136    #[test]
137    fn test_timeout_converts_to_timeout_error() {
138        // Simulate timeout error
139        let err = MarketDataError::TimeoutError {
140            operation: "connection timed out".to_string(),
141        };
142        assert!(err.is_retryable());
143        assert!(matches!(err, MarketDataError::TimeoutError { .. }));
144    }
145}