armature_mail/
error.rs

1//! Mail error types.
2
3use thiserror::Error;
4
5/// Result type for mail operations.
6pub type Result<T> = std::result::Result<T, MailError>;
7
8/// Mail errors.
9#[derive(Debug, Error)]
10pub enum MailError {
11    /// SMTP connection error.
12    #[error("SMTP error: {0}")]
13    Smtp(String),
14
15    /// Invalid email address.
16    #[error("Invalid email address: {0}")]
17    InvalidAddress(String),
18
19    /// Missing required field.
20    #[error("Missing required field: {0}")]
21    MissingField(&'static str),
22
23    /// Template error.
24    #[error("Template error: {0}")]
25    Template(String),
26
27    /// Template not found.
28    #[error("Template not found: {0}")]
29    TemplateNotFound(String),
30
31    /// Attachment error.
32    #[error("Attachment error: {0}")]
33    Attachment(String),
34
35    /// Configuration error.
36    #[error("Configuration error: {0}")]
37    Config(String),
38
39    /// Provider API error.
40    #[error("Provider error: {0}")]
41    Provider(String),
42
43    /// Authentication error.
44    #[error("Authentication failed: {0}")]
45    Auth(String),
46
47    /// Rate limited.
48    #[error("Rate limited, retry after {0} seconds")]
49    RateLimited(u64),
50
51    /// I/O error.
52    #[error("I/O error: {0}")]
53    Io(#[from] std::io::Error),
54
55    /// Serialization error.
56    #[error("Serialization error: {0}")]
57    Serialization(String),
58
59    /// Network error.
60    #[error("Network error: {0}")]
61    Network(String),
62
63    /// Timeout error.
64    #[error("Operation timed out")]
65    Timeout,
66
67    /// Queue error.
68    #[error("Queue error: {0}")]
69    Queue(String),
70}
71
72impl MailError {
73    /// Check if this error is retryable.
74    pub fn is_retryable(&self) -> bool {
75        matches!(
76            self,
77            Self::Smtp(_) | Self::Network(_) | Self::Timeout | Self::RateLimited(_)
78        )
79    }
80
81    /// Get retry-after duration if rate limited.
82    pub fn retry_after(&self) -> Option<std::time::Duration> {
83        if let Self::RateLimited(secs) = self {
84            Some(std::time::Duration::from_secs(*secs))
85        } else {
86            None
87        }
88    }
89}
90
91impl From<lettre::transport::smtp::Error> for MailError {
92    fn from(err: lettre::transport::smtp::Error) -> Self {
93        Self::Smtp(err.to_string())
94    }
95}
96
97impl From<lettre::address::AddressError> for MailError {
98    fn from(err: lettre::address::AddressError) -> Self {
99        Self::InvalidAddress(err.to_string())
100    }
101}
102
103impl From<lettre::error::Error> for MailError {
104    fn from(err: lettre::error::Error) -> Self {
105        Self::Smtp(err.to_string())
106    }
107}
108
109impl From<serde_json::Error> for MailError {
110    fn from(err: serde_json::Error) -> Self {
111        Self::Serialization(err.to_string())
112    }
113}
114
115#[cfg(feature = "handlebars")]
116impl From<handlebars::RenderError> for MailError {
117    fn from(err: handlebars::RenderError) -> Self {
118        Self::Template(err.to_string())
119    }
120}
121
122#[cfg(feature = "handlebars")]
123impl From<handlebars::TemplateError> for MailError {
124    fn from(err: handlebars::TemplateError) -> Self {
125        Self::Template(err.to_string())
126    }
127}
128
129#[cfg(feature = "tera")]
130impl From<tera::Error> for MailError {
131    fn from(err: tera::Error) -> Self {
132        Self::Template(err.to_string())
133    }
134}