io_jmap/rfc8621/identity/
set.rs1use core::fmt;
59
60use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
61
62use log::trace;
63use secrecy::SecretString;
64use serde::{Deserialize, Serialize};
65use thiserror::Error;
66
67use crate::{
68 coroutine::*,
69 jmap_try,
70 rfc8620::{CORE_CAPABILITY, JmapBatch, JmapMethodError, JmapSession, send::*},
71 rfc8621::{
72 MAIL_CAPABILITY,
73 email_submission::SUBMISSION_CAPABILITY,
74 identity::{
75 JmapIdentity, JmapIdentityCreate, JmapIdentitySetItemError, JmapIdentityUpdate,
76 },
77 },
78};
79
80#[derive(Debug, Error)]
82pub enum JmapIdentitySetError {
83 #[error("JMAP Identity/set failed: missing response in method_responses")]
84 MissingResponse,
85 #[error("JMAP Identity/set failed: {0}")]
86 Send(#[from] JmapSendError),
87 #[error("JMAP Identity/set failed: serialize args: {0}")]
88 SerializeArgs(#[source] serde_json::Error),
89 #[error("JMAP Identity/set failed: parse response: {0}")]
90 ParseResponse(#[source] serde_json::Error),
91 #[error("JMAP Identity/set failed: {0}")]
92 Method(#[from] JmapMethodError),
93}
94
95#[derive(Clone, Debug, Default)]
97pub struct JmapIdentitySetArgs {
98 pub create: BTreeMap<String, JmapIdentityCreate>,
99 pub update: BTreeMap<String, JmapIdentityUpdate>,
100 pub destroy: Vec<String>,
101}
102
103impl JmapIdentitySetArgs {
104 pub fn create(
105 &mut self,
106 client_id: impl Into<String>,
107 identity: JmapIdentityCreate,
108 ) -> &mut Self {
109 self.create.insert(client_id.into(), identity);
110 self
111 }
112
113 pub fn update(&mut self, id: impl Into<String>, patch: JmapIdentityUpdate) -> &mut Self {
114 self.update.insert(id.into(), patch);
115 self
116 }
117
118 pub fn destroy(&mut self, id: impl Into<String>) -> &mut Self {
119 self.destroy.push(id.into());
120 self
121 }
122}
123
124#[derive(Clone, Debug)]
126pub struct JmapIdentitySetOutput {
127 pub new_state: String,
128 pub created: BTreeMap<String, JmapIdentity>,
129 pub updated: BTreeMap<String, Option<JmapIdentity>>,
130 pub destroyed: Vec<String>,
131 pub not_created: BTreeMap<String, JmapIdentitySetItemError>,
132 pub not_updated: BTreeMap<String, JmapIdentitySetItemError>,
133 pub not_destroyed: BTreeMap<String, JmapIdentitySetItemError>,
134 pub keep_alive: bool,
135}
136
137pub struct JmapIdentitySet {
139 state: State,
140}
141
142impl JmapIdentitySet {
143 pub fn new(
144 session: &JmapSession,
145 http_auth: &SecretString,
146 args: JmapIdentitySetArgs,
147 ) -> Result<Self, JmapIdentitySetError> {
148 let account_id = session
149 .primary_accounts
150 .get(MAIL_CAPABILITY)
151 .cloned()
152 .unwrap_or_default();
153 let api_url = &session.api_url;
154
155 let json_args = serde_json::to_value(IdentitySetRequest {
156 account_id,
157 create: args.create,
158 update: args.update,
159 destroy: args.destroy,
160 })
161 .map_err(JmapIdentitySetError::SerializeArgs)?;
162
163 let mut batch = JmapBatch::new();
164 batch.add("Identity/set", json_args);
165 let request = batch.into_request(vec![
166 CORE_CAPABILITY.into(),
167 MAIL_CAPABILITY.into(),
168 SUBMISSION_CAPABILITY.into(),
169 ]);
170
171 Ok(Self {
172 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
173 })
174 }
175}
176
177impl JmapCoroutine for JmapIdentitySet {
178 type Yield = JmapYield;
179 type Return = Result<JmapIdentitySetOutput, JmapIdentitySetError>;
180
181 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
182 trace!("Identity/set: {}", self.state);
183 match &mut self.state {
184 State::Send(send) => {
185 let JmapSendOutput {
186 response,
187 keep_alive,
188 } = jmap_try!(send, arg);
189
190 let Some((name, args, _)) = response.method_responses.into_iter().next() else {
191 return JmapCoroutineState::Complete(Err(
192 JmapIdentitySetError::MissingResponse,
193 ));
194 };
195
196 if name == "error" {
197 let err = serde_json::from_value::<JmapMethodError>(args)
198 .unwrap_or(JmapMethodError::Unknown);
199 return JmapCoroutineState::Complete(Err(err.into()));
200 }
201
202 match serde_json::from_value::<IdentitySetResponse>(args) {
203 Ok(r) => JmapCoroutineState::Complete(Ok(JmapIdentitySetOutput {
204 new_state: r.new_state.unwrap_or_default(),
205 created: BTreeMap::new(),
206 updated: BTreeMap::new(),
207 destroyed: r.destroyed.unwrap_or_default(),
208 not_created: r.not_created.unwrap_or_default(),
209 not_updated: r.not_updated.unwrap_or_default(),
210 not_destroyed: r.not_destroyed.unwrap_or_default(),
211 keep_alive,
212 })),
213 Err(err) => {
214 JmapCoroutineState::Complete(Err(JmapIdentitySetError::ParseResponse(err)))
215 }
216 }
217 }
218 }
219 }
220}
221
222enum State {
223 Send(JmapSend),
224}
225
226impl fmt::Display for State {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 match self {
229 Self::Send(_) => f.write_str("send"),
230 }
231 }
232}
233
234#[derive(Serialize)]
235#[serde(rename_all = "camelCase")]
236struct IdentitySetRequest {
237 account_id: String,
238 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
239 create: BTreeMap<String, JmapIdentityCreate>,
240 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
241 update: BTreeMap<String, JmapIdentityUpdate>,
242 #[serde(skip_serializing_if = "Vec::is_empty")]
243 destroy: Vec<String>,
244}
245
246#[derive(Deserialize)]
247#[serde(rename_all = "camelCase")]
248struct IdentitySetResponse {
249 #[serde(default)]
250 new_state: Option<String>,
251 #[serde(default)]
253 #[allow(dead_code)]
254 created: Option<serde_json::Value>,
255 #[serde(default)]
257 #[allow(dead_code)]
258 updated: Option<serde_json::Value>,
259 #[serde(default)]
260 destroyed: Option<Vec<String>>,
261 #[serde(default)]
262 not_created: Option<BTreeMap<String, JmapIdentitySetItemError>>,
263 #[serde(default)]
264 not_updated: Option<BTreeMap<String, JmapIdentitySetItemError>>,
265 #[serde(default)]
266 not_destroyed: Option<BTreeMap<String, JmapIdentitySetItemError>>,
267}