use serde::{Deserialize, Serialize};
use super::base::BaseEvent;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum BounceEvent {
SenderNotification {
base: BaseEvent,
original_queue_id: String,
bounce_queue_id: String,
},
PostmasterNotification {
base: BaseEvent,
original_queue_id: String,
bounce_queue_id: String,
},
Warning {
base: BaseEvent,
warning_type: BounceWarningType,
details: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum BounceWarningType {
MalformedRequest,
Configuration,
ResourceLimit,
Other,
}
impl BounceEvent {
pub fn base(&self) -> &BaseEvent {
match self {
BounceEvent::SenderNotification { base, .. } => base,
BounceEvent::PostmasterNotification { base, .. } => base,
BounceEvent::Warning { base, .. } => base,
}
}
pub fn event_type(&self) -> &'static str {
match self {
BounceEvent::SenderNotification { .. } => "sender_notification",
BounceEvent::PostmasterNotification { .. } => "postmaster_notification",
BounceEvent::Warning { .. } => "warning",
}
}
pub fn is_notification(&self) -> bool {
matches!(
self,
BounceEvent::SenderNotification { .. } | BounceEvent::PostmasterNotification { .. }
)
}
pub fn is_warning(&self) -> bool {
matches!(self, BounceEvent::Warning { .. })
}
pub fn get_queue_ids(&self) -> Option<(&str, &str)> {
match self {
BounceEvent::SenderNotification {
original_queue_id,
bounce_queue_id,
..
} => Some((original_queue_id, bounce_queue_id)),
BounceEvent::PostmasterNotification {
original_queue_id,
bounce_queue_id,
..
} => Some((original_queue_id, bounce_queue_id)),
BounceEvent::Warning { .. } => None,
}
}
pub fn get_warning_info(&self) -> Option<(&BounceWarningType, &str)> {
match self {
BounceEvent::Warning {
warning_type,
details,
..
} => Some((warning_type, details)),
_ => None,
}
}
pub fn format_description(&self) -> String {
match self {
BounceEvent::SenderNotification {
original_queue_id,
bounce_queue_id,
..
} => {
format!(
"为原始邮件 {} 生成发送者投递失败通知,通知邮件队列ID: {}",
original_queue_id, bounce_queue_id
)
}
BounceEvent::PostmasterNotification {
original_queue_id,
bounce_queue_id,
..
} => {
format!(
"为原始邮件 {} 生成邮件管理员投递失败通知,通知邮件队列ID: {}",
original_queue_id, bounce_queue_id
)
}
BounceEvent::Warning {
warning_type,
details,
..
} => {
let type_desc = match warning_type {
BounceWarningType::MalformedRequest => "格式错误请求",
BounceWarningType::Configuration => "配置警告",
BounceWarningType::ResourceLimit => "资源限制警告",
BounceWarningType::Other => "其他警告",
};
format!("{}: {}", type_desc, details)
}
}
}
}
impl BounceWarningType {
pub fn severity(&self) -> u8 {
match self {
BounceWarningType::MalformedRequest => 2, BounceWarningType::Configuration => 4, BounceWarningType::ResourceLimit => 5, BounceWarningType::Other => 3, }
}
pub fn suggested_action(&self) -> &'static str {
match self {
BounceWarningType::MalformedRequest => {
"检查客户端发送的请求格式,可能需要联系发送方修正"
}
BounceWarningType::Configuration => "检查并修正BOUNCE服务相关配置,重启服务使配置生效",
BounceWarningType::ResourceLimit => "立即检查系统资源使用情况,释放资源或扩容",
BounceWarningType::Other => "分析具体警告内容,采取相应的处理措施",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::base::PostfixLogLevel;
use chrono::{DateTime, Utc};
fn create_test_base_event() -> BaseEvent {
BaseEvent {
timestamp: DateTime::parse_from_rfc3339("2024-04-27T16:20:48+00:00")
.unwrap()
.with_timezone(&Utc),
hostname: "m01".to_string(),
component: "bounce".to_string(),
process_id: 133,
log_level: PostfixLogLevel::Info,
raw_message: "test message".to_string(),
}
}
#[test]
fn test_sender_notification_event() {
let base = create_test_base_event();
let event = BounceEvent::SenderNotification {
base: base.clone(),
original_queue_id: "5FC392A20996".to_string(),
bounce_queue_id: "732B92A209A3".to_string(),
};
assert_eq!(event.event_type(), "sender_notification");
assert!(event.is_notification());
assert!(!event.is_warning());
assert_eq!(event.base(), &base);
let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
assert_eq!(orig_id, "5FC392A20996");
assert_eq!(bounce_id, "732B92A209A3");
assert!(event.get_warning_info().is_none());
}
#[test]
fn test_postmaster_notification_event() {
let base = create_test_base_event();
let event = BounceEvent::PostmasterNotification {
base: base.clone(),
original_queue_id: "633F488423".to_string(),
bounce_queue_id: "6DFA788422".to_string(),
};
assert_eq!(event.event_type(), "postmaster_notification");
assert!(event.is_notification());
assert!(!event.is_warning());
let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
assert_eq!(orig_id, "633F488423");
assert_eq!(bounce_id, "6DFA788422");
}
#[test]
fn test_warning_event() {
let base = create_test_base_event();
let event = BounceEvent::Warning {
base: base.clone(),
warning_type: BounceWarningType::MalformedRequest,
details: "malformed request".to_string(),
};
assert_eq!(event.event_type(), "warning");
assert!(!event.is_notification());
assert!(event.is_warning());
let (warning_type, details) = event.get_warning_info().unwrap();
assert_eq!(*warning_type, BounceWarningType::MalformedRequest);
assert_eq!(details, "malformed request");
assert!(event.get_queue_ids().is_none());
}
#[test]
fn test_warning_type_severity() {
assert_eq!(BounceWarningType::MalformedRequest.severity(), 2);
assert_eq!(BounceWarningType::Configuration.severity(), 4);
assert_eq!(BounceWarningType::ResourceLimit.severity(), 5);
assert_eq!(BounceWarningType::Other.severity(), 3);
}
#[test]
fn test_format_description() {
let base = create_test_base_event();
let sender_event = BounceEvent::SenderNotification {
base: base.clone(),
original_queue_id: "5FC392A20996".to_string(),
bounce_queue_id: "732B92A209A3".to_string(),
};
let desc = sender_event.format_description();
assert!(desc.contains("5FC392A20996"));
assert!(desc.contains("732B92A209A3"));
assert!(desc.contains("发送者投递失败通知"));
let warning_event = BounceEvent::Warning {
base: base.clone(),
warning_type: BounceWarningType::MalformedRequest,
details: "malformed request".to_string(),
};
let desc = warning_event.format_description();
assert!(desc.contains("格式错误请求"));
assert!(desc.contains("malformed request"));
}
#[test]
fn test_serialization() {
let base = create_test_base_event();
let event = BounceEvent::SenderNotification {
base,
original_queue_id: "5FC392A20996".to_string(),
bounce_queue_id: "732B92A209A3".to_string(),
};
let serialized = serde_json::to_string(&event).unwrap();
assert!(
serialized.contains("sender_notification") || serialized.contains("SenderNotification")
);
let deserialized: BounceEvent = serde_json::from_str(&serialized).unwrap();
assert_eq!(event, deserialized);
}
}