From a95010c42e1dfc8469b1eda278d2459cc22b2f8f Mon Sep 17 00:00:00 2001 From: mdecimus Date: Wed, 24 Sep 2025 18:14:44 +0200 Subject: [PATCH] JMAP protocol layer refactoring (part 2) --- Cargo.lock | 1 - crates/common/src/config/jmap/settings.rs | 6 - crates/common/src/lib.rs | 4 +- crates/email/src/cache/mailbox.rs | 3 +- crates/email/src/mailbox/manage.rs | 2 +- crates/email/src/sieve/ingest.rs | 22 +- crates/imap/src/core/mailbox.rs | 3 +- crates/imap/src/op/create.rs | 6 +- crates/imap/src/op/search.rs | 6 +- crates/jmap-proto/Cargo.toml | 1 - crates/jmap-proto/src/error/set.rs | 10 +- crates/jmap-proto/src/method/changes.rs | 94 +-- crates/jmap-proto/src/method/copy.rs | 193 +++--- crates/jmap-proto/src/method/get.rs | 161 ++--- crates/jmap-proto/src/method/import.rs | 150 ++--- crates/jmap-proto/src/method/lookup.rs | 64 +- crates/jmap-proto/src/method/mod.rs | 2 +- crates/jmap-proto/src/method/parse.rs | 117 ++-- crates/jmap-proto/src/method/query.rs | 203 +++--- crates/jmap-proto/src/method/query_changes.rs | 138 ++-- .../jmap-proto/src/method/search_snippet.rs | 95 +-- crates/jmap-proto/src/method/set.rs | 378 ++--------- crates/jmap-proto/src/method/upload.rs | 255 +++---- crates/jmap-proto/src/method/validate.rs | 61 +- crates/jmap-proto/src/object/blob.rs | 94 ++- crates/jmap-proto/src/object/email.rs | 125 ++-- .../jmap-proto/src/object/email_submission.rs | 88 ++- crates/jmap-proto/src/object/identity.rs | 42 ++ crates/jmap-proto/src/object/mailbox.rs | 106 ++- crates/jmap-proto/src/object/mod.rs | 23 +- crates/jmap-proto/src/object/principal.rs | 46 +- .../src/object/push_subscription.rs | 45 +- crates/jmap-proto/src/object/quota.rs | 46 +- .../jmap-proto/src/object/search_snippet.rs | 8 +- crates/jmap-proto/src/object/sieve.rs | 94 ++- crates/jmap-proto/src/object/thread.rs | 53 +- .../src/object/vacation_response.rs | 48 +- crates/jmap-proto/src/request/capability.rs | 43 ++ crates/jmap-proto/src/request/deserialize.rs | 74 +++ crates/jmap-proto/src/request/method.rs | 2 +- crates/jmap-proto/src/request/mod.rs | 159 ++++- crates/jmap-proto/src/request/parser.rs | 622 ++++++++++++------ crates/jmap-proto/src/request/reference.rs | 9 +- crates/jmap-proto/src/request/websocket.rs | 168 +++-- crates/jmap-proto/src/response/mod.rs | 3 +- crates/jmap-proto/src/response/references.rs | 12 +- crates/jmap-proto/src/response/serialize.rs | 8 +- crates/jmap-proto/src/types/date.rs | 13 +- crates/jmap-proto/src/types/state.rs | 10 +- crates/migration/src/mailbox.rs | 4 +- crates/migration/src/push.rs | 2 +- crates/pop3/src/mailbox.rs | 4 +- crates/types/src/blob.rs | 8 +- crates/types/src/blob_hash.rs | 2 + crates/types/src/id.rs | 2 +- crates/types/src/keyword.rs | 22 +- crates/types/src/special_use.rs | 20 +- crates/types/src/type_state.rs | 12 +- crates/utils/src/codec/base32_custom.rs | 9 + crates/utils/src/map/vec_map.rs | 6 +- 60 files changed, 2260 insertions(+), 1747 deletions(-) create mode 100644 crates/jmap-proto/src/request/deserialize.rs diff --git a/Cargo.lock b/Cargo.lock index c3e4980c..2847910f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3918,7 +3918,6 @@ version = "0.13.3" dependencies = [ "ahash", "compact_str", - "fast-float", "hashify", "jmap-tools", "mail-parser", diff --git a/crates/common/src/config/jmap/settings.rs b/crates/common/src/config/jmap/settings.rs index edb45b3c..7df8aa55 100644 --- a/crates/common/src/config/jmap/settings.rs +++ b/crates/common/src/config/jmap/settings.rs @@ -346,9 +346,3 @@ impl JmapConfig { jmap } } - -impl ParseValue for SpecialUse { - fn parse_value(value: &str) -> Result { - SpecialUse::parse(value).ok_or_else(|| format!("Unknown folder role {:?}", value)) - } -} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index c1d88d8b..9eee2ddd 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -13,7 +13,7 @@ use calcard::common::timezone::Tz; use config::{ groupware::GroupwareConfig, imap::ImapConfig, - jmap::settings::{JmapConfig, SpecialUse}, + jmap::settings::JmapConfig, network::Network, scripts::Scripting, smtp::{ @@ -40,7 +40,7 @@ use std::{ use tinyvec::TinyVec; use tokio::sync::{Notify, Semaphore, mpsc}; use tokio_rustls::TlsConnector; -use types::acl::AclGrant; +use types::{acl::AclGrant, special_use::SpecialUse}; use utils::{ cache::{Cache, CacheItemWeight, CacheWithTtl}, snowflake::SnowflakeIdGenerator, diff --git a/crates/email/src/cache/mailbox.rs b/crates/email/src/cache/mailbox.rs index d7577d3c..bb8eacb2 100644 --- a/crates/email/src/cache/mailbox.rs +++ b/crates/email/src/cache/mailbox.rs @@ -7,13 +7,14 @@ use crate::mailbox::{ArchivedMailbox, Mailbox, manage::MailboxFnc}; use common::{ MailboxCache, MailboxesCache, MessageStoreCache, Server, auth::AccessToken, - config::jmap::settings::SpecialUse, sharing::EffectiveAcl, + sharing::EffectiveAcl, }; use store::{ahash::AHashMap, roaring::RoaringBitmap}; use trc::AddContext; use types::{ acl::{Acl, AclGrant}, collection::Collection, + special_use::SpecialUse, }; use utils::{map::bitmap::Bitmap, topological::TopologicalSort}; diff --git a/crates/email/src/mailbox/manage.rs b/crates/email/src/mailbox/manage.rs index e57d3ab4..d35e2608 100644 --- a/crates/email/src/mailbox/manage.rs +++ b/crates/email/src/mailbox/manage.rs @@ -6,7 +6,7 @@ use super::*; use crate::cache::MessageCacheFetch; -use common::{Server, config::jmap::settings::SpecialUse, storage::index::ObjectIndexBuilder}; +use common::{Server, storage::index::ObjectIndexBuilder}; use std::future::Future; use store::write::BatchBuilder; use trc::AddContext; diff --git a/crates/email/src/sieve/ingest.rs b/crates/email/src/sieve/ingest.rs index 1b93297b..2e61369e 100644 --- a/crates/email/src/sieve/ingest.rs +++ b/crates/email/src/sieve/ingest.rs @@ -13,14 +13,12 @@ use crate::{ ingest::{EmailIngest, IngestEmail, IngestSource, IngestedEmail}, }, }; -use common::{ - Server, auth::AccessToken, config::jmap::settings::SpecialUse, scripts::plugins::PluginContext, -}; +use common::{Server, auth::AccessToken, scripts::plugins::PluginContext}; use directory::{Permission, QueryParams}; use mail_parser::MessageParser; use sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve}; -use std::future::Future; use std::{borrow::Cow, sync::Arc}; +use std::{future::Future, str::FromStr}; use store::{ Deserialize, Serialize, SerializeInfallible, ahash::AHashMap, @@ -29,7 +27,9 @@ use store::{ write::{AlignedBytes, Archive, ArchiveVersion, Archiver, BatchBuilder, BlobOp}, }; use trc::{AddContext, SieveEvent}; -use types::{collection::Collection, field::SieveField, id::Id, keyword::Keyword}; +use types::{ + collection::Collection, field::SieveField, id::Id, keyword::Keyword, special_use::SpecialUse, +}; use utils::config::utils::ParseValue; struct SieveMessage<'x> { @@ -211,10 +211,10 @@ impl SieveScriptIngest for Server { } } Mailbox::Id(id) => { - if !matches!(Id::from_bytes(id.as_bytes()), Some(id) if - cache.has_mailbox_id(&id.document_id()) && - (special_use_ids.is_empty() || - special_use_ids.contains(&id.document_id()))) + if !matches!(Id::from_str(&id), Ok(id) if + cache.has_mailbox_id(&id.document_id()) && + (special_use_ids.is_empty() || + special_use_ids.contains(&id.document_id()))) { result = false; break; @@ -308,9 +308,7 @@ impl SieveScriptIngest for Server { let mut target_id = u32::MAX; // Find mailbox by Id - if let Some(mailbox_id) = - mailbox_id.and_then(|m| Id::from_bytes(m.as_bytes())) - { + if let Some(mailbox_id) = mailbox_id.and_then(|m| Id::from_str(&m).ok()) { let mailbox_id = mailbox_id.document_id(); if cache.has_mailbox_id(&mailbox_id) { target_id = mailbox_id; diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index 3a7bd1a5..e8431354 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -9,7 +9,6 @@ use crate::core::Mailbox; use ahash::AHashMap; use common::{ auth::AccessToken, - config::jmap::settings::SpecialUse, listener::{SessionStream, limiter::InFlight}, sharing::EffectiveAcl, }; @@ -25,7 +24,7 @@ use std::{ sync::{Arc, atomic::Ordering}, }; use trc::AddContext; -use types::{acl::Acl, collection::Collection, id::Id, keyword::Keyword}; +use types::{acl::Acl, collection::Collection, id::Id, keyword::Keyword, special_use::SpecialUse}; impl SessionData { pub async fn new( diff --git a/crates/imap/src/op/create.rs b/crates/imap/src/op/create.rs index 90796bdd..dbf46c77 100644 --- a/crates/imap/src/op/create.rs +++ b/crates/imap/src/op/create.rs @@ -9,9 +9,7 @@ use crate::{ op::ImapContext, spawn_op, }; -use common::{ - config::jmap::settings::SpecialUse, listener::SessionStream, storage::index::ObjectIndexBuilder, -}; +use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; use directory::Permission; use email::cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}; use imap_proto::{ @@ -22,7 +20,7 @@ use imap_proto::{ use std::time::Instant; use store::write::BatchBuilder; use trc::AddContext; -use types::{acl::Acl, collection::Collection, id::Id}; +use types::{acl::Acl, collection::Collection, id::Id, special_use::SpecialUse}; impl Session { pub async fn handle_create(&mut self, requests: Vec>) -> trc::Result<()> { diff --git a/crates/imap/src/op/search.rs b/crates/imap/src/op/search.rs index e19ece14..8843af98 100644 --- a/crates/imap/src/op/search.rs +++ b/crates/imap/src/op/search.rs @@ -22,7 +22,7 @@ use imap_proto::{ }; use mail_parser::HeaderName; use nlp::language::Language; -use std::{sync::Arc, time::Instant}; +use std::{str::FromStr, sync::Arc, time::Instant}; use store::{ SerializeInfallible, fts::{Field, FilterGroup, FtsFilter, IntoFilterGroup}, @@ -650,7 +650,7 @@ impl SessionData { include_highest_modseq = true; } search::Filter::EmailId(id) => { - if let Some(id) = Id::from_bytes(id.as_bytes()) { + if let Ok(id) = Id::from_str(&id) { filters.push(query::Filter::is_in_set( RoaringBitmap::from_sorted_iter([id.document_id()]).unwrap(), )); @@ -661,7 +661,7 @@ impl SessionData { } } search::Filter::ThreadId(id) => { - if let Some(id) = Id::from_bytes(id.as_bytes()) { + if let Ok(id) = Id::from_str(&id) { filters.push(query::Filter::is_in_set(RoaringBitmap::from_iter( cache.in_thread(id.document_id()).map(|m| m.document_id), ))); diff --git a/crates/jmap-proto/Cargo.toml b/crates/jmap-proto/Cargo.toml index 302d1c93..5e69e2b2 100644 --- a/crates/jmap-proto/Cargo.toml +++ b/crates/jmap-proto/Cargo.toml @@ -11,7 +11,6 @@ types = { path = "../types" } trc = { path = "../trc" } jmap-tools = { path = "/Users/me/code/jmap-tool" } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } -fast-float = "0.2.0" serde = { version = "1.0", features = ["derive"]} ahash = { version = "0.8.2", features = ["serde"] } serde_json = { version = "1.0", features = ["raw_value"] } diff --git a/crates/jmap-proto/src/error/set.rs b/crates/jmap-proto/src/error/set.rs index 994a3582..07e47bf4 100644 --- a/crates/jmap-proto/src/error/set.rs +++ b/crates/jmap-proto/src/error/set.rs @@ -131,14 +131,14 @@ impl SetError { self } - pub fn with_property(mut self, property: impl Into) -> Self { + pub fn with_property(mut self, property: impl Into>) -> Self { self.properties = vec![property.into()].into(); self } pub fn with_properties( mut self, - properties: impl IntoIterator>, + properties: impl IntoIterator>>, ) -> Self { self.properties = properties .into_iter() @@ -188,13 +188,13 @@ impl SetError { impl From for InvalidProperty { fn from(property: T) -> Self { - InvalidProperty::Property(property) + InvalidProperty::Property(Key::Property(property)) } } impl From<(T, T)> for InvalidProperty { fn from((a, b): (T, T)) -> Self { - InvalidProperty::Path(vec![a, b]) + InvalidProperty::Path(vec![Key::Property(a), Key::Property(b)]) } } @@ -212,7 +212,7 @@ impl serde::Serialize for InvalidProperty { if i > 0 { path.push('/'); } - let _ = write!(path, "{}", p); + let _ = write!(path, "{}", p.to_string()); } path.serialize(serializer) } diff --git a/crates/jmap-proto/src/method/changes.rs b/crates/jmap-proto/src/method/changes.rs index 75523426..5e446c67 100644 --- a/crates/jmap-proto/src/method/changes.rs +++ b/crates/jmap-proto/src/method/changes.rs @@ -4,12 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{object::JmapObject, request::method::MethodObject, types::state::State}; -use compact_str::format_compact; -use jmap_tools::Property; +use crate::{ + object::JmapObject, + request::deserialize::{DeserializeArguments, deserialize_request}, + types::state::State, +}; +use serde::{Deserialize, Deserializer}; use types::id::Id; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ChangesRequest { pub account_id: Id, pub since_state: State, @@ -41,71 +44,40 @@ pub struct ChangesResponse { pub updated_properties: Option>, } -/*#[derive(Debug, Clone, serde::Serialize)] -pub enum RequestArguments { - Email, - Mailbox, - Thread, - Identity, - EmailSubmission, - Quota, -}*/ - -impl JsonObjectParser for ChangesRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for ChangesRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = ChangesRequest { - arguments: match &parser.ctx { - MethodObject::Email => RequestArguments::Email, - MethodObject::Mailbox => RequestArguments::Mailbox, - MethodObject::Thread => RequestArguments::Thread, - MethodObject::Identity => RequestArguments::Identity, - MethodObject::EmailSubmission => RequestArguments::EmailSubmission, - MethodObject::Quota => RequestArguments::Quota, - _ => { - return Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(format_compact!("{}/changes", parser.ctx))); - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; }, - account_id: Id::default(), - since_state: State::Initial, - max_changes: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x6574_6174_5365_636e_6973 => { - request.since_state = parser - .next_token::()? - .unwrap_string("sinceQueryState")?; - } - 0x7365_676e_6168_4378_616d => { - request.max_changes = parser - .next_token::()? - .unwrap_usize_or_null("maxChanges")?; - } - - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + b"sinceQueryState" => { + self.since_state = map.next_value()?; + }, + b"maxChanges" => { + self.max_changes = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) } } -impl ChangesResponse { +impl<'de> Deserialize<'de> for ChangesRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl ChangesResponse { pub fn has_changes(&self) -> bool { !self.created.is_empty() || !self.updated.is_empty() || !self.destroyed.is_empty() } diff --git a/crates/jmap-proto/src/method/copy.rs b/crates/jmap-proto/src/method/copy.rs index 76897639..d0653035 100644 --- a/crates/jmap-proto/src/method/copy.rs +++ b/crates/jmap-proto/src/method/copy.rs @@ -7,12 +7,15 @@ use crate::{ error::set::SetError, object::{JmapObject, blob::BlobProperty}, - request::{MaybeInvalid, method::MethodObject, reference::MaybeIdReference}, + request::{ + MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, + reference::MaybeIdReference, + }, types::state::State, }; -use compact_str::format_compact; use jmap_tools::Value; -use serde::Serialize; +use serde::{Deserialize, Deserializer, Serialize}; use types::{blob::BlobId, id::Id}; use utils::map::vec_map::VecMap; @@ -27,11 +30,6 @@ pub struct CopyRequest<'x, T: JmapObject> { pub destroy_from_if_in_state: Option, } -/*#[derive(Debug, Clone)] -pub enum RequestArguments { - Email, -}*/ - #[derive(Debug, Clone, serde::Serialize)] pub struct CopyResponse { #[serde(rename = "fromAccountId")] @@ -55,7 +53,7 @@ pub struct CopyResponse { pub not_created: VecMap, SetError>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct CopyBlobRequest { pub from_account_id: Id, pub account_id: Id, @@ -79,109 +77,94 @@ pub struct CopyBlobResponse { pub not_copied: VecMap, SetError>, } -impl JsonObjectParser for CopyRequest { - fn parse(parser: &mut Parser) -> trc::Result +impl<'de, T: JmapObject> DeserializeArguments<'de> for CopyRequest<'de, T> { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = CopyRequest { - arguments: match &parser.ctx { - MethodObject::Email => RequestArguments::Email, - _ => { - return Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(format_compact!("{}/copy", parser.ctx))); - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; }, - account_id: Id::default(), - if_in_state: None, + b"ifInState" => { + self.if_in_state = map.next_value()?; + }, + b"fromAccountId" => { + self.from_account_id = map.next_value()?; + }, + b"ifFromInState" => { + self.if_from_in_state = map.next_value()?; + }, + b"create" => { + self.create = map.next_value()?; + }, + b"onSuccessDestroyOriginal" => { + self.on_success_destroy_original = map.next_value()?; + }, + b"destroyFromIfInState" => { + self.destroy_from_if_in_state = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; + } + ); + + Ok(()) + } +} + +impl<'de> DeserializeArguments<'de> for CopyBlobRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"fromAccountId" => { + self.from_account_id = map.next_value()?; + }, + b"blobIds" => { + self.blob_ids = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; + } + ); + + Ok(()) + } +} + +impl<'de, T: JmapObject> Deserialize<'de> for CopyRequest<'de, T> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'de> Deserialize<'de> for CopyBlobRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'de, T: JmapObject> Default for CopyRequest<'de, T> { + fn default() -> Self { + CopyRequest { from_account_id: Id::default(), if_from_in_state: None, - create: VecMap::default(), + account_id: Id::default(), + if_in_state: None, + create: VecMap::new(), on_success_destroy_original: None, destroy_from_if_in_state: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x6574_6165_7263 => { - request.create = - , Value<'x, P, E>>>::parse(parser)?; - } - 0x0064_4974_6e75_6f63_6341_6d6f_7266 => { - request.from_account_id = - parser.next_token::()?.unwrap_string("fromAccountId")?; - } - 0x0065_7461_7453_6e49_6d6f_7246_6669 => { - request.if_from_in_state = parser - .next_token::()? - .unwrap_string_or_null("ifFromInState")?; - } - 0x796f_7274_7365_4473_7365_6363_7553_6e6f => { - request.on_success_destroy_original = parser - .next_token::()? - .unwrap_bool_or_null("onSuccessDestroyOriginal")?; - } - 0x536e_4966_496d_6f72_4679_6f72_7473_6564 => { - request.destroy_from_if_in_state = parser - .next_token::()? - .unwrap_string_or_null("destroyFromIfInState")?; - } - 0x0065_7461_7453_6e49_6669 => { - request.if_in_state = parser - .next_token::()? - .unwrap_string_or_null("ifInState")?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } } - - Ok(request) - } -} - -impl JsonObjectParser for CopyBlobRequest { - fn parse(parser: &mut Parser) -> trc::Result - where - Self: Sized, - { - let mut request = CopyBlobRequest { - account_id: Id::default(), - from_account_id: Id::default(), - blob_ids: Vec::new(), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x0064_4974_6e75_6f63_6341_6d6f_7266 => { - request.from_account_id = - parser.next_token::()?.unwrap_string("fromAccountId")?; - } - 0x0073_6449_626f_6c62 => { - request.blob_ids = >::parse(parser)?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } - } - - Ok(request) } } diff --git a/crates/jmap-proto/src/method/get.rs b/crates/jmap-proto/src/method/get.rs index 69ea229f..29cd709e 100644 --- a/crates/jmap-proto/src/method/get.rs +++ b/crates/jmap-proto/src/method/get.rs @@ -8,39 +8,23 @@ use crate::{ object::JmapObject, request::{ MaybeInvalid, - method::MethodObject, + deserialize::{DeserializeArguments, deserialize_request}, reference::{MaybeIdReference, MaybeResultReference, ResultReference}, }, types::state::State, }; -use compact_str::format_compact; use jmap_tools::{Property, Value}; +use serde::{Deserialize, Deserializer}; use types::{blob::BlobId, id::Id}; -#[derive(Debug, Clone, serde::Deserialize)] +#[derive(Debug, Clone)] pub struct GetRequest { pub account_id: Id, pub ids: Option>>>, - pub properties: Option>>, - #[serde(flatten)] + pub properties: Option>>>, pub arguments: T::GetArguments, } -/*#[derive(Debug, Clone)] -pub enum RequestArguments { - Email(email::GetArguments), - Mailbox, - Thread, - Identity, - EmailSubmission, - PushSubscription, - SieveScript, - VacationResponse, - Principal, - Quota, - Blob(blob::GetArguments), -}*/ - #[derive(Debug, Clone, serde::Serialize)] pub struct GetResponse { #[serde(rename = "accountId")] @@ -56,107 +40,59 @@ pub struct GetResponse { pub not_found: Vec>, } -impl JsonObjectParser for GetRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de, T: JmapObject> DeserializeArguments<'de> for GetRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = GetRequest { - arguments: match &parser.ctx { - MethodObject::Email => RequestArguments::Email(Default::default()), - MethodObject::Mailbox => RequestArguments::Mailbox, - MethodObject::Thread => RequestArguments::Thread, - MethodObject::Identity => RequestArguments::Identity, - MethodObject::EmailSubmission => RequestArguments::EmailSubmission, - MethodObject::PushSubscription => RequestArguments::PushSubscription, - MethodObject::SieveScript => RequestArguments::SieveScript, - MethodObject::VacationResponse => RequestArguments::VacationResponse, - MethodObject::Principal => RequestArguments::Principal, - MethodObject::Blob => RequestArguments::Blob(Default::default()), - MethodObject::Quota => RequestArguments::Quota, - _ => { - return Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(format_compact!("{}/get", parser.ctx))); - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; }, + b"ids" => { + self.ids = map.next_value::>>>()?.map(MaybeResultReference::Value); + }, + b"properties" => { + self.properties = map.next_value::>>>()?.map(MaybeResultReference::Value); + }, + b"#ids" => { + self.ids = Some(MaybeResultReference::Reference(map.next_value::()?)); + }, + b"#properties" => { + self.properties = Some(MaybeResultReference::Reference(map.next_value::()?)); + }, + _ => { + self.arguments.deserialize_argument(key, map)?; + } + ); + + Ok(()) + } +} + +impl<'de, T: JmapObject> Deserialize<'de> for GetRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl Default for GetRequest { + fn default() -> Self { + Self { account_id: Id::default(), ids: None, properties: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x0073_6469 => { - request.ids = if !key.is_ref { - if parser.ctx != MethodObject::Blob { - >>>::parse(parser)?.map(|ids| { - MaybeReference::Value(ids.into_iter().map(Into::into).collect()) - }) - } else { - >>>::parse(parser)?.map( - |ids| { - MaybeReference::Value(ids.into_iter().map(Into::into).collect()) - }, - ) - } - } else { - Some(MaybeReference::Reference(ResultReference::parse(parser)?)) - }; - } - 0x7365_6974_7265_706f_7270 => { - request.properties = if !key.is_ref { - >>::parse(parser)?.map(MaybeReference::Value) - } else { - Some(MaybeReference::Reference(ResultReference::parse(parser)?)) - }; - } - _ => { - if !request.arguments.parse(parser, key)? { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } - } - } - - Ok(request) - } -} - -impl RequestPropertyParser for RequestArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - match self { - RequestArguments::Email(arguments) => arguments.parse(parser, property), - RequestArguments::Blob(arguments) => arguments.parse(parser, property), - _ => Ok(false), + arguments: T::GetArguments::default(), } } } -impl GetRequest { - pub fn take_arguments(&mut self) -> RequestArguments { - std::mem::replace(&mut self.arguments, RequestArguments::VacationResponse) - } - - pub fn with_arguments(self, arguments: T) -> GetRequest { - GetRequest { - arguments, - account_id: self.account_id, - ids: self.ids, - properties: self.properties, - } - } -} - -impl GetRequest { - pub fn unwrap_properties(&mut self, default: &[Property]) -> Vec { +/* +impl GetRequest { + pub fn unwrap_properties(&mut self, default: &[T::Property]) -> Vec { if let Some(mut properties) = self.properties.take().map(|p| p.unwrap()) { // Add Id Property if !properties.contains(&Property::Id) { @@ -168,7 +104,7 @@ impl GetRequest { } } - pub fn unwrap_ids(&mut self, max_objects_in_get: usize) -> trc::Result>> { + pub fn unwrap_ids(&mut self, max_objects_in_get: usize) -> trc::Result>> { if let Some(ids) = self.ids.take() { let ids = ids.unwrap(); if ids.len() <= max_objects_in_get { @@ -205,3 +141,4 @@ impl GetRequest { } } } +*/ diff --git a/crates/jmap-proto/src/method/import.rs b/crates/jmap-proto/src/method/import.rs index 3896f1b4..764c1386 100644 --- a/crates/jmap-proto/src/method/import.rs +++ b/crates/jmap-proto/src/method/import.rs @@ -7,24 +7,29 @@ use crate::{ error::set::SetError, object::email::{EmailProperty, EmailValue}, - request::reference::{MaybeIdReference, MaybeResultReference, ResultReference}, + request::{ + MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, + reference::{MaybeIdReference, MaybeResultReference, ResultReference}, + }, response::Response, types::{date::UTCDate, state::State}, }; -use jmap_tools::Value; +use jmap_tools::{Key, Value}; +use serde::{Deserialize, Deserializer}; use types::{blob::BlobId, id::Id, keyword::Keyword}; use utils::map::vec_map::VecMap; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ImportEmailRequest { pub account_id: Id, pub if_in_state: Option, pub emails: VecMap, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ImportEmail { - pub blob_id: BlobId, + pub blob_id: MaybeInvalid, pub mailbox_ids: MaybeResultReference>>, pub keywords: Vec, pub received_at: Option, @@ -51,96 +56,85 @@ pub struct ImportEmailResponse { pub not_created: VecMap>, } -impl JsonObjectParser for ImportEmailRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for ImportEmailRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = ImportEmailRequest { - account_id: Id::default(), - if_in_state: None, - emails: VecMap::new(), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x0065_7461_7453_6e49_6669 if !key.is_ref => { - request.if_in_state = parser - .next_token::()? - .unwrap_string_or_null("ifInState")?; - } - 0x736c_6961_6d65 if !key.is_ref => { - request.emails = >::parse(parser)?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"ifInState" => { + self.if_in_state = map.next_value()?; + }, + b"emails" => { + self.emails = map.next_value()?; } - } + _ => { + let _ = map.next_value::()?; + } + ); - Ok(request) + Ok(()) } } -impl JsonObjectParser for ImportEmail { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for ImportEmail { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = ImportEmail { - blob_id: BlobId::default(), - mailbox_ids: MaybeReference::Value(vec![]), - keywords: vec![], - received_at: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x6449_626f_6c62 if !key.is_ref => { - request.blob_id = parser.next_token::()?.unwrap_string("blobId")?; - } - 0x7364_4978_6f62_6c69_616d => { - request.mailbox_ids = if !key.is_ref { - MaybeReference::Value( - >>::parse(parser)?.values, - ) - } else { - MaybeReference::Reference(ResultReference::parse(parser)?) - }; - } - 0x7364_726f_7779_656b if !key.is_ref => { - request.keywords = >::parse(parser)?.values; - } - 0x7441_6465_7669_6563_6572 if !key.is_ref => { - request.received_at = parser - .next_token::()? - .unwrap_string_or_null("receivedAt")?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"blobId" => { + self.blob_id = map.next_value()?; + }, + b"keywords" => { + self.keywords = map.next_value()?; + }, + b"receivedAt" => { + self.received_at = map.next_value()?; + }, + b"mailboxIds" => { + self.mailbox_ids = MaybeResultReference::Value(map.next_value::>>()?); + }, + b"#mailboxIds" => { + self.mailbox_ids = MaybeResultReference::Reference(map.next_value::()?); + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) + } +} + +impl<'de> Deserialize<'de> for ImportEmail { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'de> Deserialize<'de> for ImportEmailRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) } } impl ImportEmailResponse { pub fn update_created_ids(&self, response: &mut Response) { for (user_id, obj) in &self.created { - if let Some(id) = obj.get(&Property::Id).as_id() { + if let Value::Object(obj) = obj + && let Some(Value::Element(EmailValue::Id(id))) = + obj.get(&Key::Property(EmailProperty::Id)) + { response.created_ids.insert(user_id.clone(), (*id).into()); } } diff --git a/crates/jmap-proto/src/method/lookup.rs b/crates/jmap-proto/src/method/lookup.rs index 326bf16f..94085139 100644 --- a/crates/jmap-proto/src/method/lookup.rs +++ b/crates/jmap-proto/src/method/lookup.rs @@ -4,12 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::request::{ + MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, +}; +use serde::{Deserialize, Deserializer}; use types::{blob::BlobId, id::Id, type_state::DataType}; use utils::map::vec_map::VecMap; -use crate::request::MaybeInvalid; - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct BlobLookupRequest { pub account_id: Id, pub type_names: Vec>, @@ -35,38 +38,35 @@ pub struct BlobInfo { pub matched_ids: VecMap>, } -impl JsonObjectParser for BlobLookupRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for BlobLookupRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = BlobLookupRequest { - account_id: Id::default(), - type_names: Vec::new(), - ids: Vec::new(), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x0073_656d_614e_6570_7974 if !key.is_ref => { - request.type_names = >>::parse(parser)?; - } - 0x0073_6469 if !key.is_ref => { - request.ids = >>::parse(parser)?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"typeNames" => { + self.type_names = map.next_value()?; + }, + b"ids" => { + self.ids = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) + } +} + +impl<'de> Deserialize<'de> for BlobLookupRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) } } diff --git a/crates/jmap-proto/src/method/mod.rs b/crates/jmap-proto/src/method/mod.rs index 1a218d98..71def917 100644 --- a/crates/jmap-proto/src/method/mod.rs +++ b/crates/jmap-proto/src/method/mod.rs @@ -20,6 +20,6 @@ pub mod upload; pub mod validate; #[inline(always)] -pub fn ahash_is_empty(map: &AHashMap) -> bool { +fn ahash_is_empty(map: &AHashMap) -> bool { map.is_empty() } diff --git a/crates/jmap-proto/src/method/parse.rs b/crates/jmap-proto/src/method/parse.rs index 9804b0d3..ebf7b0f3 100644 --- a/crates/jmap-proto/src/method/parse.rs +++ b/crates/jmap-proto/src/method/parse.rs @@ -4,21 +4,24 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{ + object::email::{EmailProperty, EmailValue}, + request::{ + MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, + }, +}; use jmap_tools::Value; +use serde::{Deserialize, Deserializer}; use types::{blob::BlobId, id::Id}; use utils::map::vec_map::VecMap; -use crate::{ - object::email::{EmailProperty, EmailValue}, - request::MaybeInvalid, -}; - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ParseEmailRequest { pub account_id: Id, pub blob_ids: Vec>, - pub properties: Option>, - pub body_properties: Option>, + pub properties: Option>>, + pub body_properties: Option>>, pub fetch_text_body_values: Option, pub fetch_html_body_values: Option, pub fetch_all_body_values: Option, @@ -43,66 +46,50 @@ pub struct ParseEmailResponse { pub not_found: Vec>, } -impl JsonObjectParser for ParseEmailRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for ParseEmailRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = ParseEmailRequest { - account_id: Id::default(), - properties: None, - blob_ids: vec![], - body_properties: None, - fetch_text_body_values: None, - fetch_html_body_values: None, - fetch_all_body_values: None, - max_body_value_bytes: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match (&key.hash[0], &key.hash[1]) { - (0x0064_4974_6e75_6f63_6361, _) if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - (0x0073_6449_626f_6c62, _) => { - request.blob_ids = >::parse(parser)?; - } - (0x7365_6974_7265_706f_7270, _) => { - request.properties = >>::parse(parser)?; - } - (0x7365_6974_7265_706f_7250_7964_6f62, _) => { - request.body_properties = >>::parse(parser)?; - } - (0x6c61_5679_646f_4274_7865_5468_6374_6566, 0x0073_6575) => { - request.fetch_text_body_values = parser - .next_token::()? - .unwrap_bool_or_null("fetchTextBodyValues")?; - } - (0x6c61_5679_646f_424c_4d54_4868_6374_6566, 0x0073_6575) => { - request.fetch_html_body_values = parser - .next_token::()? - .unwrap_bool_or_null("fetchHTMLBodyValues")?; - } - (0x756c_6156_7964_6f42_6c6c_4168_6374_6566, 0x7365) => { - request.fetch_all_body_values = parser - .next_token::()? - .unwrap_bool_or_null("fetchAllBodyValues")?; - } - (0x6574_7942_6575_6c61_5679_646f_4278_616d, 0x73) => { - request.max_body_value_bytes = parser - .next_token::()? - .unwrap_usize_or_null("maxBodyValueBytes")?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"blobIds" => { + self.blob_ids = map.next_value()?; + }, + b"properties" => { + self.properties = map.next_value()?; + }, + b"bodyProperties" => { + self.body_properties = map.next_value()?; + }, + b"fetchTextBodyValues" => { + self.fetch_text_body_values = map.next_value()?; + }, + b"fetchHTMLBodyValues" => { + self.fetch_html_body_values = map.next_value()?; + }, + b"fetchAllBodyValues" => { + self.fetch_all_body_values = map.next_value()?; + }, + b"maxBodyValueBytes" => { + self.max_body_value_bytes = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) + } +} + +impl<'de> Deserialize<'de> for ParseEmailRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) } } diff --git a/crates/jmap-proto/src/method/query.rs b/crates/jmap-proto/src/method/query.rs index 69b410f7..51cf8cbf 100644 --- a/crates/jmap-proto/src/method/query.rs +++ b/crates/jmap-proto/src/method/query.rs @@ -6,10 +6,14 @@ use crate::{ object::{JmapObject, email, mailbox}, - request::method::MethodObject, + request::{ + deserialize::{DeserializeArguments, deserialize_request}, + method::MethodObject, + }, types::{date::UTCDate, state::State}, }; use compact_str::format_compact; +use serde::{Deserialize, Deserializer, de::DeserializeOwned}; use std::fmt::Display; use store::fts::{FilterItem, FilterType, FtsFilter}; use types::{id::Id, keyword::Keyword}; @@ -17,8 +21,8 @@ use types::{id::Id, keyword::Keyword}; #[derive(Debug, Clone)] pub struct QueryRequest { pub account_id: Id, - pub filter: Vec, - pub sort: Option>, + pub filter: Vec>, + pub sort: Option>>, pub position: Option, pub anchor: Option, pub anchor_offset: Option, @@ -53,9 +57,19 @@ pub struct QueryResponse { pub limit: Option, } -#[derive(Clone, Debug)] -pub enum Filter { - Email(String), +#[derive(Clone, Debug, Deserialize)] +pub enum Filter { + Property(T), + + And, + Or, + Not, + Close, +} + +/* + +Email(String), Name(String), DomainName(String), Text(String), @@ -100,18 +114,14 @@ pub enum Filter { ResourceType(String), _T(String), - And, - Or, - Not, - Close, -} +*/ -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Comparator { +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Comparator { pub is_ascending: bool, pub collation: Option, - pub property: SortProperty, - pub keyword: Option, + pub property: T, + //pub keyword: Option, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -138,35 +148,58 @@ pub enum SortProperty { _T(String), } -#[derive(Debug, Clone)] -pub enum RequestArguments { - Email(email::QueryArguments), - Mailbox(mailbox::QueryArguments), - EmailSubmission, - SieveScript, - Principal, - Quota, +impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"filter" => { + self.filter = map.next_value()?; + }, + b"sort" => { + self.sort = map.next_value()?; + }, + b"calculateTotal" => { + self.calculate_total = map.next_value()?; + }, + b"position" => { + self.position = map.next_value()?; + }, + b"anchor" => { + self.anchor = map.next_value()?; + }, + b"anchorOffset" => { + self.anchor_offset = map.next_value()?; + }, + b"limit" => { + self.limit = map.next_value()?; + }, + _ => { + self.arguments.deserialize_argument(key, map)?; + } + ); + + Ok(()) + } } -impl JsonObjectParser for QueryRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de, T: JmapObject> Deserialize<'de> for QueryRequest { + fn deserialize(deserializer: D) -> Result where - Self: Sized, + D: Deserializer<'de>, { - let mut request = QueryRequest { - arguments: match &parser.ctx { - MethodObject::Email => RequestArguments::Email(Default::default()), - MethodObject::Mailbox => RequestArguments::Mailbox(Default::default()), - MethodObject::EmailSubmission => RequestArguments::EmailSubmission, - MethodObject::SieveScript => RequestArguments::SieveScript, - MethodObject::Principal => RequestArguments::Principal, - MethodObject::Quota => RequestArguments::Quota, - _ => { - return Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(format_compact!("{}/query", parser.ctx))); - } - }, + deserialize_request(deserializer) + } +} + +impl Default for QueryRequest { + fn default() -> Self { + Self { + account_id: Id::default(), filter: vec![], sort: None, position: None, @@ -174,72 +207,12 @@ impl JsonObjectParser for QueryRequest { anchor_offset: None, limit: None, calculate_total: None, - account_id: Id::default(), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x7265_746c_6966 => match parser.next_token::()? { - Token::DictStart => { - request.filter = parse_filter(parser)?; - } - Token::Null => (), - token => { - return Err(token.error("filter", "object or null")); - } - }, - 0x7472_6f73 => match parser.next_token::()? { - Token::ArrayStart => { - request.sort = parse_sort(parser)?.into(); - } - Token::Null => (), - token => { - return Err(token.error("sort", "array or null")); - } - }, - 0x6e6f_6974_6973_6f70 => { - request.position = parser - .next_token::()? - .unwrap_ints_or_null("position")?; - } - 0x726f_6863_6e61 => { - request.anchor = parser.next_token::()?.unwrap_string_or_null("anchor")?; - } - 0x7465_7366_664f_726f_6863_6e61 => { - request.anchor_offset = parser - .next_token::()? - .unwrap_ints_or_null("anchorOffset")?; - } - 0x0074_696d_696c => { - request.limit = parser - .next_token::()? - .unwrap_usize_or_null("limit")?; - } - 0x6c61_746f_5465_7461_6c75_636c_6163 => { - request.calculate_total = parser - .next_token::()? - .unwrap_bool_or_null("calculateTotal")?; - } - - _ => { - if !request.arguments.parse(parser, key)? { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } - } + arguments: T::QueryArguments::default(), } - - Ok(request) } } +/* pub fn parse_filter(parser: &mut Parser) -> trc::Result> { let mut filter = vec![Filter::Close]; let mut pos_stack = vec![0]; @@ -581,6 +554,8 @@ impl JsonObjectParser for SortProperty { } } + + impl Display for Filter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { @@ -720,28 +695,6 @@ impl Comparator { } } -impl QueryRequest { - pub fn take_arguments(&mut self) -> RequestArguments { - std::mem::replace(&mut self.arguments, RequestArguments::SieveScript) - } -} - -impl QueryRequest { - pub fn with_arguments(self, arguments: A) -> QueryRequest { - QueryRequest { - arguments, - account_id: self.account_id, - filter: self.filter, - sort: self.sort, - position: self.position, - anchor: self.anchor, - anchor_offset: self.anchor_offset, - limit: self.limit, - calculate_total: self.calculate_total, - } - } -} - impl From for store::query::Filter { fn from(value: Filter) -> Self { match value { @@ -797,3 +750,5 @@ impl From for Filter { } } } + +*/ diff --git a/crates/jmap-proto/src/method/query_changes.rs b/crates/jmap-proto/src/method/query_changes.rs index 515a3636..4ead4fec 100644 --- a/crates/jmap-proto/src/method/query_changes.rs +++ b/crates/jmap-proto/src/method/query_changes.rs @@ -4,9 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use super::query::{Comparator, Filter, RequestArguments, parse_filter, parse_sort}; -use crate::{object::JmapObject, request::method::MethodObject, types::state::State}; -use compact_str::format_compact; +use crate::{ + object::JmapObject, + request::deserialize::{DeserializeArguments, deserialize_request}, + types::state::State, +}; +use serde::{Deserialize, Deserializer}; use types::id::Id; #[derive(Debug, Clone)] @@ -55,87 +58,62 @@ impl AddedItem { } } -impl JsonObjectParser for QueryChangesRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de, T: JmapObject> DeserializeArguments<'de> for QueryChangesRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = QueryChangesRequest { - arguments: match &parser.ctx { - MethodObject::Email => RequestArguments::Email(Default::default()), - MethodObject::Mailbox => RequestArguments::Mailbox(Default::default()), - MethodObject::EmailSubmission => RequestArguments::EmailSubmission, - MethodObject::Quota => RequestArguments::Quota, - _ => { - return Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(format_compact!("{}/queryChanges", parser.ctx))); - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; }, - filter: vec![], - sort: None, - calculate_total: None, - account_id: Id::default(), - since_query_state: State::Initial, - max_changes: None, - up_to_id: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x7265_746c_6966 => match parser.next_token::()? { - Token::DictStart => { - request.filter = parse_filter(parser)?; - } - Token::Null => (), - token => { - return Err(token.error("filter", "object or null")); - } - }, - 0x7472_6f73 => match parser.next_token::()? { - Token::ArrayStart => { - request.sort = parse_sort(parser)?.into(); - } - Token::Null => (), - token => { - return Err(token.error("sort", "array or null")); - } - }, - 0x0065_7461_7453_7972_6575_5165_636e_6973 => { - request.since_query_state = parser - .next_token::()? - .unwrap_string("sinceQueryState")?; - } - 0x7365_676e_6168_4378_616d => { - request.max_changes = parser - .next_token::()? - .unwrap_usize_or_null("maxChanges")?; - } - 0x6449_6f54_7075 => { - request.up_to_id = - parser.next_token::()?.unwrap_string_or_null("upToId")?; - } - 0x6c61_746f_5465_7461_6c75_636c_6163 => { - request.calculate_total = parser - .next_token::()? - .unwrap_bool_or_null("calculateTotal")?; - } - - _ => { - if !request.arguments.parse(parser, key)? { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } + b"filter" => { + self.filter = map.next_value()?; + }, + b"sort" => { + self.sort = map.next_value()?; + }, + b"sinceQueryState" => { + self.since_query_state = map.next_value()?; + }, + b"maxChanges" => { + self.max_changes = map.next_value()?; + }, + b"upToId" => { + self.up_to_id = map.next_value()?; + }, + b"calculateTotal" => { + self.calculate_total = map.next_value()?; + }, + _ => { + self.arguments.deserialize_argument(key, map)?; } - } + ); - Ok(request) + Ok(()) + } +} + +impl<'de, T: JmapObject> Deserialize<'de> for QueryChangesRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl Default for QueryChangesRequest { + fn default() -> Self { + Self { + account_id: Id::default(), + filter: Vec::new(), + sort: None, + since_query_state: State::default(), + max_changes: None, + up_to_id: None, + calculate_total: None, + arguments: T::QueryArguments::default(), + } } } diff --git a/crates/jmap-proto/src/method/search_snippet.rs b/crates/jmap-proto/src/method/search_snippet.rs index 37866b8d..0e8d94c2 100644 --- a/crates/jmap-proto/src/method/search_snippet.rs +++ b/crates/jmap-proto/src/method/search_snippet.rs @@ -4,15 +4,20 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use super::query::{Filter, parse_filter}; -use crate::request::reference::{MaybeResultReference, ResultReference}; +use super::query::Filter; +use crate::request::{ + MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, + reference::{MaybeResultReference, ResultReference}, +}; +use serde::{Deserialize, Deserializer, de::DeserializeOwned}; use types::id::Id; #[derive(Debug, Clone)] -pub struct GetSearchSnippetRequest { +pub struct GetSearchSnippetRequest { pub account_id: Id, - pub filter: Vec, - pub email_ids: MaybeResultReference>, + pub filter: Vec>, + pub email_ids: MaybeResultReference>>, } #[derive(Debug, Clone, serde::Serialize)] @@ -25,7 +30,7 @@ pub struct GetSearchSnippetResponse { #[serde(rename = "notFound")] #[serde(skip_serializing_if = "Vec::is_empty")] - pub not_found: Vec, + pub not_found: Vec>, } #[derive(serde::Serialize, Clone, Debug)] @@ -40,48 +45,48 @@ pub struct SearchSnippet { pub preview: Option, } -impl JsonObjectParser for GetSearchSnippetRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de, T: DeserializeOwned> DeserializeArguments<'de> for GetSearchSnippetRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = GetSearchSnippetRequest { - account_id: Id::default(), - filter: vec![], - email_ids: MaybeReference::Value(vec![]), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x7265_746c_6966 if !key.is_ref => match parser.next_token::()? { - Token::DictStart => { - request.filter = parse_filter(parser)?; - } - Token::Null => (), - token => { - return Err(token.error("filter", "object or null")); - } - }, - 0x7364_496c_6961_6d65 => { - request.email_ids = if !key.is_ref { - MaybeReference::Value(>::parse(parser)?) - } else { - MaybeReference::Reference(ResultReference::parse(parser)?) - }; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"filter" => { + self.filter = map.next_value()?; + }, + b"emailIds" => { + self.email_ids = MaybeResultReference::Value(map.next_value::>>()?); + }, + b"#emailIds" => { + self.email_ids = MaybeResultReference::Reference(map.next_value::()?); + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) + } +} + +impl<'de, T: DeserializeOwned> Deserialize<'de> for GetSearchSnippetRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl Default for GetSearchSnippetRequest { + fn default() -> Self { + Self { + account_id: Id::default(), + filter: Vec::new(), + email_ids: MaybeResultReference::Value(Vec::new()), + } } } diff --git a/crates/jmap-proto/src/method/set.rs b/crates/jmap-proto/src/method/set.rs index 1551833a..48614ebc 100644 --- a/crates/jmap-proto/src/method/set.rs +++ b/crates/jmap-proto/src/method/set.rs @@ -10,6 +10,7 @@ use crate::{ object::JmapObject, request::{ MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, method::MethodObject, reference::{MaybeResultReference, ResultReference}, }, @@ -19,10 +20,12 @@ use crate::{ use ahash::AHashMap; use compact_str::format_compact; use jmap_tools::Value; +use serde::{Deserialize, Deserializer}; use types::{acl::Acl, blob::BlobId, id::Id, keyword::Keyword}; use utils::map::{bitmap::Bitmap, vec_map::VecMap}; #[derive(Debug, Clone)] +#[allow(clippy::type_complexity)] pub struct SetRequest<'x, T: JmapObject> { pub account_id: Id, pub if_in_state: Option, @@ -32,17 +35,6 @@ pub struct SetRequest<'x, T: JmapObject> { pub arguments: T::SetArguments, } -/*#[derive(Debug, Clone)] -pub enum RequestArguments { - Email, - Mailbox(mailbox::SetArguments), - Identity, - EmailSubmission(email_submission::SetArguments), - PushSubscription, - SieveScript(sieve::SetArguments), - VacationResponse, -}*/ - #[derive(Debug, Clone, Default, serde::Serialize)] pub struct SetResponse { #[serde(rename = "accountId")] @@ -82,306 +74,67 @@ pub struct SetResponse { pub not_destroyed: VecMap, SetError>, } -impl JsonObjectParser for SetRequest { - fn parse(parser: &mut Parser) -> trc::Result +impl<'de, T: JmapObject> DeserializeArguments<'de> for SetRequest<'de, T> { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = SetRequest { - arguments: match &parser.ctx { - MethodObject::Email => RequestArguments::Email, - MethodObject::Mailbox => RequestArguments::Mailbox(Default::default()), - MethodObject::Identity => RequestArguments::Identity, - MethodObject::EmailSubmission => { - RequestArguments::EmailSubmission(Default::default()) - } - MethodObject::PushSubscription => RequestArguments::PushSubscription, - MethodObject::VacationResponse => RequestArguments::VacationResponse, - MethodObject::SieveScript => RequestArguments::SieveScript(Default::default()), - _ => { - return Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(format_compact!("{}/set", parser.ctx))); - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; }, + b"ifInState" => { + self.if_in_state = map.next_value()?; + }, + b"create" => { + self.create = map.next_value()?; + }, + b"update" => { + self.update = map.next_value()?; + }, + b"destroy" => { + self.destroy = map.next_value::>>>()?.map(MaybeResultReference::Value); + }, + b"#destroy" => { + self.destroy = Some(MaybeResultReference::Reference(map.next_value::()?)); + } + _ => { + self.arguments.deserialize_argument(key, map)?; + } + ); + + Ok(()) + } +} + +impl<'de, T: JmapObject> Deserialize<'de> for SetRequest<'de, T> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'x, T: JmapObject> Default for SetRequest<'x, T> { + fn default() -> Self { + Self { account_id: Id::default(), if_in_state: None, create: None, update: None, destroy: None, - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x6574_6165_7263 if !key.is_ref => { - request.create = >>>::parse(parser)?; - } - 0x6574_6164_7075 if !key.is_ref => { - request.update = >>>::parse(parser)?; - } - 0x0079_6f72_7473_6564 => { - request.destroy = if !key.is_ref { - >>::parse(parser)?.map(MaybeReference::Value) - } else { - Some(MaybeReference::Reference(ResultReference::parse(parser)?)) - }; - } - 0x0065_7461_7453_6e49_6669 if !key.is_ref => { - request.if_in_state = parser - .next_token::()? - .unwrap_string_or_null("ifInState")?; - } - _ => { - if !request.arguments.parse(parser, key)? { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } - } - } - - Ok(request) - } -} - -impl JsonObjectParser for Value<'x, P, E> { - fn parse(parser: &mut Parser<'_>) -> trc::Result - where - Self: Sized, - { - let mut obj = Object(VecMap::with_capacity(8)); - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(mut key) = parser.next_dict_key::()? { - let value = if !key.is_ref { - match &key.property { - Property::Id | Property::ThreadId => parser - .next_token::()? - .unwrap_string_or_null("")? - .map(|id| SetValue::Value(Value::Id(id))) - .unwrap_or(SetValue::Value(Value::Null)), - Property::BlobId | Property::Picture => parser - .next_token::>()? - .unwrap_string_or_null("")? - .map(SetValue::from) - .unwrap_or(SetValue::Value(Value::Null)), - Property::SentAt - | Property::ReceivedAt - | Property::Expires - | Property::FromDate - | Property::ToDate => parser - .next_token::()? - .unwrap_string_or_null("")? - .map(|date| SetValue::Value(Value::Date(date))) - .unwrap_or(SetValue::Value(Value::Null)), - Property::Subject - | Property::Preview - | Property::Name - | Property::Description - | Property::Timezone - | Property::Email - | Property::Secret - | Property::DeviceClientId - | Property::Url - | Property::VerificationCode - | Property::HtmlSignature - | Property::TextSignature - | Property::Type - | Property::Charset - | Property::Disposition - | Property::Language - | Property::Location - | Property::Cid - | Property::Role - | Property::PartId => parser - .next_token::()? - .unwrap_string_or_null("")? - .map(|text| SetValue::Value(Value::Text(text))) - .unwrap_or(SetValue::Value(Value::Null)), - Property::TextBody | Property::HtmlBody => { - if let MethodObject::Email = &parser.ctx { - SetValue::Value(Value::parse::( - parser.next_token()?, - parser, - )?) - } else { - parser - .next_token::()? - .unwrap_string_or_null("")? - .map(|text| SetValue::Value(Value::Text(text))) - .unwrap_or(SetValue::Value(Value::Null)) - } - } - Property::HasAttachment - | Property::IsSubscribed - | Property::IsEnabled - | Property::IsActive => parser - .next_token::()? - .unwrap_bool_or_null("")? - .map(|bool| SetValue::Value(Value::Bool(bool))) - .unwrap_or(SetValue::Value(Value::Null)), - Property::Size | Property::SortOrder | Property::Quota => parser - .next_token::()? - .unwrap_uint_or_null("")? - .map(|uint| SetValue::Value(Value::UnsignedInt(uint))) - .unwrap_or(SetValue::Value(Value::Null)), - Property::ParentId | Property::EmailId | Property::IdentityId => parser - .next_token::>()? - .unwrap_string_or_null("")? - .map(SetValue::from) - .unwrap_or(SetValue::Value(Value::Null)), - Property::MailboxIds => { - if key.patch.is_empty() { - SetValue::from( - >>::parse(parser)?.values, - ) - } else { - key.patch.push(Value::Bool(bool::parse(parser)?)); - SetValue::Patch(key.patch) - } - } - Property::Keywords => { - if key.patch.is_empty() { - SetValue::Value(Value::List( - >::parse(parser)? - .values - .into_iter() - .map(Value::Keyword) - .collect(), - )) - } else { - key.patch.push(Value::Bool(bool::parse(parser)?)); - SetValue::Patch(key.patch) - } - } - - Property::Acl => match key.patch.len() { - 0 => { - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - let mut acls = Vec::new(); - while let Some(account) = parser.next_dict_key::()? { - acls.push(Value::Text(account)); - acls.push(Value::UnsignedInt(>::parse(parser)?.into())); - } - SetValue::Value(Value::List(acls)) - } - 1 => { - key.patch - .push(Value::UnsignedInt(>::parse(parser)?.into())); - SetValue::Patch(key.patch) - } - 2 => { - key.patch.push(Value::Bool(bool::parse(parser)?)); - SetValue::Patch(key.patch) - } - _ => unreachable!(), - }, - Property::Aliases - | Property::Attachments - | Property::Bcc - | Property::BodyStructure - | Property::BodyValues - | Property::Capabilities - | Property::Cc - | Property::Envelope - | Property::From - | Property::Headers - | Property::InReplyTo - | Property::Keys - | Property::MessageId - | Property::References - | Property::ReplyTo - | Property::Sender - | Property::SubParts - | Property::To - | Property::UndoStatus - | Property::Types => SetValue::Value(Value::parse::( - parser.next_token()?, - parser, - )?), - Property::Parameters => SetValue::Value(Value::parse::( - parser.next_token()?, - parser, - )?), - Property::Members => SetValue::Value(Value::parse::( - parser.next_token()?, - parser, - )?), - Property::Header(h) => SetValue::Value(if matches!(h.form, HeaderForm::Date) { - Value::parse::(parser.next_token()?, parser) - } else { - Value::parse::(parser.next_token()?, parser) - }?), - - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - SetValue::Value(Value::Null) - } - } - } else { - SetValue::ResultReference(ResultReference::parse(parser)?) - }; - - obj.0.append(key.property, value); - } - - Ok(obj) - } -} - -impl> From> for SetValue { - fn from(reference: MaybeReference) -> Self { - match reference { - MaybeReference::Value(id) => SetValue::IdReference(MaybeReference::Value(id.into())), - MaybeReference::Reference(reference) => { - SetValue::IdReference(MaybeReference::Reference(reference)) - } + arguments: T::SetArguments::default(), } } } -impl> From>> for SetValue { - fn from(value: Vec>) -> Self { - SetValue::IdReferences( - value - .into_iter() - .map(|reference| match reference { - MaybeReference::Value(id) => MaybeReference::Value(id.into()), - MaybeReference::Reference(reference) => MaybeReference::Reference(reference), - }) - .collect(), - ) - } -} - -impl RequestPropertyParser for RequestArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - match self { - RequestArguments::Mailbox(args) => args.parse(parser, property), - RequestArguments::EmailSubmission(args) => args.parse(parser, property), - RequestArguments::SieveScript(args) => args.parse(parser, property), - _ => Ok(false), - } - } -} - -impl SetRequest { +impl<'x, T: JmapObject> SetRequest<'x, T> { pub fn validate(&self, max_objects_in_set: usize) -> trc::Result<()> { if self.create.as_ref().map_or(0, |objs| objs.len()) + self.update.as_ref().map_or(0, |objs| objs.len()) + self.destroy.as_ref().map_or(0, |objs| { - if let MaybeReference::Value(ids) = objs { + if let MaybeResultReference::Value(ids) = objs { ids.len() } else { 0 @@ -403,45 +156,31 @@ impl SetRequest { self.create.as_ref().is_some_and(|objs| !objs.is_empty()) } - pub fn unwrap_create(&mut self) -> VecMap> { + pub fn unwrap_create(&mut self) -> VecMap> { self.create.take().unwrap_or_default() } - pub fn unwrap_update(&mut self) -> VecMap> { + pub fn unwrap_update( + &mut self, + ) -> VecMap, Value<'x, T::Property, T::Element>> { self.update.take().unwrap_or_default() } - pub fn unwrap_destroy(&mut self) -> Vec { + /*pub fn unwrap_destroy(&mut self) -> Vec { self.destroy .take() .map(|ids| ids.unwrap()) .unwrap_or_default() - } + }*/ } -impl SetRequest { - pub fn take_arguments(&mut self) -> RequestArguments { - std::mem::replace(&mut self.arguments, RequestArguments::VacationResponse) - } - - pub fn with_arguments(self, arguments: T) -> SetRequest { - SetRequest { - account_id: self.account_id, - if_in_state: self.if_in_state, - create: self.create, - update: self.update, - destroy: self.destroy, - arguments, - } - } -} - -impl SetResponse { - pub fn from_request(request: &SetRequest, max_objects: usize) -> trc::Result { +/* +impl SetResponse { + pub fn from_request(request: &SetRequest, max_objects: usize) -> trc::Result { let n_create = request.create.as_ref().map_or(0, |objs| objs.len()); let n_update = request.update.as_ref().map_or(0, |objs| objs.len()); let n_destroy = request.destroy.as_ref().map_or(0, |objs| { - if let MaybeReference::Value(ids) = objs { + if let MaybeResultReference::Value(ids) = objs { ids.len() } else { 0 @@ -527,3 +266,4 @@ impl SetResponse { !self.created.is_empty() || !self.updated.is_empty() || !self.destroyed.is_empty() } } +*/ diff --git a/crates/jmap-proto/src/method/upload.rs b/crates/jmap-proto/src/method/upload.rs index f86f0715..08ecc0d8 100644 --- a/crates/jmap-proto/src/method/upload.rs +++ b/crates/jmap-proto/src/method/upload.rs @@ -4,29 +4,37 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use std::borrow::Cow; + use super::ahash_is_empty; use crate::{ - error::set::SetError, object::blob::BlobProperty, request::reference::MaybeIdReference, + error::set::SetError, + object::blob::BlobProperty, + request::{ + deserialize::{DeserializeArguments, deserialize_request}, + reference::MaybeIdReference, + }, response::Response, }; use ahash::AHashMap; use mail_parser::decoders::base64::base64_decode; +use serde::{Deserialize, Deserializer}; use types::{blob::BlobId, id::Id}; use utils::map::vec_map::VecMap; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct BlobUploadRequest { pub account_id: Id, pub create: VecMap, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct UploadObject { pub type_: Option, pub data: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum DataSourceObject { Id { id: MaybeIdReference, @@ -34,6 +42,8 @@ pub enum DataSourceObject { offset: Option, }, Value(Vec), + #[default] + Null, } #[derive(Debug, Clone, Default, serde::Serialize)] @@ -59,148 +69,112 @@ pub struct BlobUploadResponseObject { pub size: usize, } -impl JsonObjectParser for BlobUploadRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for BlobUploadRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = BlobUploadRequest { - account_id: Id::default(), - create: VecMap::new(), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x6574_6165_7263 if !key.is_ref => { - request.create = >::parse(parser)?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"create" => { + self.create = map.next_value()?; } - } + _ => { + let _ = map.next_value::()?; + } + ); - Ok(request) + Ok(()) } } -impl JsonObjectParser for UploadObject { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for UploadObject { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = UploadObject { - type_: None, - data: Vec::new(), - }; + hashify::fnc_map!(key.as_bytes(), + b"type" => { + self.type_ = map.next_value()?; + }, + b"data" => { + self.data = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; + } + ); - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; + Ok(()) + } +} - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x6570_7974 if !key.is_ref => { - request.type_ = parser - .next_token::()? - .unwrap_string_or_null("type")?; - } - 0x6174_6164 if !key.is_ref => { - parser.next_token::()?.assert(Token::ArrayStart)?; - loop { - match parser.next_token::()? { - Token::Comma => (), - Token::ArrayEnd => break, - Token::DictStart => { - request.data.push(DataSourceObject::parse(parser)?); - } - token => return Err(token.error("", "DataSourceObject")), - } +impl<'de> DeserializeArguments<'de> for DataSourceObject { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"data:asText" => { + *self = DataSourceObject::Value(map.next_value::().map(|v| v.into_bytes())?); + }, + b"data:asBase64" => { + *self = DataSourceObject::Value(base64_decode(map.next_value::>()?.as_bytes()).ok_or_else(|| serde::de::Error::custom("Failed to decode base64 data"))?); + }, + b"blobId" => { + match self { + DataSourceObject::Id { id, .. } => { + *id = map.next_value()?; + }, + _ => { + *self = DataSourceObject::Id { + id: map.next_value()?, + length: None, + offset: None, + }; } } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; + }, + b"offset" => { + match self { + DataSourceObject::Id { offset, .. } => { + *offset = map.next_value()?; + }, + _ => { + *self = DataSourceObject::Id { + id: MaybeIdReference::Invalid("".into()), + length: None, + offset: map.next_value()?, + }; + } } + }, + b"length" => { + match self { + DataSourceObject::Id { length, .. } => { + *length = map.next_value()?; + }, + _ => { + *self = DataSourceObject::Id { + id: MaybeIdReference::Invalid("".into()), + length: map.next_value()?, + offset: None, + }; + } + } + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) } } -impl JsonObjectParser for DataSourceObject { - fn parse(parser: &mut Parser<'_>) -> trc::Result - where - Self: Sized, - { - let mut data: Option> = None; - let mut blob_id: Option> = None; - let mut offset: Option = None; - let mut length: Option = None; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0074_7865_5473_613a_6174_6164 if !key.is_ref => { - data = parser - .next_token::()? - .unwrap_string("data:asText")? - .into_bytes() - .into(); - } - 0x0034_3665_7361_4273_613a_6174_6164 if !key.is_ref => { - data = base64_decode( - parser - .next_token::()? - .unwrap_string("data:asBase64")? - .as_bytes(), - ) - .ok_or_else(|| parser.error("Failed to decode data:asBase64"))? - .into(); - } - 0x6449_626f_6c62 if !key.is_ref => { - blob_id = parser - .next_token::>()? - .unwrap_string("blobId")? - .into(); - } - 0x6874_676e_656c if !key.is_ref => { - length = parser - .next_token::()? - .unwrap_usize_or_null("length")?; - } - 0x7465_7366_666f if !key.is_ref => { - offset = parser - .next_token::()? - .unwrap_usize_or_null("offset")?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } - } - } - - if let Some(data) = data { - Ok(DataSourceObject::Value(data)) - } else if let Some(blob_id) = blob_id { - Ok(DataSourceObject::Id { - id: blob_id, - length, - offset, - }) - } else { - Err(parser.error("Missing data or blobId in DataSourceObject")) - } - } -} - -impl BlobUploadResponse { +/*impl BlobUploadResponse { pub fn update_created_ids(&self, response: &mut Response) { for (user_id, obj) in &self.created { response @@ -208,4 +182,31 @@ impl BlobUploadResponse { .insert(user_id.clone(), obj.id.clone().into()); } } +}*/ + +impl<'de> Deserialize<'de> for DataSourceObject { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'de> Deserialize<'de> for UploadObject { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'de> Deserialize<'de> for BlobUploadRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } } diff --git a/crates/jmap-proto/src/method/validate.rs b/crates/jmap-proto/src/method/validate.rs index ac909994..c8f5b88d 100644 --- a/crates/jmap-proto/src/method/validate.rs +++ b/crates/jmap-proto/src/method/validate.rs @@ -4,11 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{error::set::SetError, object::sieve::SieveProperty, request::MaybeInvalid}; -use serde::Serialize; +use crate::{ + error::set::SetError, + object::sieve::SieveProperty, + request::{ + MaybeInvalid, + deserialize::{DeserializeArguments, deserialize_request}, + }, +}; +use serde::{Deserialize, Deserializer, Serialize}; use types::{blob::BlobId, id::Id}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ValidateSieveScriptRequest { pub account_id: Id, pub blob_id: MaybeInvalid, @@ -21,34 +28,32 @@ pub struct ValidateSieveScriptResponse { pub error: Option>, } -impl JsonObjectParser for ValidateSieveScriptRequest { - fn parse(parser: &mut Parser<'_>) -> trc::Result +impl<'de> DeserializeArguments<'de> for ValidateSieveScriptRequest { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> where - Self: Sized, + A: serde::de::MapAccess<'de>, { - let mut request = ValidateSieveScriptRequest { - account_id: Id::default(), - blob_id: BlobId::default(), - }; - - parser - .next_token::()? - .assert_jmap(Token::DictStart)?; - - while let Some(key) = parser.next_dict_key::()? { - match &key.hash[0] { - 0x0064_4974_6e75_6f63_6361 if !key.is_ref => { - request.account_id = parser.next_token::()?.unwrap_string("accountId")?; - } - 0x6449_626f_6c62 if !key.is_ref => { - request.blob_id = parser.next_token::()?.unwrap_string("blobId")?; - } - _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - } + hashify::fnc_map!(key.as_bytes(), + b"accountId" => { + self.account_id = map.next_value()?; + }, + b"blobId" => { + self.blob_id = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - } + ); - Ok(request) + Ok(()) + } +} + +impl<'de> Deserialize<'de> for ValidateSieveScriptRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) } } diff --git a/crates/jmap-proto/src/object/blob.rs b/crates/jmap-proto/src/object/blob.rs index dacdfe81..4bb63510 100644 --- a/crates/jmap-proto/src/object/blob.rs +++ b/crates/jmap-proto/src/object/blob.rs @@ -4,11 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::object::{MaybeReference, parse_ref}; +use crate::{ + object::{JmapObject, MaybeReference, parse_ref}, + request::deserialize::DeserializeArguments, +}; use jmap_tools::{Element, Key, Property}; -use std::borrow::Cow; +use std::{borrow::Cow, str::FromStr}; use types::blob::BlobId; +#[derive(Debug, Clone, Default)] +pub struct Blob; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum BlobProperty { Id, @@ -21,14 +27,14 @@ pub enum BlobProperty { IsTruncated, } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DigestProperty { Sha, Sha256, Sha512, } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DataProperty { AsText, AsBase64, @@ -42,7 +48,7 @@ pub enum BlobValue { } impl Property for BlobProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { BlobProperty::parse(value) } @@ -74,12 +80,7 @@ impl Element for BlobValue { fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { if let Key::Property(prop) = key { - match prop.patch_or_prop() { - BlobProperty::Id => match parse_ref(value) { - MaybeReference::Value(v) => Some(BlobValue::Id(v)), - MaybeReference::Reference(v) => Some(BlobValue::IdReference(v)), - MaybeReference::ParseError => None, - }, + match prop { BlobProperty::BlobId => match parse_ref(value) { MaybeReference::Value(v) => Some(BlobValue::BlobId(v)), MaybeReference::Reference(v) => Some(BlobValue::IdReference(v)), @@ -119,33 +120,66 @@ impl BlobProperty { } } +impl FromStr for BlobProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + BlobProperty::parse(s).ok_or(()) + } +} + #[derive(Debug, Clone, Default)] -pub struct GetArguments { +pub struct BlobGetArguments { pub offset: Option, pub length: Option, } -/* - -impl RequestPropertyParser for GetArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - match &property.hash[0] { - 0x7465_7366_666f => { - self.offset = parser - .next_token::()? - .unwrap_usize_or_null("offset")?; +impl<'de> DeserializeArguments<'de> for BlobGetArguments { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"offset" => { + self.offset = map.next_value()?; + }, + b"length" => { + self.length = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - 0x6874_676e_656c => { - self.length = parser - .next_token::()? - .unwrap_usize_or_null("length")?; - } - _ => return Ok(false), - } + ); - Ok(true) + Ok(()) } } +impl serde::Serialize for BlobProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} -*/ +impl JmapObject for Blob { + type Property = BlobProperty; + + type Element = BlobValue; + + type Id = BlobId; + + type Filter = (); + + type Comparator = (); + + type GetArguments = BlobGetArguments; + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/email.rs b/crates/jmap-proto/src/object/email.rs index dc22d48d..710ddb62 100644 --- a/crates/jmap-proto/src/object/email.rs +++ b/crates/jmap-proto/src/object/email.rs @@ -5,14 +5,18 @@ */ use crate::{ - object::{MaybeReference, parse_ref}, + object::{JmapObject, MaybeReference, parse_ref}, + request::{MaybeInvalid, deserialize::DeserializeArguments}, types::date::UTCDate, }; use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property}; use mail_parser::HeaderName; -use std::{borrow::Cow, fmt::Display}; +use std::{borrow::Cow, fmt::Display, str::FromStr}; use types::{blob::BlobId, id::Id, keyword::Keyword}; +#[derive(Debug, Clone, Default)] +pub struct Email; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EmailProperty { // Metadata @@ -105,7 +109,7 @@ impl Property for EmailProperty { if let Some(Key::Property(key)) = key { match key.patch_or_prop() { EmailProperty::Keywords => EmailProperty::Keyword(Keyword::parse(value)).into(), - _ => EmailProperty::from_str(value, allow_patch), + _ => EmailProperty::parse(value, allow_patch), } } else { EmailProperty::parse(value, allow_patch) @@ -351,9 +355,17 @@ impl Display for HeaderForm { } } +impl FromStr for EmailProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + EmailProperty::parse(s, false).ok_or(()) + } +} + #[derive(Debug, Clone, Default)] -pub struct GetArguments { - pub body_properties: Option>, +pub struct EmailGetArguments { + pub body_properties: Option>>, pub fetch_text_body_values: Option, pub fetch_html_body_values: Option, pub fetch_all_body_values: Option, @@ -361,53 +373,80 @@ pub struct GetArguments { } #[derive(Debug, Clone, Default)] -pub struct QueryArguments { +pub struct EmailQueryArguments { pub collapse_threads: Option, } -/*impl RequestPropertyParser for GetArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - match (&property.hash[0], &property.hash[1]) { - (0x7365_6974_7265_706f_7250_7964_6f62, _) => { - self.body_properties = >>::parse(parser)?; +impl<'de> DeserializeArguments<'de> for EmailGetArguments { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"bodyProperties" => { + self.body_properties = map.next_value()?; + }, + b"fetchTextBodyValues" => { + self.fetch_text_body_values = map.next_value()?; + }, + b"fetchHTMLBodyValues" => { + self.fetch_html_body_values = map.next_value()?; + }, + b"fetchAllBodyValues" => { + self.fetch_all_body_values = map.next_value()?; + }, + b"maxBodyValueBytes" => { + self.max_body_value_bytes = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - (0x6c61_5679_646f_4274_7865_5468_6374_6566, 0x0073_6575) => { - self.fetch_text_body_values = parser - .next_token::()? - .unwrap_bool_or_null("fetchTextBodyValues")?; - } - (0x6c61_5679_646f_424c_4d54_4868_6374_6566, 0x0073_6575) => { - self.fetch_html_body_values = parser - .next_token::()? - .unwrap_bool_or_null("fetchHTMLBodyValues")?; - } - (0x756c_6156_7964_6f42_6c6c_4168_6374_6566, 0x7365) => { - self.fetch_all_body_values = parser - .next_token::()? - .unwrap_bool_or_null("fetchAllBodyValues")?; - } - (0x6574_7942_6575_6c61_5679_646f_4278_616d, 0x73) => { - self.max_body_value_bytes = parser - .next_token::()? - .unwrap_usize_or_null("maxBodyValueBytes")?; - } - _ => return Ok(false), - } + ); - Ok(true) + Ok(()) } } -impl RequestPropertyParser for QueryArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - if property.hash[0] == 0x0073_6461_6572_6854_6573_7061_6c6c_6f63 { - self.collapse_threads = parser - .next_token::()? - .unwrap_bool_or_null("collapseThreads")?; - Ok(true) +impl<'de> DeserializeArguments<'de> for EmailQueryArguments { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + if key == "collapseThreads" { + self.collapse_threads = map.next_value()?; } else { - Ok(false) + let _ = map.next_value::()?; } + + Ok(()) } } -*/ + +impl serde::Serialize for EmailProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl JmapObject for Email { + type Property = EmailProperty; + + type Element = EmailValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = EmailGetArguments; + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/email_submission.rs b/crates/jmap-proto/src/object/email_submission.rs index 1f8999f7..72495bc9 100644 --- a/crates/jmap-proto/src/object/email_submission.rs +++ b/crates/jmap-proto/src/object/email_submission.rs @@ -6,18 +6,21 @@ use crate::{ object::{ - MaybeReference, + JmapObject, MaybeReference, email::{EmailProperty, EmailValue}, parse_ref, }, - request::reference::MaybeIdReference, + request::{deserialize::DeserializeArguments, reference::MaybeIdReference}, types::date::UTCDate, }; use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property, Value}; -use std::borrow::Cow; +use std::{borrow::Cow, str::FromStr}; use types::{blob::BlobId, id::Id}; use utils::map::vec_map::VecMap; +#[derive(Debug, Clone, Default)] +pub struct EmailSubmission; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EmailSubmissionProperty { Id, @@ -74,7 +77,7 @@ pub enum Displayed { impl Property for EmailSubmissionProperty { fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { - EmailSubmissionProperty::from_str(value, key.is_none()) + EmailSubmissionProperty::parse(value, key.is_none()) } fn to_cow(&self) -> Cow<'static, str> { @@ -250,29 +253,66 @@ impl Displayed { } #[derive(Debug, Clone, Default)] -pub struct SetArguments<'x> { +pub struct EmailSubmissionSetArguments<'x> { pub on_success_update_email: - Option, Value<'x, EmailProperty, EmailValue>>>, + Option, Value<'x, EmailProperty, EmailValue>>>, pub on_success_destroy_email: Option>>, } -/*impl RequestPropertyParser for SetArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - if property.hash[0] == 0x4565_7461_6470_5573_7365_6363_7553_6e6f - && property.hash[1] == 0x6c69_616d - { - self.on_success_update_email = - , Value<'x, P, E>>>>::parse(parser)?; - Ok(true) - } else if property.hash[0] == 0x796f_7274_7365_4473_7365_6363_7553_6e6f - && property.hash[1] == 0x006c_6961_6d45 - { - self.on_success_destroy_email = - >>>::parse(parser)?; - Ok(true) - } else { - Ok(false) - } +impl<'x> DeserializeArguments<'x> for EmailSubmissionSetArguments<'x> { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'x>, + { + hashify::fnc_map!(key.as_bytes(), + b"onSuccessUpdateEmail" => { + self.on_success_update_email = map.next_value()?; + }, + b"onSuccessDestroyEmail" => { + self.on_success_destroy_email = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; + } + ); + + Ok(()) } } -*/ + +impl serde::Serialize for EmailSubmissionProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for EmailSubmissionProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + EmailSubmissionProperty::parse(s, false).ok_or(()) + } +} + +impl JmapObject for EmailSubmission { + type Property = EmailSubmissionProperty; + + type Element = EmailSubmissionValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/identity.rs b/crates/jmap-proto/src/object/identity.rs index a0f12d08..7a144968 100644 --- a/crates/jmap-proto/src/object/identity.rs +++ b/crates/jmap-proto/src/object/identity.rs @@ -8,6 +8,11 @@ use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property}; use std::{borrow::Cow, str::FromStr}; use types::id::Id; +use crate::object::JmapObject; + +#[derive(Debug, Clone, Default)] +pub struct Identity; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IdentityProperty { Id, @@ -101,3 +106,40 @@ impl IdentityProperty { } } } + +impl serde::Serialize for IdentityProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for IdentityProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + IdentityProperty::parse(s, false).ok_or(()) + } +} + +impl JmapObject for Identity { + type Property = IdentityProperty; + + type Element = IdentityValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/mailbox.rs b/crates/jmap-proto/src/object/mailbox.rs index f7062ee5..3bd565b8 100644 --- a/crates/jmap-proto/src/object/mailbox.rs +++ b/crates/jmap-proto/src/object/mailbox.rs @@ -4,12 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::borrow::Cow; +use std::{borrow::Cow, str::FromStr}; use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property}; use types::{id::Id, special_use::SpecialUse}; -use crate::object::{MaybeReference, parse_ref}; +use crate::{ + object::{JmapObject, MaybeReference, parse_ref}, + request::deserialize::DeserializeArguments, +}; + +#[derive(Debug, Clone, Default)] +pub struct Mailbox; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum MailboxProperty { @@ -89,7 +95,7 @@ impl Element for MailboxValue { MaybeReference::Reference(v) => Some(MailboxValue::IdReference(v)), MaybeReference::ParseError => None, }, - MailboxProperty::Role => SpecialUse::from_str(value).ok().map(MailboxValue::Role), + MailboxProperty::Role => SpecialUse::parse(value).map(MailboxValue::Role), _ => None, } } else { @@ -151,49 +157,85 @@ impl MailboxProperty { } #[derive(Debug, Clone, Default)] -pub struct SetArguments { +pub struct MailboxSetArguments { pub on_destroy_remove_emails: Option, } #[derive(Debug, Clone, Default)] -pub struct QueryArguments { +pub struct MailboxQueryArguments { pub sort_as_tree: Option, pub filter_as_tree: Option, } -/* -impl RequestPropertyParser for SetArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - if property.hash[0] == 0x4565_766f_6d65_5279_6f72_7473_6544_6e6f - && property.hash[1] == 0x0073_6c69_616d - { - self.on_destroy_remove_emails = parser - .next_token::()? - .unwrap_bool_or_null("onDestroyRemoveEmails")?; - Ok(true) +impl<'de> DeserializeArguments<'de> for MailboxSetArguments { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + if key == "onDestroyRemoveEmails" { + self.on_destroy_remove_emails = map.next_value()?; } else { - Ok(false) + let _ = map.next_value::()?; } + + Ok(()) } } -impl RequestPropertyParser for QueryArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - match &property.hash[0] { - 0x6565_7254_7341_7472_6f73 => { - self.sort_as_tree = parser - .next_token::()? - .unwrap_bool_or_null("sortAsTree")?; +impl<'de> DeserializeArguments<'de> for MailboxQueryArguments { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"sortAsTree" => { + self.sort_as_tree = map.next_value()?; + }, + b"filterAsTree" => { + self.filter_as_tree = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; } - 0x6565_7254_7341_7265_746c_6966 => { - self.filter_as_tree = parser - .next_token::()? - .unwrap_bool_or_null("filterAsTree")?; - } - _ => return Ok(false), - } + ); - Ok(true) + Ok(()) } } -*/ + +impl FromStr for MailboxProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + MailboxProperty::parse(s, false).ok_or(()) + } +} + +impl serde::Serialize for MailboxProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl JmapObject for Mailbox { + type Property = MailboxProperty; + + type Element = MailboxValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = MailboxSetArguments; + + type QueryArguments = MailboxQueryArguments; + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/mod.rs b/crates/jmap-proto/src/object/mod.rs index 858e90a3..4a3e50ca 100644 --- a/crates/jmap-proto/src/object/mod.rs +++ b/crates/jmap-proto/src/object/mod.rs @@ -4,9 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::str::FromStr; - +use crate::request::deserialize::DeserializeArguments; use jmap_tools::{Element, Property}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; pub mod blob; pub mod email; @@ -22,17 +23,17 @@ pub mod thread; pub mod vacation_response; pub trait JmapObject { - type Property: Property; - type Element: Element; - type Id: FromStr; + type Property: Property + FromStr + Serialize; + type Element: Element; + type Id: FromStr + Serialize; - type Filter; - type Comparator; + type Filter: for<'de> Deserialize<'de>; + type Comparator: for<'de> Deserialize<'de>; - type GetArguments; - type SetArguments; - type QueryArguments; - type CopyArguments; + type GetArguments: Default + for<'de> DeserializeArguments<'de>; + type SetArguments: Default + for<'de> DeserializeArguments<'de>; + type QueryArguments: Default + for<'de> DeserializeArguments<'de>; + type CopyArguments: Default + for<'de> DeserializeArguments<'de>; } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/jmap-proto/src/object/principal.rs b/crates/jmap-proto/src/object/principal.rs index 98467174..af122e0a 100644 --- a/crates/jmap-proto/src/object/principal.rs +++ b/crates/jmap-proto/src/object/principal.rs @@ -8,6 +8,11 @@ use jmap_tools::{Element, Key, Property}; use std::{borrow::Cow, str::FromStr}; use types::id::Id; +use crate::object::JmapObject; + +#[derive(Debug, Clone, Default)] +pub struct Principal; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PrincipalProperty { Id, @@ -35,7 +40,7 @@ pub enum PrincipalType { } impl Property for PrincipalProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { PrincipalProperty::parse(value) } @@ -58,7 +63,7 @@ impl Element for PrincipalValue { fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { if let Key::Property(prop) = key { - match prop.patch_or_prop() { + match prop { PrincipalProperty::Id => Id::from_str(value).ok().map(PrincipalValue::Id), PrincipalProperty::Type => PrincipalType::parse(value).map(PrincipalValue::Type), _ => None, @@ -111,3 +116,40 @@ impl PrincipalType { } } } + +impl serde::Serialize for PrincipalProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for PrincipalProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + PrincipalProperty::parse(s).ok_or(()) + } +} + +impl JmapObject for Principal { + type Property = PrincipalProperty; + + type Element = PrincipalValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/push_subscription.rs b/crates/jmap-proto/src/object/push_subscription.rs index c16d5657..ef6064bf 100644 --- a/crates/jmap-proto/src/object/push_subscription.rs +++ b/crates/jmap-proto/src/object/push_subscription.rs @@ -4,12 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::object::JmapObject; use crate::types::date::UTCDate; use jmap_tools::{Element, JsonPointer, JsonPointerItem}; use jmap_tools::{Key, Property}; use std::borrow::Cow; +use std::str::FromStr; use types::{id::Id, type_state::DataType}; +#[derive(Debug, Clone, Default)] +pub struct PushSubscription; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum PushSubscriptionProperty { Id, @@ -116,8 +121,44 @@ impl Element for PushSubscriptionValue { match self { PushSubscriptionValue::Id(id) => id.to_string().into(), PushSubscriptionValue::Date(utcdate) => utcdate.to_string().into(), - PushSubscriptionValue::BlobId(blob_id) => blob_id.to_string().into(), - PushSubscriptionValue::IdReference(r) => format!("#{r}").into(), + PushSubscriptionValue::Types(data_type) => data_type.as_str().into(), } } } + +impl serde::Serialize for PushSubscriptionProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for PushSubscriptionProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + PushSubscriptionProperty::parse(s, false).ok_or(()) + } +} + +impl JmapObject for PushSubscription { + type Property = PushSubscriptionProperty; + + type Element = PushSubscriptionValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/quota.rs b/crates/jmap-proto/src/object/quota.rs index 5f52a115..207b81a9 100644 --- a/crates/jmap-proto/src/object/quota.rs +++ b/crates/jmap-proto/src/object/quota.rs @@ -8,6 +8,11 @@ use jmap_tools::{Element, Key, Property}; use std::{borrow::Cow, str::FromStr}; use types::{id::Id, type_state::DataType}; +use crate::object::JmapObject; + +#[derive(Debug, Clone, Default)] +pub struct Quota; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum QuotaProperty { Id, @@ -29,7 +34,7 @@ pub enum QuotaValue { } impl Property for QuotaProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { QuotaProperty::parse(value) } @@ -72,7 +77,7 @@ impl Element for QuotaValue { fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { if let Key::Property(prop) = key { - match prop.patch_or_prop() { + match prop { QuotaProperty::Id => Id::from_str(value).ok().map(QuotaValue::Id), QuotaProperty::Types => DataType::parse(value).map(QuotaValue::Types), _ => None, @@ -89,3 +94,40 @@ impl Element for QuotaValue { } } } + +impl serde::Serialize for QuotaProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for QuotaProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + QuotaProperty::parse(s).ok_or(()) + } +} + +impl JmapObject for Quota { + type Property = QuotaProperty; + + type Element = QuotaValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/search_snippet.rs b/crates/jmap-proto/src/object/search_snippet.rs index be19b6e1..b3491b30 100644 --- a/crates/jmap-proto/src/object/search_snippet.rs +++ b/crates/jmap-proto/src/object/search_snippet.rs @@ -8,6 +8,10 @@ use jmap_tools::{Element, Key, Property}; use std::{borrow::Cow, str::FromStr}; use types::id::Id; +#[derive(Debug, Clone, Default)] +pub struct SearchSnippet; + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SearchSnippetProperty { EmailId, @@ -21,7 +25,7 @@ pub enum SearchSnippetValue { } impl Property for SearchSnippetProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { SearchSnippetProperty::parse(value) } @@ -40,7 +44,7 @@ impl Element for SearchSnippetValue { fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { if let Key::Property(prop) = key { - match prop.patch_or_prop() { + match prop { SearchSnippetProperty::EmailId => { Id::from_str(value).ok().map(SearchSnippetValue::Id) } diff --git a/crates/jmap-proto/src/object/sieve.rs b/crates/jmap-proto/src/object/sieve.rs index 7bd56b13..5ae1d35e 100644 --- a/crates/jmap-proto/src/object/sieve.rs +++ b/crates/jmap-proto/src/object/sieve.rs @@ -4,14 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_tools::{Element, Key, Property}; -use std::borrow::Cow; -use types::{blob::BlobId, id::Id}; - use crate::{ - object::{MaybeReference, parse_ref}, + object::{DeserializeArguments, JmapObject, MaybeReference, parse_ref}, request::reference::MaybeIdReference, }; +use jmap_tools::{Element, Key, Property}; +use std::{borrow::Cow, str::FromStr}; +use types::{blob::BlobId, id::Id}; + +#[derive(Debug, Clone, Default)] +pub struct Sieve; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SieveProperty { @@ -29,7 +31,7 @@ pub enum SieveValue { } impl Property for SieveProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { SieveProperty::parse(value) } @@ -49,7 +51,7 @@ impl Element for SieveValue { fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { if let Key::Property(prop) = key { - match prop.patch_or_prop() { + match prop { SieveProperty::Id => match parse_ref(value) { MaybeReference::Value(v) => Some(SieveValue::Id(v)), MaybeReference::Reference(v) => Some(SieveValue::IdReference(v)), @@ -88,31 +90,65 @@ impl SieveProperty { } #[derive(Debug, Clone, Default)] -pub struct SetArguments { +pub struct SieveSetArguments { pub on_success_activate_script: Option>, pub on_success_deactivate_script: Option, } -/* -impl RequestPropertyParser for SetArguments { - fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result { - if property.hash[0] == 0x7461_7669_7463_4173_7365_6363_7553_6e6f - && property.hash[1] == 0x0074_7069_7263_5365 - { - self.on_success_activate_script = parser - .next_token::>()? - .unwrap_string_or_null("onSuccessActivateScript")?; - Ok(true) - } else if property.hash[0] == 0x7669_7463_6165_4473_7365_6363_7553_6e6f - && property.hash[1] == 0x0074_7069_7263_5365_7461 - { - self.on_success_deactivate_script = parser - .next_token::()? - .unwrap_bool_or_null("onSuccessDeactivateScript")?; - Ok(true) - } else { - Ok(false) - } +impl<'de> DeserializeArguments<'de> for SieveSetArguments { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: serde::de::MapAccess<'de>, + { + hashify::fnc_map!(key.as_bytes(), + b"onSuccessActivateScript" => { + self.on_success_activate_script = map.next_value()?; + }, + b"onSuccessDeactivateScript" => { + self.on_success_deactivate_script = map.next_value()?; + }, + _ => { + let _ = map.next_value::()?; + } + ); + + Ok(()) } } -*/ + +impl FromStr for SieveProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + SieveProperty::parse(s).ok_or(()) + } +} + +impl serde::Serialize for SieveProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl JmapObject for Sieve { + type Property = SieveProperty; + + type Element = SieveValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = SieveSetArguments; + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/thread.rs b/crates/jmap-proto/src/object/thread.rs index 8bc3cda2..2d85edc9 100644 --- a/crates/jmap-proto/src/object/thread.rs +++ b/crates/jmap-proto/src/object/thread.rs @@ -8,6 +8,11 @@ use jmap_tools::{Element, Key, Property}; use std::{borrow::Cow, str::FromStr}; use types::id::Id; +use crate::object::JmapObject; + +#[derive(Debug, Clone, Default)] +pub struct Thread; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ThreadProperty { Id, @@ -20,7 +25,7 @@ pub enum ThreadValue { } impl Property for ThreadProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { ThreadProperty::parse(value) } @@ -37,13 +42,8 @@ impl Element for ThreadValue { type Property = ThreadProperty; fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { - if let Key::Property(prop) = key { - match prop.patch_or_prop() { - ThreadProperty::Id | ThreadProperty::EmailIds => { - Id::from_str(value).ok().map(ThreadValue::Id) - } - _ => None, - } + if let Key::Property(_) = key { + Id::from_str(value).ok().map(ThreadValue::Id) } else { None } @@ -64,3 +64,40 @@ impl ThreadProperty { ) } } + +impl serde::Serialize for ThreadProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for ThreadProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + ThreadProperty::parse(s).ok_or(()) + } +} + +impl JmapObject for Thread { + type Property = ThreadProperty; + + type Element = ThreadValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/object/vacation_response.rs b/crates/jmap-proto/src/object/vacation_response.rs index 115446e9..39aff7c4 100644 --- a/crates/jmap-proto/src/object/vacation_response.rs +++ b/crates/jmap-proto/src/object/vacation_response.rs @@ -4,11 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::types::date::UTCDate; +use crate::{object::JmapObject, types::date::UTCDate}; use jmap_tools::{Element, Key, Property}; use std::{borrow::Cow, str::FromStr}; use types::id::Id; +#[derive(Debug, Clone, Default)] +pub struct VacationResponse; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum VacationResponseProperty { Id, @@ -26,7 +29,7 @@ pub enum VacationResponseValue { } impl Property for VacationResponseProperty { - fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option { + fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option { VacationResponseProperty::parse(value) } @@ -48,7 +51,7 @@ impl Element for VacationResponseValue { fn try_parse

(key: &Key<'_, Self::Property>, value: &str) -> Option { if let Key::Property(prop) = key { - match prop.patch_or_prop() { + match prop { VacationResponseProperty::Id => { Id::from_str(value).ok().map(VacationResponseValue::Id) } @@ -68,8 +71,6 @@ impl Element for VacationResponseValue { match self { VacationResponseValue::Id(id) => id.to_string().into(), VacationResponseValue::Date(utcdate) => utcdate.to_string().into(), - VacationResponseValue::BlobId(blob_id) => blob_id.to_string().into(), - VacationResponseValue::IdReference(r) => format!("#{r}").into(), } } } @@ -86,3 +87,40 @@ impl VacationResponseProperty { ) } } + +impl serde::Serialize for VacationResponseProperty { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_cow().as_ref()) + } +} + +impl FromStr for VacationResponseProperty { + type Err = (); + + fn from_str(s: &str) -> Result { + VacationResponseProperty::parse(s).ok_or(()) + } +} + +impl JmapObject for VacationResponse { + type Property = VacationResponseProperty; + + type Element = VacationResponseValue; + + type Id = Id; + + type Filter = (); + + type Comparator = (); + + type GetArguments = (); + + type SetArguments = (); + + type QueryArguments = (); + + type CopyArguments = (); +} diff --git a/crates/jmap-proto/src/request/capability.rs b/crates/jmap-proto/src/request/capability.rs index a6cc1180..3f0404ab 100644 --- a/crates/jmap-proto/src/request/capability.rs +++ b/crates/jmap-proto/src/request/capability.rs @@ -4,7 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use std::fmt; + use crate::response::serialize::serialize_hex; +use serde::{Deserialize, Deserializer}; use types::{id::Id, type_state::DataType}; use utils::map::vec_map::VecMap; @@ -69,6 +72,10 @@ pub enum Capability { Quota = 1 << 9, } +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct CapabilityIds(pub u32); + #[derive(Debug, Clone, serde::Serialize)] #[serde(untagged)] #[allow(dead_code)] @@ -328,3 +335,39 @@ impl Capability { ) } } + +impl<'de> Deserialize<'de> for CapabilityIds { + fn deserialize(deserializer: D) -> Result + 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(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut capability_flags = 0u32; + + while let Some(capability_str) = seq.next_element::<&str>()? { + let capability = Capability::parse(capability_str).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) + } +} diff --git a/crates/jmap-proto/src/request/deserialize.rs b/crates/jmap-proto/src/request/deserialize.rs new file mode 100644 index 00000000..bb119089 --- /dev/null +++ b/crates/jmap-proto/src/request/deserialize.rs @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::{fmt, marker::PhantomData}; + +use serde::{ + Deserializer, + de::{self, MapAccess, Visitor}, +}; + +pub(crate) trait DeserializeArguments<'de> { + fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error> + where + A: MapAccess<'de>; +} + +impl<'de> DeserializeArguments<'de> for () { + fn deserialize_argument(&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 +where + T: DeserializeArguments<'de> + Default, + D: Deserializer<'de>, +{ + struct DirectArgumentsVisitor { + _phantom: PhantomData, + } + + impl DirectArgumentsVisitor { + fn new() -> Self { + Self { + _phantom: PhantomData, + } + } + } + + impl<'de, T> Visitor<'de> for DirectArgumentsVisitor + 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(self, mut map: A) -> Result + 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::::new()) +} diff --git a/crates/jmap-proto/src/request/method.rs b/crates/jmap-proto/src/request/method.rs index 42e83e62..b1c32866 100644 --- a/crates/jmap-proto/src/request/method.rs +++ b/crates/jmap-proto/src/request/method.rs @@ -220,7 +220,7 @@ impl<'de> serde::Deserialize<'de> for MethodName { let value = <&str>::deserialize(deserializer)?; MethodName::parse(value).ok_or_else(|| { - serde::de::Error::custom(format!("Invalid method name: {}", value)) + serde::de::Error::custom(format!("Invalid method name: {:?}", value)) }) } } diff --git a/crates/jmap-proto/src/request/mod.rs b/crates/jmap-proto/src/request/mod.rs index a173193a..2fb14fe7 100644 --- a/crates/jmap-proto/src/request/mod.rs +++ b/crates/jmap-proto/src/request/mod.rs @@ -5,33 +5,41 @@ */ pub mod capability; +pub mod deserialize; pub mod method; pub mod parser; pub mod reference; pub mod websocket; -use jmap_tools::{Null, Value}; - use self::method::MethodName; -use crate::method::{ - changes::ChangesRequest, - copy::{self, CopyBlobRequest, CopyRequest}, - get::{self, GetRequest}, - import::ImportEmailRequest, - lookup::BlobLookupRequest, - parse::ParseEmailRequest, - query::{self, QueryRequest}, - query_changes::QueryChangesRequest, - search_snippet::GetSearchSnippetRequest, - set::{self, SetRequest}, - upload::BlobUploadRequest, - validate::ValidateSieveScriptRequest, +use crate::{ + method::{ + changes::ChangesRequest, + copy::{CopyBlobRequest, CopyRequest}, + get::GetRequest, + import::ImportEmailRequest, + lookup::BlobLookupRequest, + parse::ParseEmailRequest, + query::QueryRequest, + query_changes::QueryChangesRequest, + search_snippet::GetSearchSnippetRequest, + set::SetRequest, + upload::BlobUploadRequest, + validate::ValidateSieveScriptRequest, + }, + object::{ + blob::Blob, email::Email, email_submission::EmailSubmission, identity::Identity, + mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, quota::Quota, + sieve::Sieve, thread::Thread, vacation_response::VacationResponse, + }, + request::capability::CapabilityIds, }; -use std::{collections::HashMap, fmt::Debug}; +use jmap_tools::{Null, Value}; +use std::{collections::HashMap, fmt::Debug, str::FromStr}; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Request<'x> { - pub using: u32, + pub using: CapabilityIds, pub method_calls: Vec>>, pub created_ids: Option>, } @@ -45,25 +53,120 @@ pub struct Call { #[derive(Debug)] pub enum RequestMethod<'x> { - //Get(GetRequest), - //Set(SetRequest), + Get(GetRequestMethod), + Set(SetRequestMethod<'x>), Changes(ChangesRequest), - //Copy(CopyRequest), - CopyBlob(CopyBlobRequest), + Copy(CopyRequestMethod<'x>), ImportEmail(ImportEmailRequest), ParseEmail(ParseEmailRequest), - //QueryChanges(QueryChangesRequest), - //Query(QueryRequest), - SearchSnippet(GetSearchSnippetRequest), + Query(QueryRequestMethod), + QueryChanges(QueryChangesRequestMethod), + SearchSnippet(GetSearchSnippetRequest<()>), ValidateScript(ValidateSieveScriptRequest), LookupBlob(BlobLookupRequest), UploadBlob(BlobUploadRequest), Echo(Value<'x, Null, Null>), - Error(String), + Error(trc::Error), +} + +#[derive(Debug)] +pub enum GetRequestMethod { + Email(GetRequest), + Mailbox(GetRequest), + Thread(GetRequest), + Identity(GetRequest), + EmailSubmission(GetRequest), + PushSubscription(GetRequest), + Sieve(GetRequest), + VacationResponse(GetRequest), + Principal(GetRequest), + Quota(GetRequest), + Blob(GetRequest), +} + +#[derive(Debug)] +pub enum SetRequestMethod<'x> { + Email(SetRequest<'x, Email>), + Mailbox(SetRequest<'x, Mailbox>), + Identity(SetRequest<'x, Identity>), + EmailSubmission(SetRequest<'x, EmailSubmission>), + PushSubscription(SetRequest<'x, PushSubscription>), + Sieve(SetRequest<'x, Sieve>), + VacationResponse(SetRequest<'x, VacationResponse>), +} + +#[derive(Debug)] +pub enum CopyRequestMethod<'x> { + Email(CopyRequest<'x, Email>), + Blob(CopyBlobRequest), +} + +#[derive(Debug)] +pub enum QueryRequestMethod { + Email(QueryRequest), + Mailbox(QueryRequest), + EmailSubmission(QueryRequest), + Sieve(QueryRequest), + Principal(QueryRequest), + Quota(QueryRequest), +} + +#[derive(Debug)] +pub enum QueryChangesRequestMethod { + Email(QueryChangesRequest), + Mailbox(QueryChangesRequest), + EmailSubmission(QueryChangesRequest), + Sieve(QueryChangesRequest), + Principal(QueryChangesRequest), + Quota(QueryChangesRequest), } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum MaybeInvalid { - Id(V), +pub enum MaybeInvalid { + Value(V), Invalid(String), } + +impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeInvalid { + fn deserialize(deserializer: D) -> Result + 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 serde::Serialize for MaybeInvalid { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + MaybeInvalid::Value(id) => id.serialize(serializer), + MaybeInvalid::Invalid(str) => serializer.serialize_str(str), + } + } +} + +impl Default for MaybeInvalid { + 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, + } + } +} diff --git a/crates/jmap-proto/src/request/parser.rs b/crates/jmap-proto/src/request/parser.rs index e28fdc41..927ddb0f 100644 --- a/crates/jmap-proto/src/request/parser.rs +++ b/crates/jmap-proto/src/request/parser.rs @@ -4,224 +4,436 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::collections::HashMap; - -use compact_str::ToCompactString; - -use crate::method::{ - changes::ChangesRequest, - copy::{CopyBlobRequest, CopyRequest}, - get::GetRequest, - import::ImportEmailRequest, - lookup::BlobLookupRequest, - parse::ParseEmailRequest, - query::QueryRequest, - query_changes::QueryChangesRequest, - search_snippet::GetSearchSnippetRequest, - set::SetRequest, - upload::BlobUploadRequest, - validate::ValidateSieveScriptRequest, -}; - use super::{ Call, Request, RequestMethod, - capability::Capability, - echo::Echo, method::{MethodFunction, MethodName, MethodObject}, }; +use crate::request::{ + CopyRequestMethod, GetRequestMethod, QueryChangesRequestMethod, QueryRequestMethod, + SetRequestMethod, + deserialize::{DeserializeArguments, deserialize_request}, +}; +use serde::{ + Deserialize, Deserializer, + de::{self, SeqAccess, Visitor}, +}; +use std::fmt::{self, Display}; -impl Request { - pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result { +impl<'x> Request<'x> { + pub fn parse(json: &'x [u8], max_calls: usize, max_size: usize) -> trc::Result { if json.len() <= max_size { - let mut request = Request { - using: 0, - method_calls: Vec::new(), - created_ids: None, - }; - let mut found_valid_keys = false; - let mut parser = Parser::new(json); - parser.next_token::()?.assert(Token::DictStart)?; - while let Some(key) = parser.next_dict_key::()? { - found_valid_keys |= request.parse_key(&mut parser, max_calls, key)?; - } - - if found_valid_keys { - Ok(request) - } else { - Err(trc::JmapEvent::NotRequest + match serde_json::from_slice::(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() - .details("Invalid JMAP request")) + .details(err.to_string())), } } else { Err(trc::LimitEvent::SizeRequest.into_err()) } } +} - pub(crate) fn parse_key( - &mut self, - parser: &mut Parser, - max_calls: usize, - key: u128, - ) -> trc::Result { - match key { - 0x0067_6e69_7375 => { - parser.next_token::()?.assert(Token::ArrayStart)?; - loop { - match parser.next_token::()? { - Token::String(capability) => { - self.using |= capability as u32; - } - Token::Comma => (), - Token::ArrayEnd => break, - token => return Err(token.error("capability", &token.to_string())), - } - } - Ok(true) - } - 0x0073_6c6c_6143_646f_6874_656d => { - parser - .next_token::()? - .assert_jmap(Token::ArrayStart)?; - loop { - match parser.next_token::()? { - Token::ArrayStart => (), - Token::Comma => continue, - Token::ArrayEnd => break, - _ => { - return Err(trc::JmapEvent::NotRequest - .into_err() - .details("Invalid JMAP request")); - } - }; - if self.method_calls.len() < max_calls { - let method_name = match parser.next_token::() { - Ok(Token::String(method)) => method, - Ok(_) => { - return Err(trc::JmapEvent::NotRequest - .into_err() - .details("Invalid JMAP request")); - } - Err(err) - if err.matches(trc::EventType::Jmap( - trc::JmapEvent::InvalidArguments, - )) => - { - MethodName::error() - } - Err(err) => { - return Err(err); - } - }; - parser.next_token::()?.assert_jmap(Token::Comma)?; - parser.ctx = method_name.obj; - let start_depth_array = parser.depth_array; - let start_depth_dict = parser.depth_dict; - - let method = match (&method_name.fnc, &method_name.obj) { - ( - MethodFunction::Get, - MethodObject::Email - | MethodObject::Mailbox - | MethodObject::Thread - | MethodObject::Identity - | MethodObject::EmailSubmission - | MethodObject::PushSubscription - | MethodObject::VacationResponse - | MethodObject::SieveScript - | MethodObject::Principal - | MethodObject::Quota - | MethodObject::Blob, - ) => GetRequest::parse(parser).map(RequestMethod::Get), - (MethodFunction::Get, MethodObject::SearchSnippet) => { - GetSearchSnippetRequest::parse(parser) - .map(RequestMethod::SearchSnippet) - } - (MethodFunction::Query, _) => { - QueryRequest::parse(parser).map(RequestMethod::Query) - } - (MethodFunction::Set, _) => { - SetRequest::parse(parser).map(RequestMethod::Set) - } - (MethodFunction::Changes, _) => { - ChangesRequest::parse(parser).map(RequestMethod::Changes) - } - (MethodFunction::QueryChanges, _) => { - QueryChangesRequest::parse(parser).map(RequestMethod::QueryChanges) - } - (MethodFunction::Copy, MethodObject::Email) => { - CopyRequest::parse(parser).map(RequestMethod::Copy) - } - (MethodFunction::Copy, MethodObject::Blob) => { - CopyBlobRequest::parse(parser).map(RequestMethod::CopyBlob) - } - (MethodFunction::Lookup, MethodObject::Blob) => { - BlobLookupRequest::parse(parser).map(RequestMethod::LookupBlob) - } - (MethodFunction::Upload, MethodObject::Blob) => { - BlobUploadRequest::parse(parser).map(RequestMethod::UploadBlob) - } - (MethodFunction::Import, MethodObject::Email) => { - ImportEmailRequest::parse(parser).map(RequestMethod::ImportEmail) - } - (MethodFunction::Parse, MethodObject::Email) => { - ParseEmailRequest::parse(parser).map(RequestMethod::ParseEmail) - } - (MethodFunction::Validate, MethodObject::SieveScript) => { - ValidateSieveScriptRequest::parse(parser) - .map(RequestMethod::ValidateScript) - } - (MethodFunction::Echo, MethodObject::Core) => { - Echo::parse(parser).map(RequestMethod::Echo) - } - _ => Err(trc::JmapEvent::UnknownMethod - .into_err() - .details(method_name.to_compact_string())), - }; - - let method = match method { - Ok(method) => method, - Err(err) if !err.is_jmap_method_error() => { - parser.skip_token(start_depth_array, start_depth_dict)?; - RequestMethod::Error(err) - } - Err(err) => { - return Err(err); - } - }; - - parser.next_token::()?.assert_jmap(Token::Comma)?; - let id = parser.next_token::()?.unwrap_string("")?; - parser - .next_token::()? - .assert_jmap(Token::ArrayEnd)?; - self.method_calls.push(Call { - id, - method, - name: method_name, - }); - } else { - return Err(trc::LimitEvent::CallsIn.into_err()); - } - } - Ok(true) - } - 0x7364_4964_6574_6165_7263 => { - let mut created_ids = HashMap::new(); - parser.next_token::()?.assert(Token::DictStart)?; - while let Some(key) = parser.next_dict_key::()? { - created_ids.insert( - key, - parser.next_token::()?.unwrap_string("createdIds")?, - ); - } - self.created_ids = Some(created_ids); - Ok(true) - } +impl<'de> DeserializeArguments<'de> for Request<'de> { + fn deserialize_argument(&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()?; + }, _ => { - parser.skip_token(parser.depth_array, parser.depth_dict)?; - Ok(false) + let _ = map.next_value::()?; } - } + ); + + Ok(()) + } +} + +struct CallVisitor; + +impl<'de> Visitor<'de> for CallVisitor { + type Value = Call>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an array with 3 elements") + } + + fn visit_seq(self, mut seq: V) -> Result>, V::Error> + where + V: SeqAccess<'de>, + { + let method_name = seq + .next_element::<&str>()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let name = match MethodName::parse(method_name) { + Some(name) => name, + None => { + // Ignore the rest of the call + let _ = seq + .next_element::()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + let id = seq + .next_element::()? + .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::Email(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::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::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::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::SieveScript) => match seq.next_element() { + Ok(Some(value)) => { + RequestMethod::QueryChanges(QueryChangesRequestMethod::Sieve(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::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::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::ParseEmail(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)); + } + }, + _ => unreachable!(), + }; + + let id = seq + .next_element::()? + .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(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_request(deserializer) + } +} + +impl<'de> Deserialize<'de> for Call> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(CallVisitor) } } @@ -344,7 +556,7 @@ mod tests { #[test] fn parse_request() { - println!("{:?}", Request::parse(TEST.as_bytes(), 10, 10240)); - println!("{:?}", Request::parse(TEST2.as_bytes(), 10, 10240)); + println!("{:#?}", Request::parse(TEST.as_bytes(), 10, 10240)); + println!("{:#?}", Request::parse(TEST2.as_bytes(), 10, 10240)); } } diff --git a/crates/jmap-proto/src/request/reference.rs b/crates/jmap-proto/src/request/reference.rs index 7f1b30ff..245f8f07 100644 --- a/crates/jmap-proto/src/request/reference.rs +++ b/crates/jmap-proto/src/request/reference.rs @@ -7,7 +7,6 @@ use super::method::MethodName; use jmap_tools::{JsonPointer, Null}; use std::{fmt::Display, str::FromStr}; -use types::id::Id; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ResultReference { @@ -70,7 +69,7 @@ impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeIdReference { } } -impl serde::Serialize for MaybeIdReference { +impl serde::Serialize for MaybeIdReference { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -82,3 +81,9 @@ impl serde::Serialize for MaybeIdReference { } } } + +impl Default for MaybeResultReference { + fn default() -> Self { + MaybeResultReference::Value(V::default()) + } +} diff --git a/crates/jmap-proto/src/request/websocket.rs b/crates/jmap-proto/src/request/websocket.rs index ebc0e3d3..11056d29 100644 --- a/crates/jmap-proto/src/request/websocket.rs +++ b/crates/jmap-proto/src/request/websocket.rs @@ -7,11 +7,15 @@ use super::Request; use crate::{ error::request::{RequestError, RequestErrorType, RequestLimitError}, - request::Call, + request::{Call, deserialize::DeserializeArguments}, response::{Response, ResponseMethod, serialize::serialize_hex}, types::state::State, }; -use std::{borrow::Cow, collections::HashMap}; +use serde::{ + Deserialize, Deserializer, + de::{self, MapAccess, Visitor}, +}; +use std::{borrow::Cow, collections::HashMap, fmt}; use types::{id::Id, type_state::DataType}; use utils::map::vec_map::VecMap; @@ -22,12 +26,12 @@ pub struct WebSocketRequest<'x> { } #[derive(Debug, serde::Serialize)] -pub struct WebSocketResponse { +pub struct WebSocketResponse<'x> { #[serde(rename = "@type")] _type: WebSocketResponseType, #[serde(rename = "methodResponses")] - method_responses: Vec>, + method_responses: Vec>>, #[serde(rename = "sessionState")] #[serde(serialize_with = "serialize_hex")] @@ -105,72 +109,19 @@ enum MessageType { None, } -impl WebSocketMessage { - pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result { +impl<'x> WebSocketMessage<'x> { + pub fn parse(json: &'x [u8], max_calls: usize, max_size: usize) -> trc::Result { if json.len() <= max_size { - 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; - - let mut parser = Parser::new(json); - parser.next_token::()?.assert(Token::DictStart)?; - while let Some(key) = parser.next_dict_key::()? { - match key { - 0x0065_7079_7440 => { - let rt = parser - .next_token::()? - .unwrap_string("@type")?; - message_type = match (rt.hash[0], rt.hash[1]) { - (0x0074_7365_7571_6552, 0) => MessageType::Request, - (0x616e_4568_7375_5074_656b_636f_5362_6557, 0x656c62) => { - MessageType::PushEnable - } - (0x7369_4468_7375_5074_656b_636f_5362_6557, 0x656c6261) => { - MessageType::PushDisable - } - _ => MessageType::None, - }; - } - 0x0073_6570_7954_6174_6164 => { - push_enable.data_types = - >>::parse(&mut parser)?.unwrap_or_default(); - found_push_keys = true; - } - 0x0065_7461_7453_6873_7570 => { - push_enable.push_state = parser - .next_token::()? - .unwrap_string_or_null("pushState")?; - found_push_keys = true; - } - 0x6469 => { - request.id = parser.next_token::()?.unwrap_string_or_null("id")?; - } - _ => { - found_request_keys |= - request.request.parse_key(&mut parser, max_calls, key)?; - } + match serde_json::from_slice::(json) { + Ok(WebSocketMessage::Request(req)) + if req.request.method_calls.len() > max_calls => + { + Err(trc::LimitEvent::CallsIn.into_err()) } - } - - 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(trc::JmapEvent::NotRequest + Ok(msg) => Ok(msg), + Err(err) => Err(trc::JmapEvent::NotRequest .into_err() - .details("Invalid WebSocket JMAP request")), + .details(format!("Invalid WebSocket JMAP request {err}"))), } } else { Err(trc::LimitEvent::SizeRequest.into_err()) @@ -178,6 +129,85 @@ impl WebSocketMessage { } } +impl<'de: 'x, 'x> Deserialize<'de> for WebSocketMessage<'x> { + fn deserialize(deserializer: D) -> Result + 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(self, mut map: V) -> Result, 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()?; + 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) -> Self { Self { @@ -201,8 +231,8 @@ impl<'x> From> for WebSocketRequestError<'x> { } } -impl WebSocketResponse { - pub fn from_response(response: Response, request_id: Option) -> Self { +impl<'x> WebSocketResponse<'x> { + pub fn from_response(response: Response<'x>, request_id: Option) -> Self { Self { _type: WebSocketResponseType::Response, method_responses: response.method_responses, diff --git a/crates/jmap-proto/src/response/mod.rs b/crates/jmap-proto/src/response/mod.rs index ed9b7bf3..2e45dc52 100644 --- a/crates/jmap-proto/src/response/mod.rs +++ b/crates/jmap-proto/src/response/mod.rs @@ -66,7 +66,7 @@ pub struct Response<'x> { #[serde(skip_serializing_if = "HashMap::is_empty")] pub created_ids: HashMap, } - +/* impl Response<'_> { pub fn new(session_state: u32, created_ids: HashMap, capacity: usize) -> Self { Response { @@ -200,3 +200,4 @@ impl> From> for ResponseMethod { } } } +*/ diff --git a/crates/jmap-proto/src/response/references.rs b/crates/jmap-proto/src/response/references.rs index bb05e23c..e96fc9c6 100644 --- a/crates/jmap-proto/src/response/references.rs +++ b/crates/jmap-proto/src/response/references.rs @@ -8,16 +8,14 @@ use super::{Response, ResponseMethod}; use crate::{ error::set::SetError, method::{copy::CopyResponse, set::SetResponse, upload::DataSourceObject}, - request::{ - RequestMethod, - reference::{MaybeReference, ResultReference}, - }, + request::{RequestMethod, reference::ResultReference}, }; use compact_str::format_compact; use std::collections::HashMap; use types::id::Id; use utils::map::vec_map::VecMap; +/* enum EvalResult { Properties(Vec), Values(Vec), @@ -532,10 +530,6 @@ mod tests { use crate::{ request::{Request, RequestMethod}, response::Response, - types::{ - property::Property, - value::{SetValue, Value}, - }, }; use std::collections::HashMap; @@ -888,3 +882,5 @@ mod tests { } } } + +*/ diff --git a/crates/jmap-proto/src/response/serialize.rs b/crates/jmap-proto/src/response/serialize.rs index 4035017a..4e70fec8 100644 --- a/crates/jmap-proto/src/response/serialize.rs +++ b/crates/jmap-proto/src/response/serialize.rs @@ -4,13 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::ResponseMethod; +use crate::request::Call; use serde::{Serialize, ser::SerializeSeq}; -use crate::request::Call; - -use super::ResponseMethod; - -impl Serialize for Call { +impl Serialize for Call> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, diff --git a/crates/jmap-proto/src/types/date.rs b/crates/jmap-proto/src/types/date.rs index 9a2c9a51..5d5ebd75 100644 --- a/crates/jmap-proto/src/types/date.rs +++ b/crates/jmap-proto/src/types/date.rs @@ -7,7 +7,7 @@ use std::{fmt::Display, str::FromStr}; use store::SerializeInfallible; -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct UTCDate { pub year: u16, pub month: u8, @@ -216,6 +216,16 @@ impl serde::Serialize for UTCDate { } } +impl<'de> serde::Deserialize<'de> for UTCDate { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + UTCDate::from_str(<&str>::deserialize(deserializer)?) + .map_err(|_| serde::de::Error::custom("invalid JMAP UTCDate")) + } +} + impl SerializeInfallible for UTCDate { fn serialize(&self) -> Vec { (self.timestamp() as u64).serialize() @@ -237,6 +247,7 @@ impl From for UTCDate { #[cfg(test)] mod tests { use crate::types::date::UTCDate; + use std::str::FromStr; #[test] fn parse_jmap_date() { diff --git a/crates/jmap-proto/src/types/state.rs b/crates/jmap-proto/src/types/state.rs index c18a1d9a..12a4bfd1 100644 --- a/crates/jmap-proto/src/types/state.rs +++ b/crates/jmap-proto/src/types/state.rs @@ -45,13 +45,13 @@ impl State { let mut it = value.as_bytes().iter(); match it.next()? { - b'n' => Ok(State::Initial), + b'n' => Some(State::Initial), b's' => { - let mut reader = Base32Reader::new(it); + let mut reader = Base32Reader::from_iter(it); reader.next_leb128::().map(State::Exact) } b'r' => { - let mut it = Base32Reader::new(it); + let mut it = Base32Reader::from_iter(it); if let (Some(from_id), Some(to_id), Some(items_sent)) = ( it.next_leb128::(), @@ -59,7 +59,7 @@ impl State { it.next_leb128::(), ) { if items_sent > 0 { - Ok(State::Intermediate(JMAPIntermediateState { + Some(State::Intermediate(JMAPIntermediateState { from_id, to_id: from_id.saturating_add(to_id), items_sent, @@ -115,7 +115,7 @@ impl<'de> serde::Deserialize<'de> for State { D: serde::Deserializer<'de>, { State::parse(<&str>::deserialize(deserializer)?) - .map_err(|_| serde::de::Error::custom("invalid JMAP State")) + .ok_or_else(|| serde::de::Error::custom("invalid JMAP State")) } } diff --git a/crates/migration/src/mailbox.rs b/crates/migration/src/mailbox.rs index 48b18c18..465da868 100644 --- a/crates/migration/src/mailbox.rs +++ b/crates/migration/src/mailbox.rs @@ -6,7 +6,7 @@ use super::object::Object; use crate::object::{FromLegacy, Property, Value}; -use common::{Server, config::jmap::settings::SpecialUse}; +use common::Server; use email::mailbox::Mailbox; use store::{ SUBSPACE_BITMAP_TAG, SUBSPACE_BITMAP_TEXT, SUBSPACE_INDEXES, Serialize, U64_LEN, ValueKey, @@ -16,7 +16,7 @@ use store::{ }, }; use trc::AddContext; -use types::{collection::Collection, field::Field}; +use types::{collection::Collection, field::Field, special_use::SpecialUse}; use utils::config::utils::ParseValue; pub(crate) async fn migrate_mailboxes(server: &Server, account_id: u32) -> trc::Result { diff --git a/crates/migration/src/push.rs b/crates/migration/src/push.rs index 443afd61..2e86a7aa 100644 --- a/crates/migration/src/push.rs +++ b/crates/migration/src/push.rs @@ -144,7 +144,7 @@ impl FromLegacy for PushSubscription { .map(|l| l.as_slice()) .unwrap_or_default() .iter() - .filter_map(|v| v.as_string().and_then(|v| DataType::try_from(v).ok())) + .filter_map(|v| v.as_string().and_then(DataType::parse)) .collect(), keys: convert_keys(legacy.get(&Property::Keys)), } diff --git a/crates/pop3/src/mailbox.rs b/crates/pop3/src/mailbox.rs index 4f76daad..79dd1f1d 100644 --- a/crates/pop3/src/mailbox.rs +++ b/crates/pop3/src/mailbox.rs @@ -5,7 +5,7 @@ */ use crate::Session; -use common::{config::jmap::settings::SpecialUse, listener::SessionStream}; +use common::listener::SessionStream; use email::{ cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}, mailbox::INBOX_ID, @@ -16,7 +16,7 @@ use store::{ write::key::DeserializeBigEndian, }; use trc::AddContext; -use types::{collection::Collection, field::EmailField}; +use types::{collection::Collection, field::EmailField, special_use::SpecialUse}; #[derive(Default)] pub struct Mailbox { diff --git a/crates/types/src/blob.rs b/crates/types/src/blob.rs index c7637b0e..4d1367da 100644 --- a/crates/types/src/blob.rs +++ b/crates/types/src/blob.rs @@ -15,7 +15,7 @@ use crate::blob_hash::BlobHash; const B_LINKED: u8 = 0x10; const B_RESERVED: u8 = 0x20; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum BlobClass { Reserved { account_id: u32, @@ -65,14 +65,14 @@ impl BlobClass { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BlobId { pub hash: BlobHash, pub class: BlobClass, pub section: Option, } -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BlobSection { pub offset_start: usize, pub size: usize, @@ -126,7 +126,7 @@ impl BlobId { } #[allow(clippy::should_implement_trait)] - fn from_iter(it: &mut T) -> Option + pub fn from_iter(it: &mut T) -> Option where T: Iterator + Leb128Iterator, U: Borrow, diff --git a/crates/types/src/blob_hash.rs b/crates/types/src/blob_hash.rs index 8a06c8f9..3c17557c 100644 --- a/crates/types/src/blob_hash.rs +++ b/crates/types/src/blob_hash.rs @@ -16,6 +16,8 @@ pub const BLOB_HASH_LEN: usize = 32; PartialEq, Eq, Hash, + PartialOrd, + Ord, serde::Serialize, serde::Deserialize, )] diff --git a/crates/types/src/id.rs b/crates/types/src/id.rs index 79c21431..7167988f 100644 --- a/crates/types/src/id.rs +++ b/crates/types/src/id.rs @@ -8,7 +8,7 @@ use crate::DocumentId; use std::{ops::Deref, str::FromStr}; use utils::codec::base32_custom::{BASE32_ALPHABET, BASE32_INVERSE}; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)] #[repr(transparent)] pub struct Id(u64); diff --git a/crates/types/src/keyword.rs b/crates/types/src/keyword.rs index 9a3542e4..0e747b8a 100644 --- a/crates/types/src/keyword.rs +++ b/crates/types/src/keyword.rs @@ -30,6 +30,8 @@ pub const OTHER: usize = 12; Eq, Hash, Default, + PartialOrd, + Ord, serde::Serialize, )] #[serde(untagged)] @@ -65,6 +67,10 @@ pub enum Keyword { impl Keyword { pub fn parse(value: &str) -> Self { + Self::try_parse(value).unwrap_or_else(|| Keyword::Other(value.to_string())) + } + + pub fn try_parse(value: &str) -> Option { value .split_at_checked(1) .filter(|(prefix, _)| matches!(*prefix, "$" | "\\")) @@ -84,7 +90,6 @@ impl Keyword { "mdnsent" => Keyword::MdnSent ) }) - .unwrap_or_else(|| Keyword::Other(value.to_string())) } pub fn id(&self) -> Result { @@ -142,6 +147,12 @@ impl Keyword { } } +impl From for Keyword { + fn from(value: String) -> Self { + Keyword::try_parse(&value).unwrap_or(Keyword::Other(value)) + } +} + impl Display for Keyword { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -241,3 +252,12 @@ impl From<&ArchivedKeyword> for Keyword { } } } + +impl<'de> serde::Deserialize<'de> for Keyword { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Keyword::parse(<&str>::deserialize(deserializer)?)) + } +} diff --git a/crates/types/src/special_use.rs b/crates/types/src/special_use.rs index 75bd09a9..7f6ad63a 100644 --- a/crates/types/src/special_use.rs +++ b/crates/types/src/special_use.rs @@ -4,8 +4,20 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use utils::config::utils::ParseValue; + #[derive( - rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Clone, Copy, PartialEq, Eq, Hash, Debug, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Debug, + PartialOrd, + Ord, )] #[rkyv(derive(Debug))] pub enum SpecialUse { @@ -80,3 +92,9 @@ impl From<&ArchivedSpecialUse> for SpecialUse { } } } + +impl ParseValue for SpecialUse { + fn parse_value(value: &str) -> Result { + SpecialUse::parse(value).ok_or_else(|| format!("Unknown folder role {:?}", value)) + } +} diff --git a/crates/types/src/type_state.rs b/crates/types/src/type_state.rs index 8896b907..d5b621af 100644 --- a/crates/types/src/type_state.rs +++ b/crates/types/src/type_state.rs @@ -6,10 +6,10 @@ use crate::collection::SyncCollection; use serde::Serialize; -use std::fmt::Display; +use std::{fmt::Display, str::FromStr}; use utils::map::bitmap::{Bitmap, BitmapItem}; -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, PartialOrd, Ord)] #[repr(u8)] pub enum DataType { #[serde(rename = "Email")] @@ -199,6 +199,14 @@ impl DataType { } } +impl FromStr for DataType { + type Err = (); + + fn from_str(s: &str) -> Result { + DataType::parse(s).ok_or(()) + } +} + impl Display for DataType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) diff --git a/crates/utils/src/codec/base32_custom.rs b/crates/utils/src/codec/base32_custom.rs index cfc78c44..518fcca9 100644 --- a/crates/utils/src/codec/base32_custom.rs +++ b/crates/utils/src/codec/base32_custom.rs @@ -132,6 +132,15 @@ impl<'x> Base32Reader<'x> { } } + #[allow(clippy::should_implement_trait)] + pub fn from_iter(bytes: Iter<'x, u8>) -> Self { + Base32Reader { + bytes, + pos: 0, + last_byte: 0, + } + } + #[inline(always)] fn map_byte(&mut self) -> Option { match self.bytes.next() { diff --git a/crates/utils/src/map/vec_map.rs b/crates/utils/src/map/vec_map.rs index cfc1ff5e..dcd58351 100644 --- a/crates/utils/src/map/vec_map.rs +++ b/crates/utils/src/map/vec_map.rs @@ -5,7 +5,7 @@ */ use rkyv::Archive; -use serde::{Deserialize, Serialize, de::DeserializeOwned, ser::SerializeMap}; +use serde::{Deserialize, Serialize, ser::SerializeMap}; use std::{borrow::Borrow, cmp::Ordering, fmt, hash::Hash}; // A map implemented using vectors @@ -304,7 +304,7 @@ struct VecMapVisitor { phantom: std::marker::PhantomData<(K, V)>, } -impl<'de, K: Eq + PartialEq + DeserializeOwned, V: DeserializeOwned> serde::de::Visitor<'de> +impl<'de, K: Eq + PartialEq + Deserialize<'de>, V: Deserialize<'de>> serde::de::Visitor<'de> for VecMapVisitor { type Value = VecMap; @@ -326,7 +326,7 @@ impl<'de, K: Eq + PartialEq + DeserializeOwned, V: DeserializeOwned> serde::de:: } } -impl<'de, K: Eq + PartialEq + DeserializeOwned, V: DeserializeOwned> Deserialize<'de> +impl<'de, K: Eq + PartialEq + Deserialize<'de>, V: Deserialize<'de>> Deserialize<'de> for VecMap { fn deserialize(deserializer: D) -> Result