postfix_log_parser/events/
base_log.rs1use crate::utils::common_fields::{
7 ClientInfo, CommonFieldsParser, DelayInfo, EmailAddress, RelayInfo, StatusInfo,
8};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct BaseLogEntry {
15 pub queue_id: Option<String>,
18 pub message_id: Option<String>,
20
21 pub from_email: Option<EmailAddress>,
24 pub to_email: Option<EmailAddress>,
26 pub orig_to_email: Option<EmailAddress>,
28
29 pub client_info: Option<ClientInfo>,
32 pub relay_info: Option<RelayInfo>,
34
35 pub delay_info: Option<DelayInfo>,
38 pub status_info: Option<StatusInfo>,
40
41 pub size: Option<u64>,
44 pub nrcpt: Option<u32>,
46
47 pub protocol: Option<String>,
50 pub helo: Option<String>,
52 pub sasl_method: Option<String>,
54 pub sasl_username: Option<String>,
56
57 pub raw_message: String,
60}
61
62impl BaseLogEntry {
63 pub fn from_message(raw_message: &str, queue_id: Option<String>) -> Self {
72 let mut entry = BaseLogEntry {
73 queue_id,
74 message_id: CommonFieldsParser::extract_message_id(raw_message),
75 from_email: CommonFieldsParser::extract_from_email(raw_message),
76 to_email: CommonFieldsParser::extract_to_email(raw_message),
77 orig_to_email: CommonFieldsParser::extract_orig_to_email(raw_message),
78 client_info: CommonFieldsParser::extract_client_info(raw_message),
79 relay_info: CommonFieldsParser::extract_relay_info(raw_message),
80 delay_info: CommonFieldsParser::extract_delay_info(raw_message),
81 status_info: CommonFieldsParser::extract_status_info(raw_message),
82 size: CommonFieldsParser::extract_size(raw_message),
83 nrcpt: CommonFieldsParser::extract_nrcpt(raw_message),
84 protocol: CommonFieldsParser::extract_protocol(raw_message),
85 helo: CommonFieldsParser::extract_helo(raw_message),
86 sasl_method: CommonFieldsParser::extract_sasl_method(raw_message),
87 sasl_username: CommonFieldsParser::extract_sasl_username(raw_message),
88 raw_message: raw_message.to_string(),
89 };
90
91 if entry.queue_id.is_none() {
93 entry.queue_id = crate::utils::queue_id::extract_queue_id(raw_message);
94 }
95
96 entry
97 }
98
99 pub fn empty(raw_message: &str) -> Self {
101 BaseLogEntry {
102 queue_id: None,
103 message_id: None,
104 from_email: None,
105 to_email: None,
106 orig_to_email: None,
107 client_info: None,
108 relay_info: None,
109 delay_info: None,
110 status_info: None,
111 size: None,
112 nrcpt: None,
113 protocol: None,
114 helo: None,
115 sasl_method: None,
116 sasl_username: None,
117 raw_message: raw_message.to_string(),
118 }
119 }
120
121 pub fn has_delivery_info(&self) -> bool {
123 self.to_email.is_some()
124 || self.relay_info.is_some()
125 || self.status_info.is_some()
126 || self.delay_info.is_some()
127 }
128
129 pub fn has_client_info(&self) -> bool {
131 self.client_info.is_some() || self.helo.is_some()
132 }
133
134 pub fn has_auth_info(&self) -> bool {
136 self.sasl_method.is_some() || self.sasl_username.is_some()
137 }
138
139 pub fn formatted_from(&self) -> String {
141 self.from_email
142 .as_ref()
143 .map(|email| {
144 if email.is_empty {
145 "<>".to_string()
146 } else {
147 format!("<{}>", email.address)
148 }
149 })
150 .unwrap_or_else(|| "N/A".to_string())
151 }
152
153 pub fn formatted_to(&self) -> String {
155 self.to_email
156 .as_ref()
157 .map(|email| format!("<{}>", email.address))
158 .unwrap_or_else(|| "N/A".to_string())
159 }
160
161 pub fn formatted_client(&self) -> String {
163 self.client_info
164 .as_ref()
165 .map(|client| {
166 if let Some(port) = client.port {
167 format!("{}[{}]:{}", client.hostname, client.ip, port)
168 } else {
169 format!("{}[{}]", client.hostname, client.ip)
170 }
171 })
172 .unwrap_or_else(|| "N/A".to_string())
173 }
174
175 pub fn formatted_relay(&self) -> String {
177 self.relay_info
178 .as_ref()
179 .map(|relay| {
180 if relay.is_none {
181 "none".to_string()
182 } else if let Some(ip) = &relay.ip {
183 if let Some(port) = relay.port {
184 format!("{}[{}]:{}", relay.hostname, ip, port)
185 } else {
186 format!("{}[{}]", relay.hostname, ip)
187 }
188 } else {
189 relay.hostname.clone()
190 }
191 })
192 .unwrap_or_else(|| "N/A".to_string())
193 }
194
195 pub fn formatted_status(&self) -> String {
197 self.status_info
198 .as_ref()
199 .map(|status| {
200 let mut result = status.status.clone();
201 if let Some(desc) = &status.description {
202 result.push_str(&format!(" ({})", desc));
203 }
204 result
205 })
206 .unwrap_or_else(|| "N/A".to_string())
207 }
208}
209
210pub trait ExtendedLogEntry {
213 fn base_entry(&self) -> &BaseLogEntry;
215
216 fn component_type(&self) -> &'static str;
218
219 fn event_type(&self) -> &'static str;
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_base_log_entry_creation() {
229 let message =
230 "4bG4VR5z: from=<sender@example.com>, to=<recipient@example.com>, size=1234, delay=5.5";
231 let entry = BaseLogEntry::from_message(message, Some("4bG4VR5z".to_string()));
232
233 assert_eq!(entry.queue_id, Some("4bG4VR5z".to_string()));
234 assert!(entry.from_email.is_some());
235 assert!(entry.to_email.is_some());
236 assert_eq!(entry.size, Some(1234));
237 assert!(entry.delay_info.is_some());
238 assert_eq!(entry.delay_info.as_ref().unwrap().total, 5.5);
239 }
240
241 #[test]
242 fn test_formatted_methods() {
243 let message = "4bG4VR5z: from=<sender@example.com>, to=<recipient@example.com>, client=mail.example.com[192.168.1.100]:25";
244 let entry = BaseLogEntry::from_message(message, None);
245
246 assert_eq!(entry.formatted_from(), "<sender@example.com>");
247 assert_eq!(entry.formatted_to(), "<recipient@example.com>");
248 assert_eq!(
249 entry.formatted_client(),
250 "mail.example.com[192.168.1.100]:25"
251 );
252 }
253
254 #[test]
255 fn test_empty_from_address() {
256 let message = "4bG4VR5z: from=<>, to=<recipient@example.com>";
257 let entry = BaseLogEntry::from_message(message, None);
258
259 assert_eq!(entry.formatted_from(), "<>");
260 assert!(entry.from_email.as_ref().unwrap().is_empty);
261 }
262
263 #[test]
264 fn test_capability_checks() {
265 let delivery_message = "4bG4VR5z: to=<user@example.com>, relay=mx.example.com, status=sent";
266 let delivery_entry = BaseLogEntry::from_message(delivery_message, None);
267 assert!(delivery_entry.has_delivery_info());
268
269 let client_message =
270 "4bG4VR5z: client=mail.example.com[192.168.1.100], helo=<mail.example.com>";
271 let client_entry = BaseLogEntry::from_message(client_message, None);
272 assert!(client_entry.has_client_info());
273
274 let auth_message = "4bG4VR5z: sasl_method=PLAIN, sasl_username=testuser";
275 let auth_entry = BaseLogEntry::from_message(auth_message, None);
276 assert!(auth_entry.has_auth_info());
277 }
278}