Skip to main content

jmap_mail_types/
capability.rs

1//! Capability types for the JMAP for Mail extension (RFC 8621).
2//!
3//! RFC 8621 §1.3 defines three capability URIs.  Each has both a
4//! session-level capability (value in the JMAP Session `capabilities` map)
5//! and an account-level capability (value in the account's
6//! `accountCapabilities` map).  Session-level values are empty objects for
7//! all three URIs; only `urn:ietf:params:jmap:mail` and
8//! `urn:ietf:params:jmap:submission` carry non-empty account-level
9//! capabilities.
10
11use std::collections::HashMap;
12
13use serde::{Deserialize, Serialize};
14
15/// Capability URI for core JMAP for Mail support (RFC 8621 §1.3.1).
16pub const JMAP_MAIL_URI: &str = "urn:ietf:params:jmap:mail";
17
18/// Capability URI for the JMAP Email Submission extension (RFC 8621 §1.3.2).
19pub const JMAP_SUBMISSION_URI: &str = "urn:ietf:params:jmap:submission";
20
21/// Capability URI for the JMAP Vacation Response extension (RFC 8621 §1.3.3).
22pub const JMAP_VACATIONRESPONSE_URI: &str = "urn:ietf:params:jmap:vacationresponse";
23
24/// Session-level Mail capability (RFC 8621 §1.3.1).
25///
26/// The value of `capabilities["urn:ietf:params:jmap:mail"]` in the JMAP
27/// Session object.  RFC 8621 §1.3.1 mandates that this is an empty object
28/// `{}`.
29#[non_exhaustive]
30#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
31pub struct MailCapability {}
32
33/// Account-level Mail capability (RFC 8621 §1.3.1).
34///
35/// The value of `accountCapabilities["urn:ietf:params:jmap:mail"]` for a
36/// given account.  Describes server capabilities and account-level
37/// permissions for the Mail extension.
38#[non_exhaustive]
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct MailAccountCapability {
42    /// Maximum number of Mailboxes that may be assigned to a single Email,
43    /// or `null` for no limit (RFC 8621 §1.3.1).  MUST be >= 1 when set.
44    pub max_mailboxes_per_email: Option<u64>,
45
46    /// Maximum depth of the Mailbox hierarchy (one more than the maximum
47    /// number of ancestors a Mailbox may have), or `null` for no limit
48    /// (RFC 8621 §1.3.1).
49    pub max_mailbox_depth: Option<u64>,
50
51    /// Maximum length, in UTF-8 octets, allowed for a Mailbox name
52    /// (RFC 8621 §1.3.1).  MUST be at least 100.
53    pub max_size_mailbox_name: u64,
54
55    /// Maximum total size, in octets, of attachments allowed for a single
56    /// Email object (RFC 8621 §1.3.1).  This is the unencoded attachment
57    /// total; servers may enforce a separate limit on encoded message size.
58    pub max_size_attachments_per_email: u64,
59
60    /// List of values the server supports for the `property` field of the
61    /// Comparator object in an `Email/query` sort (RFC 8621 §1.3.1 / §4.4.2).
62    /// May include vendor-specific extensions; clients MUST ignore unknown
63    /// entries.
64    pub email_query_sort_options: Vec<String>,
65
66    /// If `true`, the user may create a Mailbox in this account with a
67    /// `null` parentId (RFC 8621 §1.3.1).  Permission to create a child of
68    /// an existing Mailbox is governed by `Mailbox.myRights` instead.
69    pub may_create_top_level_mailbox: bool,
70}
71
72/// Session-level Submission capability (RFC 8621 §1.3.2).
73///
74/// The value of `capabilities["urn:ietf:params:jmap:submission"]` in the
75/// JMAP Session object.  RFC 8621 §1.3.2 mandates that this is an empty
76/// object `{}`.
77#[non_exhaustive]
78#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
79pub struct SubmissionCapability {}
80
81/// Account-level Submission capability (RFC 8621 §1.3.2).
82///
83/// The value of `accountCapabilities["urn:ietf:params:jmap:submission"]`
84/// for a given account.  Describes server capabilities and account-level
85/// permissions for the Email Submission extension.
86#[non_exhaustive]
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct SubmissionAccountCapability {
90    /// Maximum delay, in seconds, that the server supports for delayed
91    /// send (RFC 8621 §1.3.2 / §7).  Zero if the server does not support
92    /// delayed send.
93    pub max_delayed_send: u64,
94
95    /// SMTP submission extensions supported by the server.  Each key is
96    /// an `ehlo-name` and the value is a list of `ehlo-args`
97    /// (RFC 8621 §1.3.2).
98    pub submission_extensions: HashMap<String, Vec<String>>,
99}
100
101/// Session-level VacationResponse capability (RFC 8621 §1.3.3).
102///
103/// The value of `capabilities["urn:ietf:params:jmap:vacationresponse"]`
104/// in the JMAP Session object.  RFC 8621 §1.3.3 mandates that this is an
105/// empty object `{}` at both session level and account level.
106#[non_exhaustive]
107#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
108pub struct VacationResponseCapability {}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    /// Oracle: hand-written JSON from RFC 8621 §1.3.1 field definitions.
115    #[test]
116    fn mail_account_capability_round_trip() {
117        let json = r#"{"maxMailboxesPerEmail":null,"maxMailboxDepth":10,"maxSizeMailboxName":100,"maxSizeAttachmentsPerEmail":50000000,"emailQuerySortOptions":["receivedAt","size"],"mayCreateTopLevelMailbox":true}"#;
118        let cap: MailAccountCapability = serde_json::from_str(json).expect("must parse");
119        assert!(cap.max_mailboxes_per_email.is_none());
120        assert_eq!(cap.max_mailbox_depth, Some(10));
121        assert_eq!(cap.max_size_mailbox_name, 100);
122        assert_eq!(cap.max_size_attachments_per_email, 50_000_000);
123        assert_eq!(
124            cap.email_query_sort_options,
125            vec!["receivedAt".to_owned(), "size".to_owned()]
126        );
127        assert!(cap.may_create_top_level_mailbox);
128        let back = serde_json::to_string(&cap).expect("serialize");
129        assert_eq!(back, json);
130    }
131
132    /// Oracle: RFC 8621 §1.3.1 requires the session-level value to be `{}`.
133    #[test]
134    fn mail_capability_session_level_is_empty_object() {
135        let cap = MailCapability::default();
136        let json = serde_json::to_string(&cap).expect("serialize");
137        assert_eq!(json, "{}");
138        let back: MailCapability = serde_json::from_str("{}").expect("must parse");
139        assert_eq!(back, cap);
140    }
141
142    /// Oracle: hand-written JSON from RFC 8621 §1.3.2 field definitions.
143    #[test]
144    fn submission_account_capability_round_trip() {
145        let json = r#"{"maxDelayedSend":86400,"submissionExtensions":{"FUTURERELEASE":["86400","2024-01-01T00:00:00Z"]}}"#;
146        let cap: SubmissionAccountCapability = serde_json::from_str(json).expect("must parse");
147        assert_eq!(cap.max_delayed_send, 86_400);
148        assert_eq!(
149            cap.submission_extensions.get("FUTURERELEASE"),
150            Some(&vec!["86400".to_owned(), "2024-01-01T00:00:00Z".to_owned()])
151        );
152        let back = serde_json::to_string(&cap).expect("serialize");
153        assert_eq!(back, json);
154    }
155
156    /// Oracle: RFC 8621 §1.3.2 requires the session-level value to be `{}`.
157    #[test]
158    fn submission_capability_session_level_is_empty_object() {
159        let cap = SubmissionCapability::default();
160        let json = serde_json::to_string(&cap).expect("serialize");
161        assert_eq!(json, "{}");
162    }
163
164    /// Oracle: RFC 8621 §1.3.3 requires the value to be `{}` at both
165    /// session-level and account-level (the same struct serves both).
166    #[test]
167    fn vacation_response_capability_is_empty_object() {
168        let cap = VacationResponseCapability::default();
169        let json = serde_json::to_string(&cap).expect("serialize");
170        assert_eq!(json, "{}");
171        let back: VacationResponseCapability = serde_json::from_str("{}").expect("must parse");
172        assert_eq!(back, cap);
173    }
174
175    /// Oracle: capability URI constants match the RFC 8621 §1.3 IANA
176    /// registrations verbatim.
177    #[test]
178    fn capability_uri_constants_match_rfc8621() {
179        assert_eq!(JMAP_MAIL_URI, "urn:ietf:params:jmap:mail");
180        assert_eq!(JMAP_SUBMISSION_URI, "urn:ietf:params:jmap:submission");
181        assert_eq!(
182            JMAP_VACATIONRESPONSE_URI,
183            "urn:ietf:params:jmap:vacationresponse"
184        );
185    }
186}