Skip to main content

io_email/mailbox/jmap/
list.rs

1//! JMAP list-mailboxes coroutine wrapping a batched Mailbox/query +
2//! Mailbox/get (RFC 8621 §2.4 + §2.5).
3//!
4//! `with_counts` only widens the requested property set: JMAP returns
5//! totalEmails/unreadEmails inline, no second round-trip.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use io_email::mailbox::jmap::list::JmapMailboxList;
11//!
12//! let mailboxes = client.run(JmapMailboxList::new(&session, &auth, true)?)?;
13//! ```
14
15use alloc::{vec, vec::Vec};
16
17use io_jmap::{
18    coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
19    rfc8620::JmapSession,
20    rfc8621::mailbox::{
21        JmapMailbox as JmapMailboxObject, JmapMailboxProperty,
22        query::{
23            JmapMailboxQuery, JmapMailboxQueryError, JmapMailboxQueryOptions,
24            JmapMailboxQueryOutput,
25        },
26    },
27};
28use log::trace;
29use secrecy::SecretString;
30use thiserror::Error;
31
32use crate::mailbox::types::Mailbox;
33
34/// Errors produced by [`JmapMailboxList`].
35#[derive(Debug, Error)]
36pub enum JmapMailboxListError {
37    #[error(transparent)]
38    Query(#[from] JmapMailboxQueryError),
39}
40
41/// I/O-free coroutine listing every JMAP mailbox in the primary mail
42/// account.
43pub struct JmapMailboxList {
44    inner: JmapMailboxQuery,
45}
46
47impl JmapMailboxList {
48    /// Requests id + name; adds totalEmails + unreadEmails when
49    /// `with_counts` is set.
50    pub fn new(
51        session: &JmapSession,
52        http_auth: &SecretString,
53        with_counts: bool,
54    ) -> Result<Self, JmapMailboxListError> {
55        trace!("prepare JMAP mailbox listing (with_counts={with_counts})");
56        let properties = if with_counts {
57            vec![
58                JmapMailboxProperty::Id,
59                JmapMailboxProperty::Name,
60                JmapMailboxProperty::TotalEmails,
61                JmapMailboxProperty::UnreadEmails,
62            ]
63        } else {
64            vec![JmapMailboxProperty::Id, JmapMailboxProperty::Name]
65        };
66        let opts = JmapMailboxQueryOptions {
67            properties: Some(properties),
68            ..Default::default()
69        };
70        Ok(Self {
71            inner: JmapMailboxQuery::new(session, http_auth, opts)?,
72        })
73    }
74}
75
76/// Converts one JMAP Mailbox object into the shared [`Mailbox`] shape.
77fn mailbox_from(mailbox: JmapMailboxObject) -> Mailbox {
78    Mailbox {
79        id: mailbox.id.unwrap_or_default(),
80        name: mailbox.name.unwrap_or_default(),
81        total: Some(u64::from(mailbox.total_emails)),
82        unread: Some(u64::from(mailbox.unread_emails)),
83    }
84}
85
86impl JmapCoroutine for JmapMailboxList {
87    type Yield = JmapYield;
88    type Return = Result<Vec<Mailbox>, JmapMailboxListError>;
89
90    fn resume(&mut self, bytes: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
91        match self.inner.resume(bytes) {
92            JmapCoroutineState::Yielded(y) => JmapCoroutineState::Yielded(y),
93            JmapCoroutineState::Complete(Ok(JmapMailboxQueryOutput { mailboxes, .. })) => {
94                let mailboxes = mailboxes.into_iter().map(mailbox_from).collect();
95                JmapCoroutineState::Complete(Ok(mailboxes))
96            }
97            JmapCoroutineState::Complete(Err(err)) => JmapCoroutineState::Complete(Err(err.into())),
98        }
99    }
100}