1pub mod add;
16pub mod config;
17pub mod delete;
18mod error;
19pub mod expunge;
20#[cfg(feature = "imap")]
21pub mod imap;
22pub mod list;
23#[cfg(feature = "maildir")]
24pub mod maildir;
25pub mod purge;
26#[cfg(feature = "sync")]
27pub mod sync;
28
29use std::{
30 fmt,
31 hash::Hash,
32 ops::{Deref, DerefMut},
33 str::FromStr,
34};
35
36#[cfg(feature = "sync")]
37pub(crate) use sync::sync;
38
39#[doc(inline)]
40pub use self::error::{Error, Result};
41
42pub const INBOX: &str = "INBOX";
43pub const SENT: &str = "Sent";
44pub const DRAFT: &str = "Drafts";
45pub const DRAFTS: &str = "Drafts";
46pub const TRASH: &str = "Trash";
47
48#[derive(Clone, Debug, Eq, PartialEq, Hash)]
58pub enum FolderKind {
59 Inbox,
64
65 Sent,
69
70 Drafts,
76
77 Trash,
82
83 UserDefined(String),
88}
89
90impl FolderKind {
91 pub fn is_inbox(&self) -> bool {
94 matches!(self, FolderKind::Inbox)
95 }
96
97 pub fn is_sent(&self) -> bool {
100 matches!(self, FolderKind::Sent)
101 }
102
103 pub fn is_drafts(&self) -> bool {
106 matches!(self, FolderKind::Drafts)
107 }
108
109 pub fn is_trash(&self) -> bool {
112 matches!(self, FolderKind::Trash)
113 }
114
115 pub fn is_user_defined(&self) -> bool {
118 matches!(self, FolderKind::UserDefined(_))
119 }
120
121 pub fn matches_inbox(folder: impl AsRef<str>) -> bool {
123 folder
124 .as_ref()
125 .parse::<FolderKind>()
126 .map(|kind| kind.is_inbox())
127 .unwrap_or_default()
128 }
129
130 pub fn matches_sent(folder: impl AsRef<str>) -> bool {
132 folder
133 .as_ref()
134 .parse::<FolderKind>()
135 .map(|kind| kind.is_sent())
136 .unwrap_or_default()
137 }
138
139 pub fn matches_drafts(folder: impl AsRef<str>) -> bool {
141 folder
142 .as_ref()
143 .parse::<FolderKind>()
144 .map(|kind| kind.is_drafts())
145 .unwrap_or_default()
146 }
147
148 pub fn matches_trash(folder: impl AsRef<str>) -> bool {
150 folder
151 .as_ref()
152 .parse::<FolderKind>()
153 .map(|kind| kind.is_trash())
154 .unwrap_or_default()
155 }
156
157 pub fn as_str(&self) -> &str {
159 match self {
160 Self::Inbox => INBOX,
161 Self::Sent => SENT,
162 Self::Drafts => DRAFTS,
163 Self::Trash => TRASH,
164 Self::UserDefined(alias) => alias.as_str(),
165 }
166 }
167}
168
169impl FromStr for FolderKind {
170 type Err = Error;
171
172 fn from_str(kind: &str) -> Result<Self> {
173 match kind {
174 kind if kind.eq_ignore_ascii_case(INBOX) => Ok(Self::Inbox),
175 kind if kind.eq_ignore_ascii_case(SENT) => Ok(Self::Sent),
176 kind if kind.eq_ignore_ascii_case(DRAFT) => Ok(Self::Drafts),
177 kind if kind.eq_ignore_ascii_case(DRAFTS) => Ok(Self::Drafts),
178 kind if kind.eq_ignore_ascii_case(TRASH) => Ok(Self::Trash),
179 kind => Err(Error::ParseFolderKindError(kind.to_owned())),
180 }
181 }
182}
183
184impl<T: AsRef<str>> From<T> for FolderKind {
185 fn from(kind: T) -> Self {
186 kind.as_ref()
187 .parse()
188 .ok()
189 .unwrap_or_else(|| Self::UserDefined(kind.as_ref().to_owned()))
190 }
191}
192
193impl fmt::Display for FolderKind {
194 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195 write!(f, "{}", self.as_str())
196 }
197}
198
199#[derive(Clone, Debug, Default, Eq)]
205pub struct Folder {
206 pub kind: Option<FolderKind>,
208
209 pub name: String,
211
212 pub desc: String,
217}
218
219impl Folder {
220 pub fn is_inbox(&self) -> bool {
222 self.kind
223 .as_ref()
224 .map(|kind| kind.is_inbox())
225 .unwrap_or_default()
226 }
227
228 pub fn is_sent(&self) -> bool {
230 self.kind
231 .as_ref()
232 .map(|kind| kind.is_sent())
233 .unwrap_or_default()
234 }
235
236 pub fn is_drafts(&self) -> bool {
238 self.kind
239 .as_ref()
240 .map(|kind| kind.is_drafts())
241 .unwrap_or_default()
242 }
243
244 pub fn is_trash(&self) -> bool {
246 self.kind
247 .as_ref()
248 .map(|kind| kind.is_trash())
249 .unwrap_or_default()
250 }
251
252 pub fn get_kind_or_name(&self) -> &str {
255 self.kind
256 .as_ref()
257 .map(FolderKind::as_str)
258 .unwrap_or(self.name.as_str())
259 }
260}
261
262impl PartialEq for Folder {
263 fn eq(&self, other: &Self) -> bool {
264 match (&self.kind, &other.kind) {
265 (Some(self_kind), Some(other_kind)) => self_kind == other_kind,
266 (None, None) => self.name == other.name,
267 _ => false,
268 }
269 }
271}
272impl Hash for Folder {
273 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
274 match &self.kind {
275 Some(kind) => kind.hash(state),
276 None => self.name.hash(state),
277 }
278 }
279}
280
281impl fmt::Display for Folder {
282 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283 match &self.kind {
284 Some(kind) => write!(f, "{kind}"),
285 None => write!(f, "{}", self.name),
286 }
287 }
288}
289
290#[derive(Clone, Debug, Default, Eq, PartialEq)]
295pub struct Folders(Vec<Folder>);
296
297impl Deref for Folders {
298 type Target = Vec<Folder>;
299
300 fn deref(&self) -> &Self::Target {
301 &self.0
302 }
303}
304
305impl DerefMut for Folders {
306 fn deref_mut(&mut self) -> &mut Self::Target {
307 &mut self.0
308 }
309}
310
311impl IntoIterator for Folders {
312 type Item = Folder;
313 type IntoIter = std::vec::IntoIter<Self::Item>;
314
315 fn into_iter(self) -> Self::IntoIter {
316 self.0.into_iter()
317 }
318}
319
320impl FromIterator<Folder> for Folders {
321 fn from_iter<T: IntoIterator<Item = Folder>>(iter: T) -> Self {
322 let mut folders = Folders::default();
323 folders.extend(iter);
324 folders
325 }
326}
327
328impl From<Folders> for Vec<Folder> {
329 fn from(val: Folders) -> Self {
330 val.0
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
337
338 use super::*;
339 fn folder_inbox_foo() -> Folder {
340 Folder {
341 kind: Some(FolderKind::Inbox),
342 name: "foo".to_owned(),
343 desc: "1".to_owned(),
344 }
345 }
346 fn folder_none_foo() -> Folder {
347 Folder {
348 kind: None,
349 name: "foo".to_owned(),
350 desc: "2".to_owned(),
351 }
352 }
353 fn folder_none_bar() -> Folder {
354 Folder {
355 kind: None,
356 name: "bar".to_owned(),
357 desc: "3".to_owned(),
358 }
359 }
360 fn folder_inbox_bar() -> Folder {
361 Folder {
362 kind: Some(FolderKind::Inbox),
363 name: "bar".to_owned(),
364 desc: "4".to_owned(),
365 }
366 }
367
368 fn hash<H: Hash>(item: H) -> u64 {
369 let mut hasher = DefaultHasher::new();
370 item.hash(&mut hasher);
371 hasher.finish()
372 }
373
374 #[test]
375 fn folder_inbox_bar_equals_inbox_foo_test() {
376 assert_eq!(folder_inbox_bar(), folder_inbox_foo());
377 }
378
379 #[test]
380 fn folder_inbox_bar_equals_inbox_foo_test_hash() {
381 assert_eq!(hash(folder_inbox_bar()), hash(folder_inbox_foo()));
382 }
383
384 #[test]
385 fn folder_none_foo_not_equals_inbox_foo_test() {
386 assert_ne!(folder_none_foo(), folder_inbox_foo());
387 }
388
389 #[test]
390 fn folder_none_foo_not_equals_inbox_foo_test_hash() {
391 assert_ne!(hash(folder_none_foo()), hash(folder_inbox_foo()));
392 }
393
394 #[test]
395 fn folder_none_foo_not_equals_none_bar_test() {
396 assert_ne!(folder_none_foo(), folder_none_bar());
397 }
398
399 #[test]
400 fn folder_none_foo_not_equals_none_bar_test_hash() {
401 assert_ne!(hash(folder_none_foo()), hash(folder_none_bar()));
402 }
403}