nautilus_architect_ax/http/
error.rs1use nautilus_network::http::HttpClientError;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21
22#[derive(Debug, Clone, Error)]
24pub enum AxBuildError {
25 #[error("Missing required symbol")]
27 MissingSymbol,
28 #[error("Invalid limit: {0}")]
30 InvalidLimit(String),
31 #[error("Invalid time range: start ({start}) must be less than end ({end})")]
33 InvalidTimeRange { start: i64, end: i64 },
34 #[error("Missing required order identifier")]
36 MissingOrderId,
37}
38
39#[derive(Clone, Debug, Deserialize, Serialize)]
44pub struct AxErrorResponse {
45 #[serde(default)]
47 pub error: Option<String>,
48 #[serde(default)]
50 pub message: Option<String>,
51 #[serde(default)]
53 pub status: Option<u16>,
54}
55
56#[derive(Debug, Clone, Error)]
58pub enum AxHttpError {
59 #[error("Missing credentials for authenticated request")]
61 MissingCredentials,
62 #[error("Session token not set (not authenticated)")]
64 MissingSessionToken,
65 #[error("AX Exchange API error: {message}")]
67 ApiError { message: String },
68 #[error("JSON error: {0}")]
70 JsonError(String),
71 #[error("Parameter validation error: {0}")]
73 ValidationError(String),
74 #[error("Build error: {0}")]
76 BuildError(#[from] AxBuildError),
77 #[error("Request canceled: {0}")]
79 Canceled(String),
80 #[error("Network error: {0}")]
82 NetworkError(String),
83 #[error("Unexpected HTTP status code {status}: {body}")]
85 UnexpectedStatus { status: u16, body: String },
86}
87
88impl From<HttpClientError> for AxHttpError {
89 fn from(error: HttpClientError) -> Self {
90 Self::NetworkError(error.to_string())
91 }
92}
93
94impl From<String> for AxHttpError {
95 fn from(error: String) -> Self {
96 Self::ValidationError(error)
97 }
98}
99
100impl From<serde_json::Error> for AxHttpError {
101 fn from(error: serde_json::Error) -> Self {
102 Self::JsonError(error.to_string())
103 }
104}
105
106impl From<AxErrorResponse> for AxHttpError {
107 fn from(error: AxErrorResponse) -> Self {
108 let message = error
109 .message
110 .or(error.error)
111 .unwrap_or_else(|| "Unknown error".to_string());
112 Self::ApiError { message }
113 }
114}
115
116impl AxHttpError {
117 #[must_use]
121 pub fn is_retryable(&self) -> bool {
122 match self {
123 Self::NetworkError(_) => true,
124 Self::UnexpectedStatus { status, .. } => *status == 429 || *status >= 500,
125 Self::MissingCredentials
126 | Self::MissingSessionToken
127 | Self::ApiError { .. }
128 | Self::JsonError(_)
129 | Self::ValidationError(_)
130 | Self::BuildError(_)
131 | Self::Canceled(_) => false,
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use rstest::rstest;
139
140 use super::*;
141
142 #[rstest]
143 fn test_architect_build_error_display() {
144 let error = AxBuildError::MissingSymbol;
145 assert_eq!(error.to_string(), "Missing required symbol");
146
147 let error = AxBuildError::InvalidLimit("must be positive".to_string());
148 assert_eq!(error.to_string(), "Invalid limit: must be positive");
149
150 let error = AxBuildError::InvalidTimeRange {
151 start: 100,
152 end: 50,
153 };
154 assert_eq!(
155 error.to_string(),
156 "Invalid time range: start (100) must be less than end (50)"
157 );
158 }
159
160 #[rstest]
161 fn test_architect_http_error_from_json_error() {
162 let json_err = serde_json::from_str::<serde_json::Value>("invalid json")
163 .expect_err("Should fail to parse");
164 let http_err = AxHttpError::from(json_err);
165
166 assert!(matches!(http_err, AxHttpError::JsonError(_)));
167 }
168
169 #[rstest]
170 fn test_architect_http_error_from_string() {
171 let error = AxHttpError::from("Test validation error".to_string());
172 assert_eq!(
173 error.to_string(),
174 "Parameter validation error: Test validation error"
175 );
176 }
177
178 #[rstest]
179 fn test_architect_error_response_to_http_error() {
180 let error_response = AxErrorResponse {
181 error: Some("INVALID_REQUEST".to_string()),
182 message: Some("Invalid parameter".to_string()),
183 status: Some(400),
184 };
185
186 let http_error = AxHttpError::from(error_response);
187 assert_eq!(
188 http_error.to_string(),
189 "AX Exchange API error: Invalid parameter"
190 );
191 }
192}