io_smtp/rfc5321/types/
atom.rs1use core::{fmt, ops::Deref};
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 SmtpAtom<'a>(pub(crate) Cow<'a, str>);
16
17impl SmtpAtom<'_> {
18 pub fn parse<'a>(bytes: &'a [u8]) -> Result<SmtpAtom<'a>, Vec<Rich<'a, u8>>> {
20 parsers::atom()
21 .then_ignore(end())
22 .parse(bytes)
23 .into_result()
24 }
25}
26
27impl fmt::Display for SmtpAtom<'_> {
28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 write!(f, "{}", self.0)
30 }
31}
32
33impl<'a> Deref for SmtpAtom<'a> {
34 type Target = str;
35
36 fn deref(&self) -> &Self::Target {
37 self.0.as_ref()
38 }
39}
40
41pub(crate) mod parsers {
42 use core::str::from_utf8;
45
46 use alloc::borrow::Cow;
47
48 use chumsky::prelude::*;
49
50 use crate::{rfc5321::SmtpAtom, utils::parsers::Extra};
51
52 pub(crate) fn atom<'a>() -> impl Parser<'a, &'a [u8], SmtpAtom<'a>, Extra<'a>> + Clone {
62 any()
63 .filter(|b| match b {
64 b if b.is_ascii_alphanumeric() => true,
65 b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' => true,
66 b'+' | b'-' | b'/' | b'=' | b'?' | b'^' | b'_' => true,
67 b'`' | b'{' | b'|' | b'}' | b'~' => true,
68 _ => false,
69 })
70 .repeated()
71 .at_least(1)
72 .to_slice()
73 .try_map(|bytes: &[u8], span| {
74 from_utf8(bytes)
75 .map_err(|_| Rich::custom(span, "invalid UTF-8 in atom"))
76 .map(Cow::from)
77 .map(SmtpAtom)
78 })
79 .labelled("atom")
80 }
81}