1use std::{array::TryFromSliceError, result::Result as StdResult};
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::crypto::CryptoError;
9
10pub type Result<T> = StdResult<T, Error>;
12
13#[derive(Error, Debug)]
15pub enum Error {
16 #[error("JSON parsing failed: {0}")]
18 Json(#[from] serde_json::Error),
19
20 #[error("API error {status_code}: {error_code} - {message}")]
22 Api {
23 status_code: u16,
24 error_code: String,
25 message: String,
26 },
27
28 #[error("HTTP transport error: {message}")]
30 HttpTransport { message: String, status_code: Option<u16> },
31
32 #[error("Request timeout after {timeout_ms}ms to {endpoint}")]
34 RequestTimeout { endpoint: String, timeout_ms: u64 },
35
36 #[error("Connection failed: {0}")]
38 Connection(String),
39
40 #[error("DNS resolution failed: {0}")]
42 DnsResolution(String),
43
44 #[error("Failed to deserialize {format} response: {error} - Response: {response}")]
46 ResponseDeserialization {
47 format: String,
48 error: String,
49 response: String,
50 },
51
52 #[error("Authentication failed: {0}")]
54 Authentication(String),
55
56 #[error("Authorization failed: {0}")]
58 Authorization(String),
59
60 #[error("Rate limit exceeded")]
62 RateLimitExceeded { retry_after_seconds: Option<u64> },
63
64 #[error("Invalid parameter '{parameter}': {message}")]
66 InvalidParameter { parameter: String, message: String },
67
68 #[error("Resource not found: {resource_type} with {identifier}")]
70 ResourceNotFound { resource_type: String, identifier: String },
71
72 #[error("Business logic error: {operation} failed - {reason}")]
74 BusinessLogic { operation: String, reason: String },
75
76 #[error("Cryptographic operation failed: {0}")]
78 Crypto(#[from] CryptoError),
79
80 #[error("Signature verification failed: {0}")]
82 VerificationError(String),
83
84 #[error("Client configuration error: {0}")]
86 Config(#[from] ConfigError),
87
88 #[error("Invalid URL: {0}")]
90 Url(#[from] url::ParseError),
91
92 #[error("Hex decoding failed: {0}")]
94 Hex(#[from] hex::FromHexError),
95
96 #[error("Invalid address format: {0}")]
98 Address(String),
99
100 #[error("Array conversion failed: expected length {expected}, got {actual}")]
102 ArrayConversion { expected: usize, actual: usize },
103
104 #[error("Validation failed: {field} - {message}")]
106 Validation { field: String, message: String },
107
108 #[error("{0}")]
110 Custom(String),
111}
112
113#[derive(Error, Debug)]
115pub enum ConfigError {
116 #[error("Invalid timeout: {0}")]
118 InvalidTimeout(String),
119
120 #[error("Invalid network configuration: {0}")]
122 InvalidNetwork(String),
123
124 #[error("Missing required configuration: {0}")]
126 MissingConfig(String),
127
128 #[error("Failed to build HTTP client: {0}")]
130 ClientBuilder(String),
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ErrorResponse {
136 pub error_code: String,
137 pub message: String,
138}
139
140impl Error {
141 pub fn api(status_code: u16, error_code: String, message: String) -> Self {
143 Self::Api {
144 status_code,
145 error_code,
146 message,
147 }
148 }
149
150 pub fn address<T: Into<String>>(msg: T) -> Self {
152 Self::Address(msg.into())
153 }
154
155 pub fn array_conversion(expected: usize, actual: usize) -> Self {
157 Self::ArrayConversion { expected, actual }
158 }
159
160 pub fn validation<T: Into<String>, U: Into<String>>(field: T, message: U) -> Self {
162 Self::Validation {
163 field: field.into(),
164 message: message.into(),
165 }
166 }
167
168 pub fn custom<T: Into<String>>(msg: T) -> Self {
170 Self::Custom(msg.into())
171 }
172
173 pub fn verification_error<T: Into<String>>(msg: T) -> Self {
175 Self::VerificationError(msg.into())
176 }
177
178 pub fn is_api_error(&self) -> bool {
180 matches!(self, Self::Api { .. })
181 }
182
183 pub fn is_config_error(&self) -> bool {
185 matches!(self, Self::Config(_))
186 }
187
188 pub fn is_crypto_error(&self) -> bool {
190 matches!(self, Self::Crypto(_))
191 }
192
193 pub fn status_code(&self) -> Option<u16> {
195 match self {
196 Self::Api { status_code, .. } => Some(*status_code),
197 _ => None,
198 }
199 }
200
201 pub fn error_code(&self) -> Option<&str> {
203 match self {
204 Self::Api { error_code, .. } => Some(error_code),
205 _ => None,
206 }
207 }
208
209 pub fn http_transport<T: Into<String>>(message: T, status_code: Option<u16>) -> Self {
211 Self::HttpTransport {
212 message: message.into(),
213 status_code,
214 }
215 }
216
217 pub fn request_timeout<T: Into<String>>(endpoint: T, timeout_ms: u64) -> Self {
219 Self::RequestTimeout {
220 endpoint: endpoint.into(),
221 timeout_ms,
222 }
223 }
224
225 pub fn connection<T: Into<String>>(message: T) -> Self {
227 Self::Connection(message.into())
228 }
229
230 pub fn dns_resolution<T: Into<String>>(message: T) -> Self {
232 Self::DnsResolution(message.into())
233 }
234
235 pub fn response_deserialization<A: Into<String>, B: Into<String>, C: Into<String>>(
237 format: A,
238 error: B,
239 response: C,
240 ) -> Self {
241 Self::ResponseDeserialization {
242 format: format.into(),
243 error: error.into(),
244 response: response.into(),
245 }
246 }
247
248 pub fn authentication<T: Into<String>>(message: T) -> Self {
250 Self::Authentication(message.into())
251 }
252
253 pub fn authorization<T: Into<String>>(message: T) -> Self {
255 Self::Authorization(message.into())
256 }
257
258 pub fn rate_limit_exceeded(retry_after_seconds: Option<u64>) -> Self {
260 Self::RateLimitExceeded { retry_after_seconds }
261 }
262
263 pub fn invalid_parameter<A: Into<String>, B: Into<String>>(parameter: A, message: B) -> Self {
265 Self::InvalidParameter {
266 parameter: parameter.into(),
267 message: message.into(),
268 }
269 }
270
271 pub fn resource_not_found<A: Into<String>, B: Into<String>>(resource_type: A, identifier: B) -> Self {
273 Self::ResourceNotFound {
274 resource_type: resource_type.into(),
275 identifier: identifier.into(),
276 }
277 }
278
279 pub fn business_logic<A: Into<String>, B: Into<String>>(operation: A, reason: B) -> Self {
281 Self::BusinessLogic {
282 operation: operation.into(),
283 reason: reason.into(),
284 }
285 }
286}
287
288impl From<TryFromSliceError> for Error {
289 fn from(_err: TryFromSliceError) -> Self {
290 Self::ArrayConversion {
291 expected: 32, actual: 0, }
294 }
295}
296
297impl From<reqwest::Error> for Error {
299 fn from(err: reqwest::Error) -> Self {
300 if err.is_timeout() {
301 Error::request_timeout(
302 err.url()
303 .map(|u| u.to_string())
304 .unwrap_or_else(|| "unknown".to_string()),
305 30000, )
307 } else if err.is_connect() {
308 Error::connection(format!("Connection failed: {}", err))
309 } else if err.is_request() {
310 Error::invalid_parameter("request", format!("Request error: {}", err))
311 } else if err.is_decode() {
312 Error::response_deserialization("JSON", err.to_string(), "Failed to decode response body")
313 } else {
314 if let Some(status) = err.status() {
316 Error::http_transport(err.to_string(), Some(status.as_u16()))
317 } else {
318 Error::http_transport(err.to_string(), None)
319 }
320 }
321 }
322}
323
324impl ConfigError {
325 pub fn invalid_timeout<T: Into<String>>(msg: T) -> Self {
327 Self::InvalidTimeout(msg.into())
328 }
329
330 pub fn invalid_network<T: Into<String>>(msg: T) -> Self {
332 Self::InvalidNetwork(msg.into())
333 }
334
335 pub fn missing_config<T: Into<String>>(msg: T) -> Self {
337 Self::MissingConfig(msg.into())
338 }
339
340 pub fn client_builder<T: Into<String>>(msg: T) -> Self {
342 Self::ClientBuilder(msg.into())
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use std::error::Error as StdError;
349
350 use super::*;
351
352 #[test]
353 fn test_error_creation_methods() {
354 let api_error = Error::api(
356 404,
357 "resource_not_found".to_string(),
358 "Transaction not found".to_string(),
359 );
360 assert!(matches!(api_error, Error::Api { status_code: 404, .. }));
361 assert_eq!(api_error.status_code(), Some(404));
362 assert_eq!(api_error.error_code(), Some("resource_not_found"));
363
364 let addr_error = Error::address("Invalid address format");
366 assert!(matches!(addr_error, Error::Address(_)));
367
368 let array_error = Error::array_conversion(32, 16);
370 assert!(matches!(
371 array_error,
372 Error::ArrayConversion {
373 expected: 32,
374 actual: 16
375 }
376 ));
377
378 let validation_error = Error::validation("email", "Invalid email format");
380 assert!(matches!(validation_error, Error::Validation { .. }));
381
382 let custom_error = Error::custom("Custom error message");
384 assert!(matches!(custom_error, Error::Custom(_)));
385 }
386
387 #[test]
388 fn test_http_transport_error_creation() {
389 let error_with_status = Error::http_transport("Connection failed", Some(500));
390 assert!(matches!(
391 error_with_status,
392 Error::HttpTransport {
393 status_code: Some(500),
394 ..
395 }
396 ));
397
398 let error_without_status = Error::http_transport("Connection failed", None);
399 assert!(matches!(
400 error_without_status,
401 Error::HttpTransport { status_code: None, .. }
402 ));
403 }
404
405 #[test]
406 fn test_request_timeout_error_creation() {
407 let timeout_error = Error::request_timeout("/api/transactions", 30000);
408 assert!(matches!(timeout_error, Error::RequestTimeout { timeout_ms: 30000, .. }));
409 }
410
411 #[test]
412 fn test_authentication_and_authorization_errors() {
413 let auth_error = Error::authentication("Invalid signature");
414 assert!(matches!(auth_error, Error::Authentication(_)));
415
416 let authz_error = Error::authorization("Insufficient permissions");
417 assert!(matches!(authz_error, Error::Authorization(_)));
418 }
419
420 #[test]
421 fn test_rate_limit_error_creation() {
422 let rate_limit_with_retry = Error::rate_limit_exceeded(Some(60));
423 assert!(matches!(
424 rate_limit_with_retry,
425 Error::RateLimitExceeded {
426 retry_after_seconds: Some(60)
427 }
428 ));
429
430 let rate_limit_without_retry = Error::rate_limit_exceeded(None);
431 assert!(matches!(
432 rate_limit_without_retry,
433 Error::RateLimitExceeded {
434 retry_after_seconds: None
435 }
436 ));
437 }
438
439 #[test]
440 fn test_parameter_and_resource_errors() {
441 let param_error = Error::invalid_parameter("amount", "Amount must be positive");
442 assert!(matches!(param_error, Error::InvalidParameter { .. }));
443
444 let resource_error = Error::resource_not_found("transaction", "0x123abc");
445 assert!(matches!(resource_error, Error::ResourceNotFound { .. }));
446 }
447
448 #[test]
449 fn test_business_logic_error_creation() {
450 let business_error = Error::business_logic("transfer", "Insufficient balance");
451 assert!(matches!(business_error, Error::BusinessLogic { .. }));
452 }
453
454 #[test]
455 fn test_connection_and_dns_errors() {
456 let conn_error = Error::connection("Failed to connect to server");
457 assert!(matches!(conn_error, Error::Connection(_)));
458
459 let dns_error = Error::dns_resolution("Could not resolve hostname");
460 assert!(matches!(dns_error, Error::DnsResolution(_)));
461 }
462
463 #[test]
464 fn test_response_deserialization_error() {
465 let deser_error = Error::response_deserialization("JSON", "unexpected end of input", "{\"invalid\":");
466 assert!(matches!(deser_error, Error::ResponseDeserialization { .. }));
467 }
468
469 #[test]
470 fn test_error_type_checking_methods() {
471 let api_error = Error::api(500, "server_error".to_string(), "Internal server error".to_string());
472 assert!(api_error.is_api_error());
473 assert!(!api_error.is_config_error());
474 assert!(!api_error.is_crypto_error());
475
476 let config_error = Error::Config(ConfigError::InvalidTimeout("Timeout too large".to_string()));
477 assert!(!config_error.is_api_error());
478 assert!(config_error.is_config_error());
479 assert!(!config_error.is_crypto_error());
480
481 let crypto_error = Error::Crypto(CryptoError::InvalidPrivateKey("Invalid key format".to_string()));
482 assert!(!crypto_error.is_api_error());
483 assert!(!crypto_error.is_config_error());
484 assert!(crypto_error.is_crypto_error());
485 }
486
487 #[test]
488 fn test_status_code_and_error_code_extraction() {
489 let api_error = Error::api(422, "business_logic_error".to_string(), "Invalid operation".to_string());
490 assert_eq!(api_error.status_code(), Some(422));
491 assert_eq!(api_error.error_code(), Some("business_logic_error"));
492
493 let non_api_error = Error::custom("Not an API error");
494 assert_eq!(non_api_error.status_code(), None);
495 assert_eq!(non_api_error.error_code(), None);
496 }
497
498 #[test]
499 fn test_crypto_error_creation() {
500 let invalid_private_key = CryptoError::invalid_private_key("Key too short");
501 assert!(matches!(invalid_private_key, CryptoError::InvalidPrivateKey(_)));
502
503 let invalid_public_key = CryptoError::invalid_public_key("Invalid format");
504 assert!(matches!(invalid_public_key, CryptoError::InvalidPublicKey(_)));
505
506 let signature_failed = CryptoError::signature_failed("Could not create signature");
507 assert!(matches!(signature_failed, CryptoError::SignatureFailed(_)));
508
509 let verification_failed = CryptoError::verification_failed("Signature mismatch");
510 assert!(matches!(verification_failed, CryptoError::VerificationFailed(_)));
511
512 let key_derivation = CryptoError::key_derivation("Derivation failed");
513 assert!(matches!(key_derivation, CryptoError::KeyDerivation(_)));
514 }
515
516 #[test]
517 fn test_config_error_creation() {
518 let invalid_timeout = ConfigError::invalid_timeout("Timeout cannot be zero");
519 assert!(matches!(invalid_timeout, ConfigError::InvalidTimeout(_)));
520
521 let invalid_network = ConfigError::invalid_network("Unknown network");
522 assert!(matches!(invalid_network, ConfigError::InvalidNetwork(_)));
523
524 let missing_config = ConfigError::missing_config("API key required");
525 assert!(matches!(missing_config, ConfigError::MissingConfig(_)));
526
527 let client_builder = ConfigError::client_builder("Failed to build HTTP client");
528 assert!(matches!(client_builder, ConfigError::ClientBuilder(_)));
529 }
530
531 #[test]
532 fn test_error_display_formatting() {
533 let api_error = Error::api(404, "not_found".to_string(), "Resource not found".to_string());
535 let display_str = format!("{}", api_error);
536 assert!(display_str.contains("API error 404"));
537 assert!(display_str.contains("not_found"));
538 assert!(display_str.contains("Resource not found"));
539
540 let timeout_error = Error::request_timeout("/api/test", 5000);
541 let timeout_str = format!("{}", timeout_error);
542 assert!(timeout_str.contains("Request timeout after 5000ms"));
543 assert!(timeout_str.contains("/api/test"));
544
545 let param_error = Error::invalid_parameter("amount", "Must be positive");
546 let param_str = format!("{}", param_error);
547 assert!(param_str.contains("Invalid parameter 'amount'"));
548 assert!(param_str.contains("Must be positive"));
549 }
550
551 #[test]
552 fn test_error_from_conversions() {
553 let crypto_error = CryptoError::invalid_private_key("Invalid key");
555 let error: Error = crypto_error.into();
556 assert!(matches!(error, Error::Crypto(_)));
557
558 let config_error = ConfigError::invalid_timeout("Invalid timeout");
560 let error: Error = config_error.into();
561 assert!(matches!(error, Error::Config(_)));
562
563 let result: StdResult<[u8; 4], TryFromSliceError> = [0u8; 2].as_slice().try_into();
566 let slice_error = result.unwrap_err();
567 let error: Error = slice_error.into();
568 assert!(matches!(
569 error,
570 Error::ArrayConversion {
571 expected: 32,
572 actual: 0
573 }
574 ));
575 }
576
577 #[test]
578 fn test_error_response_structure() {
579 let error_response = ErrorResponse {
580 error_code: "validation_error".to_string(),
581 message: "Invalid input parameters".to_string(),
582 };
583
584 let json = serde_json::to_string(&error_response).expect("Should serialize");
586 assert!(json.contains("validation_error"));
587 assert!(json.contains("Invalid input parameters"));
588
589 let deserialized: ErrorResponse = serde_json::from_str(&json).expect("Should deserialize");
591 assert_eq!(deserialized.error_code, "validation_error");
592 assert_eq!(deserialized.message, "Invalid input parameters");
593 }
594
595 #[test]
596 fn test_reqwest_error_conversion() {
597 }
606
607 #[test]
608 fn test_error_debug_formatting() {
609 let error = Error::api(500, "server_error".to_string(), "Internal error".to_string());
610 let debug_str = format!("{:?}", error);
611 assert!(debug_str.contains("Api"));
612 assert!(debug_str.contains("status_code: 500"));
613
614 let crypto_error = CryptoError::invalid_private_key("Invalid format");
615 let crypto_debug = format!("{:?}", crypto_error);
616 assert!(crypto_debug.contains("InvalidPrivateKey"));
617
618 let config_error = ConfigError::invalid_network("Unknown network");
619 let config_debug = format!("{:?}", config_error);
620 assert!(config_debug.contains("InvalidNetwork"));
621 }
622
623 #[test]
624 fn test_result_type_alias() {
625 let success_result: Result<String> = Ok("success".to_string());
627 assert!(success_result.is_ok());
628 if let Ok(value) = success_result {
629 assert_eq!(value, "success");
630 }
631
632 let error_result: Result<String> = Err(Error::custom("test error"));
633 assert!(error_result.is_err());
634 if let Err(error) = error_result {
635 assert!(matches!(error, Error::Custom(_)));
636 }
637 }
638
639 #[test]
640 fn test_error_source_chain() {
641 let crypto_error = CryptoError::invalid_private_key("Base crypto error");
644 let main_error = Error::Crypto(crypto_error);
645
646 assert!(main_error.source().is_some());
648
649 let config_error = ConfigError::invalid_timeout("Base config error");
650 let main_error = Error::Config(config_error);
651
652 assert!(main_error.source().is_some());
654 }
655}