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("AX Exchange API error: {message}")]
64 ApiError { message: String },
65 #[error("JSON error: {0}")]
67 JsonError(String),
68 #[error("Parameter validation error: {0}")]
70 ValidationError(String),
71 #[error("Build error: {0}")]
73 BuildError(#[from] AxBuildError),
74 #[error("Request canceled: {0}")]
76 Canceled(String),
77 #[error("Network error: {0}")]
79 NetworkError(String),
80 #[error("Unexpected HTTP status code {status}: {body}")]
82 UnexpectedStatus { status: u16, body: String },
83}
84
85impl From<HttpClientError> for AxHttpError {
86 fn from(error: HttpClientError) -> Self {
87 Self::NetworkError(error.to_string())
88 }
89}
90
91impl From<String> for AxHttpError {
92 fn from(error: String) -> Self {
93 Self::ValidationError(error)
94 }
95}
96
97impl From<serde_json::Error> for AxHttpError {
98 fn from(error: serde_json::Error) -> Self {
99 Self::JsonError(error.to_string())
100 }
101}
102
103impl From<AxErrorResponse> for AxHttpError {
104 fn from(error: AxErrorResponse) -> Self {
105 let message = error
106 .message
107 .or(error.error)
108 .unwrap_or_else(|| "Unknown error".to_string());
109 Self::ApiError { message }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use rstest::rstest;
116
117 use super::*;
118
119 #[rstest]
120 fn test_architect_build_error_display() {
121 let error = AxBuildError::MissingSymbol;
122 assert_eq!(error.to_string(), "Missing required symbol");
123
124 let error = AxBuildError::InvalidLimit("must be positive".to_string());
125 assert_eq!(error.to_string(), "Invalid limit: must be positive");
126
127 let error = AxBuildError::InvalidTimeRange {
128 start: 100,
129 end: 50,
130 };
131 assert_eq!(
132 error.to_string(),
133 "Invalid time range: start (100) must be less than end (50)"
134 );
135 }
136
137 #[rstest]
138 fn test_architect_http_error_from_json_error() {
139 let json_err = serde_json::from_str::<serde_json::Value>("invalid json")
140 .expect_err("Should fail to parse");
141 let http_err = AxHttpError::from(json_err);
142
143 assert!(matches!(http_err, AxHttpError::JsonError(_)));
144 }
145
146 #[rstest]
147 fn test_architect_http_error_from_string() {
148 let error = AxHttpError::from("Test validation error".to_string());
149 assert_eq!(
150 error.to_string(),
151 "Parameter validation error: Test validation error"
152 );
153 }
154
155 #[rstest]
156 fn test_architect_error_response_to_http_error() {
157 let error_response = AxErrorResponse {
158 error: Some("INVALID_REQUEST".to_string()),
159 message: Some("Invalid parameter".to_string()),
160 status: Some(400),
161 };
162
163 let http_error = AxHttpError::from(error_response);
164 assert_eq!(
165 http_error.to_string(),
166 "AX Exchange API error: Invalid parameter"
167 );
168 }
169}