1use std::fmt::Display;
2use std::str::FromStr;
3use std::sync::OnceLock;
4
5use crate::email::{EmailAddress, EmailAddressParseError};
6
7static ADDRESS_PARSER: OnceLock<mail_parser::MessageParser> = OnceLock::new();
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct Mailbox {
12 name: Option<String>,
13 email: EmailAddress,
14}
15
16impl Mailbox {
17 #[must_use]
19 pub fn name(&self) -> Option<&str> {
20 self.name.as_deref()
21 }
22
23 #[must_use]
25 pub const fn email(&self) -> &EmailAddress {
26 &self.email
27 }
28}
29
30impl From<EmailAddress> for Mailbox {
31 fn from(email: EmailAddress) -> Self {
32 Self { name: None, email }
33 }
34}
35
36impl From<(String, EmailAddress)> for Mailbox {
37 fn from((name, email): (String, EmailAddress)) -> Self {
38 Self {
39 name: Some(name),
40 email,
41 }
42 }
43}
44
45impl From<(Option<String>, EmailAddress)> for Mailbox {
46 fn from((name, email): (Option<String>, EmailAddress)) -> Self {
47 Self { name, email }
48 }
49}
50
51#[derive(Debug, thiserror::Error)]
52#[non_exhaustive]
53pub enum MailboxParseError {
54 #[error("expected a single mailbox, found {found} address item(s)")]
55 ExpectedSingleMailbox { found: usize },
56 #[error("expected mailbox but found group")]
57 UnexpectedAddressKind,
58 #[error("mailbox list contains group entries")]
59 ContainsGroupEntry,
60 #[error("mailbox parse backend failed")]
61 Backend {
62 #[source]
63 source: AddressBackendError,
64 },
65}
66
67impl FromStr for Mailbox {
68 type Err = MailboxParseError;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 if let Ok(email) = EmailAddress::from_str(s) {
72 return Ok(Self::from(email));
73 }
74
75 let addresses =
76 parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
77 if addresses.len() != 1 {
78 return Err(MailboxParseError::ExpectedSingleMailbox {
79 found: addresses.len(),
80 });
81 }
82
83 match addresses.into_iter().next() {
84 Some(Address::Mailbox(mailbox)) => Ok(mailbox),
85 _ => Err(MailboxParseError::UnexpectedAddressKind),
86 }
87 }
88}
89
90impl TryFrom<&str> for Mailbox {
91 type Error = MailboxParseError;
92
93 fn try_from(value: &str) -> Result<Self, Self::Error> {
103 Self::from_str(value)
104 }
105}
106
107impl Display for Mailbox {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 match self.name() {
124 Some(name) => {
125 write_quoted(name, f)?;
126 f.write_str(" <")?;
127 self.email.fmt(f)?;
128 f.write_str(">")
129 }
130 None => self.email.fmt(f),
131 }
132 }
133}
134
135#[cfg(feature = "serde")]
136#[derive(serde::Deserialize, PartialEq, Eq)]
137#[serde(rename_all = "snake_case")]
138enum AddressKind {
139 Mailbox,
140 Group,
141}
142
143#[cfg(feature = "schemars")]
144fn mailbox_typed_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
145 let email = generator.subschema_for::<EmailAddress>();
146 schemars::json_schema!({
147 "type": "object",
148 "properties": {
149 "type": {"const": "mailbox"},
150 "name": {"type": ["string", "null"]},
151 "email": email
152 },
153 "required": ["type", "email"]
154 })
155}
156
157#[cfg(feature = "serde")]
158impl serde::Serialize for Mailbox {
159 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
160 where
161 S: serde::Serializer,
162 {
163 use serde::ser::SerializeStruct as _;
164
165 let len = 2 + usize::from(self.name.is_some());
166 let mut value = serializer.serialize_struct("Mailbox", len)?;
167 value.serialize_field("type", "mailbox")?;
168 if let Some(name) = &self.name {
169 value.serialize_field("name", name)?;
170 }
171 value.serialize_field("email", &self.email)?;
172 value.end()
173 }
174}
175
176#[cfg(feature = "serde")]
177impl<'de> serde::Deserialize<'de> for Mailbox {
178 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
179 where
180 D: serde::Deserializer<'de>,
181 {
182 #[derive(serde::Deserialize)]
183 struct RawMailbox {
184 #[serde(rename = "type")]
185 type_: AddressKind,
186 #[serde(default)]
187 name: Option<String>,
188 email: EmailAddress,
189 }
190
191 fn from_raw<E>(raw: RawMailbox) -> Result<Mailbox, E>
192 where
193 E: serde::de::Error,
194 {
195 if raw.type_ != AddressKind::Mailbox {
196 return Err(E::custom("expected mailbox address type"));
197 }
198 Ok(Mailbox {
199 name: raw.name,
200 email: raw.email,
201 })
202 }
203
204 #[cfg(feature = "rfc5322-string-compat")]
205 {
206 struct MailboxVisitor;
211
212 impl<'de> serde::de::Visitor<'de> for MailboxVisitor {
213 type Value = Mailbox;
214
215 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.write_str("a typed mailbox object or an RFC 5322 mailbox string")
217 }
218
219 fn visit_str<E>(self, value: &str) -> Result<Mailbox, E>
220 where
221 E: serde::de::Error,
222 {
223 value.parse().map_err(E::custom)
224 }
225
226 fn visit_string<E>(self, value: String) -> Result<Mailbox, E>
227 where
228 E: serde::de::Error,
229 {
230 value.parse().map_err(E::custom)
231 }
232
233 fn visit_map<A>(self, map: A) -> Result<Mailbox, A::Error>
234 where
235 A: serde::de::MapAccess<'de>,
236 {
237 let raw = <RawMailbox as serde::Deserialize<'de>>::deserialize(
238 serde::de::value::MapAccessDeserializer::new(map),
239 )?;
240 from_raw(raw)
241 }
242 }
243
244 deserializer.deserialize_any(MailboxVisitor)
245 }
246
247 #[cfg(not(feature = "rfc5322-string-compat"))]
248 from_raw(RawMailbox::deserialize(deserializer)?)
249 }
250}
251
252#[cfg(feature = "schemars")]
253impl schemars::JsonSchema for Mailbox {
254 fn schema_name() -> std::borrow::Cow<'static, str> {
255 "Mailbox".into()
256 }
257
258 fn schema_id() -> std::borrow::Cow<'static, str> {
259 concat!(module_path!(), "::Mailbox").into()
260 }
261
262 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
263 let typed = mailbox_typed_schema(generator);
264
265 #[cfg(feature = "rfc5322-string-compat")]
266 {
267 schemars::json_schema!({
268 "oneOf": [
269 typed,
270 {
271 "type": "string",
272 "description": "RFC 5322 mailbox string"
273 }
274 ]
275 })
276 }
277
278 #[cfg(not(feature = "rfc5322-string-compat"))]
279 typed
280 }
281}
282
283#[cfg(feature = "arbitrary")]
284impl<'a> arbitrary::Arbitrary<'a> for Mailbox {
285 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
286 let email = EmailAddress::arbitrary(u)?;
287 if bool::arbitrary(u)? {
288 let name = format!("User {}", u8::arbitrary(u)?);
289 Ok(Self {
290 name: Some(name),
291 email,
292 })
293 } else {
294 Ok(Self { name: None, email })
295 }
296 }
297}
298
299#[derive(Clone, Debug, PartialEq, Eq, Hash)]
301pub struct Group {
302 name: String,
303 members: Vec<Mailbox>,
304}
305
306impl Group {
307 #[must_use]
309 pub fn name(&self) -> &str {
310 self.name.as_str()
311 }
312
313 #[must_use]
315 pub fn members(&self) -> &[Mailbox] {
316 self.members.as_slice()
317 }
318}
319
320#[derive(Debug, thiserror::Error)]
321#[non_exhaustive]
322pub enum GroupParseError {
323 #[error("expected a single group, found {found} address item(s)")]
324 ExpectedSingleGroup { found: usize },
325 #[error("expected group but found mailbox")]
326 UnexpectedAddressKind,
327 #[error("group parse backend failed")]
328 Backend {
329 #[source]
330 source: AddressBackendError,
331 },
332}
333
334impl FromStr for Group {
335 type Err = GroupParseError;
336
337 fn from_str(s: &str) -> Result<Self, Self::Err> {
338 let addresses =
339 parse_address_items(s).map_err(|source| GroupParseError::Backend { source })?;
340 if addresses.len() != 1 {
341 return Err(GroupParseError::ExpectedSingleGroup {
342 found: addresses.len(),
343 });
344 }
345
346 match addresses.into_iter().next() {
347 Some(Address::Group(group)) => Ok(group),
348 _ => Err(GroupParseError::UnexpectedAddressKind),
349 }
350 }
351}
352
353impl TryFrom<&str> for Group {
354 type Error = GroupParseError;
355
356 fn try_from(value: &str) -> Result<Self, Self::Error> {
366 Self::from_str(value)
367 }
368}
369
370impl Display for Group {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 write_quoted(self.name(), f)?;
377 f.write_str(":")?;
378 for (idx, member) in self.members().iter().enumerate() {
379 if idx > 0 {
380 f.write_str(", ")?;
381 }
382 member.fmt(f)?;
383 }
384 f.write_str(";")
385 }
386}
387
388#[cfg(feature = "schemars")]
389fn group_typed_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
390 let members = <Vec<Mailbox> as schemars::JsonSchema>::json_schema(generator);
391 schemars::json_schema!({
392 "type": "object",
393 "properties": {
394 "type": {"const": "group"},
395 "name": {"type": "string"},
396 "members": members
397 },
398 "required": ["type", "name"]
399 })
400}
401
402#[cfg(feature = "serde")]
403impl serde::Serialize for Group {
404 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
405 where
406 S: serde::Serializer,
407 {
408 use serde::ser::SerializeStruct as _;
409
410 let len = 2 + usize::from(!self.members.is_empty());
411 let mut value = serializer.serialize_struct("Group", len)?;
412 value.serialize_field("type", "group")?;
413 value.serialize_field("name", &self.name)?;
414 if !self.members.is_empty() {
415 value.serialize_field("members", &self.members)?;
416 }
417 value.end()
418 }
419}
420
421#[cfg(feature = "serde")]
422impl<'de> serde::Deserialize<'de> for Group {
423 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
424 where
425 D: serde::Deserializer<'de>,
426 {
427 #[derive(serde::Deserialize)]
428 struct RawGroup {
429 #[serde(rename = "type")]
430 type_: AddressKind,
431 name: String,
432 #[serde(default)]
433 members: Vec<Mailbox>,
434 }
435
436 fn from_raw<E>(raw: RawGroup) -> Result<Group, E>
437 where
438 E: serde::de::Error,
439 {
440 if raw.type_ != AddressKind::Group {
441 return Err(E::custom("expected group address type"));
442 }
443 Ok(Group {
444 name: raw.name,
445 members: raw.members,
446 })
447 }
448
449 #[cfg(feature = "rfc5322-string-compat")]
450 {
451 struct GroupVisitor;
452
453 impl<'de> serde::de::Visitor<'de> for GroupVisitor {
454 type Value = Group;
455
456 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457 f.write_str("a typed group object or an RFC 5322 group string")
458 }
459
460 fn visit_str<E>(self, value: &str) -> Result<Group, E>
461 where
462 E: serde::de::Error,
463 {
464 value.parse().map_err(E::custom)
465 }
466
467 fn visit_string<E>(self, value: String) -> Result<Group, E>
468 where
469 E: serde::de::Error,
470 {
471 value.parse().map_err(E::custom)
472 }
473
474 fn visit_map<A>(self, map: A) -> Result<Group, A::Error>
475 where
476 A: serde::de::MapAccess<'de>,
477 {
478 let raw = <RawGroup as serde::Deserialize<'de>>::deserialize(
479 serde::de::value::MapAccessDeserializer::new(map),
480 )?;
481 from_raw(raw)
482 }
483 }
484
485 deserializer.deserialize_any(GroupVisitor)
486 }
487
488 #[cfg(not(feature = "rfc5322-string-compat"))]
489 from_raw(RawGroup::deserialize(deserializer)?)
490 }
491}
492
493#[cfg(feature = "schemars")]
494impl schemars::JsonSchema for Group {
495 fn schema_name() -> std::borrow::Cow<'static, str> {
496 "Group".into()
497 }
498
499 fn schema_id() -> std::borrow::Cow<'static, str> {
500 concat!(module_path!(), "::Group").into()
501 }
502
503 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
504 let typed = group_typed_schema(generator);
505
506 #[cfg(feature = "rfc5322-string-compat")]
507 {
508 schemars::json_schema!({
509 "oneOf": [
510 typed,
511 {
512 "type": "string",
513 "description": "RFC 5322 group string"
514 }
515 ]
516 })
517 }
518
519 #[cfg(not(feature = "rfc5322-string-compat"))]
520 typed
521 }
522}
523
524#[cfg(feature = "arbitrary")]
525impl<'a> arbitrary::Arbitrary<'a> for Group {
526 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
527 let member_count = usize::from(u.int_in_range::<u8>(0..=3)?);
528 let mut members = Vec::with_capacity(member_count);
529 for _ in 0..member_count {
530 members.push(Mailbox::arbitrary(u)?);
531 }
532 Ok(Self {
533 name: format!("Group {}", u8::arbitrary(u)?),
534 members,
535 })
536 }
537}
538
539#[derive(Clone, Debug, PartialEq, Eq, Hash)]
548pub enum Address {
549 Mailbox(Mailbox),
550 Group(Group),
551}
552
553impl From<Mailbox> for Address {
554 fn from(value: Mailbox) -> Self {
555 Self::Mailbox(value)
556 }
557}
558
559impl From<Group> for Address {
560 fn from(value: Group) -> Self {
561 Self::Group(value)
562 }
563}
564
565impl Address {
566 pub fn mailboxes(&self) -> impl Iterator<Item = &Mailbox> {
568 match self {
569 Self::Mailbox(mailbox) => std::slice::from_ref(mailbox).iter(),
570 Self::Group(group) => group.members().iter(),
571 }
572 }
573}
574
575#[derive(Debug, thiserror::Error)]
576#[non_exhaustive]
577pub enum AddressParseError {
578 #[error("expected a single address, found {found} address item(s)")]
579 ExpectedSingleAddress { found: usize },
580 #[error("address parse backend failed")]
581 Backend {
582 #[source]
583 source: AddressBackendError,
584 },
585}
586
587impl FromStr for Address {
588 type Err = AddressParseError;
589
590 fn from_str(s: &str) -> Result<Self, Self::Err> {
591 if let Ok(email) = EmailAddress::from_str(s) {
592 return Ok(Self::Mailbox(Mailbox::from(email)));
593 }
594
595 let addresses =
596 parse_address_items(s).map_err(|source| AddressParseError::Backend { source })?;
597 if addresses.len() != 1 {
598 return Err(AddressParseError::ExpectedSingleAddress {
599 found: addresses.len(),
600 });
601 }
602
603 addresses
604 .into_iter()
605 .next()
606 .ok_or(AddressParseError::ExpectedSingleAddress { found: 0 })
607 }
608}
609
610impl TryFrom<&str> for Address {
611 type Error = AddressParseError;
612
613 fn try_from(value: &str) -> Result<Self, Self::Error> {
622 Self::from_str(value)
623 }
624}
625
626impl Display for Address {
629 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630 match self {
631 Self::Mailbox(mailbox) => mailbox.fmt(f),
632 Self::Group(group) => group.fmt(f),
633 }
634 }
635}
636
637#[cfg(feature = "serde")]
638impl serde::Serialize for Address {
639 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
640 where
641 S: serde::Serializer,
642 {
643 match self {
644 Self::Mailbox(mailbox) => serde::Serialize::serialize(mailbox, serializer),
645 Self::Group(group) => serde::Serialize::serialize(group, serializer),
646 }
647 }
648}
649
650#[cfg(feature = "serde")]
651impl<'de> serde::Deserialize<'de> for Address {
652 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
653 where
654 D: serde::Deserializer<'de>,
655 {
656 #[derive(serde::Deserialize)]
657 #[serde(tag = "type", rename_all = "snake_case")]
658 enum RawAddress {
659 Mailbox {
660 #[serde(default)]
661 name: Option<String>,
662 email: EmailAddress,
663 },
664 Group {
665 name: String,
666 #[serde(default)]
667 members: Vec<Mailbox>,
668 },
669 }
670
671 fn from_raw(raw: RawAddress) -> Address {
672 match raw {
673 RawAddress::Mailbox { name, email } => Address::Mailbox(Mailbox { name, email }),
674 RawAddress::Group { name, members } => Address::Group(Group { name, members }),
675 }
676 }
677
678 #[cfg(feature = "rfc5322-string-compat")]
679 {
680 struct AddressVisitor;
681
682 impl<'de> serde::de::Visitor<'de> for AddressVisitor {
683 type Value = Address;
684
685 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686 f.write_str("a typed address object or an RFC 5322 address string")
687 }
688
689 fn visit_str<E>(self, value: &str) -> Result<Address, E>
690 where
691 E: serde::de::Error,
692 {
693 value.parse().map_err(E::custom)
694 }
695
696 fn visit_string<E>(self, value: String) -> Result<Address, E>
697 where
698 E: serde::de::Error,
699 {
700 value.parse().map_err(E::custom)
701 }
702
703 fn visit_map<A>(self, map: A) -> Result<Address, A::Error>
704 where
705 A: serde::de::MapAccess<'de>,
706 {
707 let raw = <RawAddress as serde::Deserialize<'de>>::deserialize(
708 serde::de::value::MapAccessDeserializer::new(map),
709 )?;
710 Ok(from_raw(raw))
711 }
712 }
713
714 deserializer.deserialize_any(AddressVisitor)
715 }
716
717 #[cfg(not(feature = "rfc5322-string-compat"))]
718 Ok(from_raw(RawAddress::deserialize(deserializer)?))
719 }
720}
721
722#[cfg(feature = "schemars")]
723impl schemars::JsonSchema for Address {
724 fn schema_name() -> std::borrow::Cow<'static, str> {
725 "Address".into()
726 }
727
728 fn schema_id() -> std::borrow::Cow<'static, str> {
729 concat!(module_path!(), "::Address").into()
730 }
731
732 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
733 let mailbox = mailbox_typed_schema(generator);
734 let group = group_typed_schema(generator);
735
736 #[cfg(feature = "rfc5322-string-compat")]
737 {
738 schemars::json_schema!({
739 "oneOf": [
740 mailbox,
741 group,
742 {
743 "type": "string",
744 "description": "RFC 5322 address string"
745 }
746 ]
747 })
748 }
749
750 #[cfg(not(feature = "rfc5322-string-compat"))]
751 schemars::json_schema!({"oneOf": [mailbox, group]})
752 }
753}
754
755#[cfg(feature = "arbitrary")]
756impl<'a> arbitrary::Arbitrary<'a> for Address {
757 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
758 if bool::arbitrary(u)? {
759 Ok(Self::Mailbox(Mailbox::arbitrary(u)?))
760 } else {
761 Ok(Self::Group(Group::arbitrary(u)?))
762 }
763 }
764}
765
766macro_rules! impl_address_collection {
767 ($(#[$meta:meta])* $name:ident, $item:ty, $error:ty, $parse_fn:expr) => {
768 $(#[$meta])*
769 #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
770 pub struct $name {
771 items: Vec<$item>,
772 }
773
774 #[cfg(feature = "schemars")]
775 impl schemars::JsonSchema for $name {
776 fn inline_schema() -> bool {
777 true
778 }
779
780 fn schema_name() -> std::borrow::Cow<'static, str> {
781 stringify!($name).into()
782 }
783
784 fn schema_id() -> std::borrow::Cow<'static, str> {
785 concat!(module_path!(), "::", stringify!($name)).into()
786 }
787
788 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
789 let typed = <Vec<$item> as schemars::JsonSchema>::json_schema(generator);
790
791 #[cfg(feature = "rfc5322-string-compat")]
792 {
793 schemars::json_schema!({
794 "oneOf": [
795 typed,
796 {
797 "type": "string",
798 "description": "RFC 5322 comma-separated address list string"
799 }
800 ]
801 })
802 }
803
804 #[cfg(not(feature = "rfc5322-string-compat"))]
805 typed
806 }
807 }
808
809 impl $name {
810 #[must_use]
811 pub fn len(&self) -> usize {
812 self.items.len()
813 }
814
815 #[must_use]
816 pub fn is_empty(&self) -> bool {
817 self.items.is_empty()
818 }
819
820 pub fn iter(&self) -> std::slice::Iter<'_, $item> {
821 self.items.iter()
822 }
823
824 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, $item> {
825 self.items.iter_mut()
826 }
827
828 #[must_use]
829 pub fn as_slice(&self) -> &[$item] {
830 self.items.as_slice()
831 }
832
833 #[must_use]
834 pub fn into_vec(self) -> Vec<$item> {
835 self.items
836 }
837 }
838
839 impl From<Vec<$item>> for $name {
840 fn from(items: Vec<$item>) -> Self {
841 Self { items }
842 }
843 }
844
845 impl From<$name> for Vec<$item> {
846 fn from(value: $name) -> Self {
847 value.items
848 }
849 }
850
851 impl FromStr for $name {
852 type Err = $error;
853
854 fn from_str(s: &str) -> Result<Self, Self::Err> {
855 let items = ($parse_fn)(s)?;
856 Ok(Self { items })
857 }
858 }
859
860 impl TryFrom<&str> for $name {
861 type Error = $error;
862
863 fn try_from(value: &str) -> Result<Self, Self::Error> {
865 Self::from_str(value)
866 }
867 }
868
869 impl IntoIterator for $name {
870 type Item = $item;
871 type IntoIter = std::vec::IntoIter<$item>;
872
873 fn into_iter(self) -> Self::IntoIter {
874 self.items.into_iter()
875 }
876 }
877
878 impl<'a> IntoIterator for &'a $name {
879 type Item = &'a $item;
880 type IntoIter = std::slice::Iter<'a, $item>;
881
882 fn into_iter(self) -> Self::IntoIter {
883 self.items.iter()
884 }
885 }
886
887 impl<'a> IntoIterator for &'a mut $name {
888 type Item = &'a mut $item;
889 type IntoIter = std::slice::IterMut<'a, $item>;
890
891 fn into_iter(self) -> Self::IntoIter {
892 self.items.iter_mut()
893 }
894 }
895
896 impl AsRef<[$item]> for $name {
897 fn as_ref(&self) -> &[$item] {
898 self.items.as_slice()
899 }
900 }
901
902 impl std::iter::FromIterator<$item> for $name {
903 fn from_iter<T: IntoIterator<Item = $item>>(iter: T) -> Self {
904 Self {
905 items: iter.into_iter().collect(),
906 }
907 }
908 }
909
910 impl Extend<$item> for $name {
911 fn extend<T: IntoIterator<Item = $item>>(&mut self, iter: T) {
912 self.items.extend(iter);
913 }
914 }
915
916 impl Display for $name {
917 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
918 for (idx, item) in self.items.iter().enumerate() {
919 if idx > 0 {
920 f.write_str(", ")?;
921 }
922 item.fmt(f)?;
923 }
924 Ok(())
925 }
926 }
927
928 #[cfg(feature = "serde")]
929 impl serde::Serialize for $name {
930 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
931 where
932 S: serde::Serializer,
933 {
934 serde::Serialize::serialize(&self.items, serializer)
935 }
936 }
937
938 #[cfg(feature = "serde")]
939 impl<'de> serde::Deserialize<'de> for $name {
940 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
941 where
942 D: serde::Deserializer<'de>,
943 {
944 #[cfg(feature = "rfc5322-string-compat")]
945 {
946 struct CollectionVisitor;
947
948 impl<'de> serde::de::Visitor<'de> for CollectionVisitor {
949 type Value = $name;
950
951 fn expecting(
952 &self,
953 f: &mut std::fmt::Formatter<'_>,
954 ) -> std::fmt::Result {
955 f.write_str(concat!(
956 "a ",
957 stringify!($name),
958 " array or an RFC 5322 list string",
959 ))
960 }
961
962 fn visit_str<E>(self, value: &str) -> Result<$name, E>
963 where
964 E: serde::de::Error,
965 {
966 value.parse().map_err(E::custom)
967 }
968
969 fn visit_string<E>(self, value: String) -> Result<$name, E>
970 where
971 E: serde::de::Error,
972 {
973 value.parse().map_err(E::custom)
974 }
975
976 fn visit_seq<A>(self, mut seq: A) -> Result<$name, A::Error>
977 where
978 A: serde::de::SeqAccess<'de>,
979 {
980 let mut items = Vec::with_capacity(seq.size_hint().unwrap_or(0));
981 while let Some(item) = seq.next_element::<$item>()? {
982 items.push(item);
983 }
984 Ok($name { items })
985 }
986 }
987
988 deserializer.deserialize_any(CollectionVisitor)
989 }
990
991 #[cfg(not(feature = "rfc5322-string-compat"))]
992 {
993 let items = <Vec<$item> as serde::Deserialize>::deserialize(deserializer)?;
994 Ok(Self { items })
995 }
996 }
997 }
998
999 #[cfg(feature = "arbitrary")]
1000 impl<'a> arbitrary::Arbitrary<'a> for $name {
1001 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1002 let len = usize::from(u.int_in_range::<u8>(0..=4)?);
1003 let mut items = Vec::with_capacity(len);
1004 for _ in 0..len {
1005 items.push(<$item>::arbitrary(u)?);
1006 }
1007 Ok(Self { items })
1008 }
1009 }
1010 };
1011}
1012
1013impl_address_collection!(
1014 AddressList,
1019 Address,
1020 AddressParseError,
1021 |s| parse_address_items(s).map_err(|source| AddressParseError::Backend { source })
1022);
1023
1024impl<'a> TryFrom<Vec<&'a str>> for AddressList {
1025 type Error = AddressParseError;
1026
1027 fn try_from(value: Vec<&'a str>) -> Result<Self, Self::Error> {
1028 value
1029 .into_iter()
1030 .map(Address::from_str)
1031 .collect::<Result<Vec<_>, _>>()
1032 .map(Self::from)
1033 }
1034}
1035
1036impl<'a> TryFrom<&'a [&'a str]> for AddressList {
1037 type Error = AddressParseError;
1038
1039 fn try_from(value: &'a [&'a str]) -> Result<Self, Self::Error> {
1040 value
1041 .iter()
1042 .copied()
1043 .map(Address::from_str)
1044 .collect::<Result<Vec<_>, _>>()
1045 .map(Self::from)
1046 }
1047}
1048
1049impl<'a> TryFrom<Vec<&'a str>> for MailboxList {
1050 type Error = MailboxParseError;
1051
1052 fn try_from(value: Vec<&'a str>) -> Result<Self, Self::Error> {
1053 value
1054 .into_iter()
1055 .map(Mailbox::from_str)
1056 .collect::<Result<Vec<_>, _>>()
1057 .map(Self::from)
1058 }
1059}
1060
1061impl<'a> TryFrom<&'a [&'a str]> for MailboxList {
1062 type Error = MailboxParseError;
1063
1064 fn try_from(value: &'a [&'a str]) -> Result<Self, Self::Error> {
1065 value
1066 .iter()
1067 .copied()
1068 .map(Mailbox::from_str)
1069 .collect::<Result<Vec<_>, _>>()
1070 .map(Self::from)
1071 }
1072}
1073
1074impl_address_collection!(
1075 MailboxList,
1079 Mailbox,
1080 MailboxParseError,
1081 |s| {
1082 let addresses = parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
1083 let mut items = Vec::with_capacity(addresses.len());
1084
1085 for address in addresses {
1086 match address {
1087 Address::Mailbox(mailbox) => items.push(mailbox),
1088 Address::Group(_) => return Err(MailboxParseError::ContainsGroupEntry),
1089 }
1090 }
1091
1092 Ok(items)
1093 }
1094);
1095
1096pub const MAX_ADDRESS_INPUT_BYTES: usize = 64 * 1024;
1103
1104#[derive(Debug, thiserror::Error)]
1105#[non_exhaustive]
1106pub enum AddressBackendError {
1107 #[error("address input contains raw newline characters")]
1108 InputContainsRawNewlines,
1109 #[error("address input is {len} bytes, exceeding maximum of {max}")]
1110 #[non_exhaustive]
1111 InputTooLong { len: usize, max: usize },
1112 #[error("failed to parse address header")]
1113 HeaderParse,
1114 #[error("parsed header did not contain address data")]
1115 MissingAddress,
1116 #[error("mailbox is missing addr-spec")]
1117 MissingAddrSpec,
1118 #[error("invalid addr-spec `{input}`")]
1119 InvalidAddrSpec {
1120 input: String,
1121 #[source]
1122 source: EmailAddressParseError,
1123 },
1124 #[error("group member at index {index} is missing addr-spec")]
1125 GroupMemberMissingAddrSpec { index: usize },
1126 #[error("invalid group member addr-spec `{input}` at index {index}")]
1127 InvalidGroupMemberAddrSpec {
1128 index: usize,
1129 input: String,
1130 #[source]
1131 source: EmailAddressParseError,
1132 },
1133 #[error("group is missing a name")]
1134 GroupMissingName,
1135}
1136
1137fn write_quoted(value: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1138 f.write_str("\"")?;
1139 for ch in value.chars() {
1140 if ch == '\\' || ch == '"' {
1141 f.write_str("\\")?;
1142 }
1143 f.write_str(ch.encode_utf8(&mut [0; 4]))?;
1144 }
1145 f.write_str("\"")
1146}
1147
1148fn parse_address_items(input: &str) -> Result<Vec<Address>, AddressBackendError> {
1173 if input.len() > MAX_ADDRESS_INPUT_BYTES {
1174 return Err(AddressBackendError::InputTooLong {
1175 len: input.len(),
1176 max: MAX_ADDRESS_INPUT_BYTES,
1177 });
1178 }
1179 if input.contains('\r') || input.contains('\n') {
1180 return Err(AddressBackendError::InputContainsRawNewlines);
1181 }
1182
1183 let raw = format!("To: {input}\r\n\r\n");
1184 let parser =
1185 ADDRESS_PARSER.get_or_init(|| mail_parser::MessageParser::new().with_address_headers());
1186 let message = parser
1187 .parse_headers(raw.as_bytes())
1188 .ok_or(AddressBackendError::HeaderParse)?;
1189 let parsed = message.to().ok_or(AddressBackendError::MissingAddress)?;
1190
1191 match parsed {
1192 mail_parser::Address::List(list) => list
1193 .iter()
1194 .map(convert_mailbox)
1195 .map(|result| result.map(Address::Mailbox))
1196 .collect(),
1197 mail_parser::Address::Group(groups) => {
1205 let mut items = Vec::with_capacity(groups.len());
1206 for group in groups {
1207 if group.name.is_some() {
1208 items.push(Address::Group(convert_group(group)?));
1209 } else {
1210 for addr in group.addresses.iter() {
1211 items.push(Address::Mailbox(convert_mailbox(addr)?));
1212 }
1213 }
1214 }
1215 Ok(items)
1216 }
1217 }
1218}
1219
1220fn convert_mailbox(value: &mail_parser::Addr<'_>) -> Result<Mailbox, AddressBackendError> {
1221 let raw_email = value
1222 .address()
1223 .ok_or(AddressBackendError::MissingAddrSpec)?;
1224 let email = EmailAddress::from_str(raw_email).map_err(|source| {
1225 AddressBackendError::InvalidAddrSpec {
1226 input: raw_email.to_owned(),
1227 source,
1228 }
1229 })?;
1230
1231 Ok(match value.name() {
1232 Some(name) => Mailbox::from((name.to_owned(), email)),
1233 None => Mailbox::from(email),
1234 })
1235}
1236
1237fn convert_group(value: &mail_parser::Group<'_>) -> Result<Group, AddressBackendError> {
1238 let name = value
1239 .name
1240 .as_deref()
1241 .ok_or(AddressBackendError::GroupMissingName)?
1242 .to_owned();
1243 let mut members = Vec::with_capacity(value.addresses.len());
1244 for (index, member) in value.addresses.iter().enumerate() {
1245 let mailbox = convert_mailbox(member).map_err(|error| match error {
1246 AddressBackendError::MissingAddrSpec => {
1247 AddressBackendError::GroupMemberMissingAddrSpec { index }
1248 }
1249 AddressBackendError::InvalidAddrSpec { input, source } => {
1250 AddressBackendError::InvalidGroupMemberAddrSpec {
1251 index,
1252 input,
1253 source,
1254 }
1255 }
1256 other => other,
1257 })?;
1258 members.push(mailbox);
1259 }
1260
1261 Ok(Group { name, members })
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266 use super::*;
1267
1268 #[test]
1269 fn mailbox_from_str_accepts_rfc_examples() {
1270 let parsed = "Mary Smith <mary@x.test>".parse::<Mailbox>();
1271 assert!(parsed.is_ok(), "expected valid mailbox");
1272
1273 let parsed = "jdoe@one.test".parse::<Mailbox>();
1274 assert!(parsed.is_ok(), "expected valid mailbox");
1275 }
1276
1277 #[test]
1278 fn mailbox_from_str_rejects_group() {
1279 let parsed = "Undisclosed recipients:;".parse::<Mailbox>();
1280 assert!(matches!(
1281 parsed,
1282 Err(MailboxParseError::UnexpectedAddressKind)
1283 ));
1284 }
1285
1286 #[test]
1287 fn group_from_str_accepts_rfc_examples() {
1288 let parsed =
1289 "A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;".parse::<Group>();
1290 assert!(parsed.is_ok(), "expected valid group");
1291
1292 let parsed = "Undisclosed recipients:;".parse::<Group>();
1293 assert!(parsed.is_ok(), "expected valid group");
1294 }
1295
1296 #[test]
1297 fn address_list_roundtrip() {
1298 let list = "Mary Smith <mary@x.test>, jdoe@one.test"
1299 .parse::<AddressList>()
1300 .expect("address list should parse");
1301 let rendered = list.to_string();
1302 let reparsed = rendered
1303 .parse::<AddressList>()
1304 .expect("rendered address list should parse");
1305 assert_eq!(reparsed.as_slice(), list.as_slice());
1306 }
1307
1308 #[test]
1309 fn mailbox_from_str_rejects_input_with_raw_newline() {
1310 let parsed = "Mary Smith <mary@x.test>\nBcc: victim@example.com".parse::<Mailbox>();
1311 assert!(matches!(
1312 parsed,
1313 Err(MailboxParseError::Backend {
1314 source: AddressBackendError::InputContainsRawNewlines,
1315 })
1316 ));
1317 }
1318}