rustrade_execution/client/hyperliquid/
error.rs1use crate::error::{
4 ApiError, ConnectivityError, UnindexedApiError, UnindexedClientError, UnindexedOrderError,
5};
6use rustrade_instrument::instrument::name::InstrumentNameExchange;
7
8pub fn map_sdk_error(error: hyperliquid_rust_sdk::Error) -> UnindexedClientError {
13 let msg = error.to_string();
14 let msg_lower = msg.to_lowercase();
15
16 if msg_lower.contains("rate limit")
18 || msg_lower.contains("too many requests")
19 || msg_lower.contains("429")
20 {
21 return UnindexedClientError::Api(UnindexedApiError::RateLimit);
22 }
23
24 if msg_lower.contains("connection")
27 || msg_lower.contains("timeout")
28 || msg_lower.contains("network")
29 || msg_lower.contains("dns")
30 || msg_lower.contains("tls")
31 || msg_lower.contains("ssl")
32 || msg_lower.contains(" 500")
33 || msg_lower.contains(" 502")
34 || msg_lower.contains(" 503")
35 || msg_lower.contains(" 504")
36 || msg_lower.contains("bad gateway")
37 || msg_lower.contains("service unavailable")
38 || msg_lower.contains("gateway timeout")
39 {
40 return UnindexedClientError::Connectivity(ConnectivityError::Socket(msg));
41 }
42
43 if msg_lower.contains("signature")
46 || msg_lower.contains("unauthorized")
47 || msg_lower.contains("invalid key")
48 || msg_lower.contains("authentication")
49 || msg_lower.contains("eip712")
50 {
51 return UnindexedClientError::Api(UnindexedApiError::Unauthenticated(msg));
52 }
53
54 UnindexedClientError::Internal(msg)
56}
57
58pub fn map_order_error(
62 error: hyperliquid_rust_sdk::Error,
63 instrument: &InstrumentNameExchange,
64) -> UnindexedOrderError {
65 let msg = error.to_string();
66 let msg_lower = msg.to_lowercase();
67
68 if msg_lower.contains("unknown")
70 || msg_lower.contains("not found")
71 || msg_lower.contains("invalid symbol")
72 {
73 return UnindexedOrderError::Rejected(ApiError::InstrumentInvalid(instrument.clone(), msg));
74 }
75
76 UnindexedOrderError::Rejected(ApiError::OrderRejected(msg))
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use hyperliquid_rust_sdk::Error;
86
87 fn make_sdk_error(msg: &str) -> Error {
88 Error::GenericReader(msg.to_string())
89 }
90
91 #[test]
92 fn test_map_sdk_error_connectivity() {
93 let err = map_sdk_error(make_sdk_error("connection refused"));
94 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
95
96 let err = map_sdk_error(make_sdk_error("request timeout occurred"));
97 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
98
99 let err = map_sdk_error(make_sdk_error("DNS lookup failed"));
100 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
101
102 let err = map_sdk_error(make_sdk_error("TLS handshake error"));
103 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
104 }
105
106 #[test]
107 fn test_map_sdk_error_5xx_transient() {
108 let err = map_sdk_error(make_sdk_error("HTTP 500 Internal Server Error"));
110 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
111
112 let err = map_sdk_error(make_sdk_error("502 Bad Gateway"));
113 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
114
115 let err = map_sdk_error(make_sdk_error("503 Service Unavailable"));
116 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
117
118 let err = map_sdk_error(make_sdk_error("504 Gateway Timeout"));
119 assert!(matches!(err, UnindexedClientError::Connectivity(_)));
120 }
121
122 #[test]
123 fn test_map_sdk_error_rate_limit() {
124 let err = map_sdk_error(make_sdk_error("rate limit exceeded"));
125 assert!(matches!(
126 err,
127 UnindexedClientError::Api(UnindexedApiError::RateLimit)
128 ));
129
130 let err = map_sdk_error(make_sdk_error("too many requests"));
131 assert!(matches!(
132 err,
133 UnindexedClientError::Api(UnindexedApiError::RateLimit)
134 ));
135
136 let err = map_sdk_error(make_sdk_error("HTTP 429"));
137 assert!(matches!(
138 err,
139 UnindexedClientError::Api(UnindexedApiError::RateLimit)
140 ));
141 }
142
143 #[test]
144 fn test_map_sdk_error_auth() {
145 let err = map_sdk_error(make_sdk_error("invalid signature"));
146 assert!(matches!(
147 err,
148 UnindexedClientError::Api(UnindexedApiError::Unauthenticated(_))
149 ));
150
151 let err = map_sdk_error(make_sdk_error("unauthorized access"));
152 assert!(matches!(
153 err,
154 UnindexedClientError::Api(UnindexedApiError::Unauthenticated(_))
155 ));
156
157 let err = map_sdk_error(make_sdk_error("Error from Eip712 struct: invalid key"));
159 assert!(matches!(
160 err,
161 UnindexedClientError::Api(UnindexedApiError::Unauthenticated(_))
162 ));
163 }
164
165 #[test]
166 fn test_map_sdk_error_internal() {
167 let err = map_sdk_error(make_sdk_error("some unknown error"));
168 assert!(matches!(err, UnindexedClientError::Internal(_)));
169 }
170
171 #[test]
172 fn test_map_order_error_instrument_invalid() {
173 let instrument = InstrumentNameExchange::from("INVALID-USD-PERP");
174
175 let err = map_order_error(make_sdk_error("unknown asset"), &instrument);
176 assert!(matches!(
177 err,
178 UnindexedOrderError::Rejected(ApiError::InstrumentInvalid(_, _))
179 ));
180
181 let err = map_order_error(make_sdk_error("Asset not found"), &instrument);
182 assert!(matches!(
183 err,
184 UnindexedOrderError::Rejected(ApiError::InstrumentInvalid(_, _))
185 ));
186 }
187
188 #[test]
189 fn test_map_order_error_rejected() {
190 let instrument = InstrumentNameExchange::from("BTC-USD-PERP");
191
192 let err = map_order_error(make_sdk_error("insufficient margin"), &instrument);
193 assert!(matches!(
194 err,
195 UnindexedOrderError::Rejected(ApiError::OrderRejected(_))
196 ));
197
198 let err = map_order_error(make_sdk_error("price precision too high"), &instrument);
199 assert!(matches!(
200 err,
201 UnindexedOrderError::Rejected(ApiError::OrderRejected(_))
202 ));
203 }
204}