use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JmapRequest {
pub using: Vec<String>,
pub method_calls: Vec<JmapMethodCall>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_ids: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct JmapMethodCall(pub String, pub serde_json::Value, pub String);
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JmapResponse {
pub method_responses: Vec<JmapMethodResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_ids: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct JmapMethodResponse(pub String, pub serde_json::Value, pub String);
#[derive(Debug, Clone)]
pub enum JmapMethod {
EmailGet,
EmailSet,
EmailQuery,
MailboxGet,
MailboxSet,
MailboxQuery,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JmapErrorType {
NotJson,
NotRequest,
Limit,
UnknownCapability,
UnknownMethod,
InvalidArguments,
AccountNotFound,
AccountNotSupportedByMethod,
AccountReadOnly,
ServerFail,
ServerUnavailable,
ServerPartialFailure,
Forbidden,
}
impl JmapErrorType {
pub fn as_str(&self) -> &'static str {
match self {
Self::NotJson => "urn:ietf:params:jmap:error:notJSON",
Self::NotRequest => "urn:ietf:params:jmap:error:notRequest",
Self::Limit => "urn:ietf:params:jmap:error:limit",
Self::UnknownCapability => "urn:ietf:params:jmap:error:unknownCapability",
Self::UnknownMethod => "urn:ietf:params:jmap:error:unknownMethod",
Self::InvalidArguments => "urn:ietf:params:jmap:error:invalidArguments",
Self::AccountNotFound => "urn:ietf:params:jmap:error:accountNotFound",
Self::AccountNotSupportedByMethod => {
"urn:ietf:params:jmap:error:accountNotSupportedByMethod"
}
Self::AccountReadOnly => "urn:ietf:params:jmap:error:accountReadOnly",
Self::ServerFail => "urn:ietf:params:jmap:error:serverFail",
Self::ServerUnavailable => "urn:ietf:params:jmap:error:serverUnavailable",
Self::ServerPartialFailure => "urn:ietf:params:jmap:error:serverPartialFailure",
Self::Forbidden => "urn:ietf:params:jmap:error:forbidden",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Principal {
pub username: String,
pub account_id: String,
pub scopes: Vec<String>,
}
pub const SCOPE_ADMIN: &str = "rusmes:admin:any-account";
#[doc(hidden)]
pub fn admin_principal_for_tests() -> Principal {
Principal {
username: "test-admin".to_string(),
account_id: "test-admin-account".to_string(),
scopes: vec![SCOPE_ADMIN.to_string()],
}
}
impl Principal {
pub fn from_username(username: impl Into<String>) -> Self {
let username = username.into();
let account_id = derive_account_id(&username);
Self {
username,
account_id,
scopes: Vec::new(),
}
}
pub fn owns_account(&self, requested_account_id: &str) -> bool {
self.account_id == requested_account_id || self.scopes.iter().any(|s| s == SCOPE_ADMIN)
}
}
pub fn derive_account_id(username: &str) -> String {
format!("account-{}", username.replace('@', "-"))
}
#[derive(Debug, Clone, Serialize)]
pub struct JmapError {
#[serde(rename = "type")]
pub error_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<String>,
}
impl JmapError {
pub fn new(error_type: JmapErrorType) -> Self {
Self {
error_type: error_type.as_str().to_string(),
status: None,
detail: None,
limit: None,
}
}
pub fn with_status(mut self, status: u16) -> Self {
self.status = Some(status);
self
}
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn with_limit(mut self, limit: impl Into<String>) -> Self {
self.limit = Some(limit.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushSubscription {
pub id: String,
#[serde(rename = "deviceClientId")]
pub device_client_id: String,
pub url: String,
pub keys: Option<PushKeys>,
#[serde(skip)]
pub verification_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<chrono::DateTime<chrono::Utc>>,
pub types: Vec<String>,
#[serde(skip)]
pub verified: bool,
#[serde(skip)]
pub principal_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushKeys {
pub p256dh: String,
pub auth: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailAddress {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub email: String,
}
impl EmailAddress {
pub fn new(email: String) -> Self {
Self { name: None, email }
}
pub fn with_name(email: String, name: String) -> Self {
Self {
name: Some(name),
email,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Email {
pub id: String,
pub blob_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_id: Option<String>,
pub mailbox_ids: HashMap<String, bool>,
pub keywords: HashMap<String, bool>,
pub size: u64,
pub received_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub in_reply_to: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub references: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sender: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sent_at: Option<DateTime<Utc>>,
pub has_attachment: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_values: Option<HashMap<String, EmailBodyValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_body: Option<Vec<EmailBodyPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub html_body: Option<Vec<EmailBodyPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<EmailBodyPart>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailBodyValue {
pub value: String,
pub is_encoding_problem: bool,
pub is_truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailBodyPart {
pub part_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub charset: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disposition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailGetRequest {
pub account_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_properties: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fetch_text_body_values: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fetch_html_body_values: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fetch_all_body_values: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_body_value_bytes: Option<u64>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailGetResponse {
pub account_id: String,
pub state: String,
pub list: Vec<Email>,
pub not_found: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailSetRequest {
pub account_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub if_in_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub create: Option<HashMap<String, EmailSetObject>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destroy: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailSetObject {
pub mailbox_ids: HashMap<String, bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keywords: Option<HashMap<String, bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub received_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sender: Option<Vec<EmailAddress>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sent_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub in_reply_to: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub references: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_values: Option<HashMap<String, EmailBodyValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_body: Option<Vec<EmailBodyPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub html_body: Option<Vec<EmailBodyPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<EmailBodyPart>>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailSetResponse {
pub account_id: String,
pub old_state: String,
pub new_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<HashMap<String, Email>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated: Option<HashMap<String, Option<Email>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destroyed: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_created: Option<HashMap<String, JmapSetError>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_updated: Option<HashMap<String, JmapSetError>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_destroyed: Option<HashMap<String, JmapSetError>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct JmapSetError {
#[serde(rename = "type")]
pub error_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailQueryRequest {
pub account_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<EmailFilterCondition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort: Option<Vec<EmailSort>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub anchor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub anchor_offset: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub calculate_total: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailFilterCondition {
#[serde(skip_serializing_if = "Option::is_none")]
pub in_mailbox: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub in_mailbox_other_than: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub all_in_thread_have_keyword: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub some_in_thread_have_keyword: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub none_in_thread_have_keyword: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_keyword: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not_keyword: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_attachment: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailSort {
pub property: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_ascending: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collation: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EmailQueryResponse {
pub account_id: String,
pub query_state: String,
pub can_calculate_changes: bool,
pub position: i64,
pub ids: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
}