1use alloc::{
7 string::{String, ToString},
8 vec::Vec,
9};
10use core::num::NonZeroU32;
11
12use io_imap::types::{
13 core::Atom, flag::Flag as ImapFlag, mailbox::Mailbox as ImapMailbox, sequence::SequenceSet,
14};
15
16use crate::flag::types::{Flag, IanaFlag};
17
18#[derive(Debug)]
20pub struct InvalidMailboxName(pub String);
21
22#[derive(Debug)]
25pub enum InvalidUidSet {
26 Empty,
27 Invalid(String),
28}
29
30pub fn parse_mailbox(name: &str) -> Result<ImapMailbox<'static>, InvalidMailboxName> {
32 String::from(name)
33 .try_into()
34 .map_err(|_| InvalidMailboxName(name.to_string()))
35}
36
37pub fn parse_uids(ids: &[&str]) -> Result<SequenceSet, InvalidUidSet> {
39 if ids.is_empty() {
40 return Err(InvalidUidSet::Empty);
41 }
42
43 let uids: Vec<NonZeroU32> = ids
44 .iter()
45 .map(|s| {
46 s.parse::<NonZeroU32>()
47 .map_err(|_| InvalidUidSet::Invalid((*s).to_string()))
48 })
49 .collect::<Result<_, _>>()?;
50
51 SequenceSet::try_from(uids).map_err(|_| InvalidUidSet::Empty)
52}
53
54pub fn flag_from(flag: &Flag) -> ImapFlag<'static> {
60 match flag.iana() {
61 Some(IanaFlag::Seen) => ImapFlag::Seen,
62 Some(IanaFlag::Answered) => ImapFlag::Answered,
63 Some(IanaFlag::Flagged) => ImapFlag::Flagged,
64 Some(IanaFlag::Draft) => ImapFlag::Draft,
65 Some(IanaFlag::Deleted) => ImapFlag::Deleted,
66 Some(_) => ImapFlag::keyword(
67 Atom::try_from(String::from(flag.raw()))
68 .expect("canonical IANA keyword is a valid IMAP atom"),
69 ),
70 None => match Atom::try_from(String::from(flag.raw())) {
71 Ok(atom) => ImapFlag::keyword(atom),
72 Err(_) => ImapFlag::keyword(
73 Atom::try_from(sanitise_atom(flag.raw()))
74 .expect("sanitised atom contains only atom-safe ASCII"),
75 ),
76 },
77 }
78}
79
80fn sanitise_atom(raw: &str) -> String {
83 raw.chars()
84 .map(|c| {
85 if c.is_ascii()
86 && !c.is_control()
87 && !matches!(
88 c,
89 ' ' | '(' | ')' | '{' | '%' | '*' | '"' | '\\' | ']' | '\x7f'
90 )
91 {
92 c
93 } else {
94 '_'
95 }
96 })
97 .collect()
98}