postfix_log_parser/events/
bounce.rs1use serde::{Deserialize, Serialize};
2
3use super::base::BaseEvent;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub enum BounceEvent {
15 SenderNotification {
23 base: BaseEvent,
25 original_queue_id: String,
27 bounce_queue_id: String,
29 },
30
31 PostmasterNotification {
39 base: BaseEvent,
41 original_queue_id: String,
43 bounce_queue_id: String,
45 },
46
47 Warning {
55 base: BaseEvent,
57 warning_type: BounceWarningType,
59 details: String,
61 },
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
68pub enum BounceWarningType {
69 MalformedRequest,
77
78 Configuration,
86
87 ResourceLimit,
95
96 Other,
100}
101
102impl BounceEvent {
103 pub fn base(&self) -> &BaseEvent {
105 match self {
106 BounceEvent::SenderNotification { base, .. } => base,
107 BounceEvent::PostmasterNotification { base, .. } => base,
108 BounceEvent::Warning { base, .. } => base,
109 }
110 }
111
112 pub fn event_type(&self) -> &'static str {
114 match self {
115 BounceEvent::SenderNotification { .. } => "sender_notification",
116 BounceEvent::PostmasterNotification { .. } => "postmaster_notification",
117 BounceEvent::Warning { .. } => "warning",
118 }
119 }
120
121 pub fn is_notification(&self) -> bool {
123 matches!(
124 self,
125 BounceEvent::SenderNotification { .. } | BounceEvent::PostmasterNotification { .. }
126 )
127 }
128
129 pub fn is_warning(&self) -> bool {
131 matches!(self, BounceEvent::Warning { .. })
132 }
133
134 pub fn get_queue_ids(&self) -> Option<(&str, &str)> {
136 match self {
137 BounceEvent::SenderNotification {
138 original_queue_id,
139 bounce_queue_id,
140 ..
141 } => Some((original_queue_id, bounce_queue_id)),
142 BounceEvent::PostmasterNotification {
143 original_queue_id,
144 bounce_queue_id,
145 ..
146 } => Some((original_queue_id, bounce_queue_id)),
147 BounceEvent::Warning { .. } => None,
148 }
149 }
150
151 pub fn get_warning_info(&self) -> Option<(&BounceWarningType, &str)> {
153 match self {
154 BounceEvent::Warning {
155 warning_type,
156 details,
157 ..
158 } => Some((warning_type, details)),
159 _ => None,
160 }
161 }
162
163 pub fn format_description(&self) -> String {
165 match self {
166 BounceEvent::SenderNotification {
167 original_queue_id,
168 bounce_queue_id,
169 ..
170 } => {
171 format!(
172 "为原始邮件 {} 生成发送者投递失败通知,通知邮件队列ID: {}",
173 original_queue_id, bounce_queue_id
174 )
175 }
176 BounceEvent::PostmasterNotification {
177 original_queue_id,
178 bounce_queue_id,
179 ..
180 } => {
181 format!(
182 "为原始邮件 {} 生成邮件管理员投递失败通知,通知邮件队列ID: {}",
183 original_queue_id, bounce_queue_id
184 )
185 }
186 BounceEvent::Warning {
187 warning_type,
188 details,
189 ..
190 } => {
191 let type_desc = match warning_type {
192 BounceWarningType::MalformedRequest => "格式错误请求",
193 BounceWarningType::Configuration => "配置警告",
194 BounceWarningType::ResourceLimit => "资源限制警告",
195 BounceWarningType::Other => "其他警告",
196 };
197 format!("{}: {}", type_desc, details)
198 }
199 }
200 }
201}
202
203impl BounceWarningType {
204 pub fn severity(&self) -> u8 {
206 match self {
207 BounceWarningType::MalformedRequest => 2, BounceWarningType::Configuration => 4, BounceWarningType::ResourceLimit => 5, BounceWarningType::Other => 3, }
212 }
213
214 pub fn suggested_action(&self) -> &'static str {
216 match self {
217 BounceWarningType::MalformedRequest => {
218 "检查客户端发送的请求格式,可能需要联系发送方修正"
219 }
220 BounceWarningType::Configuration => "检查并修正BOUNCE服务相关配置,重启服务使配置生效",
221 BounceWarningType::ResourceLimit => "立即检查系统资源使用情况,释放资源或扩容",
222 BounceWarningType::Other => "分析具体警告内容,采取相应的处理措施",
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::events::base::PostfixLogLevel;
231 use chrono::{DateTime, Utc};
232
233 fn create_test_base_event() -> BaseEvent {
234 BaseEvent {
235 timestamp: DateTime::parse_from_rfc3339("2024-04-27T16:20:48+00:00")
236 .unwrap()
237 .with_timezone(&Utc),
238 hostname: "m01".to_string(),
239 component: "bounce".to_string(),
240 process_id: 133,
241 log_level: PostfixLogLevel::Info,
242 raw_message: "test message".to_string(),
243 }
244 }
245
246 #[test]
247 fn test_sender_notification_event() {
248 let base = create_test_base_event();
249 let event = BounceEvent::SenderNotification {
250 base: base.clone(),
251 original_queue_id: "5FC392A20996".to_string(),
252 bounce_queue_id: "732B92A209A3".to_string(),
253 };
254
255 assert_eq!(event.event_type(), "sender_notification");
256 assert!(event.is_notification());
257 assert!(!event.is_warning());
258 assert_eq!(event.base(), &base);
259
260 let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
261 assert_eq!(orig_id, "5FC392A20996");
262 assert_eq!(bounce_id, "732B92A209A3");
263
264 assert!(event.get_warning_info().is_none());
265 }
266
267 #[test]
268 fn test_postmaster_notification_event() {
269 let base = create_test_base_event();
270 let event = BounceEvent::PostmasterNotification {
271 base: base.clone(),
272 original_queue_id: "633F488423".to_string(),
273 bounce_queue_id: "6DFA788422".to_string(),
274 };
275
276 assert_eq!(event.event_type(), "postmaster_notification");
277 assert!(event.is_notification());
278 assert!(!event.is_warning());
279
280 let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
281 assert_eq!(orig_id, "633F488423");
282 assert_eq!(bounce_id, "6DFA788422");
283 }
284
285 #[test]
286 fn test_warning_event() {
287 let base = create_test_base_event();
288 let event = BounceEvent::Warning {
289 base: base.clone(),
290 warning_type: BounceWarningType::MalformedRequest,
291 details: "malformed request".to_string(),
292 };
293
294 assert_eq!(event.event_type(), "warning");
295 assert!(!event.is_notification());
296 assert!(event.is_warning());
297
298 let (warning_type, details) = event.get_warning_info().unwrap();
299 assert_eq!(*warning_type, BounceWarningType::MalformedRequest);
300 assert_eq!(details, "malformed request");
301
302 assert!(event.get_queue_ids().is_none());
303 }
304
305 #[test]
306 fn test_warning_type_severity() {
307 assert_eq!(BounceWarningType::MalformedRequest.severity(), 2);
308 assert_eq!(BounceWarningType::Configuration.severity(), 4);
309 assert_eq!(BounceWarningType::ResourceLimit.severity(), 5);
310 assert_eq!(BounceWarningType::Other.severity(), 3);
311 }
312
313 #[test]
314 fn test_format_description() {
315 let base = create_test_base_event();
316
317 let sender_event = BounceEvent::SenderNotification {
318 base: base.clone(),
319 original_queue_id: "5FC392A20996".to_string(),
320 bounce_queue_id: "732B92A209A3".to_string(),
321 };
322 let desc = sender_event.format_description();
323 assert!(desc.contains("5FC392A20996"));
324 assert!(desc.contains("732B92A209A3"));
325 assert!(desc.contains("发送者投递失败通知"));
326
327 let warning_event = BounceEvent::Warning {
328 base: base.clone(),
329 warning_type: BounceWarningType::MalformedRequest,
330 details: "malformed request".to_string(),
331 };
332 let desc = warning_event.format_description();
333 assert!(desc.contains("格式错误请求"));
334 assert!(desc.contains("malformed request"));
335 }
336
337 #[test]
338 fn test_serialization() {
339 let base = create_test_base_event();
340 let event = BounceEvent::SenderNotification {
341 base,
342 original_queue_id: "5FC392A20996".to_string(),
343 bounce_queue_id: "732B92A209A3".to_string(),
344 };
345
346 let serialized = serde_json::to_string(&event).unwrap();
348 assert!(
349 serialized.contains("sender_notification") || serialized.contains("SenderNotification")
350 );
351
352 let deserialized: BounceEvent = serde_json::from_str(&serialized).unwrap();
354 assert_eq!(event, deserialized);
355 }
356}