euromail/errors.rs
1use serde::Deserialize;
2
3/// Errors returned by the EuroMail SDK.
4///
5/// All API methods return `Result<T, EuroMailError>`. HTTP-level errors from the
6/// EuroMail API are mapped to specific variants based on status code, while
7/// network and deserialization failures surface as [`EuroMailError::Http`].
8///
9/// # Example
10///
11/// ```rust,no_run
12/// # use euromail::{EuroMail, EuroMailError};
13/// # async fn run() -> Result<(), EuroMailError> {
14/// let client = EuroMail::new("em_live_key");
15/// match client.get_email("nonexistent").await {
16/// Err(EuroMailError::NotFound(msg)) => eprintln!("Not found: {msg}"),
17/// Err(EuroMailError::RateLimit { retry_after, .. }) => {
18/// eprintln!("Rate limited, retry after {retry_after:?}s");
19/// }
20/// Err(e) => eprintln!("Error: {e}"),
21/// Ok(detail) => println!("Email: {}", detail.email.id),
22/// }
23/// # Ok(())
24/// # }
25/// ```
26#[derive(Debug, thiserror::Error)]
27pub enum EuroMailError {
28 /// Invalid or expired API key (HTTP 401).
29 #[error("Authentication failed: {0}")]
30 Authentication(String),
31
32 /// Request failed validation — e.g. missing required fields (HTTP 422).
33 #[error("Validation error [{code}]: {message}")]
34 Validation { code: String, message: String },
35
36 /// Too many requests. `retry_after` contains the suggested wait in seconds
37 /// if the server provided a `Retry-After` header (HTTP 429).
38 #[error("Rate limit exceeded: {message}")]
39 RateLimit {
40 retry_after: Option<u64>,
41 message: String,
42 },
43
44 /// The requested resource does not exist (HTTP 404).
45 #[error("Not found: {0}")]
46 NotFound(String),
47
48 /// Any other API error (HTTP 4xx/5xx).
49 #[error("API error [{status}] {code}: {message}")]
50 Api {
51 status: u16,
52 code: String,
53 message: String,
54 },
55
56 /// Network or deserialization error from the underlying HTTP client.
57 #[error("HTTP error: {0}")]
58 Http(#[from] reqwest::Error),
59}
60
61#[derive(Deserialize)]
62pub(crate) struct ApiErrorBody {
63 #[serde(default = "default_code")]
64 pub code: String,
65 #[serde(default = "default_message")]
66 pub message: String,
67}
68
69fn default_code() -> String {
70 "unknown".to_string()
71}
72
73fn default_message() -> String {
74 "Unknown error".to_string()
75}