1use alloc::{string::String, vec, vec::Vec};
58
59use secrecy::SecretString;
60use serde::{Deserialize, Serialize};
61use thiserror::Error;
62
63use crate::{
64 coroutine::*,
65 jmap_try,
66 rfc8620::{
67 JMAP_CORE_CAPABILITY, error::JmapMethodError, request::JmapBatch,
68 request::JmapResultReference, send::*, session::JmapSession,
69 },
70 rfc8621::{
71 JMAP_MAIL_CAPABILITY,
72 mailbox::{JmapMailbox, JmapMailboxProperty, JmapMailboxRole},
73 },
74};
75
76#[derive(Clone, Debug, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub enum JmapMailboxSortProperty {
80 Name,
82 SortOrder,
84 ParentId,
86}
87
88#[derive(Clone, Debug, Serialize)]
90#[serde(rename_all = "camelCase")]
91pub struct JmapMailboxSortComparator {
92 pub property: JmapMailboxSortProperty,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub is_ascending: Option<bool>,
97}
98
99#[derive(Clone, Debug, Default, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct JmapMailboxFilter {
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub parent_id: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub role: Option<JmapMailboxRole>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub name: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub is_subscribed: Option<bool>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub has_any_role: Option<bool>,
118}
119
120#[derive(Debug, Error)]
122pub enum JmapMailboxQueryError {
123 #[error("JMAP Mailbox/query failed: missing Mailbox/query response in method_responses")]
125 MissingQueryResponse,
126 #[error("JMAP Mailbox/query failed: missing Mailbox/get response in method_responses")]
128 MissingGetResponse,
129 #[error("JMAP Mailbox/query failed: {0}")]
131 Send(#[from] JmapSendError),
132 #[error("JMAP Mailbox/query failed: serialize args: {0}")]
134 SerializeArgs(#[source] serde_json::Error),
135 #[error("JMAP Mailbox/query failed: parse Mailbox/query response: {0}")]
137 ParseQueryResponse(#[source] serde_json::Error),
138 #[error("JMAP Mailbox/query failed: parse Mailbox/get response: {0}")]
140 ParseGetResponse(#[source] serde_json::Error),
141 #[error("JMAP Mailbox/query failed: Mailbox/query: {0}")]
143 QueryMethod(JmapMethodError),
144 #[error("JMAP Mailbox/query failed: Mailbox/get: {0}")]
146 GetMethod(JmapMethodError),
147}
148
149#[derive(Clone, Debug, Default)]
151pub struct JmapMailboxQueryOptions {
152 pub filter: Option<JmapMailboxFilter>,
154 pub sort: Option<Vec<JmapMailboxSortComparator>>,
156 pub position: Option<u64>,
158 pub limit: Option<u64>,
160 pub properties: Option<Vec<JmapMailboxProperty>>,
162}
163
164#[derive(Clone, Debug)]
166pub struct JmapMailboxQueryOutput {
167 pub mailboxes: Vec<JmapMailbox>,
169 pub total: Option<u64>,
171 pub position: u64,
173 pub query_state: String,
175 pub keep_alive: bool,
177}
178
179pub struct JmapMailboxQuery {
182 state: State,
183}
184
185impl JmapMailboxQuery {
186 pub fn new(
188 session: &JmapSession,
189 http_auth: &SecretString,
190 opts: JmapMailboxQueryOptions,
191 ) -> Result<Self, JmapMailboxQueryError> {
192 let account_id = session
193 .primary_accounts
194 .get(JMAP_MAIL_CAPABILITY)
195 .cloned()
196 .unwrap_or_default();
197 let api_url = &session.api_url;
198
199 let query_args = MailboxQueryArgs {
200 account_id: &account_id,
201 filter: opts.filter.as_ref(),
202 sort: opts.sort.as_deref(),
203 position: opts.position,
204 limit: opts.limit,
205 calculate_total: true,
206 };
207
208 let mut batch = JmapBatch::new();
209 let query_id = batch.add(
210 "Mailbox/query",
211 serde_json::to_value(&query_args).map_err(JmapMailboxQueryError::SerializeArgs)?,
212 );
213
214 let get_args = MailboxGetByRefArgs {
215 account_id: &account_id,
216 ids_ref: JmapResultReference {
217 result_of: &query_id,
218 name: "Mailbox/query",
219 path: "/ids",
220 },
221 properties: opts.properties.as_deref(),
222 };
223
224 batch.add(
225 "Mailbox/get",
226 serde_json::to_value(&get_args).map_err(JmapMailboxQueryError::SerializeArgs)?,
227 );
228
229 let request = batch.into_request(vec![
230 JMAP_CORE_CAPABILITY.into(),
231 JMAP_MAIL_CAPABILITY.into(),
232 ]);
233
234 Ok(Self {
235 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
236 })
237 }
238}
239
240impl JmapCoroutine for JmapMailboxQuery {
241 type Yield = JmapYield;
242 type Return = Result<JmapMailboxQueryOutput, JmapMailboxQueryError>;
243
244 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
245 match &mut self.state {
246 State::Send(send) => {
247 let JmapSendOutput {
248 response,
249 keep_alive,
250 } = jmap_try!(send, arg);
251
252 let mut responses = response.method_responses.into_iter();
253
254 let Some((query_name, query_args, _)) = responses.next() else {
255 return JmapCoroutineState::Complete(Err(
256 JmapMailboxQueryError::MissingQueryResponse,
257 ));
258 };
259
260 if query_name == "error" {
261 let err = serde_json::from_value::<JmapMethodError>(query_args)
262 .unwrap_or(JmapMethodError::Unknown);
263 return JmapCoroutineState::Complete(Err(JmapMailboxQueryError::QueryMethod(
264 err,
265 )));
266 }
267
268 let query_response =
269 match serde_json::from_value::<MailboxQueryResponse>(query_args) {
270 Ok(r) => r,
271 Err(err) => {
272 return JmapCoroutineState::Complete(Err(
273 JmapMailboxQueryError::ParseQueryResponse(err),
274 ));
275 }
276 };
277
278 let Some((get_name, get_args, _)) = responses.next() else {
279 return JmapCoroutineState::Complete(Err(
280 JmapMailboxQueryError::MissingGetResponse,
281 ));
282 };
283
284 if get_name == "error" {
285 let err = serde_json::from_value::<JmapMethodError>(get_args)
286 .unwrap_or(JmapMethodError::Unknown);
287 return JmapCoroutineState::Complete(Err(JmapMailboxQueryError::GetMethod(
288 err,
289 )));
290 }
291
292 match serde_json::from_value::<MailboxGetResponse>(get_args) {
293 Ok(r) => JmapCoroutineState::Complete(Ok(JmapMailboxQueryOutput {
294 mailboxes: r.list,
295 total: query_response.total,
296 position: query_response.position,
297 query_state: query_response.query_state,
298 keep_alive,
299 })),
300 Err(err) => JmapCoroutineState::Complete(Err(
301 JmapMailboxQueryError::ParseGetResponse(err),
302 )),
303 }
304 }
305 }
306 }
307}
308
309enum State {
310 Send(JmapSend),
311}
312
313#[derive(Serialize)]
314#[serde(rename_all = "camelCase")]
315struct MailboxQueryArgs<'a> {
316 account_id: &'a str,
317 #[serde(skip_serializing_if = "Option::is_none")]
318 filter: Option<&'a JmapMailboxFilter>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 sort: Option<&'a [JmapMailboxSortComparator]>,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 position: Option<u64>,
323 #[serde(skip_serializing_if = "Option::is_none")]
324 limit: Option<u64>,
325 calculate_total: bool,
326}
327
328#[derive(Serialize)]
329#[serde(rename_all = "camelCase")]
330struct MailboxGetByRefArgs<'a> {
331 account_id: &'a str,
332 #[serde(rename = "#ids")]
333 ids_ref: JmapResultReference<'a>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 properties: Option<&'a [JmapMailboxProperty]>,
336}
337
338#[derive(Deserialize)]
339#[serde(rename_all = "camelCase")]
340struct MailboxQueryResponse {
341 query_state: String,
342 #[serde(default)]
343 total: Option<u64>,
344 #[serde(default)]
345 position: u64,
346}
347
348#[derive(Deserialize)]
349#[serde(rename_all = "camelCase")]
350struct MailboxGetResponse {
351 list: Vec<JmapMailbox>,
352}