kraken_api_client/
error.rs1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum KrakenError {
8 #[error("HTTP request failed: {0}")]
10 Http(#[from] reqwest::Error),
11
12 #[error("HTTP request failed: {0}")]
14 HttpMiddleware(#[from] reqwest_middleware::Error),
15
16 #[error("WebSocket error: {0}")]
18 WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
19
20 #[error("WebSocket error: {0}")]
22 WebSocketMsg(String),
23
24 #[error("JSON error: {0}")]
26 Json(#[from] serde_json::Error),
27
28 #[error("URL parsing error: {0}")]
30 Url(#[from] url::ParseError),
31
32 #[error("Kraken API error: {0}")]
34 Api(ApiError),
35
36 #[error("Rate limit exceeded, retry after {retry_after_ms:?}ms")]
38 RateLimitExceeded {
39 retry_after_ms: Option<u64>,
41 },
42
43 #[error("Authentication error: {0}")]
45 Auth(String),
46
47 #[error("Invalid response: {0}")]
49 InvalidResponse(String),
50
51 #[error("WebSocket connection closed: {reason}")]
53 ConnectionClosed {
54 reason: String,
56 },
57
58 #[error("Request timed out")]
60 Timeout,
61
62 #[error("Missing credentials: API key and secret required for private endpoints")]
64 MissingCredentials,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct ApiError {
72 pub code: String,
74 pub message: String,
76}
77
78impl std::fmt::Display for ApiError {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(f, "{}: {}", self.code, self.message)
81 }
82}
83
84impl ApiError {
85 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
87 Self {
88 code: code.into(),
89 message: message.into(),
90 }
91 }
92
93 pub fn from_error_array(errors: &[String]) -> Option<Self> {
97 errors.first().map(|e| {
98 let parts: Vec<&str> = e.splitn(2, ':').collect();
100 if parts.len() == 2 {
101 Self::new(parts[0], parts[1])
102 } else {
103 Self::new("Unknown", e.clone())
104 }
105 })
106 }
107
108 pub fn full_code(&self) -> String {
110 format!("{}:{}", self.code, self.message)
111 }
112
113 pub fn is_rate_limit(&self) -> bool {
115 (self.code == "EAPI" && self.message.contains("Rate limit"))
116 || (self.code == "EOrder" && self.message.contains("Rate limit"))
117 }
118
119 pub fn is_invalid_nonce(&self) -> bool {
121 self.code == "EAPI" && self.message.contains("Invalid nonce")
122 }
123
124 pub fn is_invalid_key(&self) -> bool {
126 self.code == "EAPI" && self.message.contains("Invalid key")
127 }
128
129 pub fn is_invalid_signature(&self) -> bool {
131 self.code == "EAPI" && self.message.contains("Invalid signature")
132 }
133
134 pub fn is_permission_denied(&self) -> bool {
136 self.code == "EGeneral" && self.message.contains("Permission denied")
137 }
138
139 pub fn is_service_unavailable(&self) -> bool {
141 self.code == "EService" && (self.message.contains("Unavailable") || self.message.contains("Busy"))
142 }
143}
144
145pub mod error_codes {
147 pub const INVALID_ARGUMENTS: &str = "EGeneral:Invalid arguments";
149 pub const PERMISSION_DENIED: &str = "EGeneral:Permission denied";
150 pub const UNKNOWN_METHOD: &str = "EGeneral:Unknown method";
151 pub const INTERNAL_ERROR: &str = "EGeneral:Internal error";
152
153 pub const INVALID_KEY: &str = "EAPI:Invalid key";
155 pub const INVALID_SIGNATURE: &str = "EAPI:Invalid signature";
156 pub const INVALID_NONCE: &str = "EAPI:Invalid nonce";
157 pub const RATE_LIMIT_EXCEEDED: &str = "EAPI:Rate limit exceeded";
158 pub const FEATURE_DISABLED: &str = "EAPI:Feature disabled";
159
160 pub const ORDER_RATE_LIMIT: &str = "EOrder:Rate limit exceeded";
162 pub const INSUFFICIENT_FUNDS: &str = "EOrder:Insufficient funds";
163 pub const INVALID_ORDER: &str = "EOrder:Invalid order";
164 pub const ORDER_NOT_FOUND: &str = "EOrder:Unknown order";
165 pub const MARGIN_LIMIT: &str = "EOrder:Margin limit exceeded";
166
167 pub const SERVICE_UNAVAILABLE: &str = "EService:Unavailable";
169 pub const SERVICE_BUSY: &str = "EService:Busy";
170 pub const SERVICE_MARKET_IN_CANCEL_ONLY: &str = "EService:Market in cancel_only mode";
171 pub const SERVICE_MARKET_IN_POST_ONLY: &str = "EService:Market in post_only mode";
172
173 pub const UNKNOWN_ASSET_PAIR: &str = "EQuery:Unknown asset pair";
175 pub const UNKNOWN_ASSET: &str = "EQuery:Unknown asset";
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_api_error_from_array() {
184 let errors = vec!["EAPI:Invalid key".to_string()];
185 let error = ApiError::from_error_array(&errors).unwrap();
186 assert_eq!(error.code, "EAPI");
187 assert_eq!(error.message, "Invalid key");
188 assert!(error.is_invalid_key());
189 }
190
191 #[test]
192 fn test_api_error_display() {
193 let error = ApiError::new("EOrder", "Insufficient funds");
194 assert_eq!(error.to_string(), "EOrder: Insufficient funds");
195 }
196}