Skip to main content

io_jmap/rfc8621/mailbox/
types.rs

1//! JMAP Mailbox types (RFC 8621 §2).
2
3use alloc::{
4    string::{String, ToString},
5    vec::Vec,
6};
7use core::fmt;
8
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11/// A JMAP Mailbox object (RFC 8621 §2.1): a named container for emails.
12#[derive(Clone, Debug, Default, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct JmapMailbox {
15    pub id: Option<String>,
16    pub name: Option<String>,
17    /// `None` for a top-level mailbox.
18    pub parent_id: Option<String>,
19    pub role: Option<JmapMailboxRole>,
20    #[serde(default)]
21    pub sort_order: u32,
22    #[serde(default)]
23    pub total_emails: u32,
24    #[serde(default)]
25    pub unread_emails: u32,
26    #[serde(default)]
27    pub total_threads: u32,
28    #[serde(default)]
29    pub unread_threads: u32,
30    #[serde(default)]
31    pub my_rights: JmapMailboxRights,
32    #[serde(default)]
33    pub is_subscribed: bool,
34}
35
36/// Client-settable subset of [`JmapMailbox`] for `Mailbox/set` create requests
37/// (RFC 8621 §2.1). Server-assigned fields are excluded.
38#[derive(Clone, Debug, Default, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub struct JmapMailboxCreate {
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub name: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub parent_id: Option<String>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub role: Option<JmapMailboxRole>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub sort_order: Option<u32>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub is_subscribed: Option<bool>,
51}
52
53/// Patch object for `Mailbox/set` update requests (RFC 8620 §5.3): only
54/// `Some` fields are serialised.
55#[derive(Clone, Debug, Default, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct JmapMailboxUpdate {
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub name: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub parent_id: Option<String>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub role: Option<JmapMailboxRole>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub sort_order: Option<u32>,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub is_subscribed: Option<bool>,
68}
69
70/// Access rights on a mailbox (RFC 8621 §2.1).
71#[derive(Clone, Debug, Default, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct JmapMailboxRights {
74    /// May read items in the mailbox.
75    pub may_read_items: bool,
76    /// May add items to the mailbox.
77    pub may_add_items: bool,
78    /// May remove items from the mailbox.
79    pub may_remove_items: bool,
80    /// May set/unset the `$seen` keyword on items.
81    pub may_set_seen: bool,
82    /// May set/unset any keyword other than `$seen`.
83    pub may_set_keywords: bool,
84    /// May create child mailboxes.
85    pub may_create_child: bool,
86    /// May rename this mailbox.
87    pub may_rename: bool,
88    /// May delete this mailbox.
89    pub may_delete: bool,
90    /// May submit email from this mailbox.
91    pub may_submit: bool,
92}
93
94/// Mailbox role per the IANA JMAP Mailbox Roles registry (RFC 8621 §2.1).
95/// Any unknown or server-defined role is held by [`JmapMailboxRole::Other`].
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub enum JmapMailboxRole {
98    /// Primary inbox.
99    Inbox,
100    /// Archived messages.
101    Archive,
102    /// Draft messages.
103    Drafts,
104    /// Flagged / starred messages.
105    Flagged,
106    /// Messages marked as important.
107    Important,
108    /// Spam / junk messages.
109    Junk,
110    /// Sent messages.
111    Sent,
112    /// Virtual mailbox of all subscribed mailboxes.
113    Subscribed,
114    /// Deleted messages.
115    Trash,
116    /// A server-defined or unrecognised role.
117    Other(String),
118}
119
120impl fmt::Display for JmapMailboxRole {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.write_str(match self {
123            Self::Inbox => "inbox",
124            Self::Archive => "archive",
125            Self::Drafts => "drafts",
126            Self::Flagged => "flagged",
127            Self::Important => "important",
128            Self::Junk => "junk",
129            Self::Sent => "sent",
130            Self::Subscribed => "subscribed",
131            Self::Trash => "trash",
132            Self::Other(s) => s.as_str(),
133        })
134    }
135}
136
137impl Serialize for JmapMailboxRole {
138    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
139        s.serialize_str(&self.to_string())
140    }
141}
142
143impl<'de> Deserialize<'de> for JmapMailboxRole {
144    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
145        let s = String::deserialize(d)?;
146        Ok(match s.as_str() {
147            "inbox" => Self::Inbox,
148            "archive" => Self::Archive,
149            "drafts" => Self::Drafts,
150            "flagged" => Self::Flagged,
151            "important" => Self::Important,
152            "junk" => Self::Junk,
153            "sent" => Self::Sent,
154            "subscribed" => Self::Subscribed,
155            "trash" => Self::Trash,
156            _ => Self::Other(s),
157        })
158    }
159}
160
161/// [`JmapMailbox`] properties requestable in `Mailbox/get` (RFC 8621 §2.1).
162#[derive(Clone, Debug, Serialize)]
163#[serde(rename_all = "camelCase")]
164pub enum JmapMailboxProperty {
165    Id,
166    Name,
167    ParentId,
168    Role,
169    SortOrder,
170    TotalEmails,
171    UnreadEmails,
172    TotalThreads,
173    UnreadThreads,
174    MyRights,
175    IsSubscribed,
176}
177
178/// Sort property for `Mailbox/query` (RFC 8621 §2.4).
179#[derive(Clone, Debug, Serialize)]
180#[serde(rename_all = "camelCase")]
181pub enum JmapMailboxSortProperty {
182    Name,
183    SortOrder,
184    ParentId,
185}
186
187/// Sort comparator for `Mailbox/query` (RFC 8620 §5.5).
188#[derive(Clone, Debug, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct JmapMailboxSortComparator {
191    pub property: JmapMailboxSortProperty,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub is_ascending: Option<bool>,
194}
195
196/// JmapFilter for `Mailbox/query` (RFC 8621 §2.4).
197#[derive(Clone, Debug, Default, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct JmapMailboxFilter {
200    /// JmapFilter by parent mailbox ID.
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub parent_id: Option<String>,
203
204    /// JmapFilter by role.
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub role: Option<JmapMailboxRole>,
207
208    /// JmapFilter by name (substring match).
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub name: Option<String>,
211
212    /// Whether to include subscribed mailboxes only.
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub is_subscribed: Option<bool>,
215
216    /// Whether to include mailboxes with unread email only.
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub has_any_role: Option<bool>,
219}
220
221/// Per-object error returned in `Mailbox/set` responses (RFC 8621 §2.6).
222///
223/// Covers the standard RFC 8620 §5.3 set errors plus the mailbox-specific
224/// errors defined in RFC 8621 §2.6.
225#[derive(Clone, Debug, Deserialize)]
226#[serde(tag = "type", rename_all = "camelCase")]
227pub enum JmapMailboxSetItemError {
228    /// The mailbox cannot be destroyed because it has child mailboxes.
229    MailboxHasChild {
230        description: Option<String>,
231    },
232    /// The mailbox cannot be destroyed because it contains email.
233    MailboxHasEmail {
234        description: Option<String>,
235    },
236    NotFound {
237        description: Option<String>,
238    },
239    InvalidPatch {
240        description: Option<String>,
241    },
242    WillDestroy {
243        description: Option<String>,
244    },
245    InvalidProperties {
246        description: Option<String>,
247        #[serde(default)]
248        properties: Vec<String>,
249    },
250    Singleton {
251        description: Option<String>,
252    },
253    #[serde(other)]
254    Unknown,
255}