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