io_jmap/rfc8621/email/
parse.rs1use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
62
63use secrecy::SecretString;
64use serde::{Deserialize, Serialize};
65use thiserror::Error;
66
67use crate::{
68 coroutine::*,
69 jmap_try,
70 rfc8620::{
71 JMAP_CORE_CAPABILITY, error::JmapMethodError, request::JmapBatch, send::*,
72 session::JmapSession,
73 },
74 rfc8621::{
75 JMAP_MAIL_CAPABILITY,
76 email::{JmapEmail, JmapEmailProperty},
77 },
78};
79
80#[derive(Debug, Error)]
82pub enum JmapEmailParseError {
83 #[error("JMAP Email/parse failed: missing response in method_responses")]
85 MissingResponse,
86 #[error("JMAP Email/parse failed: {0}")]
88 Send(#[from] JmapSendError),
89 #[error("JMAP Email/parse failed: serialize args: {0}")]
91 SerializeArgs(#[source] serde_json::Error),
92 #[error("JMAP Email/parse failed: parse response: {0}")]
94 ParseResponse(#[source] serde_json::Error),
95 #[error("JMAP Email/parse failed: {0}")]
97 Method(#[from] JmapMethodError),
98}
99
100#[derive(Clone, Debug, Default)]
102pub struct JmapEmailParseOptions {
103 pub properties: Option<Vec<JmapEmailProperty>>,
105}
106
107#[derive(Clone, Debug)]
109pub struct JmapEmailParseOutput {
110 pub parsed: BTreeMap<String, JmapEmail>,
112 pub not_parsable: Vec<String>,
114 pub not_found: Vec<String>,
116 pub keep_alive: bool,
118}
119
120pub struct JmapEmailParse {
122 state: State,
123}
124
125impl JmapEmailParse {
126 pub fn new(
128 session: &JmapSession,
129 http_auth: &SecretString,
130 blob_ids: Vec<String>,
131 opts: JmapEmailParseOptions,
132 ) -> Result<Self, JmapEmailParseError> {
133 let account_id = session
134 .primary_accounts
135 .get(JMAP_MAIL_CAPABILITY)
136 .cloned()
137 .unwrap_or_default();
138 let api_url = &session.api_url;
139
140 let parse_args = EmailParseArgs {
141 account_id: &account_id,
142 blob_ids: &blob_ids,
143 properties: opts.properties.as_deref(),
144 fetch_text_body_values: true,
145 fetch_html_body_values: true,
146 max_body_value_bytes: None,
147 };
148
149 let mut batch = JmapBatch::new();
150 batch.add(
151 "Email/parse",
152 serde_json::to_value(&parse_args).map_err(JmapEmailParseError::SerializeArgs)?,
153 );
154 let request = batch.into_request(vec![
155 JMAP_CORE_CAPABILITY.into(),
156 JMAP_MAIL_CAPABILITY.into(),
157 ]);
158
159 Ok(Self {
160 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
161 })
162 }
163}
164
165impl JmapCoroutine for JmapEmailParse {
166 type Yield = JmapYield;
167 type Return = Result<JmapEmailParseOutput, JmapEmailParseError>;
168
169 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
170 match &mut self.state {
171 State::Send(send) => {
172 let JmapSendOutput {
173 response,
174 keep_alive,
175 } = jmap_try!(send, arg);
176
177 let Some((name, args, _)) = response.method_responses.into_iter().next() else {
178 return JmapCoroutineState::Complete(Err(JmapEmailParseError::MissingResponse));
179 };
180
181 if name == "error" {
182 let err = serde_json::from_value::<JmapMethodError>(args)
183 .unwrap_or(JmapMethodError::Unknown);
184 return JmapCoroutineState::Complete(Err(err.into()));
185 }
186
187 match serde_json::from_value::<EmailParseResponse>(args) {
188 Ok(r) => JmapCoroutineState::Complete(Ok(JmapEmailParseOutput {
189 parsed: r.parsed,
190 not_parsable: r.not_parsable.unwrap_or_default(),
191 not_found: r.not_found.unwrap_or_default(),
192 keep_alive,
193 })),
194 Err(err) => {
195 JmapCoroutineState::Complete(Err(JmapEmailParseError::ParseResponse(err)))
196 }
197 }
198 }
199 }
200 }
201}
202
203enum State {
204 Send(JmapSend),
205}
206
207#[derive(Serialize)]
208#[serde(rename_all = "camelCase")]
209struct EmailParseArgs<'a> {
210 account_id: &'a str,
211 blob_ids: &'a [String],
212 #[serde(skip_serializing_if = "Option::is_none")]
213 properties: Option<&'a [JmapEmailProperty]>,
214 fetch_text_body_values: bool,
215 #[serde(rename = "fetchHTMLBodyValues")]
216 fetch_html_body_values: bool,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 max_body_value_bytes: Option<u64>,
219}
220
221#[derive(Deserialize)]
222#[serde(rename_all = "camelCase")]
223struct EmailParseResponse {
224 #[serde(default)]
225 parsed: BTreeMap<String, JmapEmail>,
226 not_parsable: Option<Vec<String>>,
227 not_found: Option<Vec<String>>,
228}