Skip to main content

io_email/envelope/jmap/
list.rs

1//! JMAP envelope-list coroutine wrapping Email/query + Email/get
2//! batched in one HTTP round-trip.
3//!
4//! `page = 1`-indexed pagination is translated to JMAP's
5//! position/limit; sort order comes back from the server.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use io_email::envelope::jmap::list::JmapEnvelopeList;
11//!
12//! let envs = client.run(JmapEnvelopeList::new(&session, &auth, "mailbox-id", Some(1), Some(50))?)?;
13//! ```
14
15use alloc::vec::Vec;
16use core::mem;
17
18use io_jmap::{
19    coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
20    rfc8620::JmapSession,
21    rfc8621::email::{
22        JmapEmailFilter,
23        query::{
24            JmapEmailQuery as InnerQuery, JmapEmailQueryError as QueryErr, JmapEmailQueryOptions,
25        },
26    },
27};
28use log::trace;
29use secrecy::SecretString;
30use thiserror::Error;
31
32use crate::{
33    envelope::types::Envelope,
34    jmap::convert::{compute_position_limit, envelope_from, envelope_properties},
35};
36
37/// Errors produced by [`JmapEnvelopeList`].
38#[derive(Debug, Error)]
39pub enum JmapEnvelopeListError {
40    #[error(transparent)]
41    EmailQuery(#[from] QueryErr),
42    #[error("coroutine was resumed after completion")]
43    ResumedAfterDone,
44}
45
46/// I/O-free coroutine listing envelopes from a JMAP mailbox.
47pub struct JmapEnvelopeList {
48    state: State,
49}
50
51impl JmapEnvelopeList {
52    pub fn new(
53        session: &JmapSession,
54        http_auth: &SecretString,
55        mailbox: &str,
56        page: Option<u32>,
57        page_size: Option<u32>,
58    ) -> Result<Self, JmapEnvelopeListError> {
59        trace!("prepare JMAP envelope listing");
60        let (position, limit) = compute_position_limit(page, page_size);
61        let filter = JmapEmailFilter {
62            in_mailbox: Some(mailbox.into()),
63            ..JmapEmailFilter::default()
64        };
65        let opts = JmapEmailQueryOptions {
66            filter: Some(filter.into()),
67            position,
68            limit,
69            properties: Some(envelope_properties()),
70            ..Default::default()
71        };
72        let inner = InnerQuery::new(session, http_auth, opts)?;
73        Ok(Self {
74            state: State::Listing(inner),
75        })
76    }
77}
78
79enum State {
80    Listing(InnerQuery),
81    Done,
82}
83
84impl JmapCoroutine for JmapEnvelopeList {
85    type Yield = JmapYield;
86    type Return = Result<Vec<Envelope>, JmapEnvelopeListError>;
87
88    fn resume(&mut self, bytes: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
89        match mem::replace(&mut self.state, State::Done) {
90            State::Listing(mut query) => match query.resume(bytes) {
91                JmapCoroutineState::Complete(Ok(ok)) => {
92                    let envelopes = ok.emails.into_iter().map(envelope_from).collect();
93                    JmapCoroutineState::Complete(Ok(envelopes))
94                }
95                JmapCoroutineState::Yielded(y) => {
96                    self.state = State::Listing(query);
97                    JmapCoroutineState::Yielded(y)
98                }
99                JmapCoroutineState::Complete(Err(err)) => {
100                    JmapCoroutineState::Complete(Err(err.into()))
101                }
102            },
103            State::Done => {
104                JmapCoroutineState::Complete(Err(JmapEnvelopeListError::ResumedAfterDone))
105            }
106        }
107    }
108}