nautilus_hyperliquid/http/
error.rs1use thiserror::Error;
17
18#[derive(Debug, Error)]
20pub enum Error {
21 #[error("transport error: {0}")]
23 Transport(String),
24
25 #[error("serde error: {0}")]
27 Serde(#[from] serde_json::Error),
28
29 #[error("auth error: {0}")]
31 Auth(String),
32
33 #[error("rate limited (retry_after={retry_after:?}s)")]
35 RateLimit { retry_after: Option<u64> },
36
37 #[error("nonce window error: {0}")]
39 NonceWindow(String),
40
41 #[error("bad request: {0}")]
43 BadRequest(String),
44
45 #[error("exchange error: {0}")]
47 Exchange(String),
48
49 #[error("timeout")]
51 Timeout,
52
53 #[error("decode error: {0}")]
55 Decode(String),
56
57 #[error("invariant violated: {0}")]
59 Invariant(&'static str),
60
61 #[error("HTTP error {status}: {message}")]
63 Http { status: u16, message: String },
64
65 #[error("URL parse error: {0}")]
67 UrlParse(#[from] url::ParseError),
68
69 #[error("IO error: {0}")]
71 Io(#[from] std::io::Error),
72}
73
74impl Error {
75 pub fn transport(msg: impl Into<String>) -> Self {
77 Self::Transport(msg.into())
78 }
79
80 pub fn auth(msg: impl Into<String>) -> Self {
82 Self::Auth(msg.into())
83 }
84
85 pub fn rate_limit(retry_after: Option<u64>) -> Self {
87 Self::RateLimit { retry_after }
88 }
89
90 pub fn nonce_window(msg: impl Into<String>) -> Self {
92 Self::NonceWindow(msg.into())
93 }
94
95 pub fn bad_request(msg: impl Into<String>) -> Self {
97 Self::BadRequest(msg.into())
98 }
99
100 pub fn exchange(msg: impl Into<String>) -> Self {
102 Self::Exchange(msg.into())
103 }
104
105 pub fn decode(msg: impl Into<String>) -> Self {
107 Self::Decode(msg.into())
108 }
109
110 pub fn http(status: u16, message: impl Into<String>) -> Self {
112 Self::Http {
113 status,
114 message: message.into(),
115 }
116 }
117
118 pub fn from_reqwest(error: reqwest::Error) -> Self {
120 if error.is_timeout() {
121 Self::Timeout
122 } else if let Some(status) = error.status() {
123 let status_code = status.as_u16();
124 match status_code {
125 401 | 403 => Self::auth(format!("HTTP {}: authentication failed", status_code)),
126 400 => Self::bad_request(format!("HTTP {}: bad request", status_code)),
127 429 => Self::rate_limit(None), 500..=599 => Self::exchange(format!("HTTP {}: server error", status_code)),
129 _ => Self::http(status_code, format!("HTTP error: {}", error)),
130 }
131 } else if error.is_connect() || error.is_request() {
132 Self::transport(format!("Request error: {}", error))
133 } else {
134 Self::transport(format!("Unknown reqwest error: {}", error))
135 }
136 }
137
138 pub fn is_retryable(&self) -> bool {
140 match self {
141 Error::Transport(_) | Error::Timeout | Error::RateLimit { .. } => true,
142 Error::Http { status, .. } => *status >= 500,
143 _ => false,
144 }
145 }
146
147 pub fn is_rate_limited(&self) -> bool {
149 matches!(self, Error::RateLimit { .. })
150 }
151
152 pub fn is_auth_error(&self) -> bool {
154 matches!(self, Error::Auth(_))
155 }
156}
157
158pub type Result<T> = std::result::Result<T, Error>;
160
161#[cfg(test)]
166mod tests {
167 use rstest::rstest;
168
169 use super::*;
170
171 #[rstest]
172 fn test_error_constructors() {
173 let transport_err = Error::transport("Connection failed");
174 assert!(matches!(transport_err, Error::Transport(_)));
175 assert_eq!(
176 transport_err.to_string(),
177 "transport error: Connection failed"
178 );
179
180 let auth_err = Error::auth("Invalid signature");
181 assert!(auth_err.is_auth_error());
182
183 let rate_limit_err = Error::rate_limit(Some(30));
184 assert!(rate_limit_err.is_rate_limited());
185 assert!(rate_limit_err.is_retryable());
186
187 let http_err = Error::http(500, "Internal server error");
188 assert!(http_err.is_retryable());
189 }
190
191 #[rstest]
192 fn test_error_display() {
193 let err = Error::RateLimit {
194 retry_after: Some(60),
195 };
196 assert_eq!(err.to_string(), "rate limited (retry_after=Some(60)s)");
197
198 let err = Error::NonceWindow("Nonce too old".to_string());
199 assert_eq!(err.to_string(), "nonce window error: Nonce too old");
200 }
201
202 #[rstest]
203 fn test_retryable_errors() {
204 assert!(Error::transport("test").is_retryable());
205 assert!(Error::Timeout.is_retryable());
206 assert!(Error::rate_limit(None).is_retryable());
207 assert!(Error::http(500, "server error").is_retryable());
208
209 assert!(!Error::auth("test").is_retryable());
210 assert!(!Error::bad_request("test").is_retryable());
211 assert!(!Error::decode("test").is_retryable());
212 }
213}