use std::collections::BTreeSet;
use chrono::{DateTime, FixedOffset};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::email::{address::Address, flag::Flag};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub struct Envelope {
pub id: String,
#[serde(default)]
pub message_id: Option<String>,
#[serde(default)]
pub in_reply_to: Vec<String>,
#[serde(default)]
pub flags: BTreeSet<Flag>,
#[serde(default)]
pub subject: String,
#[serde(default)]
pub from: Vec<Address>,
#[serde(default)]
pub to: Vec<Address>,
#[serde(default)]
pub date: Option<DateTime<FixedOffset>>,
#[serde(default)]
pub size: u64,
#[serde(default)]
pub has_attachment: Option<bool>,
}
pub fn parse_message_ids(raw: &str) -> Vec<String> {
if raw.contains('<') {
return raw
.split('<')
.filter_map(|rest| rest.split_once('>'))
.filter_map(|(id, _)| normalize_message_id(id))
.collect();
}
raw.split_whitespace()
.filter_map(normalize_message_id)
.collect()
}
pub fn normalize_message_id(raw: &str) -> Option<String> {
let trimmed = raw.trim();
let inner = trimmed
.strip_prefix('<')
.and_then(|s| s.strip_suffix('>'))
.unwrap_or(trimmed)
.trim();
if inner.is_empty() {
None
} else {
Some(inner.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reply_to_several_parents_keeps_every_id() {
assert_eq!(parse_message_ids("<a@x.org>"), ["a@x.org"]);
assert_eq!(
parse_message_ids("<a@x.org> <b@x.org>"),
["a@x.org", "b@x.org"]
);
assert_eq!(
parse_message_ids("<a@x.org>\r\n\t<b@x.org>"),
["a@x.org", "b@x.org"]
);
assert_eq!(
parse_message_ids("<a@x.org><b@x.org>"),
["a@x.org", "b@x.org"]
);
}
#[test]
fn a_bracketless_value_still_yields_its_ids() {
assert_eq!(parse_message_ids("a@x.org"), ["a@x.org"]);
assert_eq!(parse_message_ids("a@x.org b@x.org"), ["a@x.org", "b@x.org"]);
}
#[test]
fn an_empty_or_unterminated_value_yields_nothing() {
assert!(parse_message_ids("").is_empty());
assert!(parse_message_ids(" ").is_empty());
assert!(parse_message_ids("<>").is_empty());
assert!(parse_message_ids("<a@x.org").is_empty());
}
#[test]
fn an_id_normalises_the_same_way_wherever_it_came_from() {
assert_eq!(
parse_message_ids(" <a@x.org> "),
[normalize_message_id("<a@x.org>").unwrap()]
);
}
}