Skip to main content

rusmes_jmap/
types.rs

1//! JMAP type definitions
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// JMAP request
8#[derive(Debug, Clone, Deserialize, Serialize)]
9#[serde(rename_all = "camelCase")]
10pub struct JmapRequest {
11    pub using: Vec<String>,
12    pub method_calls: Vec<JmapMethodCall>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub created_ids: Option<serde_json::Value>,
15}
16
17/// JMAP method call
18#[derive(Debug, Clone, Deserialize, Serialize)]
19pub struct JmapMethodCall(pub String, pub serde_json::Value, pub String);
20
21/// JMAP response
22#[derive(Debug, Clone, Default, Deserialize, Serialize)]
23#[serde(rename_all = "camelCase")]
24pub struct JmapResponse {
25    pub method_responses: Vec<JmapMethodResponse>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub session_state: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub created_ids: Option<serde_json::Value>,
30}
31
32/// JMAP method response
33#[derive(Debug, Clone, Deserialize, Serialize)]
34pub struct JmapMethodResponse(pub String, pub serde_json::Value, pub String);
35
36/// JMAP methods
37#[derive(Debug, Clone)]
38pub enum JmapMethod {
39    /// Email/get
40    EmailGet,
41    /// Email/set
42    EmailSet,
43    /// Email/query
44    EmailQuery,
45    /// Mailbox/get
46    MailboxGet,
47    /// Mailbox/set
48    MailboxSet,
49    /// Mailbox/query
50    MailboxQuery,
51}
52
53/// JMAP error types as defined in RFC 8620 Section 3.6
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum JmapErrorType {
56    /// The content type of the request was not "application/json" or the request did not parse as I-JSON.
57    NotJson,
58    /// The request parsed as JSON but did not match the structure defined in RFC 8620 Section 3.3.
59    NotRequest,
60    /// The server has a limit on the number of calls in a single request
61    Limit,
62    /// Unknown capability in "using" property
63    UnknownCapability,
64    /// Unknown method
65    UnknownMethod,
66    /// Invalid arguments to method
67    InvalidArguments,
68    /// Account not found or does not support this data type
69    AccountNotFound,
70    /// Account not supported by this method
71    AccountNotSupportedByMethod,
72    /// Account is read-only
73    AccountReadOnly,
74    /// Server error
75    ServerFail,
76    /// Server is unavailable
77    ServerUnavailable,
78    /// Server has a hard limit on the number of objects
79    ServerPartialFailure,
80}
81
82impl JmapErrorType {
83    /// Get the string representation of the error type
84    pub fn as_str(&self) -> &'static str {
85        match self {
86            Self::NotJson => "urn:ietf:params:jmap:error:notJSON",
87            Self::NotRequest => "urn:ietf:params:jmap:error:notRequest",
88            Self::Limit => "urn:ietf:params:jmap:error:limit",
89            Self::UnknownCapability => "urn:ietf:params:jmap:error:unknownCapability",
90            Self::UnknownMethod => "urn:ietf:params:jmap:error:unknownMethod",
91            Self::InvalidArguments => "urn:ietf:params:jmap:error:invalidArguments",
92            Self::AccountNotFound => "urn:ietf:params:jmap:error:accountNotFound",
93            Self::AccountNotSupportedByMethod => {
94                "urn:ietf:params:jmap:error:accountNotSupportedByMethod"
95            }
96            Self::AccountReadOnly => "urn:ietf:params:jmap:error:accountReadOnly",
97            Self::ServerFail => "urn:ietf:params:jmap:error:serverFail",
98            Self::ServerUnavailable => "urn:ietf:params:jmap:error:serverUnavailable",
99            Self::ServerPartialFailure => "urn:ietf:params:jmap:error:serverPartialFailure",
100        }
101    }
102}
103
104/// JMAP error response
105#[derive(Debug, Clone, Serialize)]
106pub struct JmapError {
107    #[serde(rename = "type")]
108    pub error_type: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub status: Option<u16>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub detail: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub limit: Option<String>,
115}
116
117impl JmapError {
118    /// Create a new JMAP error
119    pub fn new(error_type: JmapErrorType) -> Self {
120        Self {
121            error_type: error_type.as_str().to_string(),
122            status: None,
123            detail: None,
124            limit: None,
125        }
126    }
127
128    /// Set the status code
129    pub fn with_status(mut self, status: u16) -> Self {
130        self.status = Some(status);
131        self
132    }
133
134    /// Set the detail message
135    pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
136        self.detail = Some(detail.into());
137        self
138    }
139
140    /// Set the limit information
141    pub fn with_limit(mut self, limit: impl Into<String>) -> Self {
142        self.limit = Some(limit.into());
143        self
144    }
145}
146
147/// Email address in JMAP format
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct EmailAddress {
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub name: Option<String>,
152    pub email: String,
153}
154
155impl EmailAddress {
156    /// Create a new email address
157    pub fn new(email: String) -> Self {
158        Self { name: None, email }
159    }
160
161    /// Create a new email address with name
162    pub fn with_name(email: String, name: String) -> Self {
163        Self {
164            name: Some(name),
165            email,
166        }
167    }
168}
169
170/// Email object as defined in RFC 8621
171#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct Email {
174    /// Unique identifier for the email
175    pub id: String,
176    /// Blob ID for the raw RFC 5322 message
177    pub blob_id: String,
178    /// Thread ID
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub thread_id: Option<String>,
181    /// Mailbox IDs (map of mailbox ID to boolean)
182    pub mailbox_ids: HashMap<String, bool>,
183    /// Keywords/flags (e.g., $seen, $flagged, $draft)
184    pub keywords: HashMap<String, bool>,
185    /// Size in bytes
186    pub size: u64,
187    /// Time email was received at the server
188    pub received_at: DateTime<Utc>,
189    /// Message-ID header
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub message_id: Option<Vec<String>>,
192    /// In-Reply-To header
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub in_reply_to: Option<Vec<String>>,
195    /// References header
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub references: Option<Vec<String>>,
198    /// Sender header
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub sender: Option<Vec<EmailAddress>>,
201    /// From header
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub from: Option<Vec<EmailAddress>>,
204    /// To header
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub to: Option<Vec<EmailAddress>>,
207    /// Cc header
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub cc: Option<Vec<EmailAddress>>,
210    /// Bcc header
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub bcc: Option<Vec<EmailAddress>>,
213    /// Reply-To header
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub reply_to: Option<Vec<EmailAddress>>,
216    /// Subject header
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub subject: Option<String>,
219    /// Sent-At date from Date header
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub sent_at: Option<DateTime<Utc>>,
222    /// Has attachment
223    pub has_attachment: bool,
224    /// Preview text
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub preview: Option<String>,
227    /// Body values (for body parts)
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub body_values: Option<HashMap<String, EmailBodyValue>>,
230    /// Text body parts
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub text_body: Option<Vec<EmailBodyPart>>,
233    /// HTML body parts
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub html_body: Option<Vec<EmailBodyPart>>,
236    /// Attachments
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub attachments: Option<Vec<EmailBodyPart>>,
239}
240
241/// Email body value
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct EmailBodyValue {
245    pub value: String,
246    pub is_encoding_problem: bool,
247    pub is_truncated: bool,
248}
249
250/// Email body part
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct EmailBodyPart {
254    pub part_id: String,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub blob_id: Option<String>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub size: Option<u64>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub name: Option<String>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub r#type: Option<String>,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub charset: Option<String>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub disposition: Option<String>,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub cid: Option<String>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub language: Option<Vec<String>>,
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub location: Option<String>,
273}
274
275/// Email/get request
276#[derive(Debug, Clone, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct EmailGetRequest {
279    pub account_id: String,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub ids: Option<Vec<String>>,
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub properties: Option<Vec<String>>,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub body_properties: Option<Vec<String>>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub fetch_text_body_values: Option<bool>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub fetch_html_body_values: Option<bool>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub fetch_all_body_values: Option<bool>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub max_body_value_bytes: Option<u64>,
294}
295
296/// Email/get response
297#[derive(Debug, Clone, Serialize)]
298#[serde(rename_all = "camelCase")]
299pub struct EmailGetResponse {
300    pub account_id: String,
301    pub state: String,
302    pub list: Vec<Email>,
303    pub not_found: Vec<String>,
304}
305
306/// Email/set request
307#[derive(Debug, Clone, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct EmailSetRequest {
310    pub account_id: String,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub if_in_state: Option<String>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub create: Option<HashMap<String, EmailSetObject>>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub update: Option<HashMap<String, serde_json::Value>>,
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub destroy: Option<Vec<String>>,
319}
320
321/// Email object for Email/set create
322#[derive(Debug, Clone, Deserialize)]
323#[serde(rename_all = "camelCase")]
324pub struct EmailSetObject {
325    pub mailbox_ids: HashMap<String, bool>,
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub keywords: Option<HashMap<String, bool>>,
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub received_at: Option<DateTime<Utc>>,
330}
331
332/// Email/set response
333#[derive(Debug, Clone, Serialize)]
334#[serde(rename_all = "camelCase")]
335pub struct EmailSetResponse {
336    pub account_id: String,
337    pub old_state: String,
338    pub new_state: String,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub created: Option<HashMap<String, Email>>,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub updated: Option<HashMap<String, Option<Email>>>,
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub destroyed: Option<Vec<String>>,
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub not_created: Option<HashMap<String, JmapSetError>>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub not_updated: Option<HashMap<String, JmapSetError>>,
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub not_destroyed: Option<HashMap<String, JmapSetError>>,
351}
352
353/// JMAP set error
354#[derive(Debug, Clone, Serialize)]
355pub struct JmapSetError {
356    #[serde(rename = "type")]
357    pub error_type: String,
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub description: Option<String>,
360}
361
362/// Email/query request
363#[derive(Debug, Clone, Deserialize)]
364#[serde(rename_all = "camelCase")]
365pub struct EmailQueryRequest {
366    pub account_id: String,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub filter: Option<EmailFilterCondition>,
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub sort: Option<Vec<EmailSort>>,
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub position: Option<i64>,
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub anchor: Option<String>,
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub anchor_offset: Option<i64>,
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub limit: Option<u64>,
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub calculate_total: Option<bool>,
381}
382
383/// Email filter condition
384#[derive(Debug, Clone, Deserialize)]
385#[serde(rename_all = "camelCase")]
386pub struct EmailFilterCondition {
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub in_mailbox: Option<String>,
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub in_mailbox_other_than: Option<Vec<String>>,
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub before: Option<DateTime<Utc>>,
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub after: Option<DateTime<Utc>>,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub min_size: Option<u64>,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub max_size: Option<u64>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub all_in_thread_have_keyword: Option<String>,
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub some_in_thread_have_keyword: Option<String>,
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub none_in_thread_have_keyword: Option<String>,
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub has_keyword: Option<String>,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub not_keyword: Option<String>,
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub has_attachment: Option<bool>,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub text: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub from: Option<String>,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub to: Option<String>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub cc: Option<String>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub bcc: Option<String>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub subject: Option<String>,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub body: Option<String>,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub header: Option<Vec<String>>,
427}
428
429/// Email sort comparator
430#[derive(Debug, Clone, Deserialize)]
431#[serde(rename_all = "camelCase")]
432pub struct EmailSort {
433    pub property: String,
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub is_ascending: Option<bool>,
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub collation: Option<String>,
438}
439
440/// Email/query response
441#[derive(Debug, Clone, Serialize)]
442#[serde(rename_all = "camelCase")]
443pub struct EmailQueryResponse {
444    pub account_id: String,
445    pub query_state: String,
446    pub can_calculate_changes: bool,
447    pub position: i64,
448    pub ids: Vec<String>,
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub total: Option<u64>,
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub limit: Option<u64>,
453}