nautilus_okx/common/
failure.rs1use nautilus_live::execution::failure::CommandFailure;
19
20use crate::{
21 common::consts::{OKX_ORDER_REQUEST_TIMEOUT_CODE, should_retry_error_code},
22 http::error::OKXHttpError,
23 websocket::error::OKXWsError,
24};
25
26#[must_use]
31pub fn classify_okx_venue_code(error_code: &str, reason: impl Into<String>) -> CommandFailure {
32 let reason = reason.into();
33
34 if error_code.is_empty()
35 || should_retry_error_code(error_code)
36 || error_code == OKX_ORDER_REQUEST_TIMEOUT_CODE
37 {
38 CommandFailure::Ambiguous(reason)
39 } else {
40 CommandFailure::VenueRejected(reason)
41 }
42}
43
44#[must_use]
46pub fn classify_okx_http_failure(error: &OKXHttpError) -> CommandFailure {
47 let reason = error.to_string();
48 match error {
49 OKXHttpError::MissingCredentials | OKXHttpError::ValidationError(_) => {
50 CommandFailure::NotSent(reason)
51 }
52 OKXHttpError::OkxError { error_code, .. } => classify_okx_venue_code(error_code, reason),
53 OKXHttpError::JsonError(_)
54 | OKXHttpError::Canceled(_)
55 | OKXHttpError::HttpClientError(_)
56 | OKXHttpError::UnexpectedStatus { .. }
57 | OKXHttpError::OperationTimeout { .. }
58 | OKXHttpError::RetryBudgetExceeded(_)
59 | OKXHttpError::EmptyResponse => CommandFailure::Ambiguous(reason),
60 }
61}
62
63#[must_use]
65pub fn classify_okx_ws_failure(error: &OKXWsError) -> CommandFailure {
66 let reason = error.to_string();
67 match error {
68 OKXWsError::ClientError(_)
69 | OKXWsError::JsonError(_)
70 | OKXWsError::NoActiveClient
71 | OKXWsError::HandlerUnavailable(_) => CommandFailure::NotSent(reason),
72 OKXWsError::OkxError { error_code, .. } => classify_okx_venue_code(error_code, reason),
73 OKXWsError::ParsingError(_)
74 | OKXWsError::AuthenticationError(_)
75 | OKXWsError::TungsteniteError(_)
76 | OKXWsError::SendFailed(_)
77 | OKXWsError::OperationTimeout { .. } => CommandFailure::Ambiguous(reason),
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use nautilus_network::http::{HttpClientError, StatusCode};
84 use rstest::rstest;
85
86 use super::*;
87
88 #[rstest]
89 #[case::parameter_reject("51000", "Parameter state error", true)]
90 #[case::system_busy("50013", "System busy, please retry later", false)]
91 #[case::request_timeout("50004", "API endpoint request timeout", false)]
92 #[case::order_timeout("51149", "Order timed out. Please try again.", false)]
93 #[case::rate_limit("50011", "Request too frequent", false)]
94 #[case::missing_code("", "All operations failed", false)]
95 fn test_classify_okx_venue_code(
96 #[case] error_code: &str,
97 #[case] message: &str,
98 #[case] venue_rejected: bool,
99 ) {
100 let failure = classify_okx_venue_code(error_code, message);
101
102 if venue_rejected {
103 assert_eq!(failure, CommandFailure::VenueRejected(message.to_string()));
104 } else {
105 assert_eq!(failure, CommandFailure::Ambiguous(message.to_string()));
106 }
107 }
108
109 #[rstest]
110 fn test_classify_okx_http_missing_credentials_is_not_sent() {
111 let error = OKXHttpError::MissingCredentials;
112
113 assert_eq!(
114 classify_okx_http_failure(&error),
115 CommandFailure::NotSent(error.to_string())
116 );
117 }
118
119 #[rstest]
120 fn test_classify_okx_http_validation_is_not_sent() {
121 let error = OKXHttpError::ValidationError("invalid quantity".to_string());
122
123 assert_eq!(
124 classify_okx_http_failure(&error),
125 CommandFailure::NotSent(error.to_string())
126 );
127 }
128
129 #[rstest]
130 fn test_classify_okx_http_invalid_retry_config_is_not_sent() {
131 let error = OKXHttpError::ValidationError("invalid retry configuration".to_string());
132
133 assert_eq!(
134 classify_okx_http_failure(&error),
135 CommandFailure::NotSent(error.to_string())
136 );
137 }
138
139 #[rstest]
140 fn test_classify_okx_http_timeout_is_ambiguous() {
141 let error = OKXHttpError::OperationTimeout { timeout_ms: 1_000 };
142
143 assert_eq!(
144 classify_okx_http_failure(&error),
145 CommandFailure::Ambiguous(error.to_string())
146 );
147 }
148
149 #[rstest]
150 fn test_classify_okx_http_budget_is_ambiguous() {
151 let error = OKXHttpError::RetryBudgetExceeded("budget exceeded".to_string());
152
153 assert_eq!(
154 classify_okx_http_failure(&error),
155 CommandFailure::Ambiguous(error.to_string())
156 );
157 }
158
159 #[rstest]
160 fn test_classify_okx_http_network_is_ambiguous() {
161 let error = OKXHttpError::HttpClientError(HttpClientError::Error("timeout".to_string()));
162
163 assert_eq!(
164 classify_okx_http_failure(&error),
165 CommandFailure::Ambiguous(error.to_string())
166 );
167 }
168
169 #[rstest]
170 fn test_classify_okx_http_unexpected_status_is_ambiguous() {
171 let error = OKXHttpError::UnexpectedStatus {
172 status: StatusCode::INTERNAL_SERVER_ERROR,
173 body: String::new(),
174 };
175
176 assert_eq!(
177 classify_okx_http_failure(&error),
178 CommandFailure::Ambiguous(error.to_string())
179 );
180 }
181
182 #[rstest]
183 fn test_classify_okx_http_json_is_ambiguous() {
184 let error = OKXHttpError::JsonError("failed to deserialize".to_string());
185
186 assert_eq!(
187 classify_okx_http_failure(&error),
188 CommandFailure::Ambiguous(error.to_string())
189 );
190 }
191
192 #[rstest]
193 fn test_classify_okx_http_empty_response_is_ambiguous() {
194 let error = OKXHttpError::EmptyResponse;
195
196 assert_eq!(
197 classify_okx_http_failure(&error),
198 CommandFailure::Ambiguous(error.to_string())
199 );
200 }
201
202 #[rstest]
203 fn test_classify_okx_ws_handler_unavailable_is_not_sent() {
204 let error = OKXWsError::HandlerUnavailable("channel closed".to_string());
205
206 assert_eq!(
207 classify_okx_ws_failure(&error),
208 CommandFailure::NotSent(error.to_string())
209 );
210 }
211
212 #[rstest]
213 fn test_classify_okx_ws_no_active_client_is_not_sent() {
214 let error = OKXWsError::NoActiveClient;
215
216 assert_eq!(
217 classify_okx_ws_failure(&error),
218 CommandFailure::NotSent(error.to_string())
219 );
220 }
221
222 #[rstest]
223 fn test_classify_okx_ws_json_encode_is_not_sent() {
224 let error = OKXWsError::JsonError("Failed to serialize order: eof".to_string());
225
226 assert_eq!(
227 classify_okx_ws_failure(&error),
228 CommandFailure::NotSent(error.to_string())
229 );
230 }
231
232 #[rstest]
233 fn test_classify_okx_ws_send_failed_is_ambiguous() {
234 let error = OKXWsError::SendFailed("connection reset".to_string());
235
236 assert_eq!(
237 classify_okx_ws_failure(&error),
238 CommandFailure::Ambiguous(error.to_string())
239 );
240 }
241
242 #[rstest]
243 fn test_classify_okx_ws_timeout_is_ambiguous() {
244 let error = OKXWsError::OperationTimeout { timeout_ms: 2_000 };
245
246 assert_eq!(
247 classify_okx_ws_failure(&error),
248 CommandFailure::Ambiguous(error.to_string())
249 );
250 }
251}