io_jmap/rfc8621/email/
import.rs1use core::fmt;
68
69use alloc::{collections::BTreeMap, string::String, vec};
70
71use log::trace;
72use secrecy::SecretString;
73use serde::{Deserialize, Serialize};
74use thiserror::Error;
75
76use crate::{
77 coroutine::*,
78 jmap_try,
79 rfc8620::{CORE_CAPABILITY, JmapBatch, JmapMethodError, JmapSession, send::*},
80 rfc8621::{
81 MAIL_CAPABILITY,
82 email::{JmapEmail, JmapEmailImportArgs, JmapEmailImportItemError},
83 },
84};
85
86#[derive(Debug, Error)]
88pub enum JmapEmailImportError {
89 #[error("JMAP Email/import failed: missing response in method_responses")]
90 MissingResponse,
91 #[error("JMAP Email/import failed: {0}")]
92 Send(#[from] JmapSendError),
93 #[error("JMAP Email/import failed: serialize args: {0}")]
94 SerializeArgs(#[source] serde_json::Error),
95 #[error("JMAP Email/import failed: parse response: {0}")]
96 ParseResponse(#[source] serde_json::Error),
97 #[error("JMAP Email/import failed: {0}")]
98 Method(#[from] JmapMethodError),
99}
100
101#[derive(Clone, Debug)]
103pub struct JmapEmailImportOutput {
104 pub new_state: String,
105 pub created: BTreeMap<String, JmapEmail>,
106 pub not_created: BTreeMap<String, JmapEmailImportItemError>,
107 pub keep_alive: bool,
108}
109
110pub struct JmapEmailImport {
112 state: State,
113}
114
115impl JmapEmailImport {
116 pub fn new(
118 session: &JmapSession,
119 http_auth: &SecretString,
120 emails: BTreeMap<String, JmapEmailImportArgs>,
121 ) -> Result<Self, JmapEmailImportError> {
122 let account_id = session
123 .primary_accounts
124 .get(MAIL_CAPABILITY)
125 .cloned()
126 .unwrap_or_default();
127 let api_url = &session.api_url;
128
129 let args = serde_json::to_value(EmailImportArgs { account_id, emails })
130 .map_err(JmapEmailImportError::SerializeArgs)?;
131
132 let mut batch = JmapBatch::new();
133 batch.add("Email/import", args);
134 let request = batch.into_request(vec![CORE_CAPABILITY.into(), MAIL_CAPABILITY.into()]);
135
136 Ok(Self {
137 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
138 })
139 }
140}
141
142impl JmapCoroutine for JmapEmailImport {
143 type Yield = JmapYield;
144 type Return = Result<JmapEmailImportOutput, JmapEmailImportError>;
145
146 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
147 trace!("Email/import: {}", self.state);
148 match &mut self.state {
149 State::Send(send) => {
150 let JmapSendOutput {
151 response,
152 keep_alive,
153 } = jmap_try!(send, arg);
154
155 let Some((name, args, _)) = response.method_responses.into_iter().next() else {
156 return JmapCoroutineState::Complete(Err(
157 JmapEmailImportError::MissingResponse,
158 ));
159 };
160
161 if name == "error" {
162 let err = serde_json::from_value::<JmapMethodError>(args)
163 .unwrap_or(JmapMethodError::Unknown);
164 return JmapCoroutineState::Complete(Err(err.into()));
165 }
166
167 match serde_json::from_value::<EmailImportResponse>(args) {
168 Ok(r) => JmapCoroutineState::Complete(Ok(JmapEmailImportOutput {
169 new_state: r.new_state,
170 created: r.created,
171 not_created: r.not_created,
172 keep_alive,
173 })),
174 Err(err) => {
175 JmapCoroutineState::Complete(Err(JmapEmailImportError::ParseResponse(err)))
176 }
177 }
178 }
179 }
180 }
181}
182
183enum State {
184 Send(JmapSend),
185}
186
187impl fmt::Display for State {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 match self {
190 Self::Send(_) => f.write_str("send"),
191 }
192 }
193}
194
195#[derive(Serialize)]
196#[serde(rename_all = "camelCase")]
197struct EmailImportArgs {
198 account_id: String,
199 emails: BTreeMap<String, JmapEmailImportArgs>,
200}
201
202#[derive(Deserialize)]
203#[serde(rename_all = "camelCase")]
204struct EmailImportResponse {
205 new_state: String,
206 #[serde(default)]
207 created: BTreeMap<String, JmapEmail>,
208 #[serde(default)]
209 not_created: BTreeMap<String, JmapEmailImportItemError>,
210}