Skip to main content

rusmes_jmap/
session.rs

1//! JMAP Session Object (RFC 8620 Section 2)
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// JMAP Session object returned by the session endpoint
7/// RFC 8620 Section 2
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Session {
11    /// The set of capabilities supported by the server
12    pub capabilities: HashMap<String, Capability>,
13
14    /// A map of account ID to account information
15    pub accounts: HashMap<String, Account>,
16
17    /// A map of capability URIs to the primary account IDs
18    pub primary_accounts: HashMap<String, String>,
19
20    /// The username of the authenticated user
21    pub username: String,
22
23    /// The base URL for the JMAP API
24    pub api_url: String,
25
26    /// The URL to download blobs
27    pub download_url: String,
28
29    /// The URL to upload blobs
30    pub upload_url: String,
31
32    /// The URL for event source connections (optional)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub event_source_url: Option<String>,
35
36    /// Current session state (opaque string)
37    pub state: String,
38}
39
40/// Capability object
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Capability {
44    /// Maximum number of concurrent requests
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub max_concurrent_requests: Option<u32>,
47
48    /// Maximum number of method calls per request
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub max_calls_in_request: Option<u32>,
51
52    /// Maximum size of a request in bytes
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub max_size_request: Option<u64>,
55
56    /// Maximum size of a blob upload in bytes
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub max_size_upload: Option<u64>,
59
60    /// Maximum number of objects to return in a single get/query call
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub max_objects_in_get: Option<u32>,
63
64    /// Maximum number of objects to return in a single set call
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub max_objects_in_set: Option<u32>,
67
68    /// Collation algorithms supported
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub collation_algorithms: Option<Vec<String>>,
71}
72
73impl Default for Capability {
74    fn default() -> Self {
75        Self {
76            max_concurrent_requests: Some(4),
77            max_calls_in_request: Some(16),
78            max_size_request: Some(10 * 1024 * 1024), // 10 MB
79            max_size_upload: Some(50 * 1024 * 1024),  // 50 MB
80            max_objects_in_get: Some(500),
81            max_objects_in_set: Some(500),
82            collation_algorithms: Some(vec![
83                "i;ascii-numeric".to_string(),
84                "i;ascii-casemap".to_string(),
85            ]),
86        }
87    }
88}
89
90/// Account object
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct Account {
94    /// Display name for the account
95    pub name: String,
96
97    /// True if the user is the owner of the account
98    pub is_personal: bool,
99
100    /// True if the user has read-only access
101    pub is_read_only: bool,
102
103    /// The set of capability URIs supported for this account
104    pub account_capabilities: HashMap<String, AccountCapability>,
105}
106
107/// Account-specific capability
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct AccountCapability {
111    /// Maximum number of mailboxes allowed
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub max_mailboxes_per_email: Option<u32>,
114
115    /// Maximum depth of mailbox hierarchy
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub max_mailbox_depth: Option<u32>,
118
119    /// Maximum size of a single email in bytes
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub max_size_mailbox_name: Option<u32>,
122
123    /// Maximum number of emails in a mailbox
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub max_size_attachments_per_email: Option<u64>,
126
127    /// Email submission extensions supported
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub email_query_sort_options: Option<Vec<String>>,
130
131    /// May upload script (for Sieve)
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub may_upload_script: Option<bool>,
134}
135
136impl Default for AccountCapability {
137    fn default() -> Self {
138        Self {
139            max_mailboxes_per_email: Some(10),
140            max_mailbox_depth: Some(10),
141            max_size_mailbox_name: Some(255),
142            max_size_attachments_per_email: Some(50 * 1024 * 1024), // 50 MB
143            email_query_sort_options: Some(vec![
144                "receivedAt".to_string(),
145                "from".to_string(),
146                "subject".to_string(),
147                "size".to_string(),
148            ]),
149            may_upload_script: Some(false),
150        }
151    }
152}
153
154impl Session {
155    /// Create a new session for a user
156    pub fn new(username: String, account_id: String, base_url: String) -> Self {
157        let mut capabilities = HashMap::new();
158        let mut accounts = HashMap::new();
159        let mut primary_accounts = HashMap::new();
160
161        // Core capability (RFC 8620)
162        let core_cap_uri = "urn:ietf:params:jmap:core".to_string();
163        capabilities.insert(core_cap_uri.clone(), Capability::default());
164
165        // Mail capability (RFC 8621)
166        let mail_cap_uri = "urn:ietf:params:jmap:mail".to_string();
167        capabilities.insert(mail_cap_uri.clone(), Capability::default());
168
169        // Submission capability (RFC 8621)
170        let submission_cap_uri = "urn:ietf:params:jmap:submission".to_string();
171        capabilities.insert(submission_cap_uri.clone(), Capability::default());
172
173        // Create account capabilities
174        let mut account_caps = HashMap::new();
175        account_caps.insert(mail_cap_uri.clone(), AccountCapability::default());
176        account_caps.insert(submission_cap_uri.clone(), AccountCapability::default());
177
178        // Create the account
179        let account = Account {
180            name: username.clone(),
181            is_personal: true,
182            is_read_only: false,
183            account_capabilities: account_caps,
184        };
185
186        accounts.insert(account_id.clone(), account);
187
188        // Set primary accounts for each capability
189        primary_accounts.insert(core_cap_uri, account_id.clone());
190        primary_accounts.insert(mail_cap_uri, account_id.clone());
191        primary_accounts.insert(submission_cap_uri, account_id);
192
193        Self {
194            capabilities,
195            accounts,
196            primary_accounts,
197            username,
198            api_url: format!("{}/jmap", base_url),
199            download_url: format!("{}/download/{{accountId}}/{{blobId}}/{{name}}", base_url),
200            upload_url: format!("{}/upload/{{accountId}}", base_url),
201            event_source_url: Some(format!("{}/eventsource", base_url)),
202            state: "session-state-1".to_string(),
203        }
204    }
205}