jmap_client/email/
mod.rs

1/*
2 * Copyright Stalwart Labs LLC See the COPYING
3 * file at the top-level directory of this distribution.
4 *
5 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
8 * option. This file may not be copied, modified, or distributed
9 * except according to those terms.
10 */
11
12pub mod get;
13pub mod helpers;
14pub mod import;
15pub mod parse;
16pub mod query;
17pub mod search_snippet;
18pub mod set;
19use ahash::AHashMap;
20use chrono::{DateTime, Utc};
21use serde::{de::Visitor, Deserialize, Serialize};
22use std::fmt::{self, Display, Formatter};
23
24use crate::{
25    core::{changes::ChangesObject, request::ResultReference, Object},
26    Get, Set,
27};
28
29impl Object for Email<Set> {
30    type Property = Property;
31
32    fn requires_account_id() -> bool {
33        true
34    }
35}
36
37impl Object for Email<Get> {
38    type Property = Property;
39
40    fn requires_account_id() -> bool {
41        true
42    }
43}
44
45impl ChangesObject for Email<Set> {
46    type ChangesResponse = ();
47}
48
49impl ChangesObject for Email<Get> {
50    type ChangesResponse = ();
51}
52
53#[derive(Debug, Default, Clone, Serialize, Deserialize)]
54pub struct Email<State = Get> {
55    #[serde(skip)]
56    _create_id: Option<usize>,
57
58    #[serde(skip)]
59    _state: std::marker::PhantomData<State>,
60
61    #[serde(rename = "id")]
62    #[serde(skip_serializing_if = "Option::is_none")]
63    id: Option<String>,
64
65    #[serde(rename = "blobId")]
66    #[serde(skip_serializing_if = "Option::is_none")]
67    blob_id: Option<String>,
68
69    #[serde(rename = "threadId")]
70    #[serde(skip_serializing_if = "Option::is_none")]
71    thread_id: Option<String>,
72
73    #[serde(rename = "mailboxIds")]
74    #[serde(skip_serializing_if = "Option::is_none")]
75    mailbox_ids: Option<AHashMap<String, bool>>,
76
77    #[serde(rename = "#mailboxIds")]
78    #[serde(skip_deserializing)]
79    #[serde(skip_serializing_if = "Option::is_none")]
80    mailbox_ids_ref: Option<ResultReference>,
81
82    #[serde(rename = "keywords")]
83    #[serde(skip_serializing_if = "Option::is_none")]
84    keywords: Option<AHashMap<String, bool>>,
85
86    #[serde(rename = "size")]
87    #[serde(skip_serializing_if = "Option::is_none")]
88    size: Option<usize>,
89
90    #[serde(rename = "receivedAt")]
91    #[serde(skip_serializing_if = "Option::is_none")]
92    received_at: Option<DateTime<Utc>>,
93
94    #[cfg_attr(
95        not(feature = "debug"),
96        serde(alias = "header:Message-ID:asMessageIds")
97    )]
98    #[serde(rename = "messageId")]
99    #[serde(skip_serializing_if = "Option::is_none")]
100    message_id: Option<Vec<String>>,
101
102    #[serde(rename = "inReplyTo")]
103    #[cfg_attr(
104        not(feature = "debug"),
105        serde(alias = "header:In-Reply-To:asMessageIds")
106    )]
107    #[serde(skip_serializing_if = "Option::is_none")]
108    in_reply_to: Option<Vec<String>>,
109
110    #[serde(rename = "references")]
111    #[cfg_attr(
112        not(feature = "debug"),
113        serde(alias = "header:References:asMessageIds")
114    )]
115    #[serde(skip_serializing_if = "Option::is_none")]
116    references: Option<Vec<String>>,
117
118    #[serde(rename = "sender")]
119    #[cfg_attr(not(feature = "debug"), serde(alias = "header:Sender:asAddresses"))]
120    #[serde(skip_serializing_if = "Option::is_none")]
121    sender: Option<Vec<EmailAddress>>,
122
123    #[serde(rename = "from")]
124    #[cfg_attr(not(feature = "debug"), serde(alias = "header:From:asAddresses"))]
125    #[serde(skip_serializing_if = "Option::is_none")]
126    from: Option<Vec<EmailAddress>>,
127
128    #[serde(rename = "to")]
129    #[cfg_attr(not(feature = "debug"), serde(alias = "header:To:asAddresses"))]
130    #[serde(skip_serializing_if = "Option::is_none")]
131    to: Option<Vec<EmailAddress>>,
132
133    #[serde(rename = "cc")]
134    #[cfg_attr(not(feature = "debug"), serde(alias = "header:Cc:asAddresses"))]
135    #[serde(skip_serializing_if = "Option::is_none")]
136    cc: Option<Vec<EmailAddress>>,
137
138    #[serde(rename = "bcc")]
139    #[cfg_attr(not(feature = "debug"), serde(alias = "header:Bcc:asAddresses"))]
140    #[serde(skip_serializing_if = "Option::is_none")]
141    bcc: Option<Vec<EmailAddress>>,
142
143    #[serde(rename = "replyTo")]
144    #[cfg_attr(not(feature = "debug"), serde(alias = "header:Reply-To:asAddresses"))]
145    #[serde(skip_serializing_if = "Option::is_none")]
146    reply_to: Option<Vec<EmailAddress>>,
147
148    #[serde(rename = "subject")]
149    #[cfg_attr(not(feature = "debug"), serde(alias = "header:Subject:asText"))]
150    #[serde(skip_serializing_if = "Option::is_none")]
151    subject: Option<String>,
152
153    #[serde(rename = "sentAt")]
154    #[cfg_attr(not(feature = "debug"), serde(alias = "header:Date:asDate"))]
155    #[serde(skip_serializing_if = "Option::is_none")]
156    sent_at: Option<DateTime<Utc>>,
157
158    #[serde(rename = "bodyStructure")]
159    #[serde(skip_serializing_if = "Option::is_none")]
160    body_structure: Option<Box<EmailBodyPart>>,
161
162    #[serde(rename = "bodyValues")]
163    #[serde(skip_serializing_if = "Option::is_none")]
164    body_values: Option<AHashMap<String, EmailBodyValue>>,
165
166    #[serde(rename = "textBody")]
167    #[serde(skip_serializing_if = "Option::is_none")]
168    text_body: Option<Vec<EmailBodyPart>>,
169
170    #[serde(rename = "htmlBody")]
171    #[serde(skip_serializing_if = "Option::is_none")]
172    html_body: Option<Vec<EmailBodyPart>>,
173
174    #[serde(rename = "attachments")]
175    #[serde(skip_serializing_if = "Option::is_none")]
176    attachments: Option<Vec<EmailBodyPart>>,
177
178    #[serde(rename = "hasAttachment")]
179    #[serde(skip_serializing_if = "Option::is_none")]
180    has_attachment: Option<bool>,
181
182    #[serde(rename = "preview")]
183    #[serde(skip_serializing_if = "Option::is_none")]
184    preview: Option<String>,
185
186    #[serde(flatten)]
187    #[serde(skip_serializing_if = "std::collections::HashMap::is_empty")]
188    headers: AHashMap<Header, Option<HeaderValue>>,
189
190    #[serde(flatten)]
191    #[serde(skip_deserializing)]
192    #[serde(skip_serializing_if = "Option::is_none")]
193    patch: Option<AHashMap<String, bool>>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct EmailBodyPart<State = Get> {
198    #[serde(skip)]
199    _state: std::marker::PhantomData<State>,
200
201    #[serde(rename = "partId")]
202    #[serde(skip_serializing_if = "Option::is_none")]
203    part_id: Option<String>,
204
205    #[serde(rename = "blobId")]
206    #[serde(skip_serializing_if = "Option::is_none")]
207    blob_id: Option<String>,
208
209    #[serde(rename = "size")]
210    #[serde(skip_serializing_if = "Option::is_none")]
211    size: Option<usize>,
212
213    #[serde(rename = "headers")]
214    #[serde(skip_serializing_if = "Option::is_none")]
215    headers: Option<Vec<EmailHeader>>,
216
217    #[serde(rename = "name")]
218    #[serde(skip_serializing_if = "Option::is_none")]
219    name: Option<String>,
220
221    #[serde(rename = "type")]
222    #[serde(skip_serializing_if = "Option::is_none")]
223    type_: Option<String>,
224
225    #[serde(rename = "charset")]
226    #[serde(skip_serializing_if = "Option::is_none")]
227    charset: Option<String>,
228
229    #[serde(rename = "disposition")]
230    #[serde(skip_serializing_if = "Option::is_none")]
231    disposition: Option<String>,
232
233    #[serde(rename = "cid")]
234    #[serde(skip_serializing_if = "Option::is_none")]
235    cid: Option<String>,
236
237    #[serde(rename = "language")]
238    #[serde(skip_serializing_if = "Option::is_none")]
239    language: Option<Vec<String>>,
240
241    #[serde(rename = "location")]
242    #[serde(skip_serializing_if = "Option::is_none")]
243    location: Option<String>,
244
245    #[serde(rename = "subParts")]
246    #[serde(skip_serializing_if = "Option::is_none")]
247    sub_parts: Option<Vec<EmailBodyPart>>,
248
249    #[serde(flatten)]
250    #[serde(skip_serializing_if = "Option::is_none")]
251    header: Option<AHashMap<Header, HeaderValue>>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct EmailBodyValue<State = Get> {
256    #[serde(skip)]
257    _state: std::marker::PhantomData<State>,
258
259    #[serde(rename = "value")]
260    value: String,
261
262    #[serde(rename = "isEncodingProblem")]
263    #[serde(skip_serializing_if = "Option::is_none")]
264    is_encoding_problem: Option<bool>,
265
266    #[serde(rename = "isTruncated")]
267    #[serde(skip_serializing_if = "Option::is_none")]
268    is_truncated: Option<bool>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct EmailAddress<State = Get> {
273    #[serde(skip)]
274    _state: std::marker::PhantomData<State>,
275
276    name: Option<String>,
277    email: String,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct EmailAddressGroup<State = Get> {
282    #[serde(skip)]
283    _state: std::marker::PhantomData<State>,
284
285    name: Option<String>,
286    addresses: Vec<EmailAddress>,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct EmailHeader<State = Get> {
291    #[serde(skip)]
292    _state: std::marker::PhantomData<State>,
293
294    name: String,
295    value: String,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Hash)]
299pub enum Property {
300    Id,
301    BlobId,
302    ThreadId,
303    MailboxIds,
304    Keywords,
305    Size,
306    ReceivedAt,
307    MessageId,
308    InReplyTo,
309    References,
310    Sender,
311    From,
312    To,
313    Cc,
314    Bcc,
315    ReplyTo,
316    Subject,
317    SentAt,
318    BodyStructure,
319    BodyValues,
320    TextBody,
321    HtmlBody,
322    Attachments,
323    HasAttachment,
324    Preview,
325    Header(Header),
326    Other(String),
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(untagged)]
331pub enum HeaderValue {
332    AsDate(DateTime<Utc>),
333    AsDateAll(Vec<DateTime<Utc>>),
334    AsText(String),
335    AsTextAll(Vec<String>),
336    AsTextListAll(Vec<Vec<String>>),
337    AsAddressesAll(Vec<Vec<EmailAddress>>),
338    AsAddresses(Vec<EmailAddress>),
339    AsGroupedAddressesAll(Vec<Vec<EmailAddressGroup>>),
340    AsGroupedAddresses(Vec<EmailAddressGroup>),
341}
342
343#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
344pub struct Header {
345    pub name: String,
346    pub form: HeaderForm,
347    pub all: bool,
348}
349
350#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)]
351pub enum HeaderForm {
352    Raw,
353    Text,
354    Addresses,
355    GroupedAddresses,
356    MessageIds,
357    Date,
358    URLs,
359}
360
361impl Property {
362    fn parse(value: &str) -> Option<Self> {
363        match value {
364            "id" => Some(Property::Id),
365            "blobId" => Some(Property::BlobId),
366            "threadId" => Some(Property::ThreadId),
367            "mailboxIds" => Some(Property::MailboxIds),
368            "keywords" => Some(Property::Keywords),
369            "size" => Some(Property::Size),
370            "receivedAt" => Some(Property::ReceivedAt),
371            "messageId" => Some(Property::MessageId),
372            "inReplyTo" => Some(Property::InReplyTo),
373            "references" => Some(Property::References),
374            "sender" => Some(Property::Sender),
375            "from" => Some(Property::From),
376            "to" => Some(Property::To),
377            "cc" => Some(Property::Cc),
378            "bcc" => Some(Property::Bcc),
379            "replyTo" => Some(Property::ReplyTo),
380            "subject" => Some(Property::Subject),
381            "sentAt" => Some(Property::SentAt),
382            "hasAttachment" => Some(Property::HasAttachment),
383            "preview" => Some(Property::Preview),
384            "bodyValues" => Some(Property::BodyValues),
385            "textBody" => Some(Property::TextBody),
386            "htmlBody" => Some(Property::HtmlBody),
387            "attachments" => Some(Property::Attachments),
388            "bodyStructure" => Some(Property::BodyStructure),
389            _ if value.starts_with("header:") => Some(Property::Header(Header::parse(value)?)),
390            _ => Some(Property::Other(value.to_string())),
391        }
392    }
393}
394
395impl Display for Property {
396    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
397        match self {
398            Property::Id => write!(f, "id"),
399            Property::BlobId => write!(f, "blobId"),
400            Property::ThreadId => write!(f, "threadId"),
401            Property::MailboxIds => write!(f, "mailboxIds"),
402            Property::Keywords => write!(f, "keywords"),
403            Property::Size => write!(f, "size"),
404            Property::ReceivedAt => write!(f, "receivedAt"),
405            Property::MessageId => write!(f, "messageId"),
406            Property::InReplyTo => write!(f, "inReplyTo"),
407            Property::References => write!(f, "references"),
408            Property::Sender => write!(f, "sender"),
409            Property::From => write!(f, "from"),
410            Property::To => write!(f, "to"),
411            Property::Cc => write!(f, "cc"),
412            Property::Bcc => write!(f, "bcc"),
413            Property::ReplyTo => write!(f, "replyTo"),
414            Property::Subject => write!(f, "subject"),
415            Property::SentAt => write!(f, "sentAt"),
416            Property::BodyStructure => write!(f, "bodyStructure"),
417            Property::BodyValues => write!(f, "bodyValues"),
418            Property::TextBody => write!(f, "textBody"),
419            Property::HtmlBody => write!(f, "htmlBody"),
420            Property::Attachments => write!(f, "attachments"),
421            Property::HasAttachment => write!(f, "hasAttachment"),
422            Property::Preview => write!(f, "preview"),
423            Property::Header(header) => header.fmt(f),
424            Property::Other(other) => write!(f, "{}", other),
425        }
426    }
427}
428
429impl Serialize for Property {
430    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
431    where
432        S: serde::Serializer,
433    {
434        serializer.serialize_str(&self.to_string())
435    }
436}
437
438struct PropertyVisitor;
439
440impl<'de> Visitor<'de> for PropertyVisitor {
441    type Value = Property;
442
443    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
444        formatter.write_str("a valid JMAP e-mail property")
445    }
446
447    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
448    where
449        E: serde::de::Error,
450    {
451        Property::parse(v).ok_or_else(|| {
452            serde::de::Error::custom(format!("Failed to parse JMAP property '{}'", v))
453        })
454    }
455}
456
457impl<'de> Deserialize<'de> for Property {
458    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
459    where
460        D: serde::Deserializer<'de>,
461    {
462        deserializer.deserialize_str(PropertyVisitor)
463    }
464}
465
466impl Serialize for Header {
467    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
468    where
469        S: serde::Serializer,
470    {
471        serializer.serialize_str(&self.to_string())
472    }
473}
474
475struct HeaderVisitor;
476
477impl<'de> Visitor<'de> for HeaderVisitor {
478    type Value = Header;
479
480    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
481        formatter.write_str("a valid JMAP header")
482    }
483
484    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
485    where
486        E: serde::de::Error,
487    {
488        Header::parse(v)
489            .ok_or_else(|| serde::de::Error::custom(format!("Failed to parse JMAP header '{}'", v)))
490    }
491}
492
493impl<'de> Deserialize<'de> for Header {
494    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
495    where
496        D: serde::Deserializer<'de>,
497    {
498        deserializer.deserialize_str(HeaderVisitor)
499    }
500}
501
502impl HeaderForm {
503    pub fn parse(value: &str) -> Option<HeaderForm> {
504        match value {
505            "asText" => Some(HeaderForm::Text),
506            "asAddresses" => Some(HeaderForm::Addresses),
507            "asGroupedAddresses" => Some(HeaderForm::GroupedAddresses),
508            "asMessageIds" => Some(HeaderForm::MessageIds),
509            "asDate" => Some(HeaderForm::Date),
510            "asURLs" => Some(HeaderForm::URLs),
511            _ => None,
512        }
513    }
514}
515
516impl Header {
517    pub fn as_raw(name: impl Into<String>, all: bool) -> Header {
518        Header {
519            name: name.into(),
520            form: HeaderForm::Raw,
521            all,
522        }
523    }
524
525    pub fn as_text(name: impl Into<String>, all: bool) -> Header {
526        Header {
527            name: name.into(),
528            form: HeaderForm::Text,
529            all,
530        }
531    }
532
533    pub fn as_addresses(name: impl Into<String>, all: bool) -> Header {
534        Header {
535            name: name.into(),
536            form: HeaderForm::Addresses,
537            all,
538        }
539    }
540
541    pub fn as_grouped_addresses(name: impl Into<String>, all: bool) -> Header {
542        Header {
543            name: name.into(),
544            form: HeaderForm::GroupedAddresses,
545            all,
546        }
547    }
548
549    pub fn as_message_ids(name: impl Into<String>, all: bool) -> Header {
550        Header {
551            name: name.into(),
552            form: HeaderForm::MessageIds,
553            all,
554        }
555    }
556
557    pub fn as_date(name: impl Into<String>, all: bool) -> Header {
558        Header {
559            name: name.into(),
560            form: HeaderForm::Date,
561            all,
562        }
563    }
564
565    pub fn as_urls(name: impl Into<String>, all: bool) -> Header {
566        Header {
567            name: name.into(),
568            form: HeaderForm::URLs,
569            all,
570        }
571    }
572
573    pub fn parse(value: &str) -> Option<Header> {
574        let mut all = false;
575        let mut form = HeaderForm::Raw;
576        let mut header = None;
577        for (pos, part) in value.split(':').enumerate() {
578            match pos {
579                0 if part == "header" => (),
580                1 => {
581                    header = part.into();
582                }
583                2 | 3 if part == "all" => all = true,
584                2 => {
585                    form = HeaderForm::parse(part)?;
586                }
587                _ => return None,
588            }
589        }
590        Header {
591            name: header?.to_string(),
592            form,
593            all,
594        }
595        .into()
596    }
597}
598
599impl Display for Header {
600    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601        write!(f, "header:")?;
602        self.name.fmt(f)?;
603        self.form.fmt(f)?;
604        if self.all {
605            write!(f, ":all")
606        } else {
607            Ok(())
608        }
609    }
610}
611
612impl Display for HeaderForm {
613    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614        match self {
615            HeaderForm::Raw => Ok(()),
616            HeaderForm::Text => write!(f, ":asText"),
617            HeaderForm::Addresses => write!(f, ":asAddresses"),
618            HeaderForm::GroupedAddresses => write!(f, ":asGroupedAddresses"),
619            HeaderForm::MessageIds => write!(f, ":asMessageIds"),
620            HeaderForm::Date => write!(f, ":asDate"),
621            HeaderForm::URLs => write!(f, ":asURLs"),
622        }
623    }
624}
625
626#[derive(Debug, Clone, PartialEq, Eq, Hash)]
627pub enum BodyProperty {
628    PartId,
629    BlobId,
630    Size,
631    Headers,
632    Name,
633    Type,
634    Charset,
635    Disposition,
636    Cid,
637    Language,
638    Location,
639    SubParts,
640    Header(Header),
641}
642
643impl BodyProperty {
644    fn parse(value: &str) -> Option<BodyProperty> {
645        match value {
646            "partId" => Some(BodyProperty::PartId),
647            "blobId" => Some(BodyProperty::BlobId),
648            "size" => Some(BodyProperty::Size),
649            "name" => Some(BodyProperty::Name),
650            "type" => Some(BodyProperty::Type),
651            "charset" => Some(BodyProperty::Charset),
652            "headers" => Some(BodyProperty::Headers),
653            "disposition" => Some(BodyProperty::Disposition),
654            "cid" => Some(BodyProperty::Cid),
655            "language" => Some(BodyProperty::Language),
656            "location" => Some(BodyProperty::Location),
657            "subParts" => Some(BodyProperty::SubParts),
658            _ if value.starts_with("header:") => Some(BodyProperty::Header(Header::parse(value)?)),
659            _ => None,
660        }
661    }
662}
663
664impl Display for BodyProperty {
665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
666        match self {
667            BodyProperty::PartId => write!(f, "partId"),
668            BodyProperty::BlobId => write!(f, "blobId"),
669            BodyProperty::Size => write!(f, "size"),
670            BodyProperty::Name => write!(f, "name"),
671            BodyProperty::Type => write!(f, "type"),
672            BodyProperty::Charset => write!(f, "charset"),
673            BodyProperty::Header(header) => header.fmt(f),
674            BodyProperty::Headers => write!(f, "headers"),
675            BodyProperty::Disposition => write!(f, "disposition"),
676            BodyProperty::Cid => write!(f, "cid"),
677            BodyProperty::Language => write!(f, "language"),
678            BodyProperty::Location => write!(f, "location"),
679            BodyProperty::SubParts => write!(f, "subParts"),
680        }
681    }
682}
683
684impl Serialize for BodyProperty {
685    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
686    where
687        S: serde::Serializer,
688    {
689        serializer.serialize_str(&self.to_string())
690    }
691}
692
693struct BodyPropertyVisitor;
694
695impl<'de> Visitor<'de> for BodyPropertyVisitor {
696    type Value = BodyProperty;
697
698    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
699        formatter.write_str("a valid JMAP body property")
700    }
701
702    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
703    where
704        E: serde::de::Error,
705    {
706        BodyProperty::parse(v).ok_or_else(|| {
707            serde::de::Error::custom(format!("Failed to parse JMAP body property '{}'", v))
708        })
709    }
710}
711
712impl<'de> Deserialize<'de> for BodyProperty {
713    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
714    where
715        D: serde::Deserializer<'de>,
716    {
717        deserializer.deserialize_str(BodyPropertyVisitor)
718    }
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize)]
722pub struct MailCapabilities {
723    #[serde(rename = "maxMailboxesPerEmail")]
724    max_mailboxes_per_email: Option<usize>,
725
726    #[serde(rename = "maxMailboxDepth")]
727    max_mailbox_depth: usize,
728
729    #[serde(rename = "maxSizeMailboxName")]
730    max_size_mailbox_name: usize,
731
732    #[serde(rename = "maxSizeAttachmentsPerEmail")]
733    max_size_attachments_per_email: usize,
734
735    #[serde(rename = "emailQuerySortOptions")]
736    email_query_sort_options: Vec<String>,
737
738    #[serde(rename = "mayCreateTopLevelMailbox")]
739    may_create_top_level_mailbox: bool,
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize)]
743pub struct SubmissionCapabilities {
744    #[serde(rename = "maxDelayedSend")]
745    max_delayed_send: usize,
746
747    #[serde(rename = "submissionExtensions")]
748    submission_extensions: Vec<String>,
749}
750
751#[derive(Debug, Clone, Serialize, Default)]
752pub struct QueryArguments {
753    #[serde(rename = "collapseThreads")]
754    #[serde(skip_serializing_if = "Option::is_none")]
755    collapse_threads: Option<bool>,
756}
757
758#[derive(Debug, Clone, Serialize, Default)]
759pub struct GetArguments {
760    #[serde(rename = "bodyProperties")]
761    #[serde(skip_serializing_if = "Option::is_none")]
762    body_properties: Option<Vec<BodyProperty>>,
763
764    #[serde(rename = "fetchTextBodyValues")]
765    #[serde(skip_serializing_if = "Option::is_none")]
766    fetch_text_body_values: Option<bool>,
767
768    #[serde(rename = "fetchHTMLBodyValues")]
769    #[serde(skip_serializing_if = "Option::is_none")]
770    fetch_html_body_values: Option<bool>,
771
772    #[serde(rename = "fetchAllBodyValues")]
773    #[serde(skip_serializing_if = "Option::is_none")]
774    fetch_all_body_values: Option<bool>,
775
776    #[serde(rename = "maxBodyValueBytes")]
777    #[serde(skip_serializing_if = "Option::is_none")]
778    max_body_value_bytes: Option<usize>,
779}
780
781impl QueryArguments {
782    pub fn collapse_threads(&mut self, collapse_threads: bool) {
783        self.collapse_threads = collapse_threads.into();
784    }
785}
786
787impl GetArguments {
788    pub fn body_properties(
789        &mut self,
790        body_properties: impl IntoIterator<Item = BodyProperty>,
791    ) -> &mut Self {
792        self.body_properties = Some(body_properties.into_iter().collect());
793        self
794    }
795
796    pub fn fetch_text_body_values(&mut self, fetch_text_body_values: bool) -> &mut Self {
797        self.fetch_text_body_values = fetch_text_body_values.into();
798        self
799    }
800
801    pub fn fetch_html_body_values(&mut self, fetch_html_body_values: bool) -> &mut Self {
802        self.fetch_html_body_values = fetch_html_body_values.into();
803        self
804    }
805
806    pub fn fetch_all_body_values(&mut self, fetch_all_body_values: bool) -> &mut Self {
807        self.fetch_all_body_values = fetch_all_body_values.into();
808        self
809    }
810
811    pub fn max_body_value_bytes(&mut self, max_body_value_bytes: usize) -> &mut Self {
812        self.max_body_value_bytes = max_body_value_bytes.into();
813        self
814    }
815}
816
817impl MailCapabilities {
818    pub fn max_mailboxes_per_email(&self) -> Option<usize> {
819        self.max_mailboxes_per_email
820    }
821
822    pub fn max_mailbox_depth(&self) -> usize {
823        self.max_mailbox_depth
824    }
825
826    pub fn max_size_mailbox_name(&self) -> usize {
827        self.max_size_mailbox_name
828    }
829
830    pub fn max_size_attachments_per_email(&self) -> usize {
831        self.max_size_attachments_per_email
832    }
833
834    pub fn email_query_sort_options(&self) -> &[String] {
835        &self.email_query_sort_options
836    }
837
838    pub fn may_create_top_level_mailbox(&self) -> bool {
839        self.may_create_top_level_mailbox
840    }
841}
842
843impl SubmissionCapabilities {
844    pub fn max_delayed_send(&self) -> usize {
845        self.max_delayed_send
846    }
847
848    pub fn submission_extensions(&self) -> &[String] {
849        &self.submission_extensions
850    }
851}
852
853#[cfg(feature = "debug")]
854use std::collections::BTreeMap;
855
856#[cfg(feature = "debug")]
857#[derive(Debug, Default, Clone, Serialize, Deserialize)]
858pub struct TestEmail {
859    #[serde(rename = "mailboxIds")]
860    pub mailbox_ids: Option<BTreeMap<String, bool>>,
861
862    #[serde(skip_serializing_if = "Option::is_none")]
863    pub keywords: Option<BTreeMap<String, bool>>,
864
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub size: Option<usize>,
867
868    #[serde(rename = "receivedAt")]
869    #[serde(skip_serializing_if = "Option::is_none")]
870    pub received_at: Option<DateTime<Utc>>,
871
872    #[serde(rename = "messageId")]
873    #[serde(skip_serializing_if = "Option::is_none")]
874    pub message_id: Option<Vec<String>>,
875
876    #[serde(rename = "inReplyTo")]
877    #[serde(skip_serializing_if = "Option::is_none")]
878    pub in_reply_to: Option<Vec<String>>,
879
880    #[serde(skip_serializing_if = "Option::is_none")]
881    pub references: Option<Vec<String>>,
882
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub sender: Option<Vec<EmailAddress>>,
885
886    #[serde(skip_serializing_if = "Option::is_none")]
887    pub from: Option<Vec<EmailAddress>>,
888
889    #[serde(skip_serializing_if = "Option::is_none")]
890    pub to: Option<Vec<EmailAddress>>,
891
892    #[serde(skip_serializing_if = "Option::is_none")]
893    pub cc: Option<Vec<EmailAddress>>,
894
895    #[serde(skip_serializing_if = "Option::is_none")]
896    pub bcc: Option<Vec<EmailAddress>>,
897
898    #[serde(rename = "replyTo")]
899    #[serde(skip_serializing_if = "Option::is_none")]
900    pub reply_to: Option<Vec<EmailAddress>>,
901
902    #[serde(skip_serializing_if = "Option::is_none")]
903    pub subject: Option<String>,
904
905    #[serde(rename = "sentAt")]
906    #[serde(skip_serializing_if = "Option::is_none")]
907    pub sent_at: Option<DateTime<Utc>>,
908
909    #[serde(rename = "bodyStructure")]
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub body_structure: Option<Box<EmailBodyPart>>,
912
913    #[serde(rename = "bodyValues")]
914    #[serde(skip_serializing_if = "Option::is_none")]
915    pub body_values: Option<BTreeMap<String, EmailBodyValue>>,
916
917    #[serde(rename = "textBody")]
918    #[serde(skip_serializing_if = "Option::is_none")]
919    pub text_body: Option<Vec<EmailBodyPart>>,
920
921    #[serde(rename = "htmlBody")]
922    #[serde(skip_serializing_if = "Option::is_none")]
923    pub html_body: Option<Vec<EmailBodyPart>>,
924
925    #[serde(skip_serializing_if = "Option::is_none")]
926    pub attachments: Option<Vec<EmailBodyPart>>,
927
928    #[serde(rename = "hasAttachment")]
929    #[serde(skip_serializing_if = "Option::is_none")]
930    pub has_attachment: Option<bool>,
931
932    #[serde(skip_serializing_if = "Option::is_none")]
933    pub preview: Option<String>,
934
935    #[serde(flatten)]
936    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
937    pub headers: BTreeMap<Header, Option<HeaderValue>>,
938}
939
940#[cfg(feature = "debug")]
941impl From<Email> for TestEmail {
942    fn from(email: Email) -> Self {
943        TestEmail {
944            mailbox_ids: email.mailbox_ids.map(|ids| ids.into_iter().collect()),
945            keywords: email
946                .keywords
947                .map(|keywords| keywords.into_iter().collect()),
948            size: email.size,
949            received_at: email.received_at,
950            message_id: email.message_id,
951            in_reply_to: email.in_reply_to,
952            references: email.references,
953            sender: email.sender,
954            from: email.from,
955            to: email.to,
956            cc: email.cc,
957            bcc: email.bcc,
958            reply_to: email.reply_to,
959            subject: email.subject,
960            sent_at: email.sent_at,
961            body_structure: email.body_structure,
962            body_values: email
963                .body_values
964                .map(|body_values| body_values.into_iter().collect()),
965            text_body: email.text_body,
966            html_body: email.html_body,
967            attachments: email.attachments,
968            has_attachment: email.has_attachment,
969            preview: email.preview,
970            headers: email.headers.into_iter().collect(),
971        }
972    }
973}