use lettre::message::Mailbox;
use mail_parser::MessageParser;
use serde::de::DeserializeOwned;
use serde_json::{Value, json};
use crate::messages::ListenerType;
use crate::types::{MessagingSendError, MessagingSendErrorCode, NormalizedMessage};
pub(super) fn normalize_update(raw: &[u8]) -> Option<NormalizedMessage> {
let parser = MessageParser::default();
let message = parser.parse(raw)?;
let body = message
.body_text(0)
.map(|s| s.into_owned())
.unwrap_or_default();
let subject = message.subject().map(|s| s.to_string()).unwrap_or_default();
let from = message.from().and_then(|addresses| addresses.first());
let from_name = from.and_then(|address| address.name()).map(str::to_owned);
let from_address = from
.and_then(|address| address.address())
.map(str::to_owned);
let message_id = message.message_id().map(|s| s.to_string());
let date = message.date().map(|d| d.to_rfc3339());
if body.is_empty() && subject.is_empty() {
return None;
}
let metadata = json!({
"channel_type": ListenerType::Email,
"subject": subject,
"from_name": from_name,
"from_address": from_address,
"message_id": message_id,
"date": date,
});
Some(NormalizedMessage { body, metadata })
}
pub(super) fn deserialize_metadata<T: DeserializeOwned>(metadata: &Value) -> Result<T, MessagingSendError> {
T::deserialize(metadata).map_err(|error| MessagingSendError::Terminal {
code: MessagingSendErrorCode::Validation,
message: format!("invalid outbound email metadata: {error}"),
})
}
pub(super) fn parse_mailbox(address: &str) -> Result<Mailbox, MessagingSendError> {
address
.parse()
.map_err(|error| MessagingSendError::Terminal {
code: MessagingSendErrorCode::InvalidDestination,
message: format!("invalid email address: {error}"),
})
}
pub(super) fn sender_in_list(message: &NormalizedMessage, list: &[String]) -> bool {
if list.is_empty() {
return true;
}
let sender = message
.metadata
.get("from_address")
.and_then(|value| value.as_str())
.filter(|sender| !sender.is_empty());
sender.is_some_and(|sender| list.iter().any(|entry| entry.eq_ignore_ascii_case(sender)))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn message(from_address: serde_json::Value) -> NormalizedMessage {
NormalizedMessage {
body: String::new(),
metadata: json!({ "from_address": from_address }),
}
}
#[test]
fn empty_sender_list_is_unrestricted() {
assert!(sender_in_list(&message(json!(null)), &Vec::new()));
}
#[test]
fn restricted_sender_list_requires_nonempty_string_identity() {
let allowed = vec!["sender@example.com".to_owned()];
assert!(!sender_in_list(&message(json!(null)), &allowed));
assert!(!sender_in_list(&message(json!(42)), &allowed));
assert!(!sender_in_list(&message(json!("")), &allowed));
assert!(!sender_in_list(&message(json!("other@example.com")), &allowed));
}
#[test]
fn sender_matching_is_case_insensitive() {
let allowed = vec!["Sender@Example.com".to_owned()];
assert!(sender_in_list(&message(json!("sender@example.COM")), &allowed));
}
}