ccxt_exchanges/hyperliquid/
error.rs1use ccxt_core::Error;
6use serde_json::Value;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum HyperLiquidErrorCode {
11 InvalidSignature,
13 InsufficientMargin,
15 OrderNotFound,
17 InvalidParameter,
19 RateLimited,
21 ServerError,
23 UserNotFound,
25 InvalidAsset,
27 PositionNotFound,
29 OrderWouldCross,
31 ReduceOnlyViolation,
33 Unknown(String),
35}
36
37impl std::fmt::Display for HyperLiquidErrorCode {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::InvalidSignature => write!(f, "Invalid signature"),
41 Self::InsufficientMargin => write!(f, "Insufficient margin"),
42 Self::OrderNotFound => write!(f, "Order not found"),
43 Self::InvalidParameter => write!(f, "Invalid parameter"),
44 Self::RateLimited => write!(f, "Rate limited"),
45 Self::ServerError => write!(f, "Server error"),
46 Self::UserNotFound => write!(f, "User not found"),
47 Self::InvalidAsset => write!(f, "Invalid asset"),
48 Self::PositionNotFound => write!(f, "Position not found"),
49 Self::OrderWouldCross => write!(f, "Order would cross"),
50 Self::ReduceOnlyViolation => write!(f, "Reduce only violation"),
51 Self::Unknown(msg) => write!(f, "{}", msg),
52 }
53 }
54}
55
56impl From<HyperLiquidErrorCode> for Error {
57 fn from(code: HyperLiquidErrorCode) -> Self {
58 match code {
59 HyperLiquidErrorCode::InvalidSignature => Error::authentication("Invalid signature"),
60 HyperLiquidErrorCode::InsufficientMargin => {
61 Error::insufficient_balance("Insufficient margin")
62 }
63 HyperLiquidErrorCode::OrderNotFound => Error::invalid_request("Order not found"),
64 HyperLiquidErrorCode::InvalidParameter => Error::invalid_request("Invalid parameter"),
65 HyperLiquidErrorCode::RateLimited => Error::rate_limit("Rate limited", None),
66 HyperLiquidErrorCode::ServerError => Error::exchange("-1", "Server error"),
67 HyperLiquidErrorCode::UserNotFound => Error::authentication("User not found"),
68 HyperLiquidErrorCode::InvalidAsset => Error::bad_symbol("Invalid asset"),
69 HyperLiquidErrorCode::PositionNotFound => Error::invalid_request("Position not found"),
70 HyperLiquidErrorCode::OrderWouldCross => Error::invalid_request("Order would cross"),
71 HyperLiquidErrorCode::ReduceOnlyViolation => {
72 Error::invalid_request("Reduce only violation")
73 }
74 HyperLiquidErrorCode::Unknown(msg) => Error::exchange("-1", &msg),
75 }
76 }
77}
78
79pub fn is_error_response(response: &Value) -> bool {
93 if response.get("error").is_some() {
95 return true;
96 }
97
98 if let Some(status) = response.get("status") {
100 if status.as_str() == Some("err") {
101 return true;
102 }
103 }
104
105 if let Some(resp) = response.get("response") {
107 if let Some(s) = resp.as_str() {
108 if s.contains("error") || s.contains("Error") || s.contains("failed") {
109 return true;
110 }
111 }
112 }
113
114 false
115}
116
117pub fn parse_error(response: &Value) -> Error {
127 let message = extract_error_message(response);
129
130 let code = map_error_message(&message);
132
133 code.into()
134}
135
136fn extract_error_message(response: &Value) -> String {
138 if let Some(error) = response.get("error") {
140 if let Some(s) = error.as_str() {
141 return s.to_string();
142 }
143 }
144
145 if let Some(resp) = response.get("response") {
147 if let Some(s) = resp.as_str() {
148 return s.to_string();
149 }
150 }
151
152 if let Some(msg) = response.get("message") {
154 if let Some(s) = msg.as_str() {
155 return s.to_string();
156 }
157 }
158
159 "Unknown error".to_string()
160}
161
162fn map_error_message(message: &str) -> HyperLiquidErrorCode {
164 let lower = message.to_lowercase();
165
166 if lower.contains("signature") || lower.contains("auth") || lower.contains("unauthorized") {
167 HyperLiquidErrorCode::InvalidSignature
168 } else if lower.contains("insufficient")
169 || lower.contains("margin")
170 || lower.contains("balance")
171 {
172 HyperLiquidErrorCode::InsufficientMargin
173 } else if lower.contains("order") && lower.contains("not found") {
174 HyperLiquidErrorCode::OrderNotFound
175 } else if lower.contains("rate") || lower.contains("limit") || lower.contains("throttle") {
176 HyperLiquidErrorCode::RateLimited
177 } else if lower.contains("user") && lower.contains("not found") {
178 HyperLiquidErrorCode::UserNotFound
179 } else if lower.contains("asset") && (lower.contains("invalid") || lower.contains("not found"))
180 {
181 HyperLiquidErrorCode::InvalidAsset
182 } else if lower.contains("position") && lower.contains("not found") {
183 HyperLiquidErrorCode::PositionNotFound
184 } else if lower.contains("cross") || lower.contains("would cross") {
185 HyperLiquidErrorCode::OrderWouldCross
186 } else if lower.contains("reduce only") || lower.contains("reduce-only") {
187 HyperLiquidErrorCode::ReduceOnlyViolation
188 } else if lower.contains("invalid") || lower.contains("parameter") {
189 HyperLiquidErrorCode::InvalidParameter
190 } else if lower.contains("server") || lower.contains("internal") {
191 HyperLiquidErrorCode::ServerError
192 } else {
193 HyperLiquidErrorCode::Unknown(message.to_string())
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use serde_json::json;
201
202 #[test]
203 fn test_is_error_response_with_error_field() {
204 let response = json!({"error": "Invalid signature"});
205 assert!(is_error_response(&response));
206 }
207
208 #[test]
209 fn test_is_error_response_with_status_err() {
210 let response = json!({"status": "err", "response": "Something went wrong"});
211 assert!(is_error_response(&response));
212 }
213
214 #[test]
215 fn test_is_error_response_success() {
216 let response = json!({"status": "ok", "response": {"data": []}});
217 assert!(!is_error_response(&response));
218 }
219
220 #[test]
221 fn test_parse_error_insufficient_margin() {
222 let response = json!({"error": "Insufficient margin for order"});
223 let error = parse_error(&response);
224 assert!(error.to_string().contains("Insufficient"));
225 }
226
227 #[test]
228 fn test_parse_error_invalid_signature() {
229 let response = json!({"error": "Invalid signature"});
230 let error = parse_error(&response);
231 assert!(error.to_string().contains("signature") || error.to_string().contains("Signature"));
232 }
233
234 #[test]
235 fn test_parse_error_rate_limited() {
236 let response = json!({"error": "Rate limit exceeded"});
237 let error = parse_error(&response);
238 assert!(error.to_string().contains("Rate") || error.to_string().contains("rate"));
239 }
240
241 #[test]
242 fn test_parse_error_unknown() {
243 let response = json!({"error": "Some unknown error occurred"});
244 let error = parse_error(&response);
245 assert!(error.to_string().contains("unknown") || error.to_string().contains("Unknown"));
246 }
247
248 #[test]
249 fn test_error_code_display() {
250 assert_eq!(
251 HyperLiquidErrorCode::InvalidSignature.to_string(),
252 "Invalid signature"
253 );
254 assert_eq!(
255 HyperLiquidErrorCode::InsufficientMargin.to_string(),
256 "Insufficient margin"
257 );
258 }
259
260 #[test]
261 fn test_error_code_into_ccxt_error() {
262 let code = HyperLiquidErrorCode::InsufficientMargin;
263 let error: Error = code.into();
264 let _ = error.to_string();
266 }
267}