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