io_jmap/rfc8621/mailbox/get.rs
1//! JMAP `Mailbox/get` coroutine (RFC 8621 ยง2.5): wraps the generic [`JmapGet`]
2//! with the JMAP-Mail capability set.
3//!
4//! # Example
5//!
6//! ```rust,no_run
7//! use std::{
8//! io::{Read, Write},
9//! net::TcpStream,
10//! };
11//!
12//! use io_jmap::{
13//! coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
14//! rfc8620::JmapSession,
15//! rfc8621::mailbox::get::{JmapMailboxGet, JmapMailboxGetOptions},
16//! };
17//! use secrecy::SecretString;
18//!
19//! // Ready stream needed (TCP-connected, TLS-negociated)
20//! let mut stream = TcpStream::connect("api.example.com:443").unwrap();
21//! let mut buf = [0u8; 4096];
22//!
23//! let session: JmapSession = serde_json::from_str(r#"{
24//! "username": "",
25//! "accounts": {},
26//! "primaryAccounts": {"urn:ietf:params:jmap:mail": "a1"},
27//! "capabilities": {},
28//! "apiUrl": "https://api.example.com/jmap/",
29//! "downloadUrl": "",
30//! "uploadUrl": "",
31//! "eventSourceUrl": "",
32//! "state": ""
33//! }"#).unwrap();
34//! let auth = SecretString::from("Bearer xyz");
35//! let mut coroutine =
36//! JmapMailboxGet::new(&session, &auth, JmapMailboxGetOptions::default()).unwrap();
37//! let mut arg = None;
38//!
39//! let out = loop {
40//! match coroutine.resume(arg.take()) {
41//! JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
42//! stream.write_all(&bytes).unwrap();
43//! }
44//! JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
45//! let n = stream.read(&mut buf).unwrap();
46//! arg = Some(&buf[..n]);
47//! }
48//! JmapCoroutineState::Complete(Ok(out)) => break out,
49//! JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
50//! }
51//! };
52//!
53//! println!("{} mailboxes", out.mailboxes.len());
54//! ```
55
56use core::fmt;
57
58use alloc::{borrow::ToOwned, format, string::String, vec, vec::Vec};
59
60use log::trace;
61use secrecy::SecretString;
62use thiserror::Error;
63
64use crate::{
65 coroutine::*,
66 jmap_try,
67 rfc8620::{CORE_CAPABILITY, JmapSession, get::*},
68 rfc8621::{
69 MAIL_CAPABILITY,
70 mailbox::{JmapMailbox, JmapMailboxProperty},
71 },
72};
73
74/// Failure causes during a JMAP `Mailbox/get` flow.
75#[derive(Debug, Error)]
76pub enum JmapMailboxGetError {
77 #[error("JMAP Mailbox/get failed: {0}")]
78 Get(#[from] JmapGetError),
79}
80
81/// Options for [`JmapMailboxGet::new`].
82#[derive(Clone, Debug, Default)]
83pub struct JmapMailboxGetOptions {
84 /// Restrict the fetch to these mailbox IDs; `None` fetches all.
85 pub ids: Option<Vec<String>>,
86 /// Restrict the returned properties; `None` returns all.
87 pub properties: Option<Vec<JmapMailboxProperty>>,
88}
89
90/// Successful terminal output of [`JmapMailboxGet`].
91#[derive(Clone, Debug)]
92pub struct JmapMailboxGetOutput {
93 pub mailboxes: Vec<JmapMailbox>,
94 pub not_found: Vec<String>,
95 pub new_state: String,
96 pub keep_alive: bool,
97}
98
99/// I/O-free coroutine for the JMAP `Mailbox/get` method.
100pub struct JmapMailboxGet {
101 state: State,
102}
103
104impl JmapMailboxGet {
105 pub fn new(
106 session: &JmapSession,
107 http_auth: &SecretString,
108 opts: JmapMailboxGetOptions,
109 ) -> Result<Self, JmapMailboxGetError> {
110 let account_id = session
111 .primary_accounts
112 .get(MAIL_CAPABILITY)
113 .cloned()
114 .unwrap_or_default();
115 let api_url = &session.api_url;
116
117 let props = opts.properties.map(|ps| {
118 ps.iter()
119 .map(|p| {
120 serde_json::to_value(p)
121 .ok()
122 .and_then(|v| v.as_str().map(str::to_owned))
123 .unwrap_or_else(|| format!("{p:?}"))
124 })
125 .collect()
126 });
127
128 Ok(Self {
129 state: State::Get(JmapGet::new(
130 account_id,
131 http_auth,
132 api_url,
133 "Mailbox/get",
134 vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()],
135 JmapGetOptions {
136 ids: opts.ids,
137 properties: props,
138 },
139 )?),
140 })
141 }
142}
143
144impl JmapCoroutine for JmapMailboxGet {
145 type Yield = JmapYield;
146 type Return = Result<JmapMailboxGetOutput, JmapMailboxGetError>;
147
148 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
149 trace!("Mailbox/get: {}", self.state);
150 match &mut self.state {
151 State::Get(get) => {
152 let JmapGetOutput {
153 list,
154 not_found,
155 state,
156 keep_alive,
157 } = jmap_try!(get, arg);
158 JmapCoroutineState::Complete(Ok(JmapMailboxGetOutput {
159 mailboxes: list,
160 not_found,
161 new_state: state,
162 keep_alive,
163 }))
164 }
165 }
166 }
167}
168
169enum State {
170 Get(JmapGet<JmapMailbox>),
171}
172
173impl fmt::Display for State {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match self {
176 Self::Get(_) => f.write_str("get"),
177 }
178 }
179}