1use serde::{Deserialize, Serialize};
4use std::array::TryFromSliceError;
5use std::result::Result as StdResult;
6use thiserror::Error;
7
8pub type Result<T> = StdResult<T, Error>;
10
11#[derive(Error, Debug)]
13pub enum Error {
14 #[error("JSON parsing failed: {0}")]
16 Json(#[from] serde_json::Error),
17
18 #[error("API error {status_code}: {error_code} - {message}")]
20 Api {
21 status_code: u16,
22 error_code: String,
23 message: String,
24 },
25
26 #[error("HTTP transport error: {message}")]
28 HttpTransport {
29 message: String,
30 status_code: Option<u16>,
31 },
32
33 #[error("Request timeout after {timeout_ms}ms to {endpoint}")]
35 RequestTimeout { endpoint: String, timeout_ms: u64 },
36
37 #[error("Connection failed: {0}")]
39 Connection(String),
40
41 #[error("DNS resolution failed: {0}")]
43 DnsResolution(String),
44
45 #[error("Failed to deserialize {format} response: {error} - Response: {response}")]
47 ResponseDeserialization {
48 format: String,
49 error: String,
50 response: String,
51 },
52
53 #[error("Authentication failed: {0}")]
55 Authentication(String),
56
57 #[error("Authorization failed: {0}")]
59 Authorization(String),
60
61 #[error("Rate limit exceeded")]
63 RateLimitExceeded { retry_after_seconds: Option<u64> },
64
65 #[error("Invalid parameter '{parameter}': {message}")]
67 InvalidParameter { parameter: String, message: String },
68
69 #[error("Resource not found: {resource_type} with {identifier}")]
71 ResourceNotFound {
72 resource_type: String,
73 identifier: String,
74 },
75
76 #[error("Business logic error: {operation} failed - {reason}")]
78 BusinessLogic { operation: String, reason: String },
79
80 #[error("Cryptographic operation failed: {0}")]
82 Crypto(#[from] CryptoError),
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("RLP encoding/decoding failed: {0}")]
98 Rlp(#[from] rlp::DecoderError),
99
100 #[error("Invalid address format: {0}")]
102 Address(String),
103
104 #[error("Array conversion failed: expected length {expected}, got {actual}")]
106 ArrayConversion { expected: usize, actual: usize },
107
108 #[error("Validation failed: {field} - {message}")]
110 Validation { field: String, message: String },
111
112 #[error("{0}")]
114 Custom(String),
115}
116
117#[derive(Error, Debug)]
119pub enum CryptoError {
120 #[error("Invalid private key: {0}")]
122 InvalidPrivateKey(String),
123
124 #[error("Invalid public key: {0}")]
126 InvalidPublicKey(String),
127
128 #[error("Failed to create signature: {0}")]
130 SignatureFailed(String),
131
132 #[error("Signature verification failed: {0}")]
134 VerificationFailed(String),
135
136 #[error("Key derivation failed: {0}")]
138 KeyDerivation(String),
139}
140
141#[derive(Error, Debug)]
143pub enum ConfigError {
144 #[error("Invalid timeout: {0}")]
146 InvalidTimeout(String),
147
148 #[error("Invalid network configuration: {0}")]
150 InvalidNetwork(String),
151
152 #[error("Missing required configuration: {0}")]
154 MissingConfig(String),
155
156 #[error("Failed to build HTTP client: {0}")]
158 ClientBuilder(String),
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct ErrorResponse {
164 pub error_code: String,
165 pub message: String,
166}
167
168impl Error {
169 pub fn api(status_code: u16, error_code: String, message: String) -> Self {
171 Self::Api {
172 status_code,
173 error_code,
174 message,
175 }
176 }
177
178 pub fn address<T: Into<String>>(msg: T) -> Self {
180 Self::Address(msg.into())
181 }
182
183 pub fn array_conversion(expected: usize, actual: usize) -> Self {
185 Self::ArrayConversion { expected, actual }
186 }
187
188 pub fn validation<T: Into<String>, U: Into<String>>(field: T, message: U) -> Self {
190 Self::Validation {
191 field: field.into(),
192 message: message.into(),
193 }
194 }
195
196 pub fn custom<T: Into<String>>(msg: T) -> Self {
198 Self::Custom(msg.into())
199 }
200
201 pub fn is_api_error(&self) -> bool {
203 matches!(self, Self::Api { .. })
204 }
205
206 pub fn is_config_error(&self) -> bool {
208 matches!(self, Self::Config(_))
209 }
210
211 pub fn is_crypto_error(&self) -> bool {
213 matches!(self, Self::Crypto(_))
214 }
215
216 pub fn status_code(&self) -> Option<u16> {
218 match self {
219 Self::Api { status_code, .. } => Some(*status_code),
220 _ => None,
221 }
222 }
223
224 pub fn error_code(&self) -> Option<&str> {
226 match self {
227 Self::Api { error_code, .. } => Some(error_code),
228 _ => None,
229 }
230 }
231
232 pub fn http_transport<T: Into<String>>(message: T, status_code: Option<u16>) -> Self {
234 Self::HttpTransport {
235 message: message.into(),
236 status_code,
237 }
238 }
239
240 pub fn request_timeout<T: Into<String>>(endpoint: T, timeout_ms: u64) -> Self {
242 Self::RequestTimeout {
243 endpoint: endpoint.into(),
244 timeout_ms,
245 }
246 }
247
248 pub fn connection<T: Into<String>>(message: T) -> Self {
250 Self::Connection(message.into())
251 }
252
253 pub fn dns_resolution<T: Into<String>>(message: T) -> Self {
255 Self::DnsResolution(message.into())
256 }
257
258 pub fn response_deserialization<A: Into<String>, B: Into<String>, C: Into<String>>(
260 format: A,
261 error: B,
262 response: C,
263 ) -> Self {
264 Self::ResponseDeserialization {
265 format: format.into(),
266 error: error.into(),
267 response: response.into(),
268 }
269 }
270
271 pub fn authentication<T: Into<String>>(message: T) -> Self {
273 Self::Authentication(message.into())
274 }
275
276 pub fn authorization<T: Into<String>>(message: T) -> Self {
278 Self::Authorization(message.into())
279 }
280
281 pub fn rate_limit_exceeded(retry_after_seconds: Option<u64>) -> Self {
283 Self::RateLimitExceeded {
284 retry_after_seconds,
285 }
286 }
287
288 pub fn invalid_parameter<A: Into<String>, B: Into<String>>(parameter: A, message: B) -> Self {
290 Self::InvalidParameter {
291 parameter: parameter.into(),
292 message: message.into(),
293 }
294 }
295
296 pub fn resource_not_found<A: Into<String>, B: Into<String>>(
298 resource_type: A,
299 identifier: B,
300 ) -> Self {
301 Self::ResourceNotFound {
302 resource_type: resource_type.into(),
303 identifier: identifier.into(),
304 }
305 }
306
307 pub fn business_logic<A: Into<String>, B: Into<String>>(operation: A, reason: B) -> Self {
309 Self::BusinessLogic {
310 operation: operation.into(),
311 reason: reason.into(),
312 }
313 }
314}
315
316impl From<TryFromSliceError> for Error {
317 fn from(_err: TryFromSliceError) -> Self {
318 Self::ArrayConversion {
319 expected: 32, actual: 0, }
322 }
323}
324
325impl From<reqwest::Error> for Error {
327 fn from(err: reqwest::Error) -> Self {
328 if err.is_timeout() {
329 Error::request_timeout(
330 err.url()
331 .map(|u| u.to_string())
332 .unwrap_or_else(|| "unknown".to_string()),
333 30000, )
335 } else if err.is_connect() {
336 Error::connection(format!("Connection failed: {}", err))
337 } else if err.is_request() {
338 Error::invalid_parameter("request", format!("Request error: {}", err))
339 } else if err.is_decode() {
340 Error::response_deserialization(
341 "JSON",
342 err.to_string(),
343 "Failed to decode response body",
344 )
345 } else {
346 if let Some(status) = err.status() {
348 Error::http_transport(err.to_string(), Some(status.as_u16()))
349 } else {
350 Error::http_transport(err.to_string(), None)
351 }
352 }
353 }
354}
355
356impl CryptoError {
357 pub fn invalid_private_key<T: Into<String>>(msg: T) -> Self {
359 Self::InvalidPrivateKey(msg.into())
360 }
361
362 pub fn invalid_public_key<T: Into<String>>(msg: T) -> Self {
364 Self::InvalidPublicKey(msg.into())
365 }
366
367 pub fn signature_failed<T: Into<String>>(msg: T) -> Self {
369 Self::SignatureFailed(msg.into())
370 }
371
372 pub fn verification_failed<T: Into<String>>(msg: T) -> Self {
374 Self::VerificationFailed(msg.into())
375 }
376
377 pub fn key_derivation<T: Into<String>>(msg: T) -> Self {
379 Self::KeyDerivation(msg.into())
380 }
381}
382
383impl ConfigError {
384 pub fn invalid_timeout<T: Into<String>>(msg: T) -> Self {
386 Self::InvalidTimeout(msg.into())
387 }
388
389 pub fn invalid_network<T: Into<String>>(msg: T) -> Self {
391 Self::InvalidNetwork(msg.into())
392 }
393
394 pub fn missing_config<T: Into<String>>(msg: T) -> Self {
396 Self::MissingConfig(msg.into())
397 }
398
399 pub fn client_builder<T: Into<String>>(msg: T) -> Self {
401 Self::ClientBuilder(msg.into())
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use std::error::Error as StdError;
409
410 #[test]
411 fn test_error_creation_methods() {
412 let api_error = Error::api(
414 404,
415 "resource_not_found".to_string(),
416 "Transaction not found".to_string(),
417 );
418 assert!(matches!(
419 api_error,
420 Error::Api {
421 status_code: 404,
422 ..
423 }
424 ));
425 assert_eq!(api_error.status_code(), Some(404));
426 assert_eq!(api_error.error_code(), Some("resource_not_found"));
427
428 let addr_error = Error::address("Invalid address format");
430 assert!(matches!(addr_error, Error::Address(_)));
431
432 let array_error = Error::array_conversion(32, 16);
434 assert!(matches!(
435 array_error,
436 Error::ArrayConversion {
437 expected: 32,
438 actual: 16
439 }
440 ));
441
442 let validation_error = Error::validation("email", "Invalid email format");
444 assert!(matches!(validation_error, Error::Validation { .. }));
445
446 let custom_error = Error::custom("Custom error message");
448 assert!(matches!(custom_error, Error::Custom(_)));
449 }
450
451 #[test]
452 fn test_http_transport_error_creation() {
453 let error_with_status = Error::http_transport("Connection failed", Some(500));
454 assert!(matches!(
455 error_with_status,
456 Error::HttpTransport {
457 status_code: Some(500),
458 ..
459 }
460 ));
461
462 let error_without_status = Error::http_transport("Connection failed", None);
463 assert!(matches!(
464 error_without_status,
465 Error::HttpTransport {
466 status_code: None,
467 ..
468 }
469 ));
470 }
471
472 #[test]
473 fn test_request_timeout_error_creation() {
474 let timeout_error = Error::request_timeout("/api/transactions", 30000);
475 assert!(matches!(
476 timeout_error,
477 Error::RequestTimeout {
478 timeout_ms: 30000,
479 ..
480 }
481 ));
482 }
483
484 #[test]
485 fn test_authentication_and_authorization_errors() {
486 let auth_error = Error::authentication("Invalid signature");
487 assert!(matches!(auth_error, Error::Authentication(_)));
488
489 let authz_error = Error::authorization("Insufficient permissions");
490 assert!(matches!(authz_error, Error::Authorization(_)));
491 }
492
493 #[test]
494 fn test_rate_limit_error_creation() {
495 let rate_limit_with_retry = Error::rate_limit_exceeded(Some(60));
496 assert!(matches!(
497 rate_limit_with_retry,
498 Error::RateLimitExceeded {
499 retry_after_seconds: Some(60)
500 }
501 ));
502
503 let rate_limit_without_retry = Error::rate_limit_exceeded(None);
504 assert!(matches!(
505 rate_limit_without_retry,
506 Error::RateLimitExceeded {
507 retry_after_seconds: None
508 }
509 ));
510 }
511
512 #[test]
513 fn test_parameter_and_resource_errors() {
514 let param_error = Error::invalid_parameter("amount", "Amount must be positive");
515 assert!(matches!(param_error, Error::InvalidParameter { .. }));
516
517 let resource_error = Error::resource_not_found("transaction", "0x123abc");
518 assert!(matches!(resource_error, Error::ResourceNotFound { .. }));
519 }
520
521 #[test]
522 fn test_business_logic_error_creation() {
523 let business_error = Error::business_logic("transfer", "Insufficient balance");
524 assert!(matches!(business_error, Error::BusinessLogic { .. }));
525 }
526
527 #[test]
528 fn test_connection_and_dns_errors() {
529 let conn_error = Error::connection("Failed to connect to server");
530 assert!(matches!(conn_error, Error::Connection(_)));
531
532 let dns_error = Error::dns_resolution("Could not resolve hostname");
533 assert!(matches!(dns_error, Error::DnsResolution(_)));
534 }
535
536 #[test]
537 fn test_response_deserialization_error() {
538 let deser_error =
539 Error::response_deserialization("JSON", "unexpected end of input", "{\"invalid\":");
540 assert!(matches!(deser_error, Error::ResponseDeserialization { .. }));
541 }
542
543 #[test]
544 fn test_error_type_checking_methods() {
545 let api_error = Error::api(
546 500,
547 "server_error".to_string(),
548 "Internal server error".to_string(),
549 );
550 assert!(api_error.is_api_error());
551 assert!(!api_error.is_config_error());
552 assert!(!api_error.is_crypto_error());
553
554 let config_error =
555 Error::Config(ConfigError::InvalidTimeout("Timeout too large".to_string()));
556 assert!(!config_error.is_api_error());
557 assert!(config_error.is_config_error());
558 assert!(!config_error.is_crypto_error());
559
560 let crypto_error = Error::Crypto(CryptoError::InvalidPrivateKey(
561 "Invalid key format".to_string(),
562 ));
563 assert!(!crypto_error.is_api_error());
564 assert!(!crypto_error.is_config_error());
565 assert!(crypto_error.is_crypto_error());
566 }
567
568 #[test]
569 fn test_status_code_and_error_code_extraction() {
570 let api_error = Error::api(
571 422,
572 "business_logic_error".to_string(),
573 "Invalid operation".to_string(),
574 );
575 assert_eq!(api_error.status_code(), Some(422));
576 assert_eq!(api_error.error_code(), Some("business_logic_error"));
577
578 let non_api_error = Error::custom("Not an API error");
579 assert_eq!(non_api_error.status_code(), None);
580 assert_eq!(non_api_error.error_code(), None);
581 }
582
583 #[test]
584 fn test_crypto_error_creation() {
585 let invalid_private_key = CryptoError::invalid_private_key("Key too short");
586 assert!(matches!(
587 invalid_private_key,
588 CryptoError::InvalidPrivateKey(_)
589 ));
590
591 let invalid_public_key = CryptoError::invalid_public_key("Invalid format");
592 assert!(matches!(
593 invalid_public_key,
594 CryptoError::InvalidPublicKey(_)
595 ));
596
597 let signature_failed = CryptoError::signature_failed("Could not create signature");
598 assert!(matches!(signature_failed, CryptoError::SignatureFailed(_)));
599
600 let verification_failed = CryptoError::verification_failed("Signature mismatch");
601 assert!(matches!(
602 verification_failed,
603 CryptoError::VerificationFailed(_)
604 ));
605
606 let key_derivation = CryptoError::key_derivation("Derivation failed");
607 assert!(matches!(key_derivation, CryptoError::KeyDerivation(_)));
608 }
609
610 #[test]
611 fn test_config_error_creation() {
612 let invalid_timeout = ConfigError::invalid_timeout("Timeout cannot be zero");
613 assert!(matches!(invalid_timeout, ConfigError::InvalidTimeout(_)));
614
615 let invalid_network = ConfigError::invalid_network("Unknown network");
616 assert!(matches!(invalid_network, ConfigError::InvalidNetwork(_)));
617
618 let missing_config = ConfigError::missing_config("API key required");
619 assert!(matches!(missing_config, ConfigError::MissingConfig(_)));
620
621 let client_builder = ConfigError::client_builder("Failed to build HTTP client");
622 assert!(matches!(client_builder, ConfigError::ClientBuilder(_)));
623 }
624
625 #[test]
626 fn test_error_display_formatting() {
627 let api_error = Error::api(
629 404,
630 "not_found".to_string(),
631 "Resource not found".to_string(),
632 );
633 let display_str = format!("{}", api_error);
634 assert!(display_str.contains("API error 404"));
635 assert!(display_str.contains("not_found"));
636 assert!(display_str.contains("Resource not found"));
637
638 let timeout_error = Error::request_timeout("/api/test", 5000);
639 let timeout_str = format!("{}", timeout_error);
640 assert!(timeout_str.contains("Request timeout after 5000ms"));
641 assert!(timeout_str.contains("/api/test"));
642
643 let param_error = Error::invalid_parameter("amount", "Must be positive");
644 let param_str = format!("{}", param_error);
645 assert!(param_str.contains("Invalid parameter 'amount'"));
646 assert!(param_str.contains("Must be positive"));
647 }
648
649 #[test]
650 fn test_error_from_conversions() {
651 let crypto_error = CryptoError::invalid_private_key("Invalid key");
653 let error: Error = crypto_error.into();
654 assert!(matches!(error, Error::Crypto(_)));
655
656 let config_error = ConfigError::invalid_timeout("Invalid timeout");
658 let error: Error = config_error.into();
659 assert!(matches!(error, Error::Config(_)));
660
661 let result: StdResult<[u8; 4], std::array::TryFromSliceError> =
664 [0u8; 2].as_slice().try_into();
665 let slice_error = result.unwrap_err();
666 let error: Error = slice_error.into();
667 assert!(matches!(
668 error,
669 Error::ArrayConversion {
670 expected: 32,
671 actual: 0
672 }
673 ));
674 }
675
676 #[test]
677 fn test_error_response_structure() {
678 let error_response = ErrorResponse {
679 error_code: "validation_error".to_string(),
680 message: "Invalid input parameters".to_string(),
681 };
682
683 let json = serde_json::to_string(&error_response).expect("Should serialize");
685 assert!(json.contains("validation_error"));
686 assert!(json.contains("Invalid input parameters"));
687
688 let deserialized: ErrorResponse = serde_json::from_str(&json).expect("Should deserialize");
690 assert_eq!(deserialized.error_code, "validation_error");
691 assert_eq!(deserialized.message, "Invalid input parameters");
692 }
693
694 #[test]
695 fn test_reqwest_error_conversion() {
696 }
703
704 #[test]
705 fn test_error_debug_formatting() {
706 let error = Error::api(
707 500,
708 "server_error".to_string(),
709 "Internal error".to_string(),
710 );
711 let debug_str = format!("{:?}", error);
712 assert!(debug_str.contains("Api"));
713 assert!(debug_str.contains("status_code: 500"));
714
715 let crypto_error = CryptoError::invalid_private_key("Invalid format");
716 let crypto_debug = format!("{:?}", crypto_error);
717 assert!(crypto_debug.contains("InvalidPrivateKey"));
718
719 let config_error = ConfigError::invalid_network("Unknown network");
720 let config_debug = format!("{:?}", config_error);
721 assert!(config_debug.contains("InvalidNetwork"));
722 }
723
724 #[test]
725 fn test_result_type_alias() {
726 let success_result: Result<String> = Ok("success".to_string());
728 assert!(success_result.is_ok());
729 if let Ok(value) = success_result {
730 assert_eq!(value, "success");
731 }
732
733 let error_result: Result<String> = Err(Error::custom("test error"));
734 assert!(error_result.is_err());
735 if let Err(error) = error_result {
736 assert!(matches!(error, Error::Custom(_)));
737 }
738 }
739
740 #[test]
741 fn test_error_source_chain() {
742 let crypto_error = CryptoError::invalid_private_key("Base crypto error");
744 let main_error = Error::Crypto(crypto_error);
745
746 assert!(main_error.source().is_some());
748
749 let config_error = ConfigError::invalid_timeout("Base config error");
750 let main_error = Error::Config(config_error);
751
752 assert!(main_error.source().is_some());
754 }
755}