1use core::fmt;
58
59use alloc::{string::String, vec, vec::Vec};
60
61use log::trace;
62use secrecy::SecretString;
63use serde::{Deserialize, Serialize};
64use thiserror::Error;
65
66use crate::{
67 coroutine::*,
68 jmap_try,
69 rfc8620::{
70 CORE_CAPABILITY, JmapBatch, JmapMethodError, JmapResultReference, JmapSession, send::*,
71 },
72 rfc8621::{
73 MAIL_CAPABILITY,
74 mailbox::{JmapMailbox, JmapMailboxFilter, JmapMailboxProperty, JmapMailboxSortComparator},
75 },
76};
77
78#[derive(Debug, Error)]
80pub enum JmapMailboxQueryError {
81 #[error("JMAP Mailbox/query failed: missing Mailbox/query response in method_responses")]
82 MissingQueryResponse,
83 #[error("JMAP Mailbox/query failed: missing Mailbox/get response in method_responses")]
84 MissingGetResponse,
85 #[error("JMAP Mailbox/query failed: {0}")]
86 Send(#[from] JmapSendError),
87 #[error("JMAP Mailbox/query failed: serialize args: {0}")]
88 SerializeArgs(#[source] serde_json::Error),
89 #[error("JMAP Mailbox/query failed: parse Mailbox/query response: {0}")]
90 ParseQueryResponse(#[source] serde_json::Error),
91 #[error("JMAP Mailbox/query failed: parse Mailbox/get response: {0}")]
92 ParseGetResponse(#[source] serde_json::Error),
93 #[error("JMAP Mailbox/query failed: Mailbox/query: {0}")]
94 QueryMethod(JmapMethodError),
95 #[error("JMAP Mailbox/query failed: Mailbox/get: {0}")]
96 GetMethod(JmapMethodError),
97}
98
99#[derive(Clone, Debug, Default)]
101pub struct JmapMailboxQueryOptions {
102 pub filter: Option<JmapMailboxFilter>,
104 pub sort: Option<Vec<JmapMailboxSortComparator>>,
106 pub position: Option<u64>,
108 pub limit: Option<u64>,
110 pub properties: Option<Vec<JmapMailboxProperty>>,
112}
113
114#[derive(Clone, Debug)]
116pub struct JmapMailboxQueryOutput {
117 pub mailboxes: Vec<JmapMailbox>,
118 pub total: Option<u64>,
119 pub position: u64,
120 pub query_state: String,
121 pub keep_alive: bool,
122}
123
124pub struct JmapMailboxQuery {
127 state: State,
128}
129
130impl JmapMailboxQuery {
131 pub fn new(
132 session: &JmapSession,
133 http_auth: &SecretString,
134 opts: JmapMailboxQueryOptions,
135 ) -> Result<Self, JmapMailboxQueryError> {
136 let account_id = session
137 .primary_accounts
138 .get(MAIL_CAPABILITY)
139 .cloned()
140 .unwrap_or_default();
141 let api_url = &session.api_url;
142
143 let query_args = MailboxQueryArgs {
144 account_id: &account_id,
145 filter: opts.filter.as_ref(),
146 sort: opts.sort.as_deref(),
147 position: opts.position,
148 limit: opts.limit,
149 calculate_total: true,
150 };
151
152 let mut batch = JmapBatch::new();
153 let query_id = batch.add(
154 "Mailbox/query",
155 serde_json::to_value(&query_args).map_err(JmapMailboxQueryError::SerializeArgs)?,
156 );
157
158 let get_args = MailboxGetByRefArgs {
159 account_id: &account_id,
160 ids_ref: JmapResultReference {
161 result_of: &query_id,
162 name: "Mailbox/query",
163 path: "/ids",
164 },
165 properties: opts.properties.as_deref(),
166 };
167
168 batch.add(
169 "Mailbox/get",
170 serde_json::to_value(&get_args).map_err(JmapMailboxQueryError::SerializeArgs)?,
171 );
172
173 let request = batch.into_request(vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()]);
174
175 Ok(Self {
176 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
177 })
178 }
179}
180
181impl JmapCoroutine for JmapMailboxQuery {
182 type Yield = JmapYield;
183 type Return = Result<JmapMailboxQueryOutput, JmapMailboxQueryError>;
184
185 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
186 trace!("Mailbox/query: {}", self.state);
187 match &mut self.state {
188 State::Send(send) => {
189 let JmapSendOutput {
190 response,
191 keep_alive,
192 } = jmap_try!(send, arg);
193
194 let mut responses = response.method_responses.into_iter();
195
196 let Some((query_name, query_args, _)) = responses.next() else {
197 return JmapCoroutineState::Complete(Err(
198 JmapMailboxQueryError::MissingQueryResponse,
199 ));
200 };
201
202 if query_name == "error" {
203 let err = serde_json::from_value::<JmapMethodError>(query_args)
204 .unwrap_or(JmapMethodError::Unknown);
205 return JmapCoroutineState::Complete(Err(JmapMailboxQueryError::QueryMethod(
206 err,
207 )));
208 }
209
210 let query_response =
211 match serde_json::from_value::<MailboxQueryResponse>(query_args) {
212 Ok(r) => r,
213 Err(err) => {
214 return JmapCoroutineState::Complete(Err(
215 JmapMailboxQueryError::ParseQueryResponse(err),
216 ));
217 }
218 };
219
220 let Some((get_name, get_args, _)) = responses.next() else {
221 return JmapCoroutineState::Complete(Err(
222 JmapMailboxQueryError::MissingGetResponse,
223 ));
224 };
225
226 if get_name == "error" {
227 let err = serde_json::from_value::<JmapMethodError>(get_args)
228 .unwrap_or(JmapMethodError::Unknown);
229 return JmapCoroutineState::Complete(Err(JmapMailboxQueryError::GetMethod(
230 err,
231 )));
232 }
233
234 match serde_json::from_value::<MailboxGetResponse>(get_args) {
235 Ok(r) => JmapCoroutineState::Complete(Ok(JmapMailboxQueryOutput {
236 mailboxes: r.list,
237 total: query_response.total,
238 position: query_response.position,
239 query_state: query_response.query_state,
240 keep_alive,
241 })),
242 Err(err) => JmapCoroutineState::Complete(Err(
243 JmapMailboxQueryError::ParseGetResponse(err),
244 )),
245 }
246 }
247 }
248 }
249}
250
251enum State {
252 Send(JmapSend),
253}
254
255impl fmt::Display for State {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 match self {
258 Self::Send(_) => f.write_str("send"),
259 }
260 }
261}
262
263#[derive(Serialize)]
264#[serde(rename_all = "camelCase")]
265struct MailboxQueryArgs<'a> {
266 account_id: &'a str,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 filter: Option<&'a JmapMailboxFilter>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 sort: Option<&'a [JmapMailboxSortComparator]>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 position: Option<u64>,
273 #[serde(skip_serializing_if = "Option::is_none")]
274 limit: Option<u64>,
275 calculate_total: bool,
276}
277
278#[derive(Serialize)]
279#[serde(rename_all = "camelCase")]
280struct MailboxGetByRefArgs<'a> {
281 account_id: &'a str,
282 #[serde(rename = "#ids")]
283 ids_ref: JmapResultReference<'a>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 properties: Option<&'a [JmapMailboxProperty]>,
286}
287
288#[derive(Deserialize)]
289#[serde(rename_all = "camelCase")]
290struct MailboxQueryResponse {
291 query_state: String,
292 #[serde(default)]
293 total: Option<u64>,
294 #[serde(default)]
295 position: u64,
296}
297
298#[derive(Deserialize)]
299#[serde(rename_all = "camelCase")]
300struct MailboxGetResponse {
301 list: Vec<JmapMailbox>,
302}