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