nautilus_architect_ax/
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//! Unified error handling for the AX Exchange adapter.
17//!
18//! This module provides a comprehensive error taxonomy that distinguishes between
19//! retryable, non-retryable, and fatal errors, with proper context preservation
20//! for debugging and operational monitoring.
21
22use std::time::Duration;
23
24use nautilus_network::http::HttpClientError;
25use thiserror::Error;
26
27/// The main error type for all AX Exchange adapter operations.
28#[derive(Debug, Error)]
29pub enum AxError {
30    /// Errors that should be retried with backoff.
31    #[error("Retryable error: {source}")]
32    Retryable {
33        #[source]
34        source: AxRetryableError,
35        /// Suggested retry after duration, if provided by the server.
36        retry_after: Option<Duration>,
37    },
38
39    /// Errors that should not be retried.
40    #[error("Non-retryable error: {source}")]
41    NonRetryable {
42        #[source]
43        source: AxNonRetryableError,
44    },
45
46    /// Fatal errors that require intervention.
47    #[error("Fatal error: {source}")]
48    Fatal {
49        #[source]
50        source: AxFatalError,
51    },
52
53    /// Network transport errors.
54    #[error("Network error: {0}")]
55    Network(#[from] HttpClientError),
56
57    /// WebSocket specific errors.
58    #[error("WebSocket error: {0}")]
59    WebSocket(String),
60
61    /// JSON serialization/deserialization errors.
62    #[error("JSON error: {message}")]
63    Json {
64        message: String,
65        /// The raw JSON that failed to parse, if available.
66        raw: Option<String>,
67    },
68
69    /// Configuration errors.
70    #[error("Configuration error: {0}")]
71    Config(String),
72}
73
74/// Errors that should be retried with appropriate backoff.
75#[derive(Debug, Error)]
76pub enum AxRetryableError {
77    /// Rate limit exceeded (HTTP 429).
78    #[error("Rate limit exceeded (remaining: {remaining:?}, reset: {reset_at:?})")]
79    RateLimit {
80        remaining: Option<u32>,
81        reset_at: Option<Duration>,
82    },
83
84    /// Service unavailable (HTTP 503).
85    #[error("Service temporarily unavailable")]
86    ServiceUnavailable,
87
88    /// Gateway timeout (HTTP 504).
89    #[error("Gateway timeout")]
90    GatewayTimeout,
91
92    /// Server error (HTTP 5xx).
93    #[error("Server error (status: {status})")]
94    ServerError { status: u16 },
95
96    /// Network timeout.
97    #[error("Request timed out after {duration:?}")]
98    Timeout { duration: Duration },
99
100    /// Connection failure.
101    #[error("Connection failed: {reason}")]
102    ConnectionFailed { reason: String },
103}
104
105/// Errors that should not be retried automatically.
106#[derive(Debug, Error)]
107pub enum AxNonRetryableError {
108    /// Bad request (HTTP 400).
109    #[error("Bad request: {message}")]
110    BadRequest { message: String },
111
112    /// Unauthorized (HTTP 401).
113    #[error("Authentication failed: {message}")]
114    Unauthorized { message: String },
115
116    /// Forbidden (HTTP 403).
117    #[error("Access forbidden: {message}")]
118    Forbidden { message: String },
119
120    /// Not found (HTTP 404).
121    #[error("Resource not found: {message}")]
122    NotFound { message: String },
123
124    /// Invalid API response.
125    #[error("Invalid API response: {message}")]
126    InvalidResponse { message: String },
127
128    /// Invalid parameters.
129    #[error("Invalid parameters: {message}")]
130    InvalidParameters { message: String },
131
132    /// Order rejected.
133    #[error("Order rejected: {message}")]
134    OrderRejected { message: String },
135
136    /// Insufficient funds.
137    #[error("Insufficient funds: {message}")]
138    InsufficientFunds { message: String },
139}
140
141/// Fatal errors that require manual intervention.
142#[derive(Debug, Error)]
143pub enum AxFatalError {
144    /// API credentials are invalid or missing.
145    #[error("Invalid or missing API credentials")]
146    InvalidCredentials,
147
148    /// Account suspended or restricted.
149    #[error("Account suspended: {reason}")]
150    AccountSuspended { reason: String },
151
152    /// System misconfiguration.
153    #[error("Configuration error: {message}")]
154    SystemMisconfiguration { message: String },
155
156    /// Unrecoverable parsing error.
157    #[error("Unrecoverable parsing error: {message}")]
158    ParseError { message: String },
159}
160
161impl AxError {
162    /// Returns `true` if this error can be retried.
163    #[must_use]
164    pub fn is_retryable(&self) -> bool {
165        matches!(self, Self::Retryable { .. })
166    }
167
168    /// Returns `true` if this error is fatal.
169    #[must_use]
170    pub fn is_fatal(&self) -> bool {
171        matches!(self, Self::Fatal { .. })
172    }
173
174    /// Creates a JSON parsing error with optional raw data.
175    #[must_use]
176    pub fn json_parse(message: impl Into<String>, raw: Option<String>) -> Self {
177        Self::Json {
178            message: message.into(),
179            raw,
180        }
181    }
182
183    /// Creates a WebSocket error.
184    #[must_use]
185    pub fn websocket(message: impl Into<String>) -> Self {
186        Self::WebSocket(message.into())
187    }
188
189    /// Creates a configuration error.
190    #[must_use]
191    pub fn config(message: impl Into<String>) -> Self {
192        Self::Config(message.into())
193    }
194}