1use std::borrow::Cow;
2
3use super::{
4 Header,
5 fold::{FOLD_TARGET, FoldWriter, has_control},
6 rfc2047::write_phrase,
7};
8use crate::writer::Writer;
9
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
12pub struct EmailAddress<'x> {
13 pub name: Option<Cow<'x, str>>,
14 pub email: Cow<'x, str>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct GroupedAddresses<'x> {
20 pub name: Option<Cow<'x, str>>,
21 pub addresses: Vec<Address<'x>>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub enum Address<'x> {
27 Address(EmailAddress<'x>),
28 Group(GroupedAddresses<'x>),
29 List(Vec<Address<'x>>),
30}
31
32impl<'x> Address<'x> {
33 pub fn new_address(name: Option<impl Into<Cow<'x, str>>>, email: impl Into<Cow<'x, str>>) -> Self {
35 Address::Address(EmailAddress {
36 name: name.map(|v| v.into()),
37 email: email.into(),
38 })
39 }
40
41 pub fn new_group(name: Option<impl Into<Cow<'x, str>>>, addresses: Vec<Address<'x>>) -> Self {
43 Address::Group(GroupedAddresses {
44 name: name.map(|v| v.into()),
45 addresses,
46 })
47 }
48
49 pub fn new_list(items: Vec<Address<'x>>) -> Self {
51 Address::List(items)
52 }
53
54 pub fn unwrap_address(&self) -> &EmailAddress<'x> {
55 match self {
56 Address::Address(address) => address,
57 _ => panic!("Address is not an EmailAddress"),
58 }
59 }
60}
61
62impl<'x> From<(&'x str, &'x str)> for Address<'x> {
63 fn from(value: (&'x str, &'x str)) -> Self {
64 Address::Address(EmailAddress {
65 name: Some(value.0.into()),
66 email: value.1.into(),
67 })
68 }
69}
70
71impl From<(String, String)> for Address<'_> {
72 fn from(value: (String, String)) -> Self {
73 Address::Address(EmailAddress {
74 name: Some(value.0.into()),
75 email: value.1.into(),
76 })
77 }
78}
79
80impl<'x> From<&'x str> for Address<'x> {
81 fn from(value: &'x str) -> Self {
82 Address::Address(EmailAddress {
83 name: None,
84 email: value.into(),
85 })
86 }
87}
88
89impl From<String> for Address<'_> {
90 fn from(value: String) -> Self {
91 Address::Address(EmailAddress {
92 name: None,
93 email: value.into(),
94 })
95 }
96}
97
98impl<'x, T> From<Vec<T>> for Address<'x>
99where
100 T: Into<Address<'x>>,
101{
102 fn from(value: Vec<T>) -> Self {
103 Address::new_list(value.into_iter().map(|x| x.into()).collect())
104 }
105}
106
107impl<'x, T, U> From<(U, Vec<T>)> for Address<'x>
108where
109 T: Into<Address<'x>>,
110 U: Into<Cow<'x, str>>,
111{
112 fn from(value: (U, Vec<T>)) -> Self {
113 Address::Group(GroupedAddresses {
114 name: Some(value.0.into()),
115 addresses: value.1.into_iter().map(|x| x.into()).collect(),
116 })
117 }
118}
119
120const TAIL_NONE: &[u8] = b"";
121const TAIL_COMMA: &[u8] = b",";
122
123impl Header for Address<'_> {
124 fn write_header(&self, output: &mut impl Writer, column: usize) {
125 if let Address::Address(address) = self
126 && address.name.is_none()
127 && column + address.email.len() + 2 <= FOLD_TARGET
128 {
129 output.write_byte(b'<');
130 output.write(&address.addr_spec());
131 output.write(b">\r\n");
132 return;
133 }
134
135 let mut folder = FoldWriter::new(output, column);
136 self.write_value(&mut folder, TAIL_NONE);
137 folder.finish();
138 }
139}
140
141impl Address<'_> {
142 #[inline(always)]
143 fn writes_nothing(&self, in_group: bool) -> bool {
144 match self {
145 Address::Address(_) => false,
146 Address::Group(group) => {
147 let named = group.name.is_some();
148 (in_group || !named)
149 && group
150 .addresses
151 .iter()
152 .all(|address| address.writes_nothing(in_group || named))
153 }
154 Address::List(list) => list.iter().all(|address| address.writes_nothing(in_group)),
155 }
156 }
157
158 fn write_value<W: Writer>(&self, folder: &mut FoldWriter<'_, W>, tail: &[u8]) {
159 match self {
160 Address::Address(address) => address.write_mailbox(folder, tail),
161 Address::Group(group) => group.write_group(folder, tail),
162 Address::List(list) => write_list(folder, list, tail, false),
163 }
164 }
165}
166
167fn write_list<W: Writer>(folder: &mut FoldWriter<'_, W>, list: &[Address<'_>], tail: &[u8], in_group: bool) {
168 let Some(last) = list.iter().rposition(|address| !address.writes_nothing(in_group)) else {
169 return;
170 };
171
172 for (pos, address) in list.iter().enumerate() {
173 if pos > last {
174 return;
175 }
176 if address.writes_nothing(in_group) {
177 continue;
178 }
179
180 let item_tail = if pos == last { tail } else { TAIL_COMMA };
181
182 match address {
183 Address::Address(address) => address.write_mailbox(folder, item_tail),
184 Address::Group(group) if in_group => write_list(folder, &group.addresses, item_tail, true),
185 Address::Group(group) => group.write_group(folder, item_tail),
186 Address::List(nested) => write_list(folder, nested, item_tail, in_group),
187 }
188
189 if pos == last {
190 return;
191 }
192 folder.space();
193 }
194}
195
196impl EmailAddress<'_> {
197 #[inline]
198 fn addr_spec(&self) -> Cow<'_, [u8]> {
199 let email = self.email.as_bytes();
200 if has_control(email) {
201 Cow::Owned(email.iter().copied().filter(|byte| !byte.is_ascii_control()).collect())
202 } else {
203 Cow::Borrowed(email)
204 }
205 }
206
207 #[inline]
208 pub(crate) fn write_mailbox<W: Writer>(&self, folder: &mut FoldWriter<'_, W>, tail: &[u8]) {
209 if let Some(name) = &self.name {
210 write_phrase(folder, name, TAIL_NONE);
211 folder.space();
212 }
213
214 folder.begin_atom(self.email.len() + 2 + tail.len());
215 folder.write_byte(b'<');
216 folder.write(&self.addr_spec());
217 match tail {
218 [] => folder.write_byte(b'>'),
219 [b','] => folder.write(b">,"),
220 [b';'] => folder.write(b">;"),
221 _ => {
222 folder.write_byte(b'>');
223 folder.write_tail(tail);
224 }
225 }
226 }
227}
228
229impl Header for EmailAddress<'_> {
230 fn write_header(&self, output: &mut impl Writer, column: usize) {
231 let mut folder = FoldWriter::new(output, column);
232 self.write_mailbox(&mut folder, TAIL_NONE);
233 folder.finish();
234 }
235}
236
237impl GroupedAddresses<'_> {
238 pub(crate) fn write_group<W: Writer>(&self, folder: &mut FoldWriter<'_, W>, tail: &[u8]) {
239 let Some(name) = &self.name else {
240 return write_list(folder, &self.addresses, tail, false);
241 };
242
243 let is_empty = self.addresses.iter().all(|address| address.writes_nothing(true));
244 let (name_tail, list_tail): (&[u8], &[u8]) = if tail.is_empty() {
245 (b":;", b";")
246 } else {
247 (b":;,", b";,")
248 };
249 let name_tail = if is_empty { name_tail } else { b":" };
250
251 write_phrase(folder, name, name_tail);
252
253 if !is_empty {
254 folder.space();
255 write_list(folder, &self.addresses, list_tail, true);
256 }
257 }
258}
259
260impl Header for GroupedAddresses<'_> {
261 fn write_header(&self, output: &mut impl Writer, column: usize) {
262 let mut folder = FoldWriter::new(output, column);
263 self.write_group(&mut folder, TAIL_NONE);
264 folder.finish();
265 }
266}