5fa7a82548
- Add .gitignore and .env.example files for project setup - Create build script for proto compilation with tonic-prost - Generate Cargo.lock with all project dependencies - Configure project structure and ignore patterns for development environment
148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
use std::fmt;
|
|
|
|
const ENV_PREFIX: &str = "APP_SMTP_";
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum ConfigError {
|
|
MissingEnv { name: &'static str },
|
|
InvalidEnv { name: &'static str, reason: String },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum QueueError {
|
|
Closed,
|
|
Full,
|
|
IdExhausted,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum EmailError {
|
|
MissingSender,
|
|
MissingRecipients,
|
|
RequestSenderDisabled,
|
|
IncompleteCredentials,
|
|
InvalidAddress {
|
|
field: &'static str,
|
|
value: String,
|
|
reason: String,
|
|
},
|
|
InvalidContentType {
|
|
filename: String,
|
|
value: String,
|
|
reason: String,
|
|
},
|
|
InvalidHeader {
|
|
name: String,
|
|
reason: String,
|
|
},
|
|
ForbiddenHeader {
|
|
name: String,
|
|
},
|
|
UnsupportedAttachmentUrl {
|
|
filename: String,
|
|
url: String,
|
|
},
|
|
BuildTransport(String),
|
|
BuildMessage(String),
|
|
Send(String),
|
|
}
|
|
|
|
impl fmt::Display for ConfigError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::MissingEnv { name } => {
|
|
write!(f, "missing environment variable {ENV_PREFIX}{name}")
|
|
}
|
|
Self::InvalidEnv { name, reason } => {
|
|
write!(
|
|
f,
|
|
"invalid environment variable {ENV_PREFIX}{name}: {reason}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for QueueError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Closed => f.write_str("email queue is closed"),
|
|
Self::Full => f.write_str("email queue is full"),
|
|
Self::IdExhausted => f.write_str("email queue id space is exhausted"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for EmailError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::MissingSender => {
|
|
f.write_str("missing sender: set request.from or APP_SMTP_FROM_EMAIL")
|
|
}
|
|
Self::MissingRecipients => {
|
|
f.write_str("missing recipients: request.to must not be empty")
|
|
}
|
|
Self::RequestSenderDisabled => {
|
|
f.write_str("request.from is disabled: use APP_SMTP_FROM_EMAIL or enable APP_SMTP_ALLOW_REQUEST_FROM")
|
|
}
|
|
Self::IncompleteCredentials => {
|
|
f.write_str("APP_SMTP_USERNAME and APP_SMTP_PASSWORD must be set together")
|
|
}
|
|
Self::InvalidAddress {
|
|
field,
|
|
value,
|
|
reason,
|
|
} => write!(f, "invalid email address in {field} ({value}): {reason}"),
|
|
Self::InvalidContentType {
|
|
filename,
|
|
value,
|
|
reason,
|
|
} => write!(
|
|
f,
|
|
"invalid content type for attachment {filename} ({value}): {reason}"
|
|
),
|
|
Self::InvalidHeader { name, reason } => {
|
|
write!(f, "invalid custom header {name}: {reason}")
|
|
}
|
|
Self::ForbiddenHeader { name } => {
|
|
write!(f, "custom header {name} is managed by the mail builder")
|
|
}
|
|
Self::UnsupportedAttachmentUrl { filename, url } => write!(
|
|
f,
|
|
"attachment {filename} uses url {url}, but URL attachment fetching is not supported"
|
|
),
|
|
Self::BuildTransport(reason) => write!(f, "failed to build SMTP transport: {reason}"),
|
|
Self::BuildMessage(reason) => write!(f, "failed to build email message: {reason}"),
|
|
Self::Send(reason) => write!(f, "failed to send email: {reason}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ConfigError {}
|
|
impl std::error::Error for QueueError {}
|
|
impl std::error::Error for EmailError {}
|
|
|
|
impl EmailError {
|
|
/// 返回 true 表示该错误不可重试,应直接销毁任务
|
|
pub fn is_terminal(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::MissingSender
|
|
| Self::MissingRecipients
|
|
| Self::RequestSenderDisabled
|
|
| Self::IncompleteCredentials
|
|
| Self::InvalidAddress { .. }
|
|
| Self::InvalidContentType { .. }
|
|
| Self::InvalidHeader { .. }
|
|
| Self::ForbiddenHeader { .. }
|
|
| Self::UnsupportedAttachmentUrl { .. }
|
|
| Self::BuildTransport(_)
|
|
| Self::BuildMessage(_)
|
|
)
|
|
}
|
|
|
|
pub fn is_retryable(&self) -> bool {
|
|
!self.is_terminal()
|
|
}
|
|
}
|