Skip to main content

nautilus_kraken/http/
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//! Error types for Kraken HTTP client operations.
17
18use thiserror::Error;
19
20#[derive(Debug, Clone, Error)]
21pub enum KrakenHttpError {
22    #[error("Request not started: {0}")]
23    RequestNotStarted(String),
24
25    #[error("Network error: {0}")]
26    NetworkError(String),
27
28    #[error("API error: {}", format_api_errors(.0))]
29    ApiError(Vec<String>),
30
31    #[error("Parse error: {0}")]
32    ParseError(String),
33
34    #[error("Authentication error: {0}")]
35    AuthenticationError(String),
36
37    #[error("Missing credentials")]
38    MissingCredentials,
39}
40
41#[derive(Debug, Error)]
42pub(crate) enum KrakenSubmitOrderError {
43    #[error("Order rejected: {reason}")]
44    Rejected { reason: String },
45
46    #[error("No send status in successful response")]
47    MissingStatus,
48
49    #[error("Unknown send status: {status}")]
50    UnknownStatus { status: String },
51
52    #[error("No order ID in submit response: {detail}")]
53    MissingOrderId { detail: String },
54
55    #[error("Order lookup failed after submission: {source}")]
56    PostSubmitLookup {
57        #[source]
58        source: anyhow::Error,
59    },
60}
61
62#[derive(Debug, Error)]
63pub(crate) enum KrakenModifyOrderError {
64    #[error("Order modification rejected: {reason}")]
65    Rejected { reason: String },
66
67    #[error("Unknown edit status: {status}")]
68    UnknownStatus { status: String },
69
70    #[error("No order ID in edit response")]
71    MissingOrderId,
72}
73
74#[derive(Debug, Error)]
75pub(crate) enum KrakenBatchOrderError {
76    #[error("Order validation failed: {reason}")]
77    Validation { reason: String },
78
79    #[error("Order not sent after an earlier chunk failed")]
80    NotAttempted,
81
82    #[error("Batch response item count {actual} did not match request count {expected}")]
83    ResponseCount { expected: usize, actual: usize },
84
85    #[error("Missing batch response for {key}")]
86    MissingResponse { key: String },
87
88    #[error("Duplicate batch responses for {key}")]
89    DuplicateResponse { key: String },
90}
91
92/// Formats API error messages, handling empty error arrays.
93fn format_api_errors(errors: &[String]) -> String {
94    if errors.is_empty() {
95        "unknown error (empty error list)".to_string()
96    } else {
97        errors.join(", ")
98    }
99}
100
101impl From<anyhow::Error> for KrakenHttpError {
102    fn from(err: anyhow::Error) -> Self {
103        Self::NetworkError(err.to_string())
104    }
105}
106
107/// Returns `true` if a request producing this error should be retried.
108pub fn kraken_http_should_retry(error: &KrakenHttpError) -> bool {
109    match error {
110        KrakenHttpError::NetworkError(_) => true,
111        KrakenHttpError::ApiError(errors) => errors.iter().any(|e| e.contains("Rate limit")),
112        KrakenHttpError::RequestNotStarted(_)
113        | KrakenHttpError::ParseError(_)
114        | KrakenHttpError::AuthenticationError(_)
115        | KrakenHttpError::MissingCredentials => false,
116    }
117}