1#[derive(Debug, thiserror::Error)]
2pub enum ErrorCategory {
3 #[error("connection_error")]
4 Connection,
5 #[error("authentication_error")]
6 Authentication,
7 #[error("rate_limit")]
8 RateLimit,
9 #[error("validation_error")]
10 Validation,
11 #[error("server_error")]
12 Server,
13 #[error("config_error")]
14 Config,
15 #[error("unknown_error")]
16 Unknown,
17}
18
19#[derive(Debug, thiserror::Error)]
20pub enum IndodaxError {
21 #[error("HTTP request failed: {0}")]
22 Http(#[from] reqwest::Error),
23
24 #[cfg(all(not(target_arch = "wasm32"), any(feature = "cli", feature = "server")))]
25 #[error("WebSocket error: {0}")]
26 WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
27
28 #[error("JSON parse error: {0}")]
29 Json(#[from] serde_json::Error),
30
31 #[error("{message}")]
32 Api {
33 category: ErrorCategory,
34 message: String,
35 code: Option<String>,
36 retryable: bool,
37 },
38
39 #[error("{0}")]
40 Config(String),
41
42 #[error("{0}")]
43 Parse(String),
44
45 #[error("WebSocket token generation failed: {0}")]
46 WsToken(String),
47
48 #[error("{0}")]
49 Other(String),
50}
51
52impl IndodaxError {
53 pub fn api(message: impl Into<String>, category: ErrorCategory, code: Option<String>) -> Self {
54 let retryable = matches!(
55 category,
56 ErrorCategory::Connection | ErrorCategory::Server | ErrorCategory::RateLimit
57 );
58 IndodaxError::Api {
59 category,
60 message: message.into(),
61 code,
62 retryable,
63 }
64 }
65
66 pub fn category(&self) -> String {
67 match self {
68 IndodaxError::Api { category, .. } => category.to_string(),
69 IndodaxError::Http(_) => "connection_error".to_string(),
70 #[cfg(all(not(target_arch = "wasm32"), any(feature = "cli", feature = "server")))]
71 IndodaxError::WebSocket(_) => "connection_error".to_string(),
72 IndodaxError::Json(_) => "validation_error".to_string(),
73 IndodaxError::Config(_) => "config_error".to_string(),
74 IndodaxError::Parse(_) => "validation_error".to_string(),
75 IndodaxError::WsToken(_) => "authentication_error".to_string(),
76 IndodaxError::Other(_) => "unknown_error".to_string(),
77 }
78 }
79
80 pub fn is_retryable(&self) -> bool {
81 match self {
82 IndodaxError::Api { retryable, .. } => *retryable,
83 IndodaxError::Http(_) => true,
84 #[cfg(all(not(target_arch = "wasm32"), any(feature = "cli", feature = "server")))]
85 IndodaxError::WebSocket(_) => true,
86 _ => false,
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn test_error_category_connection() {
97 let cat = ErrorCategory::Connection;
98 assert_eq!(format!("{}", cat), "connection_error");
99 }
100
101 #[test]
102 fn test_error_category_authentication() {
103 let cat = ErrorCategory::Authentication;
104 assert_eq!(format!("{}", cat), "authentication_error");
105 }
106
107 #[test]
108 fn test_error_category_rate_limit() {
109 let cat = ErrorCategory::RateLimit;
110 assert_eq!(format!("{}", cat), "rate_limit");
111 }
112
113 #[test]
114 fn test_error_category_validation() {
115 let cat = ErrorCategory::Validation;
116 assert_eq!(format!("{}", cat), "validation_error");
117 }
118
119 #[test]
120 fn test_error_category_server() {
121 let cat = ErrorCategory::Server;
122 assert_eq!(format!("{}", cat), "server_error");
123 }
124
125 #[test]
126 fn test_error_category_config() {
127 let cat = ErrorCategory::Config;
128 assert_eq!(format!("{}", cat), "config_error");
129 }
130
131 #[test]
132 fn test_error_category_unknown() {
133 let cat = ErrorCategory::Unknown;
134 assert_eq!(format!("{}", cat), "unknown_error");
135 }
136
137 #[test]
138 fn test_indodax_error_http() {
139 }
144
145 #[test]
146 fn test_indodax_error_api_basic() {
147 let err = IndodaxError::api("test message", ErrorCategory::Server, Some("500".into()));
148 let msg = err.to_string();
149 assert!(msg.contains("test message"));
150 }
151
152 #[test]
153 fn test_indodax_error_api_category_connection() {
154 let err = IndodaxError::api("conn error", ErrorCategory::Connection, None);
155 assert_eq!(err.category(), "connection_error");
156 assert!(err.is_retryable());
157 }
158
159 #[test]
160 fn test_indodax_error_api_category_server() {
161 let err = IndodaxError::api("server error", ErrorCategory::Server, None);
162 assert_eq!(err.category(), "server_error");
163 assert!(err.is_retryable());
164 }
165
166 #[test]
167 fn test_indodax_error_api_category_rate_limit() {
168 let err = IndodaxError::api("rate limit", ErrorCategory::RateLimit, None);
169 assert_eq!(err.category(), "rate_limit");
170 assert!(err.is_retryable());
171 }
172
173 #[test]
174 fn test_indodax_error_api_category_not_retryable() {
175 let err = IndodaxError::api("auth error", ErrorCategory::Authentication, None);
176 assert!(!err.is_retryable());
177 }
178
179 #[test]
180 fn test_indodax_error_config() {
181 let err = IndodaxError::Config("config error message".into());
182 assert_eq!(err.category(), "config_error");
183 let msg = err.to_string();
184 assert!(msg.contains("config error message"));
185 }
186
187 #[test]
188 fn test_indodax_error_parse() {
189 let err = IndodaxError::Parse("parse error".into());
190 assert_eq!(err.category(), "validation_error");
191 let msg = err.to_string();
192 assert!(msg.contains("parse error"));
193 }
194
195 #[test]
196 fn test_indodax_error_other() {
197 let err = IndodaxError::Other("other error".into());
198 assert_eq!(err.category(), "unknown_error");
199 let msg = err.to_string();
200 assert!(msg.contains("other error"));
201 }
202
203 #[test]
204 fn test_indodax_error_json() {
205 let err =
206 IndodaxError::Json(serde_json::from_str::<serde_json::Value>("invalid").unwrap_err());
207 assert_eq!(err.category(), "validation_error");
208 }
209
210 #[cfg(all(not(target_arch = "wasm32"), any(feature = "cli", feature = "server")))]
211 #[test]
212 fn test_indodax_error_websocket() {
213 let err = IndodaxError::WebSocket(Box::new(
215 tokio_tungstenite::tungstenite::Error::ConnectionClosed,
216 ));
217 assert_eq!(err.category(), "connection_error");
218 assert!(err.is_retryable());
219 }
220
221 #[test]
222 fn test_api_error_retryable_field() {
223 let err = IndodaxError::api("test", ErrorCategory::Connection, None);
224 match err {
225 IndodaxError::Api { retryable, .. } => assert!(retryable),
226 _ => panic!("Expected Api error, got {:?}", err),
227 }
228 }
229
230 #[test]
231 fn test_api_error_code() {
232 let err = IndodaxError::api("test", ErrorCategory::Unknown, Some("ERR_123".into()));
233 match err {
234 IndodaxError::Api { code, .. } => assert_eq!(code, Some("ERR_123".into())),
235 _ => panic!("Expected Api error, got {:?}", err),
236 }
237 }
238}