io_jmap/rfc8621/email_submission/
query.rs1use alloc::{string::String, vec, vec::Vec};
63
64use secrecy::SecretString;
65use serde::{Deserialize, Serialize};
66use thiserror::Error;
67
68use crate::{
69 coroutine::*,
70 jmap_try,
71 rfc8620::{
72 JMAP_CORE_CAPABILITY, error::JmapMethodError, request::JmapBatch,
73 request::JmapResultReference, send::*, session::JmapSession,
74 },
75 rfc8621::{
76 JMAP_MAIL_CAPABILITY,
77 email_submission::{JMAP_SUBMISSION_CAPABILITY, JmapEmailSubmission, JmapUndoStatus},
78 },
79};
80
81#[derive(Clone, Debug, Default, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct JmapEmailSubmissionFilter {
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub identity_ids: Option<Vec<String>>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub email_ids: Option<Vec<String>>,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub thread_ids: Option<Vec<String>>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub undo_status: Option<JmapUndoStatus>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub before: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub after: Option<String>,
103}
104
105#[derive(Clone, Debug, Serialize)]
107#[serde(rename_all = "camelCase")]
108pub enum JmapEmailSubmissionSortProperty {
109 EmailId,
111 ThreadId,
113 SentAt,
115}
116
117#[derive(Clone, Debug, Serialize)]
119#[serde(rename_all = "camelCase")]
120pub struct JmapEmailSubmissionComparator {
121 pub property: JmapEmailSubmissionSortProperty,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub is_ascending: Option<bool>,
126}
127
128#[derive(Debug, Error)]
130pub enum JmapEmailSubmissionQueryError {
131 #[error(
133 "JMAP EmailSubmission/query failed: missing EmailSubmission/query response in method_responses"
134 )]
135 MissingQueryResponse,
136 #[error(
138 "JMAP EmailSubmission/query failed: missing EmailSubmission/get response in method_responses"
139 )]
140 MissingGetResponse,
141 #[error("JMAP EmailSubmission/query failed: {0}")]
143 Send(#[from] JmapSendError),
144 #[error("JMAP EmailSubmission/query failed: serialize args: {0}")]
146 SerializeArgs(#[source] serde_json::Error),
147 #[error("JMAP EmailSubmission/query failed: parse EmailSubmission/query response: {0}")]
149 ParseQueryResponse(#[source] serde_json::Error),
150 #[error("JMAP EmailSubmission/query failed: parse EmailSubmission/get response: {0}")]
152 ParseGetResponse(#[source] serde_json::Error),
153 #[error("JMAP EmailSubmission/query failed: EmailSubmission/query: {0}")]
155 QueryMethod(JmapMethodError),
156 #[error("JMAP EmailSubmission/query failed: EmailSubmission/get: {0}")]
158 GetMethod(JmapMethodError),
159}
160
161#[derive(Clone, Debug, Default)]
163pub struct JmapEmailSubmissionQueryOptions {
164 pub filter: Option<JmapEmailSubmissionFilter>,
166 pub sort: Option<Vec<JmapEmailSubmissionComparator>>,
168 pub position: Option<u64>,
170 pub limit: Option<u64>,
172}
173
174#[derive(Clone, Debug)]
176pub struct JmapEmailSubmissionQueryOutput {
177 pub submissions: Vec<JmapEmailSubmission>,
179 pub total: Option<u64>,
181 pub position: u64,
183 pub query_state: String,
185 pub keep_alive: bool,
187}
188
189pub struct JmapEmailSubmissionQuery {
192 state: State,
193}
194
195impl JmapEmailSubmissionQuery {
196 pub fn new(
198 session: &JmapSession,
199 http_auth: &SecretString,
200 opts: JmapEmailSubmissionQueryOptions,
201 ) -> Result<Self, JmapEmailSubmissionQueryError> {
202 let account_id = session
203 .primary_accounts
204 .get(JMAP_MAIL_CAPABILITY)
205 .cloned()
206 .unwrap_or_default();
207 let api_url = &session.api_url;
208
209 let query_args = SubmissionQueryArgs {
210 account_id: &account_id,
211 filter: opts.filter.as_ref(),
212 sort: opts.sort.as_deref(),
213 position: opts.position,
214 limit: opts.limit,
215 calculate_total: true,
216 };
217
218 let mut batch = JmapBatch::new();
219 let query_id = batch.add(
220 "EmailSubmission/query",
221 serde_json::to_value(&query_args)
222 .map_err(JmapEmailSubmissionQueryError::SerializeArgs)?,
223 );
224
225 let get_args = SubmissionGetByRefArgs {
226 account_id: &account_id,
227 ids_ref: JmapResultReference {
228 result_of: &query_id,
229 name: "EmailSubmission/query",
230 path: "/ids",
231 },
232 };
233
234 batch.add(
235 "EmailSubmission/get",
236 serde_json::to_value(&get_args)
237 .map_err(JmapEmailSubmissionQueryError::SerializeArgs)?,
238 );
239
240 let request = batch.into_request(vec![
241 JMAP_CORE_CAPABILITY.into(),
242 JMAP_MAIL_CAPABILITY.into(),
243 JMAP_SUBMISSION_CAPABILITY.into(),
244 ]);
245
246 Ok(Self {
247 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
248 })
249 }
250}
251
252impl JmapCoroutine for JmapEmailSubmissionQuery {
253 type Yield = JmapYield;
254 type Return = Result<JmapEmailSubmissionQueryOutput, JmapEmailSubmissionQueryError>;
255
256 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
257 match &mut self.state {
258 State::Send(send) => {
259 let JmapSendOutput {
260 response,
261 keep_alive,
262 } = jmap_try!(send, arg);
263
264 let mut responses = response.method_responses.into_iter();
265
266 let Some((query_name, query_args, _)) = responses.next() else {
267 return JmapCoroutineState::Complete(Err(
268 JmapEmailSubmissionQueryError::MissingQueryResponse,
269 ));
270 };
271
272 if query_name == "error" {
273 let err = serde_json::from_value::<JmapMethodError>(query_args)
274 .unwrap_or(JmapMethodError::Unknown);
275 return JmapCoroutineState::Complete(Err(
276 JmapEmailSubmissionQueryError::QueryMethod(err),
277 ));
278 }
279
280 let query_response =
281 match serde_json::from_value::<SubmissionQueryResponse>(query_args) {
282 Ok(r) => r,
283 Err(err) => {
284 return JmapCoroutineState::Complete(Err(
285 JmapEmailSubmissionQueryError::ParseQueryResponse(err),
286 ));
287 }
288 };
289
290 let Some((get_name, get_args, _)) = responses.next() else {
291 return JmapCoroutineState::Complete(Err(
292 JmapEmailSubmissionQueryError::MissingGetResponse,
293 ));
294 };
295
296 if get_name == "error" {
297 let err = serde_json::from_value::<JmapMethodError>(get_args)
298 .unwrap_or(JmapMethodError::Unknown);
299 return JmapCoroutineState::Complete(Err(
300 JmapEmailSubmissionQueryError::GetMethod(err),
301 ));
302 }
303
304 match serde_json::from_value::<SubmissionGetResponse>(get_args) {
305 Ok(r) => JmapCoroutineState::Complete(Ok(JmapEmailSubmissionQueryOutput {
306 submissions: r.list,
307 total: query_response.total,
308 position: query_response.position,
309 query_state: query_response.query_state,
310 keep_alive,
311 })),
312 Err(err) => JmapCoroutineState::Complete(Err(
313 JmapEmailSubmissionQueryError::ParseGetResponse(err),
314 )),
315 }
316 }
317 }
318 }
319}
320
321enum State {
322 Send(JmapSend),
323}
324
325#[derive(Serialize)]
326#[serde(rename_all = "camelCase")]
327struct SubmissionQueryArgs<'a> {
328 account_id: &'a str,
329 #[serde(skip_serializing_if = "Option::is_none")]
330 filter: Option<&'a JmapEmailSubmissionFilter>,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 sort: Option<&'a [JmapEmailSubmissionComparator]>,
333 #[serde(skip_serializing_if = "Option::is_none")]
334 position: Option<u64>,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 limit: Option<u64>,
337 calculate_total: bool,
338}
339
340#[derive(Serialize)]
341#[serde(rename_all = "camelCase")]
342struct SubmissionGetByRefArgs<'a> {
343 account_id: &'a str,
344 #[serde(rename = "#ids")]
345 ids_ref: JmapResultReference<'a>,
346}
347
348#[derive(Deserialize)]
349#[serde(rename_all = "camelCase")]
350struct SubmissionQueryResponse {
351 query_state: String,
352 #[serde(default)]
353 total: Option<u64>,
354 #[serde(default)]
355 position: u64,
356}
357
358#[derive(Deserialize)]
359#[serde(rename_all = "camelCase")]
360struct SubmissionGetResponse {
361 list: Vec<JmapEmailSubmission>,
362}