chore(project): initialize project with core configuration and dependencies
- 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
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user