io_smtp/rfc5321/types/
domain.rs1use core::fmt;
7
8use alloc::{borrow::Cow, vec::Vec};
9
10use bounded_static_derive::ToStatic;
11use chumsky::prelude::*;
12
13#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, ToStatic)]
15pub struct SmtpDomain<'a>(pub Cow<'a, str>);
16
17impl SmtpDomain<'_> {
18 pub fn parse<'a>(bytes: &'a [u8]) -> Result<SmtpDomain<'a>, Vec<Rich<'a, u8>>> {
20 parsers::domain()
21 .then_ignore(end())
22 .parse(bytes)
23 .into_result()
24 }
25}
26
27impl fmt::Display for SmtpDomain<'_> {
28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 write!(f, "{}", self.0)
30 }
31}
32
33impl<'a> From<SmtpDomain<'a>> for Cow<'a, str> {
34 fn from(domain: SmtpDomain<'a>) -> Self {
35 domain.0
36 }
37}
38
39impl AsRef<str> for SmtpDomain<'_> {
40 fn as_ref(&self) -> &str {
41 self.0.as_ref()
42 }
43}
44
45pub(crate) mod parsers {
46 use core::str::from_utf8;
49
50 use alloc::borrow::Cow;
51
52 use chumsky::prelude::*;
53
54 use crate::{rfc5321::SmtpDomain, utils::parsers::Extra};
55
56 pub(crate) fn domain<'a>() -> impl Parser<'a, &'a [u8], SmtpDomain<'a>, Extra<'a>> + Clone {
65 let sub_domain = any()
67 .filter(|b: &u8| b.is_ascii_alphanumeric())
68 .then(
69 any()
70 .filter(|b: &u8| b.is_ascii_alphanumeric() || *b == b'-')
71 .repeated()
72 .to_slice(),
73 )
74 .to_slice();
75
76 sub_domain
78 .then(
79 just(b'.')
80 .then(any().filter(|b: &u8| b.is_ascii_alphanumeric()))
81 .then(
82 any()
83 .filter(|b: &u8| b.is_ascii_alphanumeric() || *b == b'-')
84 .repeated()
85 .to_slice(),
86 )
87 .to_slice()
88 .repeated()
89 .to_slice(),
90 )
91 .to_slice()
92 .try_map(|bytes: &[u8], span| {
93 from_utf8(bytes)
94 .map_err(|_| Rich::custom(span, "invalid UTF-8 in domain"))
95 .map(Cow::from)
96 .map(SmtpDomain)
97 })
98 .labelled("domain")
99 }
100}