Skip to main content

nautilus_binance/common/
error.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Adapter-level error types aggregating HTTP and WebSocket errors.
17
18use std::fmt::Display;
19
20use crate::common::consts::{BINANCE_STATUS_UNKNOWN_CODE, BINANCE_UNEXPECTED_RESPONSE_CODE};
21
22/// Binance WebSocket streams error type shared by spot and futures clients.
23#[derive(Debug)]
24pub enum BinanceWsError {
25    /// General client error.
26    ClientError(String),
27    /// Authentication failed.
28    AuthenticationError(String),
29    /// Message parsing error.
30    ParseError(String),
31    /// Network or connection error.
32    NetworkError(String),
33    /// Operation timed out.
34    Timeout(String),
35}
36
37impl Display for BinanceWsError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::ClientError(msg) => write!(f, "Client error: {msg}"),
41            Self::AuthenticationError(msg) => write!(f, "Authentication error: {msg}"),
42            Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
43            Self::NetworkError(msg) => write!(f, "Network error: {msg}"),
44            Self::Timeout(msg) => write!(f, "Timeout: {msg}"),
45        }
46    }
47}
48
49impl std::error::Error for BinanceWsError {}
50
51/// Result type for Binance WebSocket stream operations.
52pub type BinanceWsResult<T> = Result<T, BinanceWsError>;
53
54/// Adapter-level error aggregating HTTP, WebSocket, and SBE errors.
55#[derive(Debug, thiserror::Error)]
56pub enum BinanceError {
57    /// A Spot HTTP API error.
58    #[error("Spot HTTP error: {0}")]
59    SpotHttp(#[from] crate::spot::http::error::BinanceSpotHttpError),
60
61    /// A Futures HTTP API error.
62    #[error("Futures HTTP error: {0}")]
63    FuturesHttp(#[from] crate::futures::http::error::BinanceFuturesHttpError),
64
65    /// A WebSocket streams error (spot or futures).
66    #[error("WebSocket error: {0}")]
67    WebSocket(#[from] BinanceWsError),
68
69    /// A Spot WebSocket Trading API error.
70    #[error("Spot WS API error: {0}")]
71    SpotWsApi(#[from] crate::spot::websocket::trading::error::BinanceWsApiError),
72
73    /// A Futures WebSocket Trading API error.
74    #[error("Futures WS API error: {0}")]
75    FuturesWsApi(#[from] crate::futures::websocket::trading::error::BinanceFuturesWsApiError),
76
77    /// A configuration or build error.
78    #[error("Config error: {0}")]
79    Config(String),
80}
81
82/// Binance error codes indicating rate limiting or throttling.
83const BINANCE_RATE_LIMIT_ERROR_CODES: [i64; 2] = [
84    -1003, // Too many requests; WAF limit violated
85    -1015, // Too many new orders; rate limit violated
86];
87
88/// Returns `true` when the venue error code marks a transient rate-limit failure.
89pub(crate) fn is_retryable_venue_code(code: i64) -> bool {
90    BINANCE_RATE_LIMIT_ERROR_CODES.contains(&code)
91}
92
93/// Returns `true` when the HTTP status marks a transient failure (rate limited,
94/// auto-banned, or a server error).
95pub(crate) fn is_retryable_http_status(status: u16) -> bool {
96    status == 429 || status == 418 || status >= 500
97}
98
99/// Returns `true` when the venue error code means execution status is unknown.
100///
101/// Binance documents -1006 (unexpected matching-engine response) and -1007 (backend
102/// timeout) as "send status unknown; execution status unknown" for any request.
103pub(crate) fn is_ambiguous_venue_code(code: i64) -> bool {
104    code == BINANCE_UNEXPECTED_RESPONSE_CODE || code == BINANCE_STATUS_UNKNOWN_CODE
105}
106
107#[cfg(test)]
108mod tests {
109    use rstest::rstest;
110
111    use super::*;
112
113    #[rstest]
114    #[case::too_many_requests(-1003, true)]
115    #[case::too_many_new_orders(-1015, true)]
116    #[case::illegal_characters(-1100, false)]
117    #[case::invalid_api_key(-2015, false)]
118    #[case::invalid_signature(-1022, false)]
119    #[case::unexpected_response(-1006, false)]
120    fn test_is_retryable_venue_code(#[case] code: i64, #[case] expected: bool) {
121        assert_eq!(is_retryable_venue_code(code), expected);
122    }
123
124    #[rstest]
125    #[case::rate_limited(429, true)]
126    #[case::banned(418, true)]
127    #[case::server_error(500, true)]
128    #[case::bad_gateway(502, true)]
129    #[case::bad_request(400, false)]
130    #[case::unauthorized(401, false)]
131    #[case::forbidden(403, false)]
132    #[case::success(200, false)]
133    fn test_is_retryable_http_status(#[case] status: u16, #[case] expected: bool) {
134        assert_eq!(is_retryable_http_status(status), expected);
135    }
136
137    #[rstest]
138    #[case::unexpected_response(-1006, true)]
139    #[case::status_unknown(-1007, true)]
140    #[case::rate_limit(-1003, false)]
141    #[case::no_such_order(-2013, false)]
142    fn test_is_ambiguous_venue_code(#[case] code: i64, #[case] expected: bool) {
143        assert_eq!(is_ambiguous_venue_code(code), expected);
144    }
145}