1use std::{borrow::Cow, str::from_utf8};
4
5#[cfg(feature = "arbitrary")]
6use arbitrary::Arbitrary;
7#[cfg(feature = "bounded-static")]
8use bounded_static::ToStatic;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 core::{impl_try_from, AString, IString},
14 error::{ValidationError, ValidationErrorKind},
15 mailbox::error::MailboxOtherError,
16 utils::indicators::is_list_char,
17};
18
19#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub struct ListCharString<'a>(pub(crate) Cow<'a, str>);
23
24impl<'a> ListCharString<'a> {
25 pub fn validate(value: impl AsRef<[u8]>) -> Result<(), ValidationError> {
26 let value = value.as_ref();
27
28 if value.is_empty() {
29 return Err(ValidationError::new(ValidationErrorKind::Empty));
30 }
31
32 if let Some(at) = value.iter().position(|b| !is_list_char(*b)) {
33 return Err(ValidationError::new(ValidationErrorKind::InvalidByteAt {
34 byte: value[at],
35 at,
36 }));
37 };
38
39 Ok(())
40 }
41
42 #[cfg(feature = "unvalidated")]
50 #[cfg_attr(docsrs, doc(cfg(feature = "unvalidated")))]
51 pub fn unvalidated<C>(inner: C) -> Self
52 where
53 C: Into<Cow<'a, str>>,
54 {
55 let inner = inner.into();
56
57 #[cfg(debug_assertions)]
58 Self::validate(inner.as_bytes()).unwrap();
59
60 Self(inner)
61 }
62}
63
64impl<'a> TryFrom<&'a str> for ListCharString<'a> {
65 type Error = ValidationError;
66
67 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
68 Self::validate(value)?;
69
70 Ok(Self(Cow::Borrowed(value)))
71 }
72}
73
74impl<'a> TryFrom<String> for ListCharString<'a> {
75 type Error = ValidationError;
76
77 fn try_from(value: String) -> Result<Self, Self::Error> {
78 Self::validate(&value)?;
79
80 Ok(Self(Cow::Owned(value)))
81 }
82}
83
84impl<'a> AsRef<[u8]> for ListCharString<'a> {
85 fn as_ref(&self) -> &[u8] {
86 self.0.as_bytes()
87 }
88}
89
90#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
91#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
92#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94pub enum ListMailbox<'a> {
95 Token(ListCharString<'a>),
96 String(IString<'a>),
97}
98
99impl<'a> TryFrom<&'a str> for ListMailbox<'a> {
100 type Error = ValidationError;
101
102 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
103 if s.is_empty() {
104 return Ok(ListMailbox::String(IString::Quoted(s.try_into().unwrap())));
106 }
107
108 if let Ok(lcs) = ListCharString::try_from(s) {
109 return Ok(ListMailbox::Token(lcs));
110 }
111
112 Ok(ListMailbox::String(s.try_into()?))
113 }
114}
115
116impl<'a> TryFrom<String> for ListMailbox<'a> {
117 type Error = ValidationError;
118
119 fn try_from(s: String) -> Result<Self, Self::Error> {
120 if s.is_empty() {
121 return Ok(ListMailbox::String(IString::Quoted(s.try_into().unwrap())));
123 }
124
125 if let Ok(lcs) = ListCharString::try_from(s.clone()) {
127 return Ok(ListMailbox::Token(lcs));
128 }
129
130 Ok(ListMailbox::String(s.try_into()?))
131 }
132}
133
134#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
175#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
177pub enum Mailbox<'a> {
178 Inbox,
179 Other(MailboxOther<'a>),
180}
181
182impl_try_from!(AString<'a>, 'a, &'a [u8], Mailbox<'a>);
183impl_try_from!(AString<'a>, 'a, Vec<u8>, Mailbox<'a>);
184impl_try_from!(AString<'a>, 'a, &'a str, Mailbox<'a>);
185impl_try_from!(AString<'a>, 'a, String, Mailbox<'a>);
186
187impl<'a> From<AString<'a>> for Mailbox<'a> {
188 fn from(value: AString<'a>) -> Self {
189 match from_utf8(value.as_ref()) {
190 Ok(value) if value.to_ascii_lowercase() == "inbox" => Self::Inbox,
191 _ => Self::Other(MailboxOther::try_from(value).unwrap()),
192 }
193 }
194}
195
196#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
200#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
201#[derive(Debug, Clone, PartialEq, Eq, Hash)]
202pub struct MailboxOther<'a>(pub(crate) AString<'a>);
203
204impl<'a> MailboxOther<'a> {
205 pub fn validate(value: impl AsRef<[u8]>) -> Result<(), MailboxOtherError> {
206 if value.as_ref().to_ascii_lowercase() == b"inbox" {
207 return Err(MailboxOtherError::Reserved);
208 }
209
210 Ok(())
211 }
212
213 pub fn inner(&self) -> &AString {
214 &self.0
215 }
216
217 #[cfg(feature = "unvalidated")]
225 #[cfg_attr(docsrs, doc(cfg(feature = "unvalidated")))]
226 pub fn unvalidated(value: AString<'a>) -> Self {
227 #[cfg(debug_assertions)]
228 Self::validate(&value).unwrap();
229
230 Self(value)
231 }
232}
233
234macro_rules! impl_try_from {
235 ($from:ty) => {
236 impl<'a> TryFrom<$from> for MailboxOther<'a> {
237 type Error = MailboxOtherError;
238
239 fn try_from(value: $from) -> Result<Self, Self::Error> {
240 let astring = AString::try_from(value)?;
241
242 Self::validate(&astring)?;
243
244 Ok(Self(astring))
245 }
246 }
247 };
248}
249
250impl_try_from!(&'a [u8]);
251impl_try_from!(Vec<u8>);
252impl_try_from!(&'a str);
253impl_try_from!(String);
254
255impl<'a> TryFrom<AString<'a>> for MailboxOther<'a> {
256 type Error = MailboxOtherError;
257
258 fn try_from(value: AString<'a>) -> Result<Self, Self::Error> {
259 Self::validate(&value)?;
260
261 Ok(Self(value))
262 }
263}
264
265impl<'a> AsRef<[u8]> for MailboxOther<'a> {
266 fn as_ref(&self) -> &[u8] {
267 self.0.as_ref()
268 }
269}
270
271pub mod error {
273 use thiserror::Error;
274
275 use crate::error::ValidationError;
276
277 #[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
278 pub enum MailboxOtherError {
279 #[error(transparent)]
280 Literal(#[from] ValidationError),
281 #[error("Reserved: Please use one of the typed variants")]
282 Reserved,
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use std::borrow::Cow;
289
290 use super::*;
291 use crate::core::{AString, IString, Literal, LiteralMode};
292
293 #[test]
294 fn test_conversion_mailbox() {
295 let tests = [
296 ("inbox", Mailbox::Inbox),
297 ("inboX", Mailbox::Inbox),
298 ("Inbox", Mailbox::Inbox),
299 ("InboX", Mailbox::Inbox),
300 ("INBOX", Mailbox::Inbox),
301 (
302 "INBO²",
303 Mailbox::Other(MailboxOther(AString::String(IString::Literal(Literal {
304 data: Cow::Borrowed("INBO²".as_bytes()),
305 mode: LiteralMode::Sync,
306 })))),
307 ),
308 ];
309
310 for (test, expected) in tests {
311 let got = Mailbox::try_from(test).unwrap();
312 assert_eq!(expected, got);
313
314 let got = Mailbox::try_from(String::from(test)).unwrap();
315 assert_eq!(expected, got);
316 }
317 }
318
319 #[test]
320 fn test_conversion_mailbox_failing() {
321 let tests = ["\x00", "A\x00", "\x00A"];
322
323 for test in tests {
324 assert!(Mailbox::try_from(test).is_err());
325 assert!(Mailbox::try_from(String::from(test)).is_err());
326 }
327 }
328}