Skip to main content

io_email/message/jmap/
move.rs

1//! JMAP message-move coroutine wrapping Email/set with paired
2//! AddToMailbox + RemoveFromMailbox patches per id.
3//!
4//! # Example
5//!
6//! ```rust,ignore
7//! use io_email::message::jmap::r#move::JmapMessageMove;
8//!
9//! client.run(JmapMessageMove::new(&session, &auth, "src-id", "dst-id", &["email-id"])?)?;
10//! ```
11
12use alloc::{string::String, vec::Vec};
13use core::mem;
14
15use io_jmap::{
16    coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
17    rfc8620::JmapSession,
18    rfc8621::email::set::{
19        JmapEmailSet as InnerSet, JmapEmailSetArgs, JmapEmailSetError as SetErr,
20    },
21};
22use log::trace;
23use secrecy::SecretString;
24use thiserror::Error;
25
26/// Errors produced by [`JmapMessageMove`].
27#[derive(Debug, Error)]
28pub enum JmapMessageMoveError {
29    #[error(transparent)]
30    Set(#[from] SetErr),
31    #[error("Email/set returned per-id failures: {0:?}")]
32    NotUpdated(Vec<String>),
33    #[error("coroutine was resumed after completion")]
34    ResumedAfterDone,
35}
36
37/// I/O-free coroutine moving every id from `from` to `to` (mailbox ids).
38pub struct JmapMessageMove {
39    state: State,
40}
41
42impl JmapMessageMove {
43    pub fn new(
44        session: &JmapSession,
45        http_auth: &SecretString,
46        from: &str,
47        to: &str,
48        ids: &[&str],
49    ) -> Result<Self, JmapMessageMoveError> {
50        trace!("prepare JMAP message move");
51        let mut args = JmapEmailSetArgs::default();
52        for id in ids {
53            args.add_to_mailbox(*id, to);
54            args.remove_from_mailbox(*id, from);
55        }
56        let set = InnerSet::new(session, http_auth, args)?;
57        Ok(Self {
58            state: State::Patching(set),
59        })
60    }
61}
62
63enum State {
64    Patching(InnerSet),
65    Done,
66}
67
68impl JmapCoroutine for JmapMessageMove {
69    type Yield = JmapYield;
70    type Return = Result<(), JmapMessageMoveError>;
71
72    fn resume(&mut self, bytes: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
73        match mem::replace(&mut self.state, State::Done) {
74            State::Patching(mut set) => match set.resume(bytes) {
75                JmapCoroutineState::Complete(Ok(ok)) => {
76                    if ok.not_updated.is_empty() {
77                        JmapCoroutineState::Complete(Ok(()))
78                    } else {
79                        JmapCoroutineState::Complete(Err(JmapMessageMoveError::NotUpdated(
80                            ok.not_updated.into_keys().collect(),
81                        )))
82                    }
83                }
84                JmapCoroutineState::Yielded(y) => {
85                    self.state = State::Patching(set);
86                    JmapCoroutineState::Yielded(y)
87                }
88                JmapCoroutineState::Complete(Err(err)) => {
89                    JmapCoroutineState::Complete(Err(err.into()))
90                }
91            },
92            State::Done => {
93                JmapCoroutineState::Complete(Err(JmapMessageMoveError::ResumedAfterDone))
94            }
95        }
96    }
97}