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