Skip to main content

io_email/message/imap/
delete.rs

1//! IMAP message-delete coroutine: optional SELECT, UID STORE
2//! +FLAGS (\\Deleted), then EXPUNGE (RFC 3501 ยง6.4.3).
3//!
4//! The shared API treats delete as permanent, which on IMAP requires
5//! both the flag-set and expunge.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use io_email::message::imap::delete::ImapMessageDelete;
11//!
12//! client.run(ImapMessageDelete::new("INBOX", "12", true)?)?;
13//! ```
14
15use alloc::{string::String, vec};
16use core::mem;
17
18use io_imap::{
19    codec::fragmentizer::Fragmentizer,
20    coroutine::{ImapCoroutine, ImapCoroutineState, ImapYield},
21    rfc3501::{
22        expunge::{ImapMailboxExpunge, ImapMailboxExpungeError},
23        select::{ImapMailboxSelect, ImapMailboxSelectError, ImapMailboxSelectOptions},
24        store::{ImapMessageStore, ImapMessageStoreError, ImapMessageStoreOptions},
25    },
26    types::flag::StoreType,
27};
28use log::trace;
29use thiserror::Error;
30
31use crate::{
32    flag::types::{Flag, IanaFlag},
33    imap::convert::{InvalidMailboxName, InvalidUidSet, flag_from, parse_mailbox, parse_uids},
34};
35
36/// Errors produced by [`ImapMessageDelete`].
37#[derive(Debug, Error)]
38pub enum ImapMessageDeleteError {
39    #[error(transparent)]
40    Select(#[from] ImapMailboxSelectError),
41    #[error(transparent)]
42    Store(#[from] ImapMessageStoreError),
43    #[error(transparent)]
44    Expunge(#[from] ImapMailboxExpungeError),
45    #[error("invalid IMAP mailbox `{0}`")]
46    InvalidMailbox(String),
47    #[error("invalid message UID `{0}`")]
48    InvalidUid(String),
49    #[error("empty UID set")]
50    EmptyUidSet,
51    #[error("coroutine was resumed after completion")]
52    ResumedAfterDone,
53}
54
55impl From<InvalidMailboxName> for ImapMessageDeleteError {
56    fn from(err: InvalidMailboxName) -> Self {
57        Self::InvalidMailbox(err.0)
58    }
59}
60
61impl From<InvalidUidSet> for ImapMessageDeleteError {
62    fn from(err: InvalidUidSet) -> Self {
63        match err {
64            InvalidUidSet::Empty => Self::EmptyUidSet,
65            InvalidUidSet::Invalid(s) => Self::InvalidUid(s),
66        }
67    }
68}
69
70/// I/O-free coroutine deleting and expunging a single UID.
71pub struct ImapMessageDelete {
72    state: State,
73}
74
75impl ImapMessageDelete {
76    pub fn new(mailbox: &str, id: &str, auto_select: bool) -> Result<Self, ImapMessageDeleteError> {
77        trace!("prepare IMAP message delete (auto_select={auto_select})");
78        let mbox = parse_mailbox(mailbox)?;
79        let sequence_set = parse_uids(&[id])?;
80        let imap_flags = vec![flag_from(&Flag::from_iana(IanaFlag::Deleted))];
81        let store = ImapMessageStore::new(
82            sequence_set,
83            StoreType::Add,
84            imap_flags,
85            ImapMessageStoreOptions { uid: true },
86        );
87        let state = if auto_select {
88            State::Selecting {
89                select: ImapMailboxSelect::new(mbox, ImapMailboxSelectOptions::default()),
90                store,
91            }
92        } else {
93            State::Storing(store)
94        };
95        Ok(Self { state })
96    }
97}
98
99#[allow(clippy::large_enum_variant)] // see flag_store.rs for rationale
100enum State {
101    Selecting {
102        select: ImapMailboxSelect,
103        store: ImapMessageStore,
104    },
105    Storing(ImapMessageStore),
106    Expunging(ImapMailboxExpunge),
107    Done,
108}
109
110impl ImapCoroutine for ImapMessageDelete {
111    type Yield = ImapYield;
112    type Return = Result<(), ImapMessageDeleteError>;
113
114    fn resume(
115        &mut self,
116        fragmentizer: &mut Fragmentizer,
117        mut bytes: Option<&[u8]>,
118    ) -> ImapCoroutineState<Self::Yield, Self::Return> {
119        loop {
120            match mem::replace(&mut self.state, State::Done) {
121                State::Selecting { mut select, store } => {
122                    match select.resume(fragmentizer, bytes.take()) {
123                        ImapCoroutineState::Yielded(yielded) => {
124                            self.state = State::Selecting { select, store };
125                            return ImapCoroutineState::Yielded(yielded);
126                        }
127                        ImapCoroutineState::Complete(Ok(_)) => {
128                            self.state = State::Storing(store);
129                        }
130                        ImapCoroutineState::Complete(Err(err)) => {
131                            return ImapCoroutineState::Complete(Err(err.into()));
132                        }
133                    }
134                }
135                State::Storing(mut store) => match store.resume(fragmentizer, bytes.take()) {
136                    ImapCoroutineState::Yielded(yielded) => {
137                        self.state = State::Storing(store);
138                        return ImapCoroutineState::Yielded(yielded);
139                    }
140                    ImapCoroutineState::Complete(Ok(_)) => {
141                        self.state = State::Expunging(ImapMailboxExpunge::new());
142                    }
143                    ImapCoroutineState::Complete(Err(err)) => {
144                        return ImapCoroutineState::Complete(Err(err.into()));
145                    }
146                },
147                State::Expunging(mut expunge) => match expunge.resume(fragmentizer, bytes.take()) {
148                    ImapCoroutineState::Yielded(yielded) => {
149                        self.state = State::Expunging(expunge);
150                        return ImapCoroutineState::Yielded(yielded);
151                    }
152                    ImapCoroutineState::Complete(Ok(_expunged)) => {
153                        return ImapCoroutineState::Complete(Ok(()));
154                    }
155                    ImapCoroutineState::Complete(Err(err)) => {
156                        return ImapCoroutineState::Complete(Err(err.into()));
157                    }
158                },
159                State::Done => {
160                    return ImapCoroutineState::Complete(Err(
161                        ImapMessageDeleteError::ResumedAfterDone,
162                    ));
163                }
164            }
165        }
166    }
167}