1use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Default, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct JmapEmail {
11 pub id: Option<String>,
12 pub blob_id: Option<String>,
14 pub thread_id: Option<String>,
15 pub mailbox_ids: Option<BTreeMap<String, bool>>,
17 pub keywords: Option<BTreeMap<String, bool>>,
20 pub size: Option<u64>,
22 pub received_at: Option<String>,
24 pub message_id: Option<Vec<String>>,
25 pub in_reply_to: Option<Vec<String>>,
26 pub references: Option<Vec<String>>,
27 pub sender: Option<Vec<JmapEmailAddress>>,
28 pub from: Option<Vec<JmapEmailAddress>>,
29 pub to: Option<Vec<JmapEmailAddress>>,
30 pub cc: Option<Vec<JmapEmailAddress>>,
31 pub bcc: Option<Vec<JmapEmailAddress>>,
32 pub reply_to: Option<Vec<JmapEmailAddress>>,
33 pub subject: Option<String>,
34 pub sent_at: Option<String>,
36 pub body_structure: Option<JmapEmailBodyPart>,
37 pub body_values: Option<BTreeMap<String, JmapEmailBodyValue>>,
39 pub text_body: Option<Vec<JmapEmailBodyPart>>,
40 pub html_body: Option<Vec<JmapEmailBodyPart>>,
41 pub attachments: Option<Vec<JmapEmailBodyPart>>,
42 pub has_attachment: Option<bool>,
43 pub preview: Option<String>,
45 pub headers: Option<Vec<JmapEmailHeader>>,
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct JmapEmailAddress {
53 pub name: Option<String>,
54 pub email: String,
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct JmapEmailHeader {
61 pub name: String,
63 pub value: String,
65}
66
67#[derive(Clone, Debug, Default, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct JmapEmailBodyPart {
71 pub part_id: Option<String>,
72 pub blob_id: Option<String>,
73 pub size: Option<u64>,
74 pub name: Option<String>,
76 pub r#type: Option<String>,
77 pub charset: Option<String>,
78 pub disposition: Option<String>,
80 pub cid: Option<String>,
81 pub language: Option<Vec<String>>,
82 pub location: Option<String>,
83 pub sub_parts: Option<Vec<JmapEmailBodyPart>>,
85 pub headers: Option<Vec<JmapEmailHeader>>,
86}
87
88#[derive(Clone, Debug, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct JmapEmailBodyValue {
92 pub value: String,
93 pub is_encoding_problem: bool,
95 pub is_truncated: bool,
97}
98
99#[derive(Clone, Debug, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub enum JmapEmailProperty {
103 Id,
104 BlobId,
105 ThreadId,
106 MailboxIds,
107 Keywords,
108 Size,
109 ReceivedAt,
110 MessageId,
111 InReplyTo,
112 References,
113 Sender,
114 From,
115 To,
116 Cc,
117 Bcc,
118 ReplyTo,
119 Subject,
120 SentAt,
121 BodyStructure,
122 BodyValues,
123 TextBody,
124 HtmlBody,
125 Attachments,
126 HasAttachment,
127 Preview,
128 Headers,
129}
130
131#[derive(Clone, Debug, Serialize)]
133#[serde(rename_all = "camelCase")]
134pub enum JmapEmailSortProperty {
135 ReceivedAt,
136 SentAt,
137 Size,
138 From,
139 To,
140 Subject,
141 HasAttachment,
142 Keyword,
144 AllInThreadHaveKeyword,
147 SomeInThreadHaveKeyword,
150}
151
152#[derive(Clone, Debug, Default, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct JmapEmailFilter {
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub in_mailbox: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub in_mailbox_other_than: Option<Vec<String>>,
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub before: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub after: Option<String>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub min_size: Option<u64>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub max_size: Option<u64>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub all_in_thread_have_keyword: Option<String>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub some_in_thread_have_keyword: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub none_in_thread_have_keyword: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub has_keyword: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub not_keyword: Option<String>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub has_attachment: Option<bool>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub text: Option<String>,
186 #[serde(skip_serializing_if = "Option::is_none")]
187 pub from: Option<String>,
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub to: Option<String>,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub cc: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub bcc: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub subject: Option<String>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub body: Option<String>,
198}
199
200#[derive(Clone, Debug, Serialize)]
202#[serde(rename_all = "camelCase")]
203pub struct JmapEmailComparator {
204 pub property: JmapEmailSortProperty,
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub is_ascending: Option<bool>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub collation: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
214 pub keyword: Option<String>,
215}
216
217impl JmapEmailComparator {
218 pub fn received_at_desc() -> Self {
220 Self {
221 property: JmapEmailSortProperty::ReceivedAt,
222 is_ascending: Some(false),
223 collation: None,
224 keyword: None,
225 }
226 }
227}
228
229#[derive(Clone, Debug)]
232pub enum JmapEmailPatchOp {
233 SetKeyword(String),
235 UnsetKeyword(String),
237 ReplaceKeywords(BTreeMap<String, bool>),
239 AddToMailbox(String),
241 RemoveFromMailbox(String),
243 ReplaceMailboxIds(BTreeMap<String, bool>),
245}
246
247#[derive(Clone, Debug, Default)]
251pub struct JmapEmailPatch(pub Vec<JmapEmailPatchOp>);
252
253impl JmapEmailPatch {
254 pub fn set_keyword(mut self, keyword: impl Into<String>) -> Self {
255 self.0.push(JmapEmailPatchOp::SetKeyword(keyword.into()));
256 self
257 }
258
259 pub fn unset_keyword(mut self, keyword: impl Into<String>) -> Self {
260 self.0.push(JmapEmailPatchOp::UnsetKeyword(keyword.into()));
261 self
262 }
263
264 pub fn replace_keywords(mut self, keywords: BTreeMap<String, bool>) -> Self {
265 self.0.push(JmapEmailPatchOp::ReplaceKeywords(keywords));
266 self
267 }
268
269 pub fn add_to_mailbox(mut self, id: impl Into<String>) -> Self {
270 self.0.push(JmapEmailPatchOp::AddToMailbox(id.into()));
271 self
272 }
273
274 pub fn remove_from_mailbox(mut self, id: impl Into<String>) -> Self {
275 self.0.push(JmapEmailPatchOp::RemoveFromMailbox(id.into()));
276 self
277 }
278
279 pub fn replace_mailbox_ids(mut self, ids: BTreeMap<String, bool>) -> Self {
280 self.0.push(JmapEmailPatchOp::ReplaceMailboxIds(ids));
281 self
282 }
283}
284
285impl Serialize for JmapEmailPatch {
286 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
287 use serde::ser::SerializeMap;
288 let mut map = s.serialize_map(Some(self.0.len()))?;
289 for op in &self.0 {
290 match op {
291 JmapEmailPatchOp::SetKeyword(kw) => {
292 map.serialize_entry(&format!("keywords/{kw}"), &true)?
293 }
294 JmapEmailPatchOp::UnsetKeyword(kw) => {
295 map.serialize_entry(&format!("keywords/{kw}"), &Option::<bool>::None)?
296 }
297 JmapEmailPatchOp::ReplaceKeywords(kws) => map.serialize_entry("keywords", kws)?,
298 JmapEmailPatchOp::AddToMailbox(id) => {
299 map.serialize_entry(&format!("mailboxIds/{id}"), &true)?
300 }
301 JmapEmailPatchOp::RemoveFromMailbox(id) => {
302 map.serialize_entry(&format!("mailboxIds/{id}"), &Option::<bool>::None)?
303 }
304 JmapEmailPatchOp::ReplaceMailboxIds(ids) => {
305 map.serialize_entry("mailboxIds", ids)?
306 }
307 }
308 }
309 map.end()
310 }
311}
312
313#[derive(Clone, Debug, Serialize)]
315#[serde(rename_all = "camelCase")]
316pub struct JmapEmailImportArgs {
317 pub blob_id: String,
319 pub mailbox_ids: BTreeMap<String, bool>,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub keywords: Option<BTreeMap<String, bool>>,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub received_at: Option<String>,
326}
327
328#[derive(Clone, Debug, Serialize)]
330#[serde(rename_all = "camelCase")]
331pub struct JmapEmailCopyArgs {
332 pub id: String,
334 pub mailbox_ids: BTreeMap<String, bool>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub keywords: Option<BTreeMap<String, bool>>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub received_at: Option<String>,
342}
343
344#[derive(Clone, Debug, Deserialize)]
346#[serde(tag = "type", rename_all = "camelCase")]
347pub enum JmapEmailSetItemError {
348 TooManyKeywords { description: Option<String> },
350 TooManyMailboxes { description: Option<String> },
352 BlobNotFound { description: Option<String> },
354 NotFound { description: Option<String> },
356 InvalidPatch { description: Option<String> },
358 WillDestroy { description: Option<String> },
361 InvalidProperties {
363 description: Option<String>,
364 #[serde(default)]
365 properties: Vec<String>,
366 },
367 Singleton { description: Option<String> },
370 #[serde(other)]
372 Unknown,
373}
374
375#[derive(Clone, Debug, Deserialize)]
377#[serde(tag = "type", rename_all = "camelCase")]
378pub enum JmapEmailImportItemError {
379 InvalidEmail { description: Option<String> },
381 NotFound { description: Option<String> },
383 InvalidProperties {
385 description: Option<String>,
386 #[serde(default)]
387 properties: Vec<String>,
388 },
389 #[serde(other)]
391 Unknown,
392}
393
394#[derive(Clone, Debug, Deserialize)]
396#[serde(tag = "type", rename_all = "camelCase")]
397pub enum JmapEmailCopyItemError {
398 AlreadyExists { description: Option<String> },
400 NotFound { description: Option<String> },
402 InvalidProperties {
404 description: Option<String>,
405 #[serde(default)]
406 properties: Vec<String>,
407 },
408 #[serde(other)]
410 Unknown,
411}