Skip to main content

io_email/message/jmap/
copy.rs

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