io_jmap/rfc8621/identity/
set.rs1use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
59
60use secrecy::SecretString;
61use serde::{Deserialize, Serialize};
62use thiserror::Error;
63
64use crate::{
65 coroutine::*,
66 jmap_try,
67 rfc8620::{
68 JMAP_CORE_CAPABILITY, error::JmapMethodError, request::JmapBatch, send::*,
69 session::JmapSession,
70 },
71 rfc8621::{
72 JMAP_MAIL_CAPABILITY, email::JmapEmailAddress,
73 email_submission::JMAP_SUBMISSION_CAPABILITY, identity::JmapIdentity,
74 },
75};
76
77#[derive(Clone, Debug, Default, Serialize)]
79#[serde(rename_all = "camelCase")]
80pub struct JmapIdentityCreate {
81 pub name: String,
83 pub email: String,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub reply_to: Option<Vec<JmapEmailAddress>>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub bcc: Option<Vec<JmapEmailAddress>>,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub text_signature: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub html_signature: Option<String>,
97}
98
99#[derive(Clone, Debug, Default, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct JmapIdentityUpdate {
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub name: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub reply_to: Option<Vec<JmapEmailAddress>>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub bcc: Option<Vec<JmapEmailAddress>>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub text_signature: Option<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub html_signature: Option<String>,
120}
121
122#[derive(Clone, Debug, Deserialize)]
124#[serde(tag = "type", rename_all = "camelCase")]
125pub enum JmapIdentitySetItemError {
126 NotFound {
128 description: Option<String>,
130 },
131 InvalidPatch {
133 description: Option<String>,
135 },
136 WillDestroy {
139 description: Option<String>,
141 },
142 InvalidProperties {
144 description: Option<String>,
146 #[serde(default)]
148 properties: Vec<String>,
149 },
150 Singleton {
153 description: Option<String>,
155 },
156 #[serde(other)]
158 Unknown,
159}
160
161#[derive(Debug, Error)]
163pub enum JmapIdentitySetError {
164 #[error("JMAP Identity/set failed: missing response in method_responses")]
166 MissingResponse,
167 #[error("JMAP Identity/set failed: {0}")]
169 Send(#[from] JmapSendError),
170 #[error("JMAP Identity/set failed: serialize args: {0}")]
172 SerializeArgs(#[source] serde_json::Error),
173 #[error("JMAP Identity/set failed: parse response: {0}")]
175 ParseResponse(#[source] serde_json::Error),
176 #[error("JMAP Identity/set failed: {0}")]
178 Method(#[from] JmapMethodError),
179}
180
181#[derive(Clone, Debug, Default)]
183pub struct JmapIdentitySetArgs {
184 pub create: BTreeMap<String, JmapIdentityCreate>,
186 pub update: BTreeMap<String, JmapIdentityUpdate>,
188 pub destroy: Vec<String>,
190}
191
192impl JmapIdentitySetArgs {
193 pub fn create(
195 &mut self,
196 client_id: impl Into<String>,
197 identity: JmapIdentityCreate,
198 ) -> &mut Self {
199 self.create.insert(client_id.into(), identity);
200 self
201 }
202
203 pub fn update(&mut self, id: impl Into<String>, patch: JmapIdentityUpdate) -> &mut Self {
205 self.update.insert(id.into(), patch);
206 self
207 }
208
209 pub fn destroy(&mut self, id: impl Into<String>) -> &mut Self {
211 self.destroy.push(id.into());
212 self
213 }
214}
215
216#[derive(Clone, Debug)]
218pub struct JmapIdentitySetOutput {
219 pub new_state: String,
221 pub created: BTreeMap<String, JmapIdentity>,
223 pub updated: BTreeMap<String, Option<JmapIdentity>>,
225 pub destroyed: Vec<String>,
227 pub not_created: BTreeMap<String, JmapIdentitySetItemError>,
229 pub not_updated: BTreeMap<String, JmapIdentitySetItemError>,
231 pub not_destroyed: BTreeMap<String, JmapIdentitySetItemError>,
233 pub keep_alive: bool,
235}
236
237pub struct JmapIdentitySet {
239 state: State,
240}
241
242impl JmapIdentitySet {
243 pub fn new(
245 session: &JmapSession,
246 http_auth: &SecretString,
247 args: JmapIdentitySetArgs,
248 ) -> Result<Self, JmapIdentitySetError> {
249 let account_id = session
250 .primary_accounts
251 .get(JMAP_MAIL_CAPABILITY)
252 .cloned()
253 .unwrap_or_default();
254 let api_url = &session.api_url;
255
256 let json_args = serde_json::to_value(IdentitySetRequest {
257 account_id,
258 create: args.create,
259 update: args.update,
260 destroy: args.destroy,
261 })
262 .map_err(JmapIdentitySetError::SerializeArgs)?;
263
264 let mut batch = JmapBatch::new();
265 batch.add("Identity/set", json_args);
266 let request = batch.into_request(vec![
267 JMAP_CORE_CAPABILITY.into(),
268 JMAP_MAIL_CAPABILITY.into(),
269 JMAP_SUBMISSION_CAPABILITY.into(),
270 ]);
271
272 Ok(Self {
273 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
274 })
275 }
276}
277
278impl JmapCoroutine for JmapIdentitySet {
279 type Yield = JmapYield;
280 type Return = Result<JmapIdentitySetOutput, JmapIdentitySetError>;
281
282 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
283 match &mut self.state {
284 State::Send(send) => {
285 let JmapSendOutput {
286 response,
287 keep_alive,
288 } = jmap_try!(send, arg);
289
290 let Some((name, args, _)) = response.method_responses.into_iter().next() else {
291 return JmapCoroutineState::Complete(Err(
292 JmapIdentitySetError::MissingResponse,
293 ));
294 };
295
296 if name == "error" {
297 let err = serde_json::from_value::<JmapMethodError>(args)
298 .unwrap_or(JmapMethodError::Unknown);
299 return JmapCoroutineState::Complete(Err(err.into()));
300 }
301
302 match serde_json::from_value::<IdentitySetResponse>(args) {
303 Ok(r) => JmapCoroutineState::Complete(Ok(JmapIdentitySetOutput {
304 new_state: r.new_state.unwrap_or_default(),
305 created: BTreeMap::new(),
306 updated: BTreeMap::new(),
307 destroyed: r.destroyed.unwrap_or_default(),
308 not_created: r.not_created.unwrap_or_default(),
309 not_updated: r.not_updated.unwrap_or_default(),
310 not_destroyed: r.not_destroyed.unwrap_or_default(),
311 keep_alive,
312 })),
313 Err(err) => {
314 JmapCoroutineState::Complete(Err(JmapIdentitySetError::ParseResponse(err)))
315 }
316 }
317 }
318 }
319 }
320}
321
322enum State {
323 Send(JmapSend),
324}
325
326#[derive(Serialize)]
327#[serde(rename_all = "camelCase")]
328struct IdentitySetRequest {
329 account_id: String,
330 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
331 create: BTreeMap<String, JmapIdentityCreate>,
332 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
333 update: BTreeMap<String, JmapIdentityUpdate>,
334 #[serde(skip_serializing_if = "Vec::is_empty")]
335 destroy: Vec<String>,
336}
337
338#[derive(Deserialize)]
339#[serde(rename_all = "camelCase")]
340struct IdentitySetResponse {
341 #[serde(default)]
342 new_state: Option<String>,
343 #[serde(default)]
345 #[allow(dead_code)]
346 created: Option<serde_json::Value>,
347 #[serde(default)]
349 #[allow(dead_code)]
350 updated: Option<serde_json::Value>,
351 #[serde(default)]
352 destroyed: Option<Vec<String>>,
353 #[serde(default)]
354 not_created: Option<BTreeMap<String, JmapIdentitySetItemError>>,
355 #[serde(default)]
356 not_updated: Option<BTreeMap<String, JmapIdentitySetItemError>>,
357 #[serde(default)]
358 not_destroyed: Option<BTreeMap<String, JmapIdentitySetItemError>>,
359}