mexc_rs/spot/v3/
mod.rs

1use crate::spot::SignQueryError;
2use num_traits::FromPrimitive;
3use reqwest::StatusCode;
4use std::fmt::{Display, Formatter};
5
6pub mod account_information;
7pub mod avg_price;
8pub mod cancel_all_open_orders_on_a_symbol;
9pub mod cancel_order;
10pub mod create_user_data_stream;
11pub mod default_symbols;
12pub mod depth;
13pub mod enums;
14pub mod exchange_information;
15pub mod get_open_orders;
16pub mod get_order;
17pub mod keep_alive_user_data_stream;
18pub mod klines;
19pub mod models;
20pub mod order;
21pub mod ping;
22pub mod query_order;
23pub mod time;
24pub mod trades;
25
26pub type ApiResult<T> = Result<T, ApiError>;
27
28// https://mxcdevelop.github.io/apidocs/spot_v3_en/#base-endpoint
29#[derive(Debug, thiserror::Error)]
30pub enum ApiError {
31    /// HTTP 4XX return codes are used for malformed requests; the issue is on the sender's side.
32    #[error("Malformed request")]
33    MalformedRequest,
34
35    /// HTTP 403 return code is used when the WAF Limit (Web Application Firewall) has been violated.
36    #[error("Web application firewall (WAF) violated")]
37    WebApplicationFirewallViolated,
38
39    /// HTTP 429 return code is used when breaking a request rate limit.
40    #[error("Rate limit exceeded")]
41    RateLimitExceeded,
42
43    /// HTTP 5XX return codes are used for internal errors; the issue is on MEXC's side. It is important to NOT treat this as a failure operation; the execution status is UNKNOWN and could have been a success.
44    #[error("Internal server error")]
45    InternalServerError,
46
47    #[error("Reqwest error: {0}")]
48    ReqwestError(reqwest::Error),
49
50    #[error("Serde url encoded error: {0}")]
51    SerdeUrlencodedError(#[from] serde_urlencoded::ser::Error),
52
53    #[error("Serde json error: {0}")]
54    SerdeJsonError(#[from] serde_json::Error),
55
56    /// Unable to parse response
57    #[error("Unable to parse response")]
58    UnableToParseResponse,
59
60    #[error("Error response: {0:?}")]
61    ErrorResponse(#[from] ErrorResponse),
62
63    #[error("Sign query error: {0}")]
64    SignQueryError(#[from] SignQueryError),
65}
66
67impl From<reqwest::Error> for ApiError {
68    fn from(err: reqwest::Error) -> Self {
69        let status = match err.status() {
70            None => {
71                return Self::ReqwestError(err);
72            }
73            Some(status) => status,
74        };
75
76        match status {
77            StatusCode::BAD_REQUEST => Self::MalformedRequest,
78            StatusCode::FORBIDDEN => Self::WebApplicationFirewallViolated,
79            StatusCode::TOO_MANY_REQUESTS => Self::RateLimitExceeded,
80            StatusCode::INTERNAL_SERVER_ERROR => Self::InternalServerError,
81            _ => Self::ReqwestError(err),
82        }
83    }
84}
85
86#[derive(Debug, serde::Deserialize)]
87#[serde(untagged)]
88pub enum ApiResponse<T> {
89    Success(T),
90    Error(ErrorResponse),
91    ErrorStringifiedCode(ErrorResponseStringifiedCode),
92}
93
94impl<T> ApiResponse<T> {
95    pub fn into_result(self) -> Result<T, ErrorResponse> {
96        match self {
97            Self::Success(output) => Ok(output),
98            Self::Error(err) => Err(err),
99            Self::ErrorStringifiedCode(esc) => {
100                Err(esc.try_into().map_err(|_err| ErrorResponse {
101                    msg: "Stringified error code cannot be parsed".to_string(),
102                    code: ErrorCode::InvalidResponse,
103                    _extend: None,
104                })?)
105            }
106        }
107    }
108
109    pub fn into_api_result(self) -> ApiResult<T> {
110        match self {
111            Self::Success(output) => Ok(output),
112            Self::Error(response) => Err(ApiError::ErrorResponse(response)),
113            Self::ErrorStringifiedCode(esc) => {
114                Err(ApiError::ErrorResponse(esc.try_into().map_err(|_err| {
115                    ErrorResponse {
116                        msg: "Stringified error code cannot be parsed".to_string(),
117                        code: ErrorCode::InvalidResponse,
118                        _extend: None,
119                    }
120                })?))
121            }
122        }
123    }
124}
125
126#[derive(Debug, serde::Deserialize)]
127pub struct ErrorResponse {
128    pub code: ErrorCode,
129    pub msg: String,
130    pub _extend: Option<serde_json::Value>,
131}
132
133#[derive(Debug, serde::Deserialize)]
134pub struct ErrorResponseStringifiedCode {
135    pub code: String,
136    pub msg: String,
137    pub _extend: Option<serde_json::Value>,
138}
139
140impl TryFrom<ErrorResponseStringifiedCode> for ErrorResponse {
141    type Error = ();
142
143    fn try_from(value: ErrorResponseStringifiedCode) -> Result<Self, Self::Error> {
144        let code = match value.code.parse::<i32>() {
145            Ok(code) => code,
146            Err(_) => return Err(()),
147        };
148        let code = match ErrorCode::from_i32(code) {
149            Some(code) => code,
150            None => return Err(()),
151        };
152
153        Ok(Self {
154            code,
155            msg: value.msg,
156            _extend: value._extend,
157        })
158    }
159}
160
161#[derive(
162    Debug,
163    PartialEq,
164    Eq,
165    Hash,
166    serde_repr::Deserialize_repr,
167    strum_macros::IntoStaticStr,
168    num_derive::FromPrimitive,
169)]
170#[repr(i32)]
171pub enum ErrorCode {
172    UnknownOrderSent = -2011,
173    OperationNotAllowed = 26,
174    ApiKeyRequired = 400,
175    NoAuthority = 401,
176    AccessDenied = 403,
177    TooManyRequests = 429,
178    InternalError = 500,
179    ServiceUnavailable = 503,
180    GatewayTimeout = 504,
181    SignatureVerificationFailed = 602,
182    UserDoesNotExist = 10001,
183    BadSymbol = 10007,
184    UserIdCannotBeNull = 10015,
185    InvalidAccessKey = 10072,
186    InvalidRequestTime = 10073,
187    AmountCannotBeNull = 10095,
188    AmountDecimalPlacesIsTooLong = 10096,
189    AmountIsError = 10097,
190    RiskControlSystemDetectedAbnormal = 10098,
191    UserSubAccountDoesNotOpen = 10099,
192    ThisCurrencyTransferIsNotSupported = 10100,
193    InsufficientBalance = 10101,
194    AmountCannotBeZeroOrNegative = 10102,
195    ThisAccountTransferIsNotSupported = 10103,
196    TransferOperationProcessing = 10200,
197    TransferInFailed = 10201,
198    TransferOutFailed = 10202,
199    TransferIsDisabled = 10206,
200    TransferIsForbidden = 10211,
201    ThisWithdrawalAddressIsNotOnTheCommonlyUsedAddressListOrHasBeenInvalidated = 10212,
202    NoAddressAvailablePleaseTryAgainLater = 10216,
203    AssetFlowWritingFailedPleaseTryAgain = 10219,
204    CurrencyCannotBeNull = 10222,
205    CurrencyDoesNotExist = 10232,
206    IntermediateAccountDoesNotConfiguredInRedisredis = 10259,
207    DueToRiskControlWithdrawalIsUnavailablePleaseTryAgainLater = 10265,
208    RemarkLengthIsTooLong = 10268,
209    SubsystemIsNotSupported = 20001,
210    InternalSystemErrorPleaseContactSupport = 20002,
211    RecordDoesNotExist = 22222,
212    SuspendedTransactionForTheSymbol = 30000,
213    TheCurrentTransactionDirectionIsNotAllowedToPlaceAnOrder = 30001,
214    TheMinimumTransactionVolumeCannotBeLessThan = 30002,
215    TheMaximumTransactionVolumeCannotBeGreaterThan = 30003,
216    InsufficientPosition = 30004,
217    Oversold = 30005,
218    NoValidTradePrice = 30010,
219    InvalidSymbol = 30014,
220    TradingDisabled = 30016,
221    MarketOrderIsDisabled = 30018,
222    ApiMarketOrderIsDisabled = 30019,
223    NoPermissionForTheSymbol = 30020,
224    NoExistOpponentOrder = 30025,
225    InvalidOrderIds = 30026,
226    TheCurrencyHasReachedTheMaximumPositionLimitTheBuyingIsSuspended = 30027,
227    TheCurrencyTriggeredThePlatformRiskControlTheSellingIsSuspended = 30028,
228    CannotExceedTheMaximumOrderLimit = 30029,
229    CannotExceedTheMaximumPosition = 30032,
230    CurrentOrderTypeCanNotPlaceOrder = 30041,
231    ParamIsError = 33333,
232    ParamCannotBeNull = 44444,
233    YourAccountIsAbnormal = 60005,
234    PairUserBanTradeApikey = 70011,
235    ApiKeyFormatInvalid = 700001,
236    SignatureForThisRequestIsNotValid = 700002,
237    TimestampForThisRequestIsOutsideOfTheRecvWindow = 700003,
238    ParamOrigClientOrderIdOrOrderIdMustBeSentButBothWereEmptyNull = 700004,
239    RecvWindowMustLessThan60000 = 700005,
240    IpNonWhiteList = 700006,
241    NoPermissionToAccessTheEndpoint = 700007,
242    IllegalCharactersFoundInParameter = 700008,
243    // PairNotFound = 730001,
244    // YourInputParamIsInvalid = 730002,
245    RequestFailedPleaseContactTheCustomerService = 730000,
246    // UserInformationError = 730001,
247    PairNotFoundOrUserInformationError = 730001,
248    // ParameterError = 730002,
249    YourInputParamIsInvalidOrParameterError = 730002,
250    UnsupportedOperationPleaseContactTheCustomerService = 730003,
251    UnusualUserStatus = 730100,
252    SubAccountNameCannotBeNull = 730600,
253    SubAccountNameMustBeACombinationOf8To32LettersAndNumbers = 730601,
254    SubAccountRemarksCannotBeNull = 730602,
255    ApiKeyRemarksCannotBeNull = 730700,
256    ApiKeyPermissionCannotBeNull = 730701,
257    ApiKeyPermissionDoesNotExist = 730702,
258    TheIpInformationIsIncorrectAndAMaximumOf10IPsAreAllowedToBeBoundOnly = 730703,
259    TheBoundIpFormatIsIncorrectPleaseRefill = 730704,
260    AtMost30GroupsOfApiKeysAreAllowedToBeCreatedOnly = 730705,
261    ApiKeyInformationDoesNotExist = 730706,
262    AccessKeyCannotBeNull = 730707,
263    UserNameAlreadyExists = 730101,
264    SubAccountDoesNotExist = 140001,
265    SubAccountIsForbidden = 140002,
266    OrderDoesNotExist = -2013,
267    InvalidResponse = -1234568,
268}
269
270impl Display for ErrorCode {
271    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
272        let s: &'static str = self.into();
273        write!(f, "{}", s)
274    }
275}
276
277impl std::error::Error for ErrorCode {}
278
279impl Display for ErrorResponse {
280    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
281        write!(f, "Error {}: {}", self.code, self.msg)?;
282        if let Some(extend) = &self._extend {
283            write!(f, " (extend: {:?})", extend)?;
284        }
285
286        Ok(())
287    }
288}
289
290impl std::error::Error for ErrorResponse {}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn deserialize_error_parameter_error() {
298        let json = r#"
299            {"code":"730002","msg":"Parameter error"}
300        "#;
301        let response = serde_json::from_str::<ApiResponse<()>>(json).unwrap();
302        eprintln!("{:#?}", response);
303        let result = response.into_result();
304        eprintln!("{:#?}", &result);
305        assert!(result.is_err());
306        let err = result.unwrap_err();
307        assert_eq!(err.code, ErrorCode::YourInputParamIsInvalidOrParameterError);
308    }
309}