1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[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#[derive(Debug, Clone, Deserialize, Serialize)]
19pub struct JmapMethodCall(pub String, pub serde_json::Value, pub String);
20
21#[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#[derive(Debug, Clone, Deserialize, Serialize)]
34pub struct JmapMethodResponse(pub String, pub serde_json::Value, pub String);
35
36#[derive(Debug, Clone)]
38pub enum JmapMethod {
39 EmailGet,
41 EmailSet,
43 EmailQuery,
45 MailboxGet,
47 MailboxSet,
49 MailboxQuery,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum JmapErrorType {
56 NotJson,
58 NotRequest,
60 Limit,
62 UnknownCapability,
64 UnknownMethod,
66 InvalidArguments,
68 AccountNotFound,
70 AccountNotSupportedByMethod,
72 AccountReadOnly,
74 ServerFail,
76 ServerUnavailable,
78 ServerPartialFailure,
80 Forbidden,
88}
89
90impl JmapErrorType {
91 pub fn as_str(&self) -> &'static str {
93 match self {
94 Self::NotJson => "urn:ietf:params:jmap:error:notJSON",
95 Self::NotRequest => "urn:ietf:params:jmap:error:notRequest",
96 Self::Limit => "urn:ietf:params:jmap:error:limit",
97 Self::UnknownCapability => "urn:ietf:params:jmap:error:unknownCapability",
98 Self::UnknownMethod => "urn:ietf:params:jmap:error:unknownMethod",
99 Self::InvalidArguments => "urn:ietf:params:jmap:error:invalidArguments",
100 Self::AccountNotFound => "urn:ietf:params:jmap:error:accountNotFound",
101 Self::AccountNotSupportedByMethod => {
102 "urn:ietf:params:jmap:error:accountNotSupportedByMethod"
103 }
104 Self::AccountReadOnly => "urn:ietf:params:jmap:error:accountReadOnly",
105 Self::ServerFail => "urn:ietf:params:jmap:error:serverFail",
106 Self::ServerUnavailable => "urn:ietf:params:jmap:error:serverUnavailable",
107 Self::ServerPartialFailure => "urn:ietf:params:jmap:error:serverPartialFailure",
108 Self::Forbidden => "urn:ietf:params:jmap:error:forbidden",
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Principal {
121 pub username: String,
123 pub account_id: String,
128 pub scopes: Vec<String>,
132}
133
134pub const SCOPE_ADMIN: &str = "rusmes:admin:any-account";
139
140#[doc(hidden)]
147pub fn admin_principal_for_tests() -> Principal {
148 Principal {
149 username: "test-admin".to_string(),
150 account_id: "test-admin-account".to_string(),
151 scopes: vec![SCOPE_ADMIN.to_string()],
152 }
153}
154
155impl Principal {
156 pub fn from_username(username: impl Into<String>) -> Self {
159 let username = username.into();
160 let account_id = derive_account_id(&username);
161 Self {
162 username,
163 account_id,
164 scopes: Vec::new(),
165 }
166 }
167
168 pub fn owns_account(&self, requested_account_id: &str) -> bool {
171 self.account_id == requested_account_id || self.scopes.iter().any(|s| s == SCOPE_ADMIN)
172 }
173}
174
175pub fn derive_account_id(username: &str) -> String {
179 format!("account-{}", username.replace('@', "-"))
180}
181
182#[derive(Debug, Clone, Serialize)]
184pub struct JmapError {
185 #[serde(rename = "type")]
186 pub error_type: String,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub status: Option<u16>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub detail: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub limit: Option<String>,
193}
194
195impl JmapError {
196 pub fn new(error_type: JmapErrorType) -> Self {
198 Self {
199 error_type: error_type.as_str().to_string(),
200 status: None,
201 detail: None,
202 limit: None,
203 }
204 }
205
206 pub fn with_status(mut self, status: u16) -> Self {
208 self.status = Some(status);
209 self
210 }
211
212 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
214 self.detail = Some(detail.into());
215 self
216 }
217
218 pub fn with_limit(mut self, limit: impl Into<String>) -> Self {
220 self.limit = Some(limit.into());
221 self
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct PushSubscription {
229 pub id: String,
231
232 #[serde(rename = "deviceClientId")]
235 pub device_client_id: String,
236
237 pub url: String,
239
240 pub keys: Option<PushKeys>,
243
244 #[serde(skip)]
247 pub verification_code: Option<String>,
248
249 #[serde(skip_serializing_if = "Option::is_none")]
252 pub expires: Option<chrono::DateTime<chrono::Utc>>,
253
254 pub types: Vec<String>,
257
258 #[serde(skip)]
262 pub verified: bool,
263
264 #[serde(skip)]
267 pub principal_id: String,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct PushKeys {
273 pub p256dh: String,
275 pub auth: String,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct EmailAddress {
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub name: Option<String>,
284 pub email: String,
285}
286
287impl EmailAddress {
288 pub fn new(email: String) -> Self {
290 Self { name: None, email }
291 }
292
293 pub fn with_name(email: String, name: String) -> Self {
295 Self {
296 name: Some(name),
297 email,
298 }
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct Email {
306 pub id: String,
308 pub blob_id: String,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub thread_id: Option<String>,
313 pub mailbox_ids: HashMap<String, bool>,
315 pub keywords: HashMap<String, bool>,
317 pub size: u64,
319 pub received_at: DateTime<Utc>,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub message_id: Option<Vec<String>>,
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub in_reply_to: Option<Vec<String>>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub references: Option<Vec<String>>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub sender: Option<Vec<EmailAddress>>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub from: Option<Vec<EmailAddress>>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub to: Option<Vec<EmailAddress>>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub cc: Option<Vec<EmailAddress>>,
342 #[serde(skip_serializing_if = "Option::is_none")]
344 pub bcc: Option<Vec<EmailAddress>>,
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub reply_to: Option<Vec<EmailAddress>>,
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub subject: Option<String>,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub sent_at: Option<DateTime<Utc>>,
354 pub has_attachment: bool,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub preview: Option<String>,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub body_values: Option<HashMap<String, EmailBodyValue>>,
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub text_body: Option<Vec<EmailBodyPart>>,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub html_body: Option<Vec<EmailBodyPart>>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub attachments: Option<Vec<EmailBodyPart>>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct EmailBodyValue {
377 pub value: String,
378 pub is_encoding_problem: bool,
379 pub is_truncated: bool,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct EmailBodyPart {
386 pub part_id: String,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 pub blob_id: Option<String>,
389 #[serde(skip_serializing_if = "Option::is_none")]
390 pub size: Option<u64>,
391 #[serde(skip_serializing_if = "Option::is_none")]
392 pub name: Option<String>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub r#type: Option<String>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub charset: Option<String>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub disposition: Option<String>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub cid: Option<String>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub language: Option<Vec<String>>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub location: Option<String>,
405}
406
407#[derive(Debug, Clone, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct EmailGetRequest {
411 pub account_id: String,
412 #[serde(skip_serializing_if = "Option::is_none")]
413 pub ids: Option<Vec<String>>,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub properties: Option<Vec<String>>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 pub body_properties: Option<Vec<String>>,
418 #[serde(skip_serializing_if = "Option::is_none")]
419 pub fetch_text_body_values: Option<bool>,
420 #[serde(skip_serializing_if = "Option::is_none")]
421 pub fetch_html_body_values: Option<bool>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub fetch_all_body_values: Option<bool>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 pub max_body_value_bytes: Option<u64>,
426}
427
428#[derive(Debug, Clone, Serialize)]
430#[serde(rename_all = "camelCase")]
431pub struct EmailGetResponse {
432 pub account_id: String,
433 pub state: String,
434 pub list: Vec<Email>,
435 pub not_found: Vec<String>,
436}
437
438#[derive(Debug, Clone, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct EmailSetRequest {
442 pub account_id: String,
443 #[serde(skip_serializing_if = "Option::is_none")]
444 pub if_in_state: Option<String>,
445 #[serde(skip_serializing_if = "Option::is_none")]
446 pub create: Option<HashMap<String, EmailSetObject>>,
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub update: Option<HashMap<String, serde_json::Value>>,
449 #[serde(skip_serializing_if = "Option::is_none")]
450 pub destroy: Option<Vec<String>>,
451}
452
453#[derive(Debug, Clone, Deserialize)]
455#[serde(rename_all = "camelCase")]
456pub struct EmailSetObject {
457 pub mailbox_ids: HashMap<String, bool>,
459 #[serde(skip_serializing_if = "Option::is_none")]
461 pub keywords: Option<HashMap<String, bool>>,
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub received_at: Option<DateTime<Utc>>,
465 #[serde(skip_serializing_if = "Option::is_none")]
467 pub from: Option<Vec<EmailAddress>>,
468 #[serde(skip_serializing_if = "Option::is_none")]
470 pub to: Option<Vec<EmailAddress>>,
471 #[serde(skip_serializing_if = "Option::is_none")]
473 pub cc: Option<Vec<EmailAddress>>,
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub bcc: Option<Vec<EmailAddress>>,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub reply_to: Option<Vec<EmailAddress>>,
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub sender: Option<Vec<EmailAddress>>,
483 #[serde(skip_serializing_if = "Option::is_none")]
485 pub subject: Option<String>,
486 #[serde(skip_serializing_if = "Option::is_none")]
488 pub sent_at: Option<DateTime<Utc>>,
489 #[serde(skip_serializing_if = "Option::is_none")]
491 pub in_reply_to: Option<Vec<String>>,
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub references: Option<Vec<String>>,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub message_id: Option<Vec<String>>,
498 #[serde(skip_serializing_if = "Option::is_none")]
500 pub body_values: Option<HashMap<String, EmailBodyValue>>,
501 #[serde(skip_serializing_if = "Option::is_none")]
503 pub text_body: Option<Vec<EmailBodyPart>>,
504 #[serde(skip_serializing_if = "Option::is_none")]
506 pub html_body: Option<Vec<EmailBodyPart>>,
507 #[serde(skip_serializing_if = "Option::is_none")]
509 pub attachments: Option<Vec<EmailBodyPart>>,
510}
511
512#[derive(Debug, Clone, Serialize)]
514#[serde(rename_all = "camelCase")]
515pub struct EmailSetResponse {
516 pub account_id: String,
517 pub old_state: String,
518 pub new_state: String,
519 #[serde(skip_serializing_if = "Option::is_none")]
520 pub created: Option<HashMap<String, Email>>,
521 #[serde(skip_serializing_if = "Option::is_none")]
522 pub updated: Option<HashMap<String, Option<Email>>>,
523 #[serde(skip_serializing_if = "Option::is_none")]
524 pub destroyed: Option<Vec<String>>,
525 #[serde(skip_serializing_if = "Option::is_none")]
526 pub not_created: Option<HashMap<String, JmapSetError>>,
527 #[serde(skip_serializing_if = "Option::is_none")]
528 pub not_updated: Option<HashMap<String, JmapSetError>>,
529 #[serde(skip_serializing_if = "Option::is_none")]
530 pub not_destroyed: Option<HashMap<String, JmapSetError>>,
531}
532
533#[derive(Debug, Clone, Serialize)]
535pub struct JmapSetError {
536 #[serde(rename = "type")]
537 pub error_type: String,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 pub description: Option<String>,
540}
541
542#[derive(Debug, Clone, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct EmailQueryRequest {
546 pub account_id: String,
547 #[serde(skip_serializing_if = "Option::is_none")]
548 pub filter: Option<EmailFilterCondition>,
549 #[serde(skip_serializing_if = "Option::is_none")]
550 pub sort: Option<Vec<EmailSort>>,
551 #[serde(skip_serializing_if = "Option::is_none")]
552 pub position: Option<i64>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 pub anchor: Option<String>,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 pub anchor_offset: Option<i64>,
557 #[serde(skip_serializing_if = "Option::is_none")]
558 pub limit: Option<u64>,
559 #[serde(skip_serializing_if = "Option::is_none")]
560 pub calculate_total: Option<bool>,
561}
562
563#[derive(Debug, Clone, Deserialize)]
565#[serde(rename_all = "camelCase")]
566pub struct EmailFilterCondition {
567 #[serde(skip_serializing_if = "Option::is_none")]
568 pub in_mailbox: Option<String>,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub in_mailbox_other_than: Option<Vec<String>>,
571 #[serde(skip_serializing_if = "Option::is_none")]
572 pub before: Option<DateTime<Utc>>,
573 #[serde(skip_serializing_if = "Option::is_none")]
574 pub after: Option<DateTime<Utc>>,
575 #[serde(skip_serializing_if = "Option::is_none")]
576 pub min_size: Option<u64>,
577 #[serde(skip_serializing_if = "Option::is_none")]
578 pub max_size: Option<u64>,
579 #[serde(skip_serializing_if = "Option::is_none")]
580 pub all_in_thread_have_keyword: Option<String>,
581 #[serde(skip_serializing_if = "Option::is_none")]
582 pub some_in_thread_have_keyword: Option<String>,
583 #[serde(skip_serializing_if = "Option::is_none")]
584 pub none_in_thread_have_keyword: Option<String>,
585 #[serde(skip_serializing_if = "Option::is_none")]
586 pub has_keyword: Option<String>,
587 #[serde(skip_serializing_if = "Option::is_none")]
588 pub not_keyword: Option<String>,
589 #[serde(skip_serializing_if = "Option::is_none")]
590 pub has_attachment: Option<bool>,
591 #[serde(skip_serializing_if = "Option::is_none")]
592 pub text: Option<String>,
593 #[serde(skip_serializing_if = "Option::is_none")]
594 pub from: Option<String>,
595 #[serde(skip_serializing_if = "Option::is_none")]
596 pub to: Option<String>,
597 #[serde(skip_serializing_if = "Option::is_none")]
598 pub cc: Option<String>,
599 #[serde(skip_serializing_if = "Option::is_none")]
600 pub bcc: Option<String>,
601 #[serde(skip_serializing_if = "Option::is_none")]
602 pub subject: Option<String>,
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub body: Option<String>,
605 #[serde(skip_serializing_if = "Option::is_none")]
606 pub header: Option<Vec<String>>,
607}
608
609#[derive(Debug, Clone, Deserialize)]
611#[serde(rename_all = "camelCase")]
612pub struct EmailSort {
613 pub property: String,
614 #[serde(skip_serializing_if = "Option::is_none")]
615 pub is_ascending: Option<bool>,
616 #[serde(skip_serializing_if = "Option::is_none")]
617 pub collation: Option<String>,
618}
619
620#[derive(Debug, Clone, Serialize)]
622#[serde(rename_all = "camelCase")]
623pub struct EmailQueryResponse {
624 pub account_id: String,
625 pub query_state: String,
626 pub can_calculate_changes: bool,
627 pub position: i64,
628 pub ids: Vec<String>,
629 #[serde(skip_serializing_if = "Option::is_none")]
630 pub total: Option<u64>,
631 #[serde(skip_serializing_if = "Option::is_none")]
632 pub limit: Option<u64>,
633}