Skip to main content

bullet_ws_interface/
error.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use strum::Display;
3
4/// Error codes aligned with Binance API
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
6#[repr(i32)]
7pub enum ErrorCode {
8    // general errors
9    #[strum(serialize = "Unknown error")]
10    Unknown = -1000,
11    #[strum(serialize = "Disconnected")]
12    Disconnected = -1001,
13    #[strum(serialize = "Unauthorized")]
14    Unauthorized = -1002,
15    #[strum(serialize = "Too many requests")]
16    TooManyRequests = -1003,
17    #[strum(serialize = "Unexpected response")]
18    UnexpectedResponse = -1006,
19    #[strum(serialize = "Timeout")]
20    Timeout = -1007,
21    #[strum(serialize = "Unknown order")]
22    UnknownOrder = -1014,
23    #[strum(serialize = "Too many orders")]
24    TooManyOrders = -1015,
25    #[strum(serialize = "Service unavailable")]
26    ServiceUnavailable = -1016,
27    #[strum(serialize = "Unsupported operation")]
28    UnsupportedOperation = -1020,
29    #[strum(serialize = "Invalid timestamp")]
30    InvalidTimestamp = -1021,
31    #[strum(serialize = "Invalid signature")]
32    InvalidSignature = -1022,
33    #[strum(serialize = "Mandatory parameter missing")]
34    MandatoryParamMissing = -1102,
35    #[strum(serialize = "Bad precision")]
36    BadPrecision = -1111,
37    #[strum(serialize = "Invalid order type")]
38    InvalidOrderType = -1116,
39    #[strum(serialize = "Invalid side")]
40    InvalidSide = -1117,
41    #[strum(serialize = "Invalid symbol")]
42    InvalidSymbol = -1122,
43
44    // custom error for when address is not passed or invalid
45    #[strum(serialize = "Invalid user address")]
46    InvalidUserAddress = -1123,
47
48    // order errors
49    #[strum(serialize = "New order rejected")]
50    NewOrderRejected = -2010,
51    #[strum(serialize = "Cancel rejected")]
52    CancelRejected = -2011,
53    #[strum(serialize = "No such order")]
54    NoSuchOrder = -2013,
55    #[strum(serialize = "API key format invalid")]
56    ApiKeyFormatInvalid = -2014,
57    #[strum(serialize = "Invalid API key/IP/permissions")]
58    InvalidApiKeyIpPermissions = -2015,
59    #[strum(serialize = "Order would immediately trigger")]
60    OrderWouldTrigger = -2021,
61
62    // subscription errors (custom codes)
63    #[strum(serialize = "Invalid subscription format")]
64    InvalidSubscriptionFormat = -1004,
65    #[strum(serialize = "Symbol not found")]
66    SymbolNotFound = -1005,
67    #[strum(serialize = "Validation error")]
68    ValidationError = -1008,
69    #[strum(serialize = "Subscription already exists")]
70    SubscriptionExists = -1010,
71
72    // server errors (internal)
73    #[strum(serialize = "Client not found")]
74    ClientNotFound = -4001,
75    #[strum(serialize = "Could not send message")]
76    CouldNotSendMessage = -4002,
77}
78
79impl Serialize for ErrorCode {
80    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81        serializer.serialize_i32(*self as i32)
82    }
83}
84
85impl<'de> Deserialize<'de> for ErrorCode {
86    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
87        let code = i32::deserialize(deserializer)?;
88        Ok(match code {
89            -1000 => ErrorCode::Unknown,
90            -1001 => ErrorCode::Disconnected,
91            -1002 => ErrorCode::Unauthorized,
92            -1003 => ErrorCode::TooManyRequests,
93            -1004 => ErrorCode::InvalidSubscriptionFormat,
94            -1005 => ErrorCode::SymbolNotFound,
95            -1006 => ErrorCode::UnexpectedResponse,
96            -1007 => ErrorCode::Timeout,
97            -1008 => ErrorCode::ValidationError,
98            -1010 => ErrorCode::SubscriptionExists,
99            -1014 => ErrorCode::UnknownOrder,
100            -1015 => ErrorCode::TooManyOrders,
101            -1016 => ErrorCode::ServiceUnavailable,
102            -1020 => ErrorCode::UnsupportedOperation,
103            -1021 => ErrorCode::InvalidTimestamp,
104            -1022 => ErrorCode::InvalidSignature,
105            -1102 => ErrorCode::MandatoryParamMissing,
106            -1111 => ErrorCode::BadPrecision,
107            -1116 => ErrorCode::InvalidOrderType,
108            -1117 => ErrorCode::InvalidSide,
109            -1122 => ErrorCode::InvalidSymbol,
110            -1123 => ErrorCode::InvalidUserAddress,
111            -2010 => ErrorCode::NewOrderRejected,
112            -2011 => ErrorCode::CancelRejected,
113            -2013 => ErrorCode::NoSuchOrder,
114            -2014 => ErrorCode::ApiKeyFormatInvalid,
115            -2015 => ErrorCode::InvalidApiKeyIpPermissions,
116            -2021 => ErrorCode::OrderWouldTrigger,
117            -4001 => ErrorCode::ClientNotFound,
118            -4002 => ErrorCode::CouldNotSendMessage,
119            _ => ErrorCode::Unknown,
120        })
121    }
122}
123
124/// WebSocket error for both internal handling and client responses.
125/// Serializes as `{"param": "...", "code": N, "msg": "..."}` for JSON responses.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct WSError {
128    #[serde(skip_serializing_if = "Option::is_none")]
129    param: Option<String>,
130    code: ErrorCode,
131    #[serde(rename = "msg")]
132    message: String,
133}
134
135impl std::error::Error for WSError {}
136
137impl std::fmt::Display for WSError {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{}", self.message)
140    }
141}
142
143impl WSError {
144    /// Create error with default message from code
145    pub fn new(code: ErrorCode) -> Self {
146        Self {
147            code,
148            message: code.to_string(),
149            param: None,
150        }
151    }
152
153    /// Create error with custom message
154    pub fn with_message(code: ErrorCode, message: impl Into<String>) -> Self {
155        Self {
156            code,
157            message: message.into(),
158            param: None,
159        }
160    }
161
162    /// Create error with param context (for atomic batch failures)
163    pub fn with_param(
164        code: ErrorCode,
165        param: impl Into<String>,
166        message: impl Into<String>,
167    ) -> Self {
168        Self {
169            code,
170            message: message.into(),
171            param: Some(param.into()),
172        }
173    }
174
175    /// Get the error code
176    pub fn error_code(&self) -> ErrorCode {
177        self.code
178    }
179
180    /// Get the numeric error code
181    pub fn code(&self) -> i32 {
182        self.code as i32
183    }
184
185    /// Get the param that caused the error
186    pub fn param(&self) -> Option<&str> {
187        self.param.as_deref()
188    }
189
190    /// Get the error message
191    pub fn message(&self) -> &str {
192        &self.message
193    }
194
195    // Convenience constructors
196    pub fn invalid_request(msg: impl Into<String>) -> Self {
197        Self::with_message(ErrorCode::ValidationError, msg)
198    }
199
200    pub fn invalid_subscription(msg: impl Into<String>) -> Self {
201        Self::with_message(ErrorCode::InvalidSubscriptionFormat, msg)
202    }
203
204    pub fn invalid_subscription_with_param(
205        param: impl Into<String>,
206        msg: impl Into<String>,
207    ) -> Self {
208        Self::with_param(ErrorCode::InvalidSubscriptionFormat, param, msg)
209    }
210
211    pub fn symbol_not_found(symbol: &str) -> Self {
212        Self::with_param(
213            ErrorCode::InvalidSymbol,
214            symbol,
215            format!("symbol not found: {symbol}"),
216        )
217    }
218
219    pub fn unauthorized(msg: impl Into<String>) -> Self {
220        Self::with_message(ErrorCode::Unauthorized, msg)
221    }
222
223    pub fn unauthorized_with_param(param: impl Into<String>, msg: impl Into<String>) -> Self {
224        Self::with_param(ErrorCode::Unauthorized, param, msg)
225    }
226
227    pub fn subscription_exists(param: impl Into<String>) -> Self {
228        Self::with_param(
229            ErrorCode::SubscriptionExists,
230            param,
231            "subscription already exists",
232        )
233    }
234
235    pub fn mandatory_param_missing(msg: impl Into<String>) -> Self {
236        Self::with_message(ErrorCode::MandatoryParamMissing, msg)
237    }
238
239    pub fn server_busy(msg: impl Into<String>) -> Self {
240        Self::with_message(ErrorCode::Disconnected, msg)
241    }
242}