Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
object::{email::EmailComparator, file_node::FileNodeComparator},
|
||||
response::serialize::serialize_hex,
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use types::{id::Id, type_state::DataType};
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct Session {
|
||||
#[serde(rename(serialize = "capabilities"))]
|
||||
pub capabilities: VecMap<Capability, Capabilities>,
|
||||
#[serde(rename(serialize = "accounts"))]
|
||||
pub accounts: VecMap<Id, Account>,
|
||||
#[serde(rename(serialize = "primaryAccounts"))]
|
||||
pub primary_accounts: VecMap<Capability, Id>,
|
||||
#[serde(rename(serialize = "username"))]
|
||||
pub username: String,
|
||||
#[serde(rename(serialize = "apiUrl"))]
|
||||
pub api_url: String,
|
||||
#[serde(rename(serialize = "downloadUrl"))]
|
||||
pub download_url: String,
|
||||
#[serde(rename(serialize = "uploadUrl"))]
|
||||
pub upload_url: String,
|
||||
#[serde(rename(serialize = "eventSourceUrl"))]
|
||||
pub event_source_url: String,
|
||||
#[serde(rename(serialize = "state"))]
|
||||
#[serde(serialize_with = "serialize_hex")]
|
||||
pub state: u32,
|
||||
#[serde(skip)]
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct Account {
|
||||
#[serde(rename(serialize = "name"))]
|
||||
pub name: String,
|
||||
#[serde(rename(serialize = "isPersonal"))]
|
||||
pub is_personal: bool,
|
||||
#[serde(rename(serialize = "isReadOnly"))]
|
||||
pub is_read_only: bool,
|
||||
#[serde(rename(serialize = "accountCapabilities"))]
|
||||
pub account_capabilities: VecMap<Capability, Capabilities>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, serde::Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Capability {
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:core"))]
|
||||
Core = 1 << 0,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:mail"))]
|
||||
Mail = 1 << 1,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:submission"))]
|
||||
Submission = 1 << 2,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:vacationresponse"))]
|
||||
VacationResponse = 1 << 3,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:contacts"))]
|
||||
Contacts = 1 << 4,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:contacts:parse"))]
|
||||
ContactsParse = 1 << 5,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:calendars"))]
|
||||
Calendars = 1 << 6,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:calendars:parse"))]
|
||||
CalendarsParse = 1 << 7,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:websocket"))]
|
||||
WebSocket = 1 << 8,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:sieve"))]
|
||||
Sieve = 1 << 9,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:blob"))]
|
||||
Blob = 1 << 10,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:quota"))]
|
||||
Quota = 1 << 11,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:principals"))]
|
||||
Principals = 1 << 12,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:principals:owner"))]
|
||||
PrincipalsOwner = 1 << 13,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:principals:availability"))]
|
||||
PrincipalsAvailability = 1 << 14,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:filenode"))]
|
||||
FileNode = 1 << 15,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:mail:share"))]
|
||||
MailShare = 1 << 16,
|
||||
#[serde(rename(serialize = "urn:stalwart:jmap"))]
|
||||
Stalwart = 1 << 17,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:webpush-vapid"))]
|
||||
WebPushVapid = 1 << 18,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:emailpush"))]
|
||||
EmailPush = 1 << 19,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct CapabilityIds(pub u32);
|
||||
|
||||
impl CapabilityIds {
|
||||
pub fn contains(&self, capability: Capability) -> bool {
|
||||
self.0 & capability as u32 != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Capabilities {
|
||||
Core(CoreCapabilities),
|
||||
Mail(MailCapabilities),
|
||||
Submission(SubmissionCapabilities),
|
||||
WebSocket(WebSocketCapabilities),
|
||||
SieveAccount(SieveAccountCapabilities),
|
||||
SieveSession(SieveSessionCapabilities),
|
||||
Blob(BlobCapabilities),
|
||||
Contacts(ContactsCapabilities),
|
||||
Principals(PrincipalCapabilities),
|
||||
PrincipalsAvailability(PrincipalAvailabilityCapabilities),
|
||||
Calendar(CalendarCapabilities),
|
||||
FileNode(FileNodeCapabilities),
|
||||
WebPush(WebPushCapabilities),
|
||||
Empty(EmptyCapabilities),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct CoreCapabilities {
|
||||
#[serde(rename(serialize = "maxSizeUpload"))]
|
||||
pub max_size_upload: u64,
|
||||
#[serde(rename(serialize = "maxConcurrentUpload"))]
|
||||
pub max_concurrent_upload: u64,
|
||||
#[serde(rename(serialize = "maxSizeRequest"))]
|
||||
pub max_size_request: u64,
|
||||
#[serde(rename(serialize = "maxConcurrentRequests"))]
|
||||
pub max_concurrent_requests: u64,
|
||||
#[serde(rename(serialize = "maxCallsInRequest"))]
|
||||
pub max_calls_in_request: u64,
|
||||
#[serde(rename(serialize = "maxObjectsInGet"))]
|
||||
pub max_objects_in_get: u64,
|
||||
#[serde(rename(serialize = "maxObjectsInSet"))]
|
||||
pub max_objects_in_set: u64,
|
||||
#[serde(rename(serialize = "collationAlgorithms"))]
|
||||
pub collation_algorithms: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct WebSocketCapabilities {
|
||||
#[serde(rename(serialize = "url"))]
|
||||
pub url: String,
|
||||
#[serde(rename(serialize = "supportsPush"))]
|
||||
pub supports_push: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct SieveSessionCapabilities {
|
||||
#[serde(rename(serialize = "implementation"))]
|
||||
pub implementation: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct SieveAccountCapabilities {
|
||||
#[serde(rename(serialize = "maxSizeScriptName"))]
|
||||
pub max_script_name: u64,
|
||||
#[serde(rename(serialize = "maxSizeScript"))]
|
||||
pub max_script_size: u64,
|
||||
#[serde(rename(serialize = "maxNumberScripts"))]
|
||||
pub max_scripts: u64,
|
||||
#[serde(rename(serialize = "maxNumberRedirects"))]
|
||||
pub max_redirects: u64,
|
||||
#[serde(rename(serialize = "sieveExtensions"))]
|
||||
pub extensions: Vec<String>,
|
||||
#[serde(rename(serialize = "notificationMethods"))]
|
||||
pub notification_methods: Option<Vec<String>>,
|
||||
#[serde(rename(serialize = "externalLists"))]
|
||||
pub ext_lists: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct MailCapabilities {
|
||||
#[serde(rename(serialize = "maxMailboxesPerEmail"))]
|
||||
pub max_mailboxes_per_email: Option<u64>,
|
||||
#[serde(rename(serialize = "maxMailboxDepth"))]
|
||||
pub max_mailbox_depth: u64,
|
||||
#[serde(rename(serialize = "maxSizeMailboxName"))]
|
||||
pub max_size_mailbox_name: u64,
|
||||
#[serde(rename(serialize = "maxSizeAttachmentsPerEmail"))]
|
||||
pub max_size_attachments_per_email: u64,
|
||||
#[serde(rename(serialize = "emailQuerySortOptions"))]
|
||||
pub email_query_sort_options: Vec<EmailComparator>,
|
||||
#[serde(rename(serialize = "mayCreateTopLevelMailbox"))]
|
||||
pub may_create_top_level_mailbox: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct SubmissionCapabilities {
|
||||
#[serde(rename(serialize = "maxDelayedSend"))]
|
||||
pub max_delayed_send: u64,
|
||||
#[serde(rename(serialize = "submissionExtensions"))]
|
||||
pub submission_extensions: VecMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct BlobCapabilities {
|
||||
#[serde(rename(serialize = "maxSizeBlobSet"))]
|
||||
pub max_size_blob_set: u64,
|
||||
#[serde(rename(serialize = "maxDataSources"))]
|
||||
pub max_data_sources: u64,
|
||||
#[serde(rename(serialize = "supportedTypeNames"))]
|
||||
pub supported_type_names: Vec<DataType>,
|
||||
#[serde(rename(serialize = "supportedDigestAlgorithms"))]
|
||||
pub supported_digest_algorithms: Vec<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct CalendarCapabilities {
|
||||
#[serde(rename(serialize = "maxCalendarsPerEvent"))]
|
||||
pub max_calendars_per_event: Option<u64>,
|
||||
#[serde(rename(serialize = "minDateTime"))]
|
||||
pub min_date_time: UTCDate,
|
||||
#[serde(rename(serialize = "maxDateTime"))]
|
||||
pub max_date_time: UTCDate,
|
||||
#[serde(rename(serialize = "maxExpandedQueryDuration"))]
|
||||
pub max_expanded_query_duration: String,
|
||||
#[serde(rename(serialize = "maxParticipantsPerEvent"))]
|
||||
pub max_participants_per_event: Option<u64>,
|
||||
#[serde(rename(serialize = "mayCreateCalendar"))]
|
||||
pub may_create_calendar: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct ContactsCapabilities {
|
||||
#[serde(rename(serialize = "maxAddressBooksPerCard"))]
|
||||
pub max_address_books_per_card: Option<u64>,
|
||||
#[serde(rename(serialize = "mayCreateAddressBook"))]
|
||||
pub may_create_address_book: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PrincipalAvailabilityCapabilities {
|
||||
#[serde(rename(serialize = "maxAvailabilityDuration"))]
|
||||
pub max_availability_duration: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PrincipalCapabilities {
|
||||
#[serde(rename(serialize = "currentUserPrincipalId"))]
|
||||
pub current_user_principal_id: Option<Id>,
|
||||
}
|
||||
|
||||
/*#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PrincipalOwnerCapabilities {
|
||||
#[serde(rename(serialize = "accountIdForPrincipal"))]
|
||||
pub account_id_for_principal: Id,
|
||||
|
||||
#[serde(rename(serialize = "principalId"))]
|
||||
pub principal_id: Id,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PrincipalCalendarCapabilities {
|
||||
#[serde(rename(serialize = "accountIdForPrincipal"))]
|
||||
pub account_id_for_principal: Option<Id>,
|
||||
#[serde(rename(serialize = "mayGetAvailability"))]
|
||||
pub may_get_availability: bool,
|
||||
#[serde(rename(serialize = "mayShareWith"))]
|
||||
pub may_share_with: bool,
|
||||
#[serde(rename(serialize = "calendarAddress"))]
|
||||
pub calendar_address: String,
|
||||
}*/
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct FileNodeCapabilities {
|
||||
#[serde(rename(serialize = "maxFileNodeDepth"))]
|
||||
pub max_file_node_depth: Option<u64>,
|
||||
#[serde(rename(serialize = "maxSizeFileNodeName"))]
|
||||
pub max_size_file_node_name: u64,
|
||||
#[serde(rename(serialize = "forbiddenNameChars"))]
|
||||
pub forbidden_name_chars: Option<String>,
|
||||
#[serde(rename(serialize = "forbiddenNodeNames"))]
|
||||
pub forbidden_node_names: Option<Vec<String>>,
|
||||
#[serde(rename(serialize = "fileNodeQuerySortOptions"))]
|
||||
pub file_node_query_sort_options: Vec<FileNodeComparator>,
|
||||
#[serde(rename(serialize = "mayCreateTopLevelFileNode"))]
|
||||
pub may_create_top_level_file_node: bool,
|
||||
#[serde(rename(serialize = "caseInsensitiveNames"))]
|
||||
pub case_insensitive_names: bool,
|
||||
#[serde(rename(serialize = "webTrashUrl"))]
|
||||
pub web_trash_url: Option<String>,
|
||||
#[serde(rename(serialize = "webUrlTemplate"))]
|
||||
pub web_url_template: Option<String>,
|
||||
#[serde(rename(serialize = "webWriteUrlTemplate"))]
|
||||
pub web_write_url_template: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct WebPushCapabilities {
|
||||
#[serde(rename(serialize = "applicationServerKey"))]
|
||||
pub application_server_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize)]
|
||||
pub struct EmptyCapabilities {}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct BaseCapabilities {
|
||||
pub session: VecMap<Capability, Capabilities>,
|
||||
pub account: AHashMap<Capability, Capabilities>,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Capability::Core => "urn:ietf:params:jmap:core",
|
||||
Capability::Mail => "urn:ietf:params:jmap:mail",
|
||||
Capability::Submission => "urn:ietf:params:jmap:submission",
|
||||
Capability::VacationResponse => "urn:ietf:params:jmap:vacationresponse",
|
||||
Capability::Contacts => "urn:ietf:params:jmap:contacts",
|
||||
Capability::ContactsParse => "urn:ietf:params:jmap:contacts:parse",
|
||||
Capability::Calendars => "urn:ietf:params:jmap:calendars",
|
||||
Capability::CalendarsParse => "urn:ietf:params:jmap:calendars:parse",
|
||||
Capability::WebSocket => "urn:ietf:params:jmap:websocket",
|
||||
Capability::Sieve => "urn:ietf:params:jmap:sieve",
|
||||
Capability::Blob => "urn:ietf:params:jmap:blob",
|
||||
Capability::Quota => "urn:ietf:params:jmap:quota",
|
||||
Capability::Principals => "urn:ietf:params:jmap:principals",
|
||||
Capability::PrincipalsOwner => "urn:ietf:params:jmap:principals:owner",
|
||||
Capability::PrincipalsAvailability => "urn:ietf:params:jmap:principals:availability",
|
||||
Capability::FileNode => "urn:ietf:params:jmap:filenode",
|
||||
Capability::MailShare => "urn:ietf:params:jmap:mail:share",
|
||||
Capability::Stalwart => "urn:stalwart:jmap",
|
||||
Capability::WebPushVapid => "urn:ietf:params:jmap:webpush-vapid",
|
||||
Capability::EmailPush => "urn:ietf:params:jmap:emailpush",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_capabilities() -> &'static [Capability] {
|
||||
&[
|
||||
Capability::Core,
|
||||
Capability::Mail,
|
||||
Capability::Submission,
|
||||
Capability::VacationResponse,
|
||||
Capability::Contacts,
|
||||
Capability::ContactsParse,
|
||||
Capability::Calendars,
|
||||
Capability::CalendarsParse,
|
||||
Capability::WebSocket,
|
||||
Capability::Sieve,
|
||||
Capability::Blob,
|
||||
Capability::Quota,
|
||||
Capability::Principals,
|
||||
Capability::PrincipalsAvailability,
|
||||
Capability::FileNode,
|
||||
Capability::MailShare,
|
||||
Capability::Stalwart,
|
||||
Capability::WebPushVapid,
|
||||
Capability::EmailPush,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(base_url: impl Into<String>, base_capabilities: &BaseCapabilities) -> Session {
|
||||
let base_url = base_url.into();
|
||||
let mut capabilities = base_capabilities.session.clone();
|
||||
capabilities.append(
|
||||
Capability::WebSocket,
|
||||
Capabilities::WebSocket(WebSocketCapabilities::new(&base_url)),
|
||||
);
|
||||
|
||||
Session {
|
||||
capabilities,
|
||||
accounts: VecMap::new(),
|
||||
primary_accounts: VecMap::new(),
|
||||
username: "".to_string(),
|
||||
api_url: format!("{}/jmap/", base_url),
|
||||
download_url: format!(
|
||||
"{}/jmap/download/{{accountId}}/{{blobId}}/{{name}}?accept={{type}}",
|
||||
base_url
|
||||
),
|
||||
upload_url: format!("{}/jmap/upload/{{accountId}}/", base_url),
|
||||
event_source_url: format!(
|
||||
"{}/jmap/eventsource/?types={{types}}&closeafter={{closeafter}}&ping={{ping}}",
|
||||
base_url
|
||||
),
|
||||
base_url,
|
||||
state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, state: u32) {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &str {
|
||||
&self.api_url
|
||||
}
|
||||
|
||||
pub fn base_url(&self) -> &str {
|
||||
&self.base_url
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SieveSessionCapabilities {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
implementation: "Stalwart v1.0.0",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocketCapabilities {
|
||||
pub fn new(base_url: &str) -> Self {
|
||||
WebSocketCapabilities {
|
||||
url: format!(
|
||||
"ws{}/jmap/ws",
|
||||
base_url.strip_prefix("http").unwrap_or_default()
|
||||
),
|
||||
supports_push: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
pub fn to_account_capabilities(
|
||||
&self,
|
||||
current_user_principal_id: Option<Id>,
|
||||
may_create: bool,
|
||||
) -> Capabilities {
|
||||
match self {
|
||||
Capabilities::Contacts(contacts_capabilities) => {
|
||||
Capabilities::Contacts(ContactsCapabilities {
|
||||
may_create_address_book: may_create,
|
||||
..contacts_capabilities.clone()
|
||||
})
|
||||
}
|
||||
Capabilities::Principals(_) => Capabilities::Principals(PrincipalCapabilities {
|
||||
current_user_principal_id,
|
||||
}),
|
||||
Capabilities::Calendar(calendar_capabilities) => {
|
||||
Capabilities::Calendar(CalendarCapabilities {
|
||||
may_create_calendar: may_create,
|
||||
..calendar_capabilities.clone()
|
||||
})
|
||||
}
|
||||
Capabilities::FileNode(file_node_capabilities) => {
|
||||
Capabilities::FileNode(FileNodeCapabilities {
|
||||
may_create_top_level_file_node: may_create,
|
||||
..file_node_capabilities.clone()
|
||||
})
|
||||
}
|
||||
_ => self.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(s.as_bytes(),
|
||||
"urn:ietf:params:jmap:core" => Capability::Core,
|
||||
"urn:ietf:params:jmap:mail" => Capability::Mail,
|
||||
"urn:ietf:params:jmap:submission" => Capability::Submission,
|
||||
"urn:ietf:params:jmap:vacationresponse" => Capability::VacationResponse,
|
||||
"urn:ietf:params:jmap:contacts" => Capability::Contacts,
|
||||
"urn:ietf:params:jmap:calendars" => Capability::Calendars,
|
||||
"urn:ietf:params:jmap:websocket" => Capability::WebSocket,
|
||||
"urn:ietf:params:jmap:sieve" => Capability::Sieve,
|
||||
"urn:ietf:params:jmap:blob" => Capability::Blob,
|
||||
"urn:ietf:params:jmap:quota" => Capability::Quota,
|
||||
"urn:ietf:params:jmap:principals" => Capability::Principals,
|
||||
"urn:ietf:params:jmap:principals:owner" => Capability::PrincipalsOwner,
|
||||
"urn:ietf:params:jmap:filenode" => Capability::FileNode,
|
||||
"urn:ietf:params:jmap:principals:availability" => Capability::PrincipalsAvailability,
|
||||
"urn:ietf:params:jmap:contacts:parse" => Capability::ContactsParse,
|
||||
"urn:ietf:params:jmap:calendars:parse" => Capability::CalendarsParse,
|
||||
"urn:ietf:params:jmap:mail:share" => Capability::MailShare,
|
||||
"urn:stalwart:jmap" => Capability::Stalwart,
|
||||
"urn:ietf:params:jmap:webpush-vapid" => Capability::WebPushVapid,
|
||||
"urn:ietf:params:jmap:emailpush" => Capability::EmailPush,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for CapabilityIds {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct CapabilityIdsVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for CapabilityIdsVisitor {
|
||||
type Value = CapabilityIds;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("an array of capability strings")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut capability_flags = 0u32;
|
||||
|
||||
while let Some(capability_str) = seq.next_element::<std::borrow::Cow<str>>()? {
|
||||
let capability =
|
||||
Capability::parse(capability_str.as_ref()).ok_or_else(|| {
|
||||
serde::de::Error::custom(format!(
|
||||
"Unknown capability: {capability_str:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
capability_flags |= capability as u32;
|
||||
}
|
||||
|
||||
Ok(CapabilityIds(capability_flags))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(CapabilityIdsVisitor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use serde::{
|
||||
Deserializer,
|
||||
de::{self, MapAccess, Visitor},
|
||||
};
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
pub trait DeserializeArguments<'de> {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: MapAccess<'de>;
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for () {
|
||||
fn deserialize_argument<A>(&mut self, _key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let _: de::IgnoredAny = map.next_value()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_request<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
T: DeserializeArguments<'de> + Default,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct DirectArgumentsVisitor<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> DirectArgumentsVisitor<T> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for DirectArgumentsVisitor<T>
|
||||
where
|
||||
T: DeserializeArguments<'de> + Default,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a JMAP request object")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let mut target = T::default();
|
||||
|
||||
while let Some(key) = map.next_key::<&str>()? {
|
||||
target
|
||||
.deserialize_argument(key, &mut map)
|
||||
.map_err(de::Error::custom)?;
|
||||
}
|
||||
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(DirectArgumentsVisitor::<T>::new())
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::request::capability::Capability;
|
||||
use registry::{
|
||||
schema::prelude::{OBJ_SINGLETON, ObjectType},
|
||||
types::EnumImpl,
|
||||
};
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MethodName {
|
||||
pub obj: MethodObject,
|
||||
pub fnc: MethodFunction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MethodObject {
|
||||
Email,
|
||||
Mailbox,
|
||||
Core,
|
||||
Blob,
|
||||
PushSubscription,
|
||||
Thread,
|
||||
SearchSnippet,
|
||||
Identity,
|
||||
EmailSubmission,
|
||||
VacationResponse,
|
||||
SieveScript,
|
||||
Principal,
|
||||
Quota,
|
||||
Calendar,
|
||||
CalendarEvent,
|
||||
CalendarEventNotification,
|
||||
AddressBook,
|
||||
ContactCard,
|
||||
FileNode,
|
||||
ParticipantIdentity,
|
||||
ShareNotification,
|
||||
Registry(ObjectType),
|
||||
}
|
||||
|
||||
impl MethodObject {
|
||||
pub fn capability(&self) -> Capability {
|
||||
match self {
|
||||
MethodObject::Email
|
||||
| MethodObject::Mailbox
|
||||
| MethodObject::Thread
|
||||
| MethodObject::SearchSnippet => Capability::Mail,
|
||||
MethodObject::Core | MethodObject::PushSubscription => Capability::Core,
|
||||
MethodObject::Blob => Capability::Blob,
|
||||
MethodObject::Identity | MethodObject::EmailSubmission => Capability::Submission,
|
||||
MethodObject::VacationResponse => Capability::VacationResponse,
|
||||
MethodObject::SieveScript => Capability::Sieve,
|
||||
MethodObject::Principal | MethodObject::ShareNotification => Capability::Principals,
|
||||
MethodObject::Quota => Capability::Quota,
|
||||
MethodObject::Calendar
|
||||
| MethodObject::CalendarEvent
|
||||
| MethodObject::CalendarEventNotification
|
||||
| MethodObject::ParticipantIdentity => Capability::Calendars,
|
||||
MethodObject::AddressBook | MethodObject::ContactCard => Capability::Contacts,
|
||||
MethodObject::FileNode => Capability::FileNode,
|
||||
MethodObject::Registry(_) => Capability::Stalwart,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MethodFunction {
|
||||
Get,
|
||||
Set,
|
||||
Changes,
|
||||
Query,
|
||||
QueryChanges,
|
||||
Copy,
|
||||
Import,
|
||||
Parse,
|
||||
Validate,
|
||||
Lookup,
|
||||
Upload,
|
||||
Echo,
|
||||
GetAvailability,
|
||||
}
|
||||
|
||||
impl Display for MethodName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl MethodName {
|
||||
pub fn new(obj: MethodObject, fnc: MethodFunction) -> Self {
|
||||
Self { obj, fnc }
|
||||
}
|
||||
|
||||
pub fn error() -> Self {
|
||||
Self {
|
||||
obj: MethodObject::Thread,
|
||||
fnc: MethodFunction::Echo,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Cow<'static, str> {
|
||||
match (self.fnc, self.obj) {
|
||||
(MethodFunction::Get, MethodObject::PushSubscription) => "PushSubscription/get",
|
||||
(MethodFunction::Set, MethodObject::PushSubscription) => "PushSubscription/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Mailbox) => "Mailbox/get",
|
||||
(MethodFunction::Changes, MethodObject::Mailbox) => "Mailbox/changes",
|
||||
(MethodFunction::Query, MethodObject::Mailbox) => "Mailbox/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::Mailbox) => "Mailbox/queryChanges",
|
||||
(MethodFunction::Set, MethodObject::Mailbox) => "Mailbox/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Thread) => "Thread/get",
|
||||
(MethodFunction::Changes, MethodObject::Thread) => "Thread/changes",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Email) => "Email/get",
|
||||
(MethodFunction::Changes, MethodObject::Email) => "Email/changes",
|
||||
(MethodFunction::Query, MethodObject::Email) => "Email/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::Email) => "Email/queryChanges",
|
||||
(MethodFunction::Set, MethodObject::Email) => "Email/set",
|
||||
(MethodFunction::Copy, MethodObject::Email) => "Email/copy",
|
||||
(MethodFunction::Import, MethodObject::Email) => "Email/import",
|
||||
(MethodFunction::Parse, MethodObject::Email) => "Email/parse",
|
||||
|
||||
(MethodFunction::Get, MethodObject::SearchSnippet) => "SearchSnippet/get",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Identity) => "Identity/get",
|
||||
(MethodFunction::Changes, MethodObject::Identity) => "Identity/changes",
|
||||
(MethodFunction::Set, MethodObject::Identity) => "Identity/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::EmailSubmission) => "EmailSubmission/get",
|
||||
(MethodFunction::Changes, MethodObject::EmailSubmission) => "EmailSubmission/changes",
|
||||
(MethodFunction::Query, MethodObject::EmailSubmission) => "EmailSubmission/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::EmailSubmission) => {
|
||||
"EmailSubmission/queryChanges"
|
||||
}
|
||||
(MethodFunction::Set, MethodObject::EmailSubmission) => "EmailSubmission/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::VacationResponse) => "VacationResponse/get",
|
||||
(MethodFunction::Set, MethodObject::VacationResponse) => "VacationResponse/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::SieveScript) => "SieveScript/get",
|
||||
(MethodFunction::Set, MethodObject::SieveScript) => "SieveScript/set",
|
||||
(MethodFunction::Query, MethodObject::SieveScript) => "SieveScript/query",
|
||||
(MethodFunction::Validate, MethodObject::SieveScript) => "SieveScript/validate",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Principal) => "Principal/get",
|
||||
(MethodFunction::Set, MethodObject::Principal) => "Principal/set",
|
||||
(MethodFunction::Query, MethodObject::Principal) => "Principal/query",
|
||||
(MethodFunction::Changes, MethodObject::Principal) => "Principal/changes",
|
||||
(MethodFunction::QueryChanges, MethodObject::Principal) => "Principal/queryChanges",
|
||||
(MethodFunction::GetAvailability, MethodObject::Principal) => {
|
||||
"Principal/getAvailability"
|
||||
}
|
||||
|
||||
(MethodFunction::Get, MethodObject::Quota) => "Quota/get",
|
||||
(MethodFunction::Changes, MethodObject::Quota) => "Quota/changes",
|
||||
(MethodFunction::Query, MethodObject::Quota) => "Quota/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::Quota) => "Quota/queryChanges",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Blob) => "Blob/get",
|
||||
(MethodFunction::Copy, MethodObject::Blob) => "Blob/copy",
|
||||
(MethodFunction::Lookup, MethodObject::Blob) => "Blob/lookup",
|
||||
(MethodFunction::Upload, MethodObject::Blob) => "Blob/upload",
|
||||
|
||||
(MethodFunction::Get, MethodObject::AddressBook) => "AddressBook/get",
|
||||
(MethodFunction::Changes, MethodObject::AddressBook) => "AddressBook/changes",
|
||||
(MethodFunction::Set, MethodObject::AddressBook) => "AddressBook/set",
|
||||
(MethodFunction::Query, MethodObject::AddressBook) => "AddressBook/query",
|
||||
|
||||
(MethodFunction::Get, MethodObject::ContactCard) => "ContactCard/get",
|
||||
(MethodFunction::Changes, MethodObject::ContactCard) => "ContactCard/changes",
|
||||
(MethodFunction::Query, MethodObject::ContactCard) => "ContactCard/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::ContactCard) => "ContactCard/queryChanges",
|
||||
(MethodFunction::Set, MethodObject::ContactCard) => "ContactCard/set",
|
||||
(MethodFunction::Copy, MethodObject::ContactCard) => "ContactCard/copy",
|
||||
(MethodFunction::Parse, MethodObject::ContactCard) => "ContactCard/parse",
|
||||
|
||||
(MethodFunction::Get, MethodObject::FileNode) => "FileNode/get",
|
||||
(MethodFunction::Changes, MethodObject::FileNode) => "FileNode/changes",
|
||||
(MethodFunction::Query, MethodObject::FileNode) => "FileNode/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::FileNode) => "FileNode/queryChanges",
|
||||
(MethodFunction::Set, MethodObject::FileNode) => "FileNode/set",
|
||||
(MethodFunction::Copy, MethodObject::FileNode) => "FileNode/copy",
|
||||
|
||||
(MethodFunction::Get, MethodObject::ShareNotification) => "ShareNotification/get",
|
||||
(MethodFunction::Changes, MethodObject::ShareNotification) => {
|
||||
"ShareNotification/changes"
|
||||
}
|
||||
(MethodFunction::Query, MethodObject::ShareNotification) => "ShareNotification/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::ShareNotification) => {
|
||||
"ShareNotification/queryChanges"
|
||||
}
|
||||
(MethodFunction::Set, MethodObject::ShareNotification) => "ShareNotification/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::Calendar) => "Calendar/get",
|
||||
(MethodFunction::Changes, MethodObject::Calendar) => "Calendar/changes",
|
||||
(MethodFunction::Set, MethodObject::Calendar) => "Calendar/set",
|
||||
(MethodFunction::Query, MethodObject::Calendar) => "Calendar/query",
|
||||
|
||||
(MethodFunction::Get, MethodObject::CalendarEvent) => "CalendarEvent/get",
|
||||
(MethodFunction::Changes, MethodObject::CalendarEvent) => "CalendarEvent/changes",
|
||||
(MethodFunction::Query, MethodObject::CalendarEvent) => "CalendarEvent/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::CalendarEvent) => {
|
||||
"CalendarEvent/queryChanges"
|
||||
}
|
||||
(MethodFunction::Set, MethodObject::CalendarEvent) => "CalendarEvent/set",
|
||||
(MethodFunction::Copy, MethodObject::CalendarEvent) => "CalendarEvent/copy",
|
||||
(MethodFunction::Parse, MethodObject::CalendarEvent) => "CalendarEvent/parse",
|
||||
|
||||
(MethodFunction::Get, MethodObject::CalendarEventNotification) => {
|
||||
"CalendarEventNotification/get"
|
||||
}
|
||||
(MethodFunction::Changes, MethodObject::CalendarEventNotification) => {
|
||||
"CalendarEventNotification/changes"
|
||||
}
|
||||
(MethodFunction::Query, MethodObject::CalendarEventNotification) => {
|
||||
"CalendarEventNotification/query"
|
||||
}
|
||||
(MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => {
|
||||
"CalendarEventNotification/queryChanges"
|
||||
}
|
||||
(MethodFunction::Set, MethodObject::CalendarEventNotification) => {
|
||||
"CalendarEventNotification/set"
|
||||
}
|
||||
|
||||
(MethodFunction::Get, MethodObject::ParticipantIdentity) => "ParticipantIdentity/get",
|
||||
(MethodFunction::Changes, MethodObject::ParticipantIdentity) => {
|
||||
"ParticipantIdentity/changes"
|
||||
}
|
||||
(MethodFunction::Set, MethodObject::ParticipantIdentity) => "ParticipantIdentity/set",
|
||||
|
||||
(MethodFunction::Echo, MethodObject::Core) => "Core/echo",
|
||||
(method, MethodObject::Registry(obj)) => {
|
||||
return Cow::Owned(format!("x:{}/{}", obj.as_str(), method.as_str()));
|
||||
}
|
||||
_ => "error",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(s.as_bytes(),
|
||||
"PushSubscription/get" => (MethodObject::PushSubscription, MethodFunction::Get),
|
||||
"PushSubscription/set" => (MethodObject::PushSubscription, MethodFunction::Set),
|
||||
|
||||
"Mailbox/get" => (MethodObject::Mailbox, MethodFunction::Get),
|
||||
"Mailbox/changes" => (MethodObject::Mailbox, MethodFunction::Changes),
|
||||
"Mailbox/query" => (MethodObject::Mailbox, MethodFunction::Query),
|
||||
"Mailbox/queryChanges" => (MethodObject::Mailbox, MethodFunction::QueryChanges),
|
||||
"Mailbox/set" => (MethodObject::Mailbox, MethodFunction::Set),
|
||||
|
||||
"Thread/get" => (MethodObject::Thread, MethodFunction::Get),
|
||||
"Thread/changes" => (MethodObject::Thread, MethodFunction::Changes),
|
||||
|
||||
"Email/get" => (MethodObject::Email, MethodFunction::Get),
|
||||
"Email/changes" => (MethodObject::Email, MethodFunction::Changes),
|
||||
"Email/query" => (MethodObject::Email, MethodFunction::Query),
|
||||
"Email/queryChanges" => (MethodObject::Email, MethodFunction::QueryChanges),
|
||||
"Email/set" => (MethodObject::Email, MethodFunction::Set),
|
||||
"Email/copy" => (MethodObject::Email, MethodFunction::Copy),
|
||||
"Email/import" => (MethodObject::Email, MethodFunction::Import),
|
||||
"Email/parse" => (MethodObject::Email, MethodFunction::Parse),
|
||||
|
||||
"SearchSnippet/get" => (MethodObject::SearchSnippet, MethodFunction::Get),
|
||||
|
||||
"Identity/get" => (MethodObject::Identity, MethodFunction::Get),
|
||||
"Identity/changes" => (MethodObject::Identity, MethodFunction::Changes),
|
||||
"Identity/set" => (MethodObject::Identity, MethodFunction::Set),
|
||||
|
||||
"EmailSubmission/get" => (MethodObject::EmailSubmission, MethodFunction::Get),
|
||||
"EmailSubmission/changes" => (MethodObject::EmailSubmission, MethodFunction::Changes),
|
||||
"EmailSubmission/query" => (MethodObject::EmailSubmission, MethodFunction::Query),
|
||||
"EmailSubmission/queryChanges" => (MethodObject::EmailSubmission, MethodFunction::QueryChanges),
|
||||
"EmailSubmission/set" => (MethodObject::EmailSubmission, MethodFunction::Set),
|
||||
|
||||
"VacationResponse/get" => (MethodObject::VacationResponse, MethodFunction::Get),
|
||||
"VacationResponse/set" => (MethodObject::VacationResponse, MethodFunction::Set),
|
||||
|
||||
"SieveScript/get" => (MethodObject::SieveScript, MethodFunction::Get),
|
||||
"SieveScript/set" => (MethodObject::SieveScript, MethodFunction::Set),
|
||||
"SieveScript/query" => (MethodObject::SieveScript, MethodFunction::Query),
|
||||
"SieveScript/validate" => (MethodObject::SieveScript, MethodFunction::Validate),
|
||||
|
||||
"Principal/get" => (MethodObject::Principal, MethodFunction::Get),
|
||||
"Principal/set" => (MethodObject::Principal, MethodFunction::Set),
|
||||
"Principal/query" => (MethodObject::Principal, MethodFunction::Query),
|
||||
"Principal/changes" => (MethodObject::Principal, MethodFunction::Changes),
|
||||
"Principal/queryChanges" => (MethodObject::Principal, MethodFunction::QueryChanges),
|
||||
"Principal/getAvailability" => (MethodObject::Principal, MethodFunction::GetAvailability),
|
||||
|
||||
"Quota/get" => (MethodObject::Quota, MethodFunction::Get),
|
||||
"Quota/changes" => (MethodObject::Quota, MethodFunction::Changes),
|
||||
"Quota/query" => (MethodObject::Quota, MethodFunction::Query),
|
||||
"Quota/queryChanges" => (MethodObject::Quota, MethodFunction::QueryChanges),
|
||||
|
||||
"Blob/get" => (MethodObject::Blob, MethodFunction::Get),
|
||||
"Blob/copy" => (MethodObject::Blob, MethodFunction::Copy),
|
||||
"Blob/lookup" => (MethodObject::Blob, MethodFunction::Lookup),
|
||||
"Blob/upload" => (MethodObject::Blob, MethodFunction::Upload),
|
||||
|
||||
"AddressBook/get" => (MethodObject::AddressBook, MethodFunction::Get),
|
||||
"AddressBook/changes" => (MethodObject::AddressBook, MethodFunction::Changes),
|
||||
"AddressBook/set" => (MethodObject::AddressBook, MethodFunction::Set),
|
||||
"AddressBook/query" => (MethodObject::AddressBook, MethodFunction::Query),
|
||||
|
||||
"ContactCard/get" => (MethodObject::ContactCard, MethodFunction::Get),
|
||||
"ContactCard/changes" => (MethodObject::ContactCard, MethodFunction::Changes),
|
||||
"ContactCard/query" => (MethodObject::ContactCard, MethodFunction::Query),
|
||||
"ContactCard/queryChanges" => (MethodObject::ContactCard, MethodFunction::QueryChanges),
|
||||
"ContactCard/set" => (MethodObject::ContactCard, MethodFunction::Set),
|
||||
"ContactCard/copy" => (MethodObject::ContactCard, MethodFunction::Copy),
|
||||
"ContactCard/parse" => (MethodObject::ContactCard, MethodFunction::Parse),
|
||||
|
||||
"FileNode/get" => (MethodObject::FileNode, MethodFunction::Get),
|
||||
"FileNode/changes" => (MethodObject::FileNode, MethodFunction::Changes),
|
||||
"FileNode/query" => (MethodObject::FileNode, MethodFunction::Query),
|
||||
"FileNode/queryChanges" => (MethodObject::FileNode, MethodFunction::QueryChanges),
|
||||
"FileNode/set" => (MethodObject::FileNode, MethodFunction::Set),
|
||||
"FileNode/copy" => (MethodObject::FileNode, MethodFunction::Copy),
|
||||
|
||||
"ShareNotification/get" => (MethodObject::ShareNotification, MethodFunction::Get),
|
||||
"ShareNotification/changes" => (MethodObject::ShareNotification, MethodFunction::Changes),
|
||||
"ShareNotification/set" => (MethodObject::ShareNotification, MethodFunction::Set),
|
||||
"ShareNotification/query" => (MethodObject::ShareNotification, MethodFunction::Query),
|
||||
"ShareNotification/queryChanges" => (MethodObject::ShareNotification, MethodFunction::QueryChanges),
|
||||
|
||||
"Calendar/get" => (MethodObject::Calendar, MethodFunction::Get),
|
||||
"Calendar/changes" => (MethodObject::Calendar, MethodFunction::Changes),
|
||||
"Calendar/set" => (MethodObject::Calendar, MethodFunction::Set),
|
||||
"Calendar/query" => (MethodObject::Calendar, MethodFunction::Query),
|
||||
|
||||
"CalendarEvent/get" => (MethodObject::CalendarEvent, MethodFunction::Get),
|
||||
"CalendarEvent/changes" => (MethodObject::CalendarEvent, MethodFunction::Changes),
|
||||
"CalendarEvent/query" => (MethodObject::CalendarEvent, MethodFunction::Query),
|
||||
"CalendarEvent/queryChanges" => (MethodObject::CalendarEvent, MethodFunction::QueryChanges),
|
||||
"CalendarEvent/set" => (MethodObject::CalendarEvent, MethodFunction::Set),
|
||||
"CalendarEvent/copy" => (MethodObject::CalendarEvent, MethodFunction::Copy),
|
||||
"CalendarEvent/parse" => (MethodObject::CalendarEvent, MethodFunction::Parse),
|
||||
|
||||
"CalendarEventNotification/get" => (MethodObject::CalendarEventNotification, MethodFunction::Get),
|
||||
"CalendarEventNotification/changes" => (MethodObject::CalendarEventNotification, MethodFunction::Changes),
|
||||
"CalendarEventNotification/set" => (MethodObject::CalendarEventNotification, MethodFunction::Set),
|
||||
"CalendarEventNotification/query" => (MethodObject::CalendarEventNotification, MethodFunction::Query),
|
||||
"CalendarEventNotification/queryChanges" => (MethodObject::CalendarEventNotification, MethodFunction::QueryChanges),
|
||||
|
||||
"ParticipantIdentity/get" => (MethodObject::ParticipantIdentity, MethodFunction::Get),
|
||||
"ParticipantIdentity/changes" => (MethodObject::ParticipantIdentity, MethodFunction::Changes),
|
||||
"ParticipantIdentity/set" => (MethodObject::ParticipantIdentity, MethodFunction::Set),
|
||||
|
||||
"Core/echo" => (MethodObject::Core, MethodFunction::Echo),
|
||||
|
||||
).or_else(|| {
|
||||
let (obj, fnc) = s.strip_prefix("x:")?.split_once('/')?;
|
||||
let obj = ObjectType::parse(obj)?;
|
||||
let fnc = hashify::tiny_map!(fnc.as_bytes(),
|
||||
"get" => MethodFunction::Get,
|
||||
"set" => MethodFunction::Set,
|
||||
"query" => MethodFunction::Query,
|
||||
)?;
|
||||
|
||||
if obj.flags() & OBJ_SINGLETON == 0 || fnc != MethodFunction::Query {
|
||||
(MethodObject::Registry(obj), fnc).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).map(|(obj, fnc)| MethodName { obj, fnc })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MethodObject {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
MethodObject::Blob => "Blob",
|
||||
MethodObject::EmailSubmission => "EmailSubmission",
|
||||
MethodObject::SearchSnippet => "SearchSnippet",
|
||||
MethodObject::Identity => "Identity",
|
||||
MethodObject::VacationResponse => "VacationResponse",
|
||||
MethodObject::PushSubscription => "PushSubscription",
|
||||
MethodObject::SieveScript => "SieveScript",
|
||||
MethodObject::Principal => "Principal",
|
||||
MethodObject::Core => "Core",
|
||||
MethodObject::Mailbox => "Mailbox",
|
||||
MethodObject::Thread => "Thread",
|
||||
MethodObject::Email => "Email",
|
||||
MethodObject::Quota => "Quota",
|
||||
MethodObject::AddressBook => "AddressBook",
|
||||
MethodObject::ContactCard => "ContactCard",
|
||||
MethodObject::FileNode => "FileNode",
|
||||
MethodObject::ParticipantIdentity => "ParticipantIdentity",
|
||||
MethodObject::Calendar => "Calendar",
|
||||
MethodObject::CalendarEvent => "CalendarEvent",
|
||||
MethodObject::CalendarEventNotification => "CalendarEventNotification",
|
||||
MethodObject::ShareNotification => "ShareNotification",
|
||||
MethodObject::Registry(obj) => {
|
||||
f.write_str("x:")?;
|
||||
return f.write_str(obj.as_str());
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MethodFunction {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
MethodFunction::Get => "get",
|
||||
MethodFunction::Set => "set",
|
||||
MethodFunction::Changes => "changes",
|
||||
MethodFunction::Query => "query",
|
||||
MethodFunction::QueryChanges => "queryChanges",
|
||||
MethodFunction::Copy => "copy",
|
||||
MethodFunction::Import => "import",
|
||||
MethodFunction::Parse => "parse",
|
||||
MethodFunction::Validate => "validate",
|
||||
MethodFunction::Lookup => "lookup",
|
||||
MethodFunction::Upload => "upload",
|
||||
MethodFunction::Echo => "echo",
|
||||
MethodFunction::GetAvailability => "getAvailability",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MethodObject {
|
||||
pub fn unwrap_registry(self) -> ObjectType {
|
||||
match self {
|
||||
MethodObject::Registry(obj) => obj,
|
||||
_ => panic!("Not a registry method object"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for MethodName {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = <Cow<str>>::deserialize(deserializer)?;
|
||||
|
||||
MethodName::parse(value.as_ref())
|
||||
.ok_or_else(|| serde::de::Error::custom(format!("Invalid method name: {:?}", value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for MethodName {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str().as_ref())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod capability;
|
||||
pub mod deserialize;
|
||||
pub mod method;
|
||||
pub mod parser;
|
||||
pub mod reference;
|
||||
pub mod websocket;
|
||||
|
||||
use self::method::MethodName;
|
||||
use crate::{
|
||||
method::{
|
||||
availability::GetAvailabilityRequest,
|
||||
changes::ChangesRequest,
|
||||
copy::{CopyBlobRequest, CopyRequest},
|
||||
get::GetRequest,
|
||||
import::ImportEmailRequest,
|
||||
lookup::BlobLookupRequest,
|
||||
parse::ParseRequest,
|
||||
query::QueryRequest,
|
||||
query_changes::QueryChangesRequest,
|
||||
search_snippet::GetSearchSnippetRequest,
|
||||
set::SetRequest,
|
||||
upload::BlobUploadRequest,
|
||||
validate::ValidateSieveScriptRequest,
|
||||
},
|
||||
object::{
|
||||
AnyId, addressbook::AddressBook, blob::Blob, calendar::Calendar,
|
||||
calendar_event::CalendarEvent, calendar_event_notification::CalendarEventNotification,
|
||||
contact::ContactCard, email::Email, email_submission::EmailSubmission, file_node::FileNode,
|
||||
identity::Identity, mailbox::Mailbox, participant_identity::ParticipantIdentity,
|
||||
principal::Principal, push_subscription::PushSubscription, quota::Quota,
|
||||
registry::Registry, share_notification::ShareNotification, sieve::Sieve, thread::Thread,
|
||||
vacation_response::VacationResponse,
|
||||
},
|
||||
request::{capability::CapabilityIds, reference::MaybeIdReference},
|
||||
};
|
||||
use jmap_tools::{Null, Value};
|
||||
use std::{collections::HashMap, fmt::Debug, str::FromStr};
|
||||
use types::id::Id;
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
pub const INVALID_ACCOUNT_ID: u64 = u64::MAX - 1;
|
||||
|
||||
pub fn deserialize_account_id<'de, A>(map: &mut A) -> Result<Id, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
Ok(map
|
||||
.next_value::<MaybeInvalid<Id>>()?
|
||||
.try_unwrap()
|
||||
.unwrap_or_else(|| Id::from(INVALID_ACCOUNT_ID)))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request<'x> {
|
||||
pub using: CapabilityIds,
|
||||
pub method_calls: Vec<Call<RequestMethod<'x>>>,
|
||||
pub created_ids: Option<HashMap<String, AnyId>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Call<T> {
|
||||
pub id: String,
|
||||
pub name: MethodName,
|
||||
pub method: T,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RequestMethod<'x> {
|
||||
Get(GetRequestMethod),
|
||||
Set(SetRequestMethod<'x>),
|
||||
Changes(Box<ChangesRequest>),
|
||||
Copy(CopyRequestMethod<'x>),
|
||||
ImportEmail(Box<ImportEmailRequest>),
|
||||
Parse(ParseRequestMethod),
|
||||
Query(QueryRequestMethod),
|
||||
QueryChanges(QueryChangesRequestMethod),
|
||||
SearchSnippet(Box<GetSearchSnippetRequest>),
|
||||
ValidateScript(Box<ValidateSieveScriptRequest>),
|
||||
LookupBlob(Box<BlobLookupRequest>),
|
||||
UploadBlob(Box<BlobUploadRequest>),
|
||||
Echo(Value<'x, Null, Null>),
|
||||
Error(trc::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GetRequestMethod {
|
||||
Email(Box<GetRequest<Email>>),
|
||||
Mailbox(Box<GetRequest<Mailbox>>),
|
||||
Thread(Box<GetRequest<Thread>>),
|
||||
Identity(Box<GetRequest<Identity>>),
|
||||
EmailSubmission(Box<GetRequest<EmailSubmission>>),
|
||||
PushSubscription(Box<GetRequest<PushSubscription>>),
|
||||
Sieve(Box<GetRequest<Sieve>>),
|
||||
VacationResponse(Box<GetRequest<VacationResponse>>),
|
||||
Principal(Box<GetRequest<Principal>>),
|
||||
PrincipalAvailability(Box<GetAvailabilityRequest>),
|
||||
Quota(Box<GetRequest<Quota>>),
|
||||
Blob(Box<GetRequest<Blob>>),
|
||||
AddressBook(Box<GetRequest<AddressBook>>),
|
||||
ContactCard(Box<GetRequest<ContactCard>>),
|
||||
FileNode(Box<GetRequest<FileNode>>),
|
||||
Calendar(Box<GetRequest<Calendar>>),
|
||||
CalendarEvent(Box<GetRequest<CalendarEvent>>),
|
||||
CalendarEventNotification(Box<GetRequest<CalendarEventNotification>>),
|
||||
ParticipantIdentity(Box<GetRequest<ParticipantIdentity>>),
|
||||
ShareNotification(Box<GetRequest<ShareNotification>>),
|
||||
Registry(Box<GetRequest<Registry>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SetRequestMethod<'x> {
|
||||
Email(Box<SetRequest<'x, Email>>),
|
||||
Mailbox(Box<SetRequest<'x, Mailbox>>),
|
||||
Identity(Box<SetRequest<'x, Identity>>),
|
||||
EmailSubmission(Box<SetRequest<'x, EmailSubmission>>),
|
||||
PushSubscription(Box<SetRequest<'x, PushSubscription>>),
|
||||
Sieve(Box<SetRequest<'x, Sieve>>),
|
||||
VacationResponse(Box<SetRequest<'x, VacationResponse>>),
|
||||
AddressBook(Box<SetRequest<'x, AddressBook>>),
|
||||
ContactCard(Box<SetRequest<'x, ContactCard>>),
|
||||
FileNode(Box<SetRequest<'x, FileNode>>),
|
||||
ShareNotification(Box<SetRequest<'x, ShareNotification>>),
|
||||
Calendar(Box<SetRequest<'x, Calendar>>),
|
||||
CalendarEvent(Box<SetRequest<'x, CalendarEvent>>),
|
||||
CalendarEventNotification(Box<SetRequest<'x, CalendarEventNotification>>),
|
||||
ParticipantIdentity(Box<SetRequest<'x, ParticipantIdentity>>),
|
||||
Registry(Box<SetRequest<'x, Registry>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CopyRequestMethod<'x> {
|
||||
Email(Box<CopyRequest<'x, Email>>),
|
||||
ContactCard(Box<CopyRequest<'x, ContactCard>>),
|
||||
CalendarEvent(Box<CopyRequest<'x, CalendarEvent>>),
|
||||
FileNode(Box<CopyRequest<'x, FileNode>>),
|
||||
Blob(Box<CopyBlobRequest>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryRequestMethod {
|
||||
Email(Box<QueryRequest<Email>>),
|
||||
Mailbox(Box<QueryRequest<Mailbox>>),
|
||||
EmailSubmission(Box<QueryRequest<EmailSubmission>>),
|
||||
Sieve(Box<QueryRequest<Sieve>>),
|
||||
Principal(Box<QueryRequest<Principal>>),
|
||||
Quota(Box<QueryRequest<Quota>>),
|
||||
AddressBook(Box<QueryRequest<AddressBook>>),
|
||||
ContactCard(Box<QueryRequest<ContactCard>>),
|
||||
FileNode(Box<QueryRequest<FileNode>>),
|
||||
Calendar(Box<QueryRequest<Calendar>>),
|
||||
CalendarEvent(Box<QueryRequest<CalendarEvent>>),
|
||||
CalendarEventNotification(Box<QueryRequest<CalendarEventNotification>>),
|
||||
ShareNotification(Box<QueryRequest<ShareNotification>>),
|
||||
Registry(Box<QueryRequest<Registry>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryChangesRequestMethod {
|
||||
Email(Box<QueryChangesRequest<Email>>),
|
||||
Mailbox(Box<QueryChangesRequest<Mailbox>>),
|
||||
EmailSubmission(Box<QueryChangesRequest<EmailSubmission>>),
|
||||
Principal(Box<QueryChangesRequest<Principal>>),
|
||||
Quota(Box<QueryChangesRequest<Quota>>),
|
||||
ContactCard(Box<QueryChangesRequest<ContactCard>>),
|
||||
FileNode(Box<QueryChangesRequest<FileNode>>),
|
||||
CalendarEvent(Box<QueryChangesRequest<CalendarEvent>>),
|
||||
CalendarEventNotification(Box<QueryChangesRequest<CalendarEventNotification>>),
|
||||
ShareNotification(Box<QueryChangesRequest<ShareNotification>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseRequestMethod {
|
||||
Email(Box<ParseRequest<Email>>),
|
||||
ContactCard(Box<ParseRequest<ContactCard>>),
|
||||
CalendarEvent(Box<ParseRequest<CalendarEvent>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MaybeInvalid<V: FromStr> {
|
||||
Value(V),
|
||||
Invalid(String),
|
||||
}
|
||||
|
||||
impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeInvalid<V> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = <&str>::deserialize(deserializer)?;
|
||||
|
||||
if let Ok(id) = V::from_str(value) {
|
||||
Ok(MaybeInvalid::Value(id))
|
||||
} else {
|
||||
Ok(MaybeInvalid::Invalid(value.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromStr + serde::Serialize> serde::Serialize for MaybeInvalid<V> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
MaybeInvalid::Value(v) => v.serialize(serializer),
|
||||
MaybeInvalid::Invalid(s) => serializer.serialize_str(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromStr> From<V> for MaybeInvalid<V> {
|
||||
fn from(value: V) -> Self {
|
||||
MaybeInvalid::Value(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromStr> Default for MaybeInvalid<V> {
|
||||
fn default() -> Self {
|
||||
MaybeInvalid::Invalid("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for Request<'_> {
|
||||
fn default() -> Self {
|
||||
Request {
|
||||
using: CapabilityIds::default(),
|
||||
method_calls: Vec::new(),
|
||||
created_ids: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MaybeInvalid<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
pub fn try_unwrap(self) -> Option<T> {
|
||||
match self {
|
||||
MaybeInvalid::Value(id) => Some(id),
|
||||
MaybeInvalid::Invalid(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoValid {
|
||||
type Item;
|
||||
|
||||
fn into_valid(self) -> impl Iterator<Item = Self::Item>;
|
||||
}
|
||||
|
||||
impl<T: FromStr> IntoValid for Vec<MaybeInvalid<T>> {
|
||||
type Item = T;
|
||||
|
||||
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
|
||||
self.into_iter().filter_map(|v| v.try_unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStr> IntoValid for Vec<MaybeIdReference<T>> {
|
||||
type Item = T;
|
||||
|
||||
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
|
||||
self.into_iter().filter_map(|v| v.try_unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStr + Eq, V> IntoValid for VecMap<MaybeInvalid<T>, V> {
|
||||
type Item = (T, V);
|
||||
|
||||
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
|
||||
self.into_iter()
|
||||
.filter_map(|(k, v)| k.try_unwrap().map(|k| (k, v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStr + Eq, V> IntoValid for VecMap<MaybeIdReference<T>, V> {
|
||||
type Item = (T, V);
|
||||
|
||||
fn into_valid(self) -> impl Iterator<Item = Self::Item> {
|
||||
self.into_iter()
|
||||
.filter_map(|(k, v)| k.try_unwrap().map(|k| (k, v)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
Call, Request, RequestMethod,
|
||||
method::{MethodFunction, MethodName, MethodObject},
|
||||
};
|
||||
use crate::request::{
|
||||
CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod,
|
||||
QueryRequestMethod, SetRequestMethod, deserialize::DeserializeArguments,
|
||||
};
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
de::{self, SeqAccess, Visitor},
|
||||
};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
impl<'x> Request<'x> {
|
||||
pub fn parse(json: &'x [u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
|
||||
if json.len() <= max_size {
|
||||
match serde_json::from_slice::<Request>(json) {
|
||||
Ok(request) => {
|
||||
if request.method_calls.len() <= max_calls {
|
||||
Ok(request)
|
||||
} else {
|
||||
Err(trc::LimitEvent::CallsIn.into_err())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(trc::JmapEvent::NotRequest
|
||||
.into_err()
|
||||
.reason(err.to_string())
|
||||
.details(String::from_utf8_lossy(json).into_owned())),
|
||||
}
|
||||
} else {
|
||||
Err(trc::LimitEvent::SizeRequest.into_err())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for Request<'de> {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"using" => {
|
||||
self.using = map.next_value()?;
|
||||
},
|
||||
b"methodCalls" => {
|
||||
self.method_calls = map.next_value()?;
|
||||
},
|
||||
b"createdIds" => {
|
||||
self.created_ids = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct CallVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for CallVisitor {
|
||||
type Value = Call<RequestMethod<'de>>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("an array with 3 elements")
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, mut seq: V) -> Result<Call<RequestMethod<'de>>, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
let method_name = seq
|
||||
.next_element::<std::borrow::Cow<str>>()?
|
||||
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||
let name = match MethodName::parse(method_name.as_ref()) {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
// Ignore the rest of the call
|
||||
let _ = seq
|
||||
.next_element::<serde::de::IgnoredAny>()?
|
||||
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
||||
let id = seq
|
||||
.next_element::<String>()?
|
||||
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
|
||||
|
||||
return Ok(Call {
|
||||
id,
|
||||
method: RequestMethod::Error(
|
||||
trc::JmapEvent::UnknownMethod
|
||||
.into_err()
|
||||
.details(method_name.to_string()),
|
||||
),
|
||||
name: MethodName::error(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let method = match (&name.fnc, &name.obj) {
|
||||
(MethodFunction::Get, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Mailbox) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Mailbox(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Thread) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Thread(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Identity) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Identity(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::EmailSubmission) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::EmailSubmission(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::PushSubscription) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::PushSubscription(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::VacationResponse) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::VacationResponse(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::SieveScript) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Sieve(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Principal) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Principal(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Quota) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Quota(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Blob) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Blob(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Calendar) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Calendar(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::CalendarEvent) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::CalendarEvent(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::CalendarEventNotification) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::Get(GetRequestMethod::CalendarEventNotification(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::Get, MethodObject::ParticipantIdentity) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ParticipantIdentity(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::AddressBook) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AddressBook(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::FileNode) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::FileNode(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::ShareNotification) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ShareNotification(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::SearchSnippet) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::SearchSnippet(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::Registry(_)) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Registry(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::Mailbox) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Mailbox(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::Identity) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Identity(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::EmailSubmission) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::EmailSubmission(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::PushSubscription) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::PushSubscription(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::VacationResponse) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::VacationResponse(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::SieveScript) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Sieve(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::Calendar) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Calendar(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::CalendarEvent) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::CalendarEvent(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::CalendarEventNotification) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::Set(SetRequestMethod::CalendarEventNotification(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::Set, MethodObject::ParticipantIdentity) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ParticipantIdentity(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::AddressBook) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AddressBook(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::FileNode) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::FileNode(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::ShareNotification) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ShareNotification(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::Registry(_)) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::Registry(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Mailbox) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Mailbox(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::EmailSubmission) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::EmailSubmission(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::SieveScript) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Sieve(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Principal) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Principal(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Quota) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Quota(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Calendar) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Calendar(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::CalendarEvent) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::CalendarEvent(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::CalendarEventNotification) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::Query(QueryRequestMethod::CalendarEventNotification(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::Query, MethodObject::AddressBook) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::AddressBook(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::FileNode) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::FileNode(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::ShareNotification) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::Query(QueryRequestMethod::ShareNotification(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Registry(_)) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Registry(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::Email(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::Mailbox) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::Mailbox(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::EmailSubmission) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::QueryChanges(
|
||||
QueryChangesRequestMethod::EmailSubmission(value),
|
||||
),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::QueryChanges, MethodObject::Principal) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::Principal(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::Quota) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::Quota(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::CalendarEvent) => match seq.next_element()
|
||||
{
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::CalendarEvent(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::QueryChanges(
|
||||
QueryChangesRequestMethod::CalendarEventNotification(value),
|
||||
),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::QueryChanges, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::ContactCard(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::FileNode) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::FileNode(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::ShareNotification) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::QueryChanges(
|
||||
QueryChangesRequestMethod::ShareNotification(value),
|
||||
),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::Changes, _) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Changes(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Copy, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Copy, MethodObject::Blob) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::Blob(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Copy, MethodObject::CalendarEvent) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::CalendarEvent(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Copy, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Copy, MethodObject::FileNode) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::FileNode(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Lookup, MethodObject::Blob) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::LookupBlob(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Upload, MethodObject::Blob) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::UploadBlob(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Import, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::ImportEmail(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Parse, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Parse, MethodObject::CalendarEvent) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::CalendarEvent(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Parse, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::GetAvailability, MethodObject::Principal) => {
|
||||
match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::Get(GetRequestMethod::PrincipalAvailability(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
}
|
||||
}
|
||||
(MethodFunction::Validate, MethodObject::SieveScript) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::ValidateScript(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Echo, MethodObject::Core) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Echo(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(de::Error::custom(format!(
|
||||
"Invalid method function/object combination: {}",
|
||||
method_name
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let id = seq
|
||||
.next_element::<String>()?
|
||||
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
|
||||
|
||||
Ok(Call { id, method, name })
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestMethod<'_> {
|
||||
fn invalid(err: impl Display) -> Self {
|
||||
RequestMethod::Error(
|
||||
trc::JmapEvent::InvalidArguments
|
||||
.into_err()
|
||||
.details(err.to_string()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Request<'de> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct RequestVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RequestVisitor {
|
||||
type Value = Request<'de>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a JMAP request object")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::MapAccess<'de>,
|
||||
{
|
||||
let mut target = Request::default();
|
||||
let mut has_using = false;
|
||||
let mut has_method_calls = false;
|
||||
|
||||
while let Some(key) = map.next_key::<&str>()? {
|
||||
match key {
|
||||
"using" => has_using = true,
|
||||
"methodCalls" => has_method_calls = true,
|
||||
_ => {}
|
||||
}
|
||||
target
|
||||
.deserialize_argument(key, &mut map)
|
||||
.map_err(de::Error::custom)?;
|
||||
}
|
||||
|
||||
if !has_using || !has_method_calls {
|
||||
return Err(de::Error::custom(
|
||||
"Request is missing the \"using\" or \"methodCalls\" property.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(RequestVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Call<RequestMethod<'de>> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(CallVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::request::Request;
|
||||
|
||||
const TEST: &str = r#"
|
||||
{
|
||||
"using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
|
||||
"methodCalls": [
|
||||
[ "method1", {
|
||||
"arg1": "arg1data",
|
||||
"arg2": "arg2data"
|
||||
}, "c1" ],
|
||||
[ "Core/echo", {
|
||||
"hello": true,
|
||||
"high": 5
|
||||
}, "c2" ],
|
||||
[ "method3", {"hello": [{"a": {"b": true}}]}, "c3" ]
|
||||
],
|
||||
"createdIds": {
|
||||
"c1": "m1",
|
||||
"c2": "m2"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
const TEST1: &str = r#"
|
||||
{
|
||||
"using": [
|
||||
"urn:ietf:params:jmap:core",
|
||||
"urn:ietf:params:jmap:mail"
|
||||
],
|
||||
"methodCalls": [
|
||||
[
|
||||
"Email/query",
|
||||
{
|
||||
"accountId": "0",
|
||||
"filter": { "conditions": [ { "hasKeyword": "music", "maxSize": 455 }, { "hasKeyword": "video" }, { "operator": "AND", "conditions": [ { "subject": "test" }, { "minSize": 100 } ] } ], "operator": "OR" },
|
||||
"sort": [
|
||||
{
|
||||
"property": "subject",
|
||||
"isAscending": true
|
||||
},
|
||||
{
|
||||
"property": "allInThreadHaveKeyword",
|
||||
"isAscending": false,
|
||||
"keyword": "$seen"
|
||||
},
|
||||
{
|
||||
"keyword": "$junk",
|
||||
"property": "someInThreadHaveKeyword",
|
||||
"collation": "i;octet",
|
||||
"isAscending": false
|
||||
}
|
||||
],
|
||||
"position": 0,
|
||||
"limit": 10
|
||||
},
|
||||
"c1"
|
||||
]
|
||||
],
|
||||
"createdIds": {}
|
||||
}
|
||||
"#;
|
||||
|
||||
const TEST2: &str = r##"
|
||||
{
|
||||
"using": [
|
||||
"urn:ietf:params:jmap:submission",
|
||||
"urn:ietf:params:jmap:mail",
|
||||
"urn:ietf:params:jmap:core"
|
||||
],
|
||||
"methodCalls": [
|
||||
[
|
||||
"Email/set",
|
||||
{
|
||||
"accountId": "c",
|
||||
"create": {
|
||||
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
||||
"mailboxIds": {
|
||||
"9": true
|
||||
},
|
||||
"subject": "test",
|
||||
"from": [
|
||||
{
|
||||
"name": "Foo",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
],
|
||||
"to": [
|
||||
{
|
||||
"name": null,
|
||||
"email": "[email protected]"
|
||||
}
|
||||
],
|
||||
"cc": [],
|
||||
"bcc": [],
|
||||
"replyTo": [
|
||||
{
|
||||
"name": null,
|
||||
"email": "[email protected]"
|
||||
}
|
||||
],
|
||||
"htmlBody": [
|
||||
{
|
||||
"partId": "c37ee58b-e224-4799-88e6-1d7484e3b782",
|
||||
"type": "text/html"
|
||||
}
|
||||
],
|
||||
"bodyValues": {
|
||||
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
||||
"value": "<p>test email<br></p>",
|
||||
"isEncodingProblem": false,
|
||||
"isTruncated": false
|
||||
}
|
||||
},
|
||||
"header:User-Agent:asText": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"c0"
|
||||
],
|
||||
[
|
||||
"EmailSubmission/set",
|
||||
{
|
||||
"accountId": "c",
|
||||
"create": {
|
||||
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
||||
"identityId": "a",
|
||||
"emailId": "#c37ee58b-e224-4799-88e6-1d7484e3b782",
|
||||
"envelope": {
|
||||
"mailFrom": {
|
||||
"email": "[email protected]"
|
||||
},
|
||||
"rcptTo": [
|
||||
{
|
||||
"email": "[email protected]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"onSuccessUpdateEmail": {
|
||||
"#c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
||||
"mailboxIds/d": true,
|
||||
"mailboxIds/9": null,
|
||||
"keywords/$seen": true,
|
||||
"keywords/$draft": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"c1"
|
||||
]
|
||||
]
|
||||
}
|
||||
"##;
|
||||
|
||||
const TEST_ESCAPED_SOLIDUS: &str = r#"
|
||||
{
|
||||
"using": [ "urn:ietf:params:jmap:core" ],
|
||||
"methodCalls": [
|
||||
[ "Core\/echo", { "hello": true }, "c1" ]
|
||||
]
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn parse_request() {
|
||||
println!("{:#?}", Request::parse(TEST.as_bytes(), 10, 10240));
|
||||
println!("{:#?}", Request::parse(TEST1.as_bytes(), 10, 10240));
|
||||
println!("{:#?}", Request::parse(TEST2.as_bytes(), 10, 10240));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_method_name_with_escaped_solidus() {
|
||||
let request = Request::parse(TEST_ESCAPED_SOLIDUS.as_bytes(), 10, 10240)
|
||||
.expect("escaped solidus in method name must parse");
|
||||
assert_eq!(request.method_calls.len(), 1);
|
||||
assert!(matches!(
|
||||
request.method_calls[0].method,
|
||||
super::RequestMethod::Echo(_)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::method::MethodName;
|
||||
use jmap_tools::{JsonPointer, Null};
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ResultReference {
|
||||
#[serde(rename = "resultOf")]
|
||||
pub result_of: String,
|
||||
pub name: MethodName,
|
||||
pub path: JsonPointer<Null>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum MaybeIdReference<V: FromStr> {
|
||||
Id(V),
|
||||
Reference(String),
|
||||
Invalid(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MaybeResultReference<V> {
|
||||
Value(V),
|
||||
Reference(ResultReference),
|
||||
}
|
||||
|
||||
impl Display for ResultReference {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{{ resultOf: {}, name: {}, path: {} }}",
|
||||
self.result_of, self.name, self.path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromStr + Display> Display for MaybeIdReference<V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MaybeIdReference::Id(id) => write!(f, "{}", id),
|
||||
MaybeIdReference::Reference(str) => write!(f, "#{}", str),
|
||||
MaybeIdReference::Invalid(str) => write!(f, "{}", str),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeIdReference<V> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = <Cow<'de, str>>::deserialize(deserializer)?;
|
||||
|
||||
if let Some(reference) = value.strip_prefix('#') {
|
||||
if reference.is_empty() {
|
||||
return Ok(MaybeIdReference::Invalid(value.into_owned()));
|
||||
}
|
||||
Ok(MaybeIdReference::Reference(reference.to_string()))
|
||||
} else if let Ok(id) = V::from_str(value.as_ref()) {
|
||||
Ok(MaybeIdReference::Id(id))
|
||||
} else {
|
||||
Ok(MaybeIdReference::Invalid(value.into_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromStr> FromStr for MaybeIdReference<V> {
|
||||
type Err = V::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(reference) = s.strip_prefix('#') {
|
||||
if reference.is_empty() {
|
||||
return Ok(MaybeIdReference::Invalid(s.to_string()));
|
||||
}
|
||||
Ok(MaybeIdReference::Reference(reference.to_string()))
|
||||
} else if let Ok(id) = V::from_str(s) {
|
||||
Ok(MaybeIdReference::Id(id))
|
||||
} else {
|
||||
Ok(MaybeIdReference::Invalid(s.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Display + FromStr> serde::Serialize for MaybeIdReference<V> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
MaybeIdReference::Id(id) => serializer.serialize_str(&id.to_string()),
|
||||
MaybeIdReference::Reference(str) => serializer.serialize_str(&format!("#{}", str)),
|
||||
MaybeIdReference::Invalid(str) => serializer.serialize_str(str),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Default> Default for MaybeResultReference<V> {
|
||||
fn default() -> Self {
|
||||
MaybeResultReference::Value(V::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> MaybeResultReference<T> {
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
MaybeResultReference::Value(v) => v,
|
||||
MaybeResultReference::Reference(_) => T::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStr> MaybeIdReference<T> {
|
||||
pub fn try_unwrap(self) -> Option<T> {
|
||||
match self {
|
||||
MaybeIdReference::Id(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::Request;
|
||||
use crate::{
|
||||
error::request::{RequestError, RequestErrorType, RequestLimitError},
|
||||
object::AnyId,
|
||||
request::{Call, deserialize::DeserializeArguments},
|
||||
response::{Response, ResponseMethod, serialize::serialize_hex, status::PushObject},
|
||||
};
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
de::{self, MapAccess, Visitor},
|
||||
};
|
||||
use std::{borrow::Cow, collections::HashMap, fmt};
|
||||
use types::type_state::DataType;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebSocketRequest<'x> {
|
||||
pub id: Option<String>,
|
||||
pub request: Request<'x>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct WebSocketResponse<'x> {
|
||||
#[serde(rename = "@type")]
|
||||
_type: WebSocketResponseType,
|
||||
|
||||
#[serde(rename = "methodResponses")]
|
||||
method_responses: Vec<Call<ResponseMethod<'x>>>,
|
||||
|
||||
#[serde(rename = "sessionState")]
|
||||
#[serde(serialize_with = "serialize_hex")]
|
||||
session_state: u32,
|
||||
|
||||
#[serde(rename(deserialize = "createdIds"))]
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
created_ids: HashMap<String, AnyId>,
|
||||
|
||||
#[serde(rename = "requestId")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
request_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize)]
|
||||
pub enum WebSocketResponseType {
|
||||
Response,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct WebSocketPushEnable {
|
||||
pub data_types: Vec<DataType>,
|
||||
pub push_state: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WebSocketMessage<'x> {
|
||||
Request(WebSocketRequest<'x>),
|
||||
PushEnable(WebSocketPushEnable),
|
||||
PushDisable,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Debug)]
|
||||
pub struct WebSocketPushObject {
|
||||
#[serde(flatten)]
|
||||
pub push: PushObject,
|
||||
|
||||
#[serde(rename = "pushState")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub push_state: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct WebSocketRequestError<'x> {
|
||||
#[serde(rename = "@type")]
|
||||
pub type_: WebSocketRequestErrorType,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
p_type: RequestErrorType,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
limit: Option<RequestLimitError>,
|
||||
status: u16,
|
||||
detail: Cow<'x, str>,
|
||||
|
||||
#[serde(rename = "requestId")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub request_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Debug)]
|
||||
pub enum WebSocketRequestErrorType {
|
||||
RequestError,
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
Request,
|
||||
PushEnable,
|
||||
PushDisable,
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'x> WebSocketMessage<'x> {
|
||||
pub fn parse(json: &'x [u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
|
||||
if json.len() <= max_size {
|
||||
match serde_json::from_slice::<Self>(json) {
|
||||
Ok(WebSocketMessage::Request(req))
|
||||
if req.request.method_calls.len() > max_calls =>
|
||||
{
|
||||
Err(trc::LimitEvent::CallsIn.into_err())
|
||||
}
|
||||
Ok(msg) => Ok(msg),
|
||||
Err(err) => Err(trc::JmapEvent::NotRequest
|
||||
.into_err()
|
||||
.details(format!("Invalid WebSocket JMAP request {err}"))),
|
||||
}
|
||||
} else {
|
||||
Err(trc::LimitEvent::SizeRequest.into_err())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de: 'x, 'x: 'de> Deserialize<'de> for WebSocketMessage<'x> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(WebSocketMessageVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct WebSocketMessageVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for WebSocketMessageVisitor {
|
||||
type Value = WebSocketMessage<'de>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a WebSocketMessage as a map")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<WebSocketMessage<'de>, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut message_type = MessageType::None;
|
||||
let mut request = WebSocketRequest {
|
||||
id: None,
|
||||
request: Request::default(),
|
||||
};
|
||||
let mut push_enable = WebSocketPushEnable::default();
|
||||
|
||||
let mut found_request_keys = false;
|
||||
let mut found_push_keys = false;
|
||||
|
||||
while let Some(key) = map.next_key::<&str>()? {
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"@type" => {
|
||||
message_type = MessageType::parse(map.next_value()?);
|
||||
},
|
||||
b"dataTypes" => {
|
||||
push_enable.data_types = map.next_value::<Option<Vec<DataType>>>()?.unwrap_or_default();
|
||||
found_push_keys = true;
|
||||
},
|
||||
b"pushState" => {
|
||||
push_enable.push_state = map.next_value()?;
|
||||
found_push_keys = true;
|
||||
},
|
||||
b"id" => {
|
||||
request.id = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
request.request.deserialize_argument(key, &mut map)?;
|
||||
found_request_keys = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
match message_type {
|
||||
MessageType::Request if found_request_keys => Ok(WebSocketMessage::Request(request)),
|
||||
MessageType::PushEnable if found_push_keys => {
|
||||
Ok(WebSocketMessage::PushEnable(push_enable))
|
||||
}
|
||||
MessageType::PushDisable if !found_request_keys && !found_push_keys => {
|
||||
Ok(WebSocketMessage::PushDisable)
|
||||
}
|
||||
_ => Err(de::Error::custom("Invalid WebSocket JMAP request")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageType {
|
||||
fn parse(s: &str) -> Self {
|
||||
hashify::tiny_map!(s.as_bytes(),
|
||||
b"Request" => MessageType::Request,
|
||||
b"WebSocketPushEnable" => MessageType::PushEnable,
|
||||
b"WebSocketPushDisable" => MessageType::PushDisable,
|
||||
)
|
||||
.unwrap_or(MessageType::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> WebSocketRequestError<'x> {
|
||||
pub fn from_error(error: RequestError<'x>, request_id: Option<String>) -> Self {
|
||||
Self {
|
||||
type_: WebSocketRequestErrorType::RequestError,
|
||||
p_type: error.p_type,
|
||||
limit: error.limit,
|
||||
status: error.status,
|
||||
detail: error.detail,
|
||||
request_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<RequestError<'x>> for WebSocketRequestError<'x> {
|
||||
fn from(value: RequestError<'x>) -> Self {
|
||||
Self::from_error(value, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> WebSocketResponse<'x> {
|
||||
pub fn from_response(response: Response<'x>, request_id: Option<String>) -> Self {
|
||||
Self {
|
||||
_type: WebSocketResponseType::Response,
|
||||
method_responses: response.method_responses,
|
||||
session_state: response.session_state,
|
||||
created_ids: response.created_ids,
|
||||
request_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocketPushObject {
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user