diff --git a/Cargo.lock b/Cargo.lock index fea091bc..11090f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1285,6 +1285,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "trc", + "types", "unicode-security", "utils", "whatlang", @@ -1737,11 +1738,11 @@ dependencies = [ "hashify", "http_proto", "hyper 1.7.0", - "jmap_proto", "percent-encoding", "rkyv", "store", "trc", + "types", "utils", ] @@ -1949,7 +1950,6 @@ dependencies = [ "compact_str", "deadpool 0.10.0", "futures", - "jmap_proto", "ldap3", "mail-builder", "mail-parser", @@ -1976,6 +1976,7 @@ dependencies = [ "tokio-rustls 0.26.2", "totp-rs", "trc", + "types", "utils", ] @@ -2230,7 +2231,6 @@ dependencies = [ "directory", "groupware", "hashify", - "jmap_proto", "mail-builder", "mail-parser", "nlp", @@ -2249,6 +2249,7 @@ dependencies = [ "store", "tokio", "trc", + "types", "utils", ] @@ -2848,12 +2849,12 @@ dependencies = [ "dav-proto", "directory", "hashify", - "jmap_proto", "percent-encoding", "rkyv", "store", "tokio", "trc", + "types", "utils", ] @@ -3171,6 +3172,7 @@ dependencies = [ "store", "tokio", "trc", + "types", "utils", "x509-parser 0.17.0", ] @@ -3558,7 +3560,6 @@ dependencies = [ "email", "imap_proto", "indexmap 2.11.1", - "jmap_proto", "mail-parser", "mail-send", "md5 0.8.0", @@ -3571,6 +3572,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "trc", + "types", "utils", ] @@ -3582,11 +3584,11 @@ dependencies = [ "chrono", "compact_str", "hashify", - "jmap_proto", "mail-parser", "store", "tokio", "trc", + "types", ] [[package]] @@ -3891,6 +3893,7 @@ dependencies = [ "tokio-tungstenite 0.27.0", "trc", "tungstenite 0.27.0", + "types", "utils", ] @@ -3931,6 +3934,7 @@ dependencies = [ "store", "tokio", "trc", + "types", "utils", ] @@ -4426,6 +4430,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "trc", + "types", "utils", ] @@ -4523,7 +4528,6 @@ dependencies = [ "directory", "email", "groupware", - "jmap_proto", "lz4_flex", "mail-auth", "mail-parser", @@ -4536,6 +4540,7 @@ dependencies = [ "store", "tokio", "trc", + "types", "utils", ] @@ -5521,7 +5526,6 @@ dependencies = [ "directory", "email", "imap", - "jmap_proto", "mail-parser", "mail-send", "rustls 0.23.31", @@ -5529,6 +5533,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "trc", + "types", "utils", ] @@ -7411,6 +7416,7 @@ dependencies = [ "store", "tokio", "trc", + "types", "utils", ] @@ -7633,6 +7639,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "trc", + "types", "utils", "webpki-roots 1.0.2", "x509-parser 0.17.0", @@ -7768,7 +7775,6 @@ dependencies = [ "imap", "jemallocator", "jmap", - "jmap_proto", "managesieve", "migration", "pop3", @@ -7778,6 +7784,7 @@ dependencies = [ "store", "tokio", "trc", + "types", "utils", ] @@ -7861,6 +7868,7 @@ dependencies = [ "tokio-postgres", "tokio-rustls 0.26.2", "trc", + "types", "utils", "xxhash-rust", "zenoh", @@ -8082,6 +8090,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "trc", + "types", "utils", ] @@ -8690,6 +8699,19 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "types" +version = "0.13.3" +dependencies = [ + "blake3", + "compact_str", + "hashify", + "rkyv", + "serde", + "trc", + "utils", +] + [[package]] name = "typewit" version = "1.14.2" diff --git a/Cargo.toml b/Cargo.toml index 0be977e4..916a24cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "crates/main", + "crates/types", "crates/http", "crates/http-proto", "crates/jmap", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index c67ebb60..1f689ea0 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -11,6 +11,7 @@ nlp = { path = "../nlp" } store = { path = "../store" } trc = { path = "../trc" } directory = { path = "../directory" } +types = { path = "../types" } jmap_proto = { path = "../jmap-proto" } imap_proto = { path = "../imap-proto" } sieve-rs = { version = "0.7", features = ["rkyv", "serde"] } diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index 1159eca7..80daaab0 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -18,16 +18,13 @@ use directory::{ manage::{ChangedPrincipals, ManageDirectory}, }, }; -use jmap_proto::{ - request::RequestMethod, - types::{acl::Acl, collection::Collection, id::Id}, -}; use std::{ hash::{DefaultHasher, Hash, Hasher}, sync::Arc, }; use store::{query::acl::AclQuery, rand}; use trc::AddContext; +use types::{acl::Acl, collection::Collection}; use utils::map::{ bitmap::{Bitmap, BitmapItem}, vec_map::VecMap, @@ -163,7 +160,7 @@ impl Server { { if !access_token.is_member(acl_item.to_account_id) { let acl = Bitmap::::from(acl_item.permissions); - let collection = Collection::from(acl_item.to_collection); + let collection = acl_item.to_collection; if !collection.is_valid() { return Err(trc::StoreEvent::DataCorruption .ctx(trc::Key::Reason, "Corrupted collection found in ACL key.") @@ -485,8 +482,7 @@ impl AccessToken { !self.is_member(account_id) && self.access_to.iter().any(|(id, _)| *id == account_id) } - pub fn shared_accounts(&self, collection: impl Into) -> impl Iterator { - let collection = collection.into(); + pub fn shared_accounts(&self, collection: Collection) -> impl Iterator { self.member_of .iter() .chain(self.access_to.iter().filter_map(move |(id, cols)| { @@ -510,152 +506,6 @@ impl AccessToken { self.is_member(to_account_id) || self.access_to.iter().any(|(id, _)| *id == to_account_id) } - pub fn assert_has_access( - &self, - to_account_id: Id, - to_collection: Collection, - ) -> trc::Result<&Self> { - if self.has_access(to_account_id.document_id(), to_collection) { - Ok(self) - } else { - Err(trc::JmapEvent::Forbidden.into_err().details(format!( - "You do not have access to account {}", - to_account_id - ))) - } - } - - pub fn assert_is_member(&self, account_id: Id) -> trc::Result<&Self> { - if self.is_member(account_id.document_id()) { - Ok(self) - } else { - Err(trc::JmapEvent::Forbidden - .into_err() - .details(format!("You are not an owner of account {}", account_id))) - } - } - - pub fn assert_has_jmap_permission(&self, request: &RequestMethod) -> trc::Result<()> { - let permission = match request { - RequestMethod::Get(m) => match &m.arguments { - jmap_proto::method::get::RequestArguments::Email(_) => Permission::JmapEmailGet, - jmap_proto::method::get::RequestArguments::Mailbox => Permission::JmapMailboxGet, - jmap_proto::method::get::RequestArguments::Thread => Permission::JmapThreadGet, - jmap_proto::method::get::RequestArguments::Identity => Permission::JmapIdentityGet, - jmap_proto::method::get::RequestArguments::EmailSubmission => { - Permission::JmapEmailSubmissionGet - } - jmap_proto::method::get::RequestArguments::PushSubscription => { - Permission::JmapPushSubscriptionGet - } - jmap_proto::method::get::RequestArguments::SieveScript => { - Permission::JmapSieveScriptGet - } - jmap_proto::method::get::RequestArguments::VacationResponse => { - Permission::JmapVacationResponseGet - } - jmap_proto::method::get::RequestArguments::Principal => { - Permission::JmapPrincipalGet - } - jmap_proto::method::get::RequestArguments::Quota => Permission::JmapQuotaGet, - jmap_proto::method::get::RequestArguments::Blob(_) => Permission::JmapBlobGet, - }, - RequestMethod::Set(m) => match &m.arguments { - jmap_proto::method::set::RequestArguments::Email => Permission::JmapEmailSet, - jmap_proto::method::set::RequestArguments::Mailbox(_) => Permission::JmapMailboxSet, - jmap_proto::method::set::RequestArguments::Identity => Permission::JmapIdentitySet, - jmap_proto::method::set::RequestArguments::EmailSubmission(_) => { - Permission::JmapEmailSubmissionSet - } - jmap_proto::method::set::RequestArguments::PushSubscription => { - Permission::JmapPushSubscriptionSet - } - jmap_proto::method::set::RequestArguments::SieveScript(_) => { - Permission::JmapSieveScriptSet - } - jmap_proto::method::set::RequestArguments::VacationResponse => { - Permission::JmapVacationResponseSet - } - }, - RequestMethod::Changes(m) => match m.arguments { - jmap_proto::method::changes::RequestArguments::Email => { - Permission::JmapEmailChanges - } - jmap_proto::method::changes::RequestArguments::Mailbox => { - Permission::JmapMailboxChanges - } - jmap_proto::method::changes::RequestArguments::Thread => { - Permission::JmapThreadChanges - } - jmap_proto::method::changes::RequestArguments::Identity => { - Permission::JmapIdentityChanges - } - jmap_proto::method::changes::RequestArguments::EmailSubmission => { - Permission::JmapEmailSubmissionChanges - } - jmap_proto::method::changes::RequestArguments::Quota => { - Permission::JmapQuotaChanges - } - }, - RequestMethod::Copy(m) => match m.arguments { - jmap_proto::method::copy::RequestArguments::Email => Permission::JmapEmailCopy, - }, - RequestMethod::CopyBlob(_) => Permission::JmapBlobCopy, - RequestMethod::ImportEmail(_) => Permission::JmapEmailImport, - RequestMethod::ParseEmail(_) => Permission::JmapEmailParse, - RequestMethod::QueryChanges(m) => match m.arguments { - jmap_proto::method::query::RequestArguments::Email(_) => { - Permission::JmapEmailQueryChanges - } - jmap_proto::method::query::RequestArguments::Mailbox(_) => { - Permission::JmapMailboxQueryChanges - } - jmap_proto::method::query::RequestArguments::EmailSubmission => { - Permission::JmapEmailSubmissionQueryChanges - } - jmap_proto::method::query::RequestArguments::SieveScript => { - Permission::JmapSieveScriptQueryChanges - } - jmap_proto::method::query::RequestArguments::Principal => { - Permission::JmapPrincipalQueryChanges - } - jmap_proto::method::query::RequestArguments::Quota => { - Permission::JmapQuotaQueryChanges - } - }, - RequestMethod::Query(m) => match m.arguments { - jmap_proto::method::query::RequestArguments::Email(_) => Permission::JmapEmailQuery, - jmap_proto::method::query::RequestArguments::Mailbox(_) => { - Permission::JmapMailboxQuery - } - jmap_proto::method::query::RequestArguments::EmailSubmission => { - Permission::JmapEmailSubmissionQuery - } - jmap_proto::method::query::RequestArguments::SieveScript => { - Permission::JmapSieveScriptQuery - } - jmap_proto::method::query::RequestArguments::Principal => { - Permission::JmapPrincipalQuery - } - jmap_proto::method::query::RequestArguments::Quota => Permission::JmapQuotaQuery, - }, - RequestMethod::SearchSnippet(_) => Permission::JmapSearchSnippet, - RequestMethod::ValidateScript(_) => Permission::JmapSieveScriptValidate, - RequestMethod::LookupBlob(_) => Permission::JmapBlobLookup, - RequestMethod::UploadBlob(_) => Permission::JmapBlobUpload, - RequestMethod::Echo(_) => Permission::JmapEcho, - RequestMethod::Error(_) => return Ok(()), - }; - - if self.has_permission(permission) { - Ok(()) - } else { - Err(trc::JmapEvent::Forbidden - .into_err() - .details("You are not authorized to perform this action")) - } - } - pub fn as_resource_token(&self) -> ResourceToken { ResourceToken { account_id: self.primary_id, diff --git a/crates/common/src/auth/mod.rs b/crates/common/src/auth/mod.rs index f8bc6692..7ec54752 100644 --- a/crates/common/src/auth/mod.rs +++ b/crates/common/src/auth/mod.rs @@ -9,10 +9,10 @@ use directory::{ Directory, FALLBACK_ADMIN_ID, Permission, Permissions, Principal, QueryParams, Type, backend::internal::lookup::DirectoryStore, core::secret::verify_secret_hash, }; -use jmap_proto::types::collection::Collection; use mail_send::Credentials; use oauth::GrantType; use std::{net::IpAddr, sync::Arc}; +use types::collection::Collection; use utils::{ cache::CacheItemWeight, map::{bitmap::Bitmap, vec_map::VecMap}, diff --git a/crates/common/src/config/jmap/capabilities.rs b/crates/common/src/config/jmap/capabilities.rs index 1770f664..dea606ae 100644 --- a/crates/common/src/config/jmap/capabilities.rs +++ b/crates/common/src/config/jmap/capabilities.rs @@ -4,18 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use ahash::AHashSet; -use jmap_proto::{ - request::capability::{ - BlobCapabilities, Capabilities, Capability, CoreCapabilities, EmptyCapabilities, - MailCapabilities, SieveAccountCapabilities, SieveSessionCapabilities, - SubmissionCapabilities, - }, - types::type_state::DataType, -}; -use utils::{config::Config, map::vec_map::VecMap}; - use super::settings::JmapConfig; +use ahash::AHashSet; +use jmap_proto::request::capability::{ + BlobCapabilities, Capabilities, Capability, CoreCapabilities, EmptyCapabilities, + MailCapabilities, SieveAccountCapabilities, SieveSessionCapabilities, SubmissionCapabilities, +}; +use types::type_state::DataType; +use utils::{config::Config, map::vec_map::VecMap}; impl JmapConfig { pub fn add_capabilities(&mut self, config: &mut Config) { diff --git a/crates/common/src/config/jmap/settings.rs b/crates/common/src/config/jmap/settings.rs index 9acfd93d..d3a5e610 100644 --- a/crates/common/src/config/jmap/settings.rs +++ b/crates/common/src/config/jmap/settings.rs @@ -4,10 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{str::FromStr, time::Duration}; - use jmap_proto::request::capability::BaseCapabilities; use nlp::language::Language; +use std::{str::FromStr, time::Duration}; use utils::config::{Config, Rate, cron::SimpleCron, utils::ParseValue}; #[derive(Default, Clone)] diff --git a/crates/common/src/core.rs b/crates/common/src/core.rs index e03d1825..f1e5987a 100644 --- a/crates/common/src/core.rs +++ b/crates/common/src/core.rs @@ -17,13 +17,6 @@ use crate::{ ipc::{BroadcastEvent, StateEvent}, }; use directory::{Directory, QueryParams, Type, backend::internal::manage::ManageDirectory}; -use jmap_proto::types::{ - blob::BlobId, - collection::{Collection, SyncCollection}, - property::Property, - state::StateChange, - type_state::DataType, -}; use mail_auth::IpLookupStrategy; use sieve::Sieve; use std::{ @@ -31,8 +24,8 @@ use std::{ time::Duration, }; use store::{ - BitmapKey, BlobClass, BlobStore, Deserialize, FtsStore, InMemoryStore, IndexKey, IterateParams, - Key, LogKey, SUBSPACE_LOGS, SerializeInfallible, Store, U32_LEN, U64_LEN, ValueKey, + BitmapKey, BlobStore, Deserialize, FtsStore, InMemoryStore, IndexKey, IterateParams, Key, + LogKey, SUBSPACE_LOGS, SerializeInfallible, Store, U32_LEN, U64_LEN, ValueKey, dispatch::DocumentSet, roaring::RoaringBitmap, write::{ @@ -41,7 +34,13 @@ use store::{ }, }; use trc::AddContext; -use utils::BlobHash; +use types::{ + blob::{BlobClass, BlobId}, + blob_hash::BlobHash, + collection::{Collection, SyncCollection}, + field::{EmailField, Field}, + type_state::{DataType, StateChange}, +}; impl Server { #[inline(always)] @@ -353,14 +352,14 @@ impl Server { account_id, collection: Collection::Email.into(), document_id: 0, - field: Property::Size.into(), + field: EmailField::Size.into(), key: 0u32.serialize(), }, IndexKey { account_id, collection: Collection::Email.into(), document_id: u32::MAX, - field: Property::Size.into(), + field: EmailField::Size.into(), key: u32::MAX.serialize(), }, ) @@ -503,7 +502,7 @@ impl Server { account_id, collection: collection.into(), document_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await .add_context(|err| { @@ -520,9 +519,8 @@ impl Server { account_id: u32, collection: Collection, document_id: u32, - property: impl AsRef + Sync + Send, + property: Field, ) -> trc::Result>> { - let property = property.as_ref(); self.core .storage .data @@ -563,13 +561,13 @@ impl Server { account_id, collection, document_id: documents.min(), - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }, ValueKey { account_id, collection, document_id: documents.max(), - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }, ), |key, value| { @@ -654,12 +652,12 @@ impl Server { let mut state_change = StateChange::new(account_id, assigned_ids.last_change_id(account_id)?); for changed_collection in changed_collections.changed_containers { - if let Some(data_type) = DataType::try_from_id(changed_collection, true) { + if let Some(data_type) = DataType::try_from_sync(changed_collection, true) { state_change.set_change(data_type); } } for changed_collection in changed_collections.changed_items { - if let Some(data_type) = DataType::try_from_id(changed_collection, false) { + if let Some(data_type) = DataType::try_from_sync(changed_collection, false) { state_change.set_change(data_type); } } @@ -753,21 +751,18 @@ impl Server { // Write truncation entry for cache let mut batch = BatchBuilder::new(); - batch - .with_account_id(account_id) - .with_collection(collection) - .set( - ValueClass::Any(AnyClass { - subspace: SUBSPACE_LOGS, - key: LogKey { - account_id, - collection, - change_id: first_change_id, - } - .serialize(0), - }), - Vec::new(), - ); + batch.with_account_id(account_id).set( + ValueClass::Any(AnyClass { + subspace: SUBSPACE_LOGS, + key: LogKey { + account_id, + collection, + change_id: first_change_id, + } + .serialize(0), + }), + Vec::new(), + ); self.store() .write(batch.build_all()) .await diff --git a/crates/common/src/enterprise/undelete.rs b/crates/common/src/enterprise/undelete.rs index b3f97352..6cd793f5 100644 --- a/crates/common/src/enterprise/undelete.rs +++ b/crates/common/src/enterprise/undelete.rs @@ -8,6 +8,7 @@ * */ +use crate::Core; use serde::{Deserialize, Serialize}; use store::{ IterateParams, U32_LEN, U64_LEN, ValueKey, @@ -18,9 +19,7 @@ use store::{ }, }; use trc::AddContext; -use utils::{BLOB_HASH_LEN, BlobHash}; - -use crate::Core; +use types::blob_hash::{BLOB_HASH_LEN, BlobHash}; #[derive(Debug, Serialize, Deserialize)] pub struct DeletedBlob { diff --git a/crates/common/src/ipc.rs b/crates/common/src/ipc.rs index 53c33668..18d56047 100644 --- a/crates/common/src/ipc.rs +++ b/crates/common/src/ipc.rs @@ -4,24 +4,22 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{sync::Arc, time::Instant}; - -use ahash::RandomState; -use jmap_proto::types::{state::StateChange, type_state::DataType}; -use mail_auth::{ - dmarc::Dmarc, - mta_sts::TlsRpt, - report::{Record, tlsrpt::FailureDetails}, -}; -use store::{BlobStore, InMemoryStore, Store}; -use tokio::sync::mpsc; -use utils::map::bitmap::Bitmap; - use crate::config::smtp::{ queue::QueueName, report::AggregateFrequency, resolver::{Policy, Tlsa}, }; +use ahash::RandomState; +use mail_auth::{ + dmarc::Dmarc, + mta_sts::TlsRpt, + report::{Record, tlsrpt::FailureDetails}, +}; +use std::{sync::Arc, time::Instant}; +use store::{BlobStore, InMemoryStore, Store}; +use tokio::sync::mpsc; +use types::type_state::{DataType, StateChange}; +use utils::map::bitmap::Bitmap; pub enum HousekeeperEvent { AcmeReschedule { diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 688068e3..c1d88d8b 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -25,7 +25,6 @@ use config::{ telemetry::Metrics, }; use ipc::{BroadcastEvent, HousekeeperEvent, QueueEvent, ReportingEvent, StateEvent}; -use jmap_proto::types::value::AclGrant; use listener::{asn::AsnGeoLookupData, blocked::Security, tls::AcmeProviders}; use mail_auth::{MX, Txt}; use manager::webadmin::{Resource, WebAdminManager}; @@ -41,6 +40,7 @@ use std::{ use tinyvec::TinyVec; use tokio::sync::{Notify, Semaphore, mpsc}; use tokio_rustls::TlsConnector; +use types::acl::AclGrant; use utils::{ cache::{Cache, CacheItemWeight, CacheWithTtl}, snowflake::SnowflakeIdGenerator, @@ -123,10 +123,6 @@ pub const KV_LOCK_HOUSEKEEPER: u8 = 24; pub const KV_LOCK_DAV: u8 = 25; pub const KV_SIEVE_ID: u8 = 26; -pub const IDX_UID: u8 = 0; -pub const IDX_EMAIL: u8 = 1; -pub const IDX_CREATED: u8 = 2; - #[derive(Clone)] pub struct Server { pub inner: Arc, diff --git a/crates/common/src/manager/backup.rs b/crates/common/src/manager/backup.rs index 8771639e..6d7d73e1 100644 --- a/crates/common/src/manager/backup.rs +++ b/crates/common/src/manager/backup.rs @@ -4,6 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::Core; +use ahash::{AHashMap, AHashSet}; use std::{ collections::BTreeSet, io::{BufWriter, Write}, @@ -11,9 +13,6 @@ use std::{ path::{Path, PathBuf}, sync::mpsc::{self, SyncSender}, }; - -use ahash::{AHashMap, AHashSet}; -use jmap_proto::types::{collection::Collection, property::Property}; use store::{ BitmapKey, Deserialize, IndexKey, IterateParams, LogKey, SUBSPACE_BITMAP_ID, SUBSPACE_BITMAP_TAG, SUBSPACE_BITMAP_TEXT, SerializeInfallible, U32_LEN, U64_LEN, ValueKey, @@ -22,15 +21,17 @@ use store::{ QueueEvent, TagValue, ValueClass, key::DeserializeBigEndian, }, }; - +use types::{ + blob_hash::{BLOB_HASH_LEN, BlobHash}, + collection::Collection, + field::{Field, MailboxField}, +}; use utils::{ - BLOB_HASH_LEN, BlobHash, UnwrapFailure, + UnwrapFailure, codec::leb128::{Leb128_, Leb128Reader}, failed, }; -use crate::Core; - pub(super) const MAGIC_MARKER: u8 = 123; pub(super) const FILE_VERSION: u8 = 2; @@ -196,21 +197,21 @@ impl Core { // Obtain UID counter if collection == u8::from(Collection::Mailbox) - && u8::from(Property::Value) == field + && u8::from(Field::ARCHIVE) == field { let value = store .get_counter(ValueKey { account_id, collection, document_id, - class: ValueClass::Property(Property::EmailIds.into()), + class: MailboxField::UidCounter.into(), }) .await .failed("Failed to get counter"); if value != 0 { writer .send(Op::KeyValue(( - vec![u8::from(Property::EmailIds)], + vec![u8::from(MailboxField::UidCounter)], value.serialize(), ))) .failed("Failed to send key value"); diff --git a/crates/common/src/manager/config.rs b/crates/common/src/manager/config.rs index e55c1ee1..da17745a 100644 --- a/crates/common/src/manager/config.rs +++ b/crates/common/src/manager/config.rs @@ -17,8 +17,8 @@ use store::{ write::{BatchBuilder, ValueClass}, }; use trc::AddContext; +use types::semver::Semver; use utils::{ - Semver, config::{Config, ConfigKey}, glob::GlobPattern, }; diff --git a/crates/common/src/manager/restore.rs b/crates/common/src/manager/restore.rs index 8e7bd5ba..ef978866 100644 --- a/crates/common/src/manager/restore.rs +++ b/crates/common/src/manager/restore.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::Core; +use ahash::AHashMap; use std::{ io::ErrorKind, path::{Path, PathBuf}, }; - -use crate::Core; -use ahash::AHashMap; -use jmap_proto::types::{collection::Collection, property::Property}; use store::{ BlobStore, Key, LogKey, SUBSPACE_LOGS, SerializeInfallible, Store, U32_LEN, roaring::RoaringBitmap, @@ -28,7 +26,8 @@ use tokio::{ fs::File, io::{AsyncReadExt, BufReader}, }; -use utils::{BlobHash, UnwrapFailure, failed}; +use types::{blob_hash::BlobHash, collection::Collection, field::MailboxField}; +use utils::{UnwrapFailure, failed}; use super::backup::{DeserializeBytes, FILE_VERSION, Family, MAGIC_MARKER, Op}; @@ -65,9 +64,9 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) { let mut reader = OpReader::new(path).await; let mut account_id = u32::MAX; let mut document_id = u32::MAX; - let mut collection = u8::MAX; + let mut collection = Collection::None; + let mut collection_raw = u8::MAX; let mut family = Family::None; - let email_collection = u8::from(Collection::Email); let mut due = now(); let mut batch_size = 0; @@ -83,7 +82,8 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) { batch.with_account_id(account_id); } Op::Collection(c) => { - collection = c; + collection_raw = c; + collection = Collection::from(c); batch.with_collection(collection); } Op::DocumentId(d) => { @@ -99,8 +99,8 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) { .as_slice() .deserialize_u8(0) .expect("Failed to deserialize field"); - if collection == u8::from(Collection::Mailbox) - && u8::from(Property::EmailIds) == field + if collection == Collection::Mailbox + && u8::from(MailboxField::UidCounter) == field { batch.add( ValueClass::Property(field), @@ -145,7 +145,7 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) { let hash = BlobHash::try_from_hash_slice(&key).expect("Invalid blob hash"); if account_id != u32::MAX && document_id != u32::MAX { - if reader.version == 1 && collection == email_collection { + if reader.version == 1 && collection == Collection::Email { batch.set( ValueClass::TaskQueue(TaskQueueClass::IndexEmail { due, @@ -301,7 +301,7 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) { ), }, 4 => { - if reader.version == 1 && collection == email_collection { + if reader.version == 1 && collection == Collection::Email { continue; } @@ -357,7 +357,7 @@ async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) { subspace: SUBSPACE_LOGS, key: LogKey { account_id, - collection, + collection: collection_raw, change_id, } .serialize(0), diff --git a/crates/common/src/sharing/acl.rs b/crates/common/src/sharing/acl.rs index f11bbe6b..93b631e7 100644 --- a/crates/common/src/sharing/acl.rs +++ b/crates/common/src/sharing/acl.rs @@ -4,116 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{Server, auth::AccessToken}; +use crate::Server; use directory::{ - QueryParams, Type, - backend::internal::{ - PrincipalField, - manage::{ChangedPrincipals, ManageDirectory}, - }, + Type, + backend::internal::{PrincipalField, manage::ChangedPrincipals}, }; -use jmap_proto::{ - error::set::SetError, - types::{ - acl::Acl, - property::Property, - value::{AclGrant, ArchivedAclGrant, MaybePatchValue, Value}, - }, -}; -use utils::map::bitmap::Bitmap; +use types::acl::{AclGrant, ArchivedAclGrant}; impl Server { - pub async fn acl_set( - &self, - changes: &mut Vec, - current: Option<&[AclGrant]>, - acl_changes: MaybePatchValue, - ) -> Result<(), SetError> { - match acl_changes { - MaybePatchValue::Value(Value::List(values)) => { - *changes = self.map_acl_set(values).await?; - } - MaybePatchValue::Patch(patch) => { - let (mut patch, is_update) = self.map_acl_patch(patch).await?; - if let Some(changes_) = current { - *changes = changes_.to_vec(); - } - - if let Some(is_set) = is_update { - if !patch.grants.is_empty() { - if let Some(acl_item) = changes - .iter_mut() - .find(|item| item.account_id == patch.account_id) - { - let item = patch.grants.pop().unwrap(); - if is_set { - acl_item.grants.insert(item); - } else { - acl_item.grants.remove(item); - if acl_item.grants.is_empty() { - changes.retain(|item| item.account_id != patch.account_id); - } - } - } else if is_set { - changes.push(patch); - } - } - } else if !patch.grants.is_empty() { - if let Some(acl_item) = changes - .iter_mut() - .find(|item| item.account_id == patch.account_id) - { - acl_item.grants = patch.grants; - } else { - changes.push(patch); - } - } else { - changes.retain(|item| item.account_id != patch.account_id); - } - } - _ => { - return Err(SetError::invalid_properties() - .with_property(Property::Acl) - .with_description("Invalid ACL property.")); - } - } - Ok(()) - } - - pub async fn acl_get( - &self, - value: &[AclGrant], - access_token: &AccessToken, - account_id: u32, - ) -> Value { - if access_token.is_member(account_id) - || value.iter().any(|item| { - access_token.is_member(item.account_id) && item.grants.contains(Acl::Administer) - }) - { - let mut acl_obj = jmap_proto::types::value::Object::with_capacity(value.len() / 2); - for item in value { - if let Some(name) = self - .store() - .get_principal_name(item.account_id) - .await - .unwrap_or_default() - { - acl_obj.append( - Property::_T(name), - item.grants - .map(|acl_item| Value::Text(acl_item.to_string())) - .collect::>(), - ); - } - } - - Value::Object(acl_obj) - } else { - Value::Null - } - } - pub async fn refresh_acls(&self, acl_changes: &[AclGrant], current: Option<&[AclGrant]>) { let mut changed_principals = ChangedPrincipals::new(); if let Some(acl_current) = current { @@ -205,77 +103,4 @@ impl Server { self.invalidate_principal_caches(changed_principals).await; } - - pub async fn map_acl_set(&self, acl_set: Vec) -> Result, SetError> { - let mut acls = Vec::with_capacity(acl_set.len() / 2); - for item in acl_set.chunks_exact(2) { - if let (Value::Text(account_name), Value::UnsignedInt(grants)) = (&item[0], &item[1]) { - match self - .core - .storage - .directory - .query(QueryParams::name(account_name).with_return_member_of(false)) - .await - { - Ok(Some(principal)) => { - acls.push(AclGrant { - account_id: principal.id(), - grants: Bitmap::from(*grants), - }); - } - Ok(None) => { - return Err(SetError::invalid_properties() - .with_property(Property::Acl) - .with_description(format!("Account {account_name} does not exist."))); - } - _ => { - return Err(SetError::forbidden() - .with_property(Property::Acl) - .with_description("Temporary server failure during lookup")); - } - } - } else { - return Err(SetError::invalid_properties() - .with_property(Property::Acl) - .with_description("Invalid ACL value found.")); - } - } - - Ok(acls) - } - - pub async fn map_acl_patch( - &self, - acl_patch: Vec, - ) -> Result<(AclGrant, Option), SetError> { - if let (Value::Text(account_name), Value::UnsignedInt(grants)) = - (&acl_patch[0], &acl_patch[1]) - { - match self - .core - .storage - .directory - .query(QueryParams::name(account_name).with_return_member_of(false)) - .await - { - Ok(Some(principal)) => Ok(( - AclGrant { - account_id: principal.id(), - grants: Bitmap::from(*grants), - }, - acl_patch.get(2).map(|v| v.as_bool().unwrap_or(false)), - )), - Ok(None) => Err(SetError::invalid_properties() - .with_property(Property::Acl) - .with_description(format!("Account {account_name} does not exist."))), - _ => Err(SetError::forbidden() - .with_property(Property::Acl) - .with_description("Temporary server failure during lookup")), - } - } else { - Err(SetError::invalid_properties() - .with_property(Property::Acl) - .with_description("Invalid ACL value found.")) - } - } } diff --git a/crates/common/src/sharing/document.rs b/crates/common/src/sharing/document.rs index 60766b23..68e92b28 100644 --- a/crates/common/src/sharing/document.rs +++ b/crates/common/src/sharing/document.rs @@ -4,13 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_proto::types::{acl::Acl, collection::Collection}; +use crate::{Server, auth::AccessToken}; use store::{ValueKey, query::acl::AclQuery, roaring::RoaringBitmap, write::ValueClass}; use trc::AddContext; +use types::{acl::Acl, collection::Collection}; use utils::map::bitmap::Bitmap; -use crate::{Server, auth::AccessToken}; - impl Server { pub async fn shared_containers( &self, diff --git a/crates/common/src/sharing/mod.rs b/crates/common/src/sharing/mod.rs index 6a4c57d9..3a30e69b 100644 --- a/crates/common/src/sharing/mod.rs +++ b/crates/common/src/sharing/mod.rs @@ -5,11 +5,8 @@ */ use crate::auth::AccessToken; -use jmap_proto::types::{ - acl::Acl, - value::{AclGrant, ArchivedAclGrant}, -}; use rkyv::vec::ArchivedVec; +use types::acl::{Acl, AclGrant, ArchivedAclGrant}; use utils::map::bitmap::Bitmap; pub mod acl; diff --git a/crates/common/src/sharing/resources.rs b/crates/common/src/sharing/resources.rs index 14bbf9e7..14802173 100644 --- a/crates/common/src/sharing/resources.rs +++ b/crates/common/src/sharing/resources.rs @@ -5,8 +5,8 @@ */ use crate::{DavResources, auth::AccessToken}; -use jmap_proto::types::acl::Acl; use store::roaring::RoaringBitmap; +use types::acl::Acl; use utils::map::bitmap::Bitmap; impl DavResources { diff --git a/crates/common/src/storage/blob.rs b/crates/common/src/storage/blob.rs index 293d043c..11ac6770 100644 --- a/crates/common/src/storage/blob.rs +++ b/crates/common/src/storage/blob.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_proto::types::blob::BlobSection; +use crate::Server; use mail_parser::{ Encoding, decoders::{base64::base64_decode, quoted_printable::quoted_printable_decode}, }; -use utils::BlobHash; - -use crate::Server; +use types::{blob::BlobSection, blob_hash::BlobHash}; impl Server { pub async fn get_blob_section( diff --git a/crates/common/src/storage/index.rs b/crates/common/src/storage/index.rs index cec0f30f..6d2d2ad3 100644 --- a/crates/common/src/storage/index.rs +++ b/crates/common/src/storage/index.rs @@ -4,8 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::auth::AsTenantId; use ahash::AHashSet; -use jmap_proto::types::{property::Property, value::AclGrant}; use rkyv::{ option::ArchivedOption, primitive::{ArchivedU32, ArchivedU64}, @@ -16,22 +16,20 @@ use store::{ Serialize, SerializeInfallible, write::{Archive, Archiver, BatchBuilder, BlobOp, DirectoryClass, IntoOperations, TagValue}, }; -use utils::BlobHash; - -use crate::auth::AsTenantId; +use types::{acl::AclGrant, blob_hash::BlobHash, collection::SyncCollection, field::Field}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum IndexValue<'x> { Index { - field: u8, + field: Field, value: IndexItem<'x>, }, IndexList { - field: u8, + field: Field, value: Vec>, }, Tag { - field: u8, + field: Field, value: Vec, }, Blob { @@ -41,14 +39,14 @@ pub enum IndexValue<'x> { used: u32, }, LogContainer { - sync_collection: u8, + sync_collection: SyncCollection, }, LogContainerProperty { - sync_collection: u8, + sync_collection: SyncCollection, ids: Vec, }, LogItem { - sync_collection: u8, + sync_collection: SyncCollection, prefix: Option, }, Acl { @@ -297,14 +295,14 @@ impl IntoOperations } if N::is_versioned() { let (offset, bytes) = Archiver::new(changes).serialize_versioned()?; - batch.set_versioned(Property::Value, bytes, offset); + batch.set_versioned(Field::ARCHIVE, bytes, offset); } else { - batch.set(Property::Value, Archiver::new(changes).serialize()?); + batch.set(Field::ARCHIVE, Archiver::new(changes).serialize()?); } } (Some(current), Some(changes)) => { // Update - batch.assert_value(Property::Value, ¤t); + batch.assert_value(Field::ARCHIVE, ¤t); for (current, change) in current.inner.index_values().zip(changes.index_values()) { if current != change { merge_index(batch, current, change, self.tenant_id)?; @@ -325,19 +323,19 @@ impl IntoOperations } if N::is_versioned() { let (offset, bytes) = Archiver::new(changes).serialize_versioned()?; - batch.set_versioned(Property::Value, bytes, offset); + batch.set_versioned(Field::ARCHIVE, bytes, offset); } else { - batch.set(Property::Value, Archiver::new(changes).serialize()?); + batch.set(Field::ARCHIVE, Archiver::new(changes).serialize()?); } } (Some(current), None) => { // Deletion - batch.assert_value(Property::Value, ¤t); + batch.assert_value(Field::ARCHIVE, ¤t); for item in current.inner.index_values() { build_index(batch, item, self.tenant_id, false); } - batch.clear(Property::Value); + batch.clear(Field::ARCHIVE); } (None, None) => unreachable!(), } diff --git a/crates/common/src/storage/state.rs b/crates/common/src/storage/state.rs index 940fc06a..39a996f7 100644 --- a/crates/common/src/storage/state.rs +++ b/crates/common/src/storage/state.rs @@ -4,11 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_proto::types::{state::StateChange, type_state::DataType}; -use tokio::sync::mpsc; -use utils::map::bitmap::Bitmap; - use crate::{IPC_CHANNEL_BUFFER, Server, ipc::StateEvent}; +use tokio::sync::mpsc; +use types::type_state::{DataType, StateChange}; +use utils::map::bitmap::Bitmap; impl Server { pub async fn subscribe_state_manager( diff --git a/crates/dav/Cargo.toml b/crates/dav/Cargo.toml index f2fc6de5..1395f997 100644 --- a/crates/dav/Cargo.toml +++ b/crates/dav/Cargo.toml @@ -12,7 +12,7 @@ utils = { path = "../utils" } groupware = { path = "../groupware" } directory = { path = "../directory" } http_proto = { path = "../http-proto" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } trc = { path = "../trc" } calcard = { version = "0.1.3", features = ["rkyv"] } hashify = { version = "0.2" } diff --git a/crates/dav/src/calendar/copy_move.rs b/crates/dav/src/calendar/copy_move.rs index 12818fa0..32a46aed 100644 --- a/crates/dav/src/calendar/copy_move.rs +++ b/crates/dav/src/calendar/copy_move.rs @@ -4,6 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::assert_is_unique_uid; +use crate::{ + DavError, DavMethod, + common::{ + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, + file::DavFileResource, +}; use calcard::common::timezone::Tz; use common::{DavName, Server, auth::AccessToken}; use dav_proto::{Depth, RequestHeaders}; @@ -14,23 +23,12 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ +use store::write::{BatchBuilder, now}; +use trc::AddContext; +use types::{ acl::Acl, collection::{Collection, SyncCollection, VanishedCollection}, }; -use store::write::{BatchBuilder, now}; -use trc::AddContext; - -use crate::{ - DavError, DavMethod, - common::{ - lock::{LockRequestHandler, ResourceState}, - uri::DavUriResource, - }, - file::DavFileResource, -}; - -use super::assert_is_unique_uid; pub(crate) trait CalendarCopyMoveRequestHandler: Sync + Send { fn handle_calendar_copy_move_request( diff --git a/crates/dav/src/calendar/delete.rs b/crates/dav/src/calendar/delete.rs index 88fe546e..4b1b541a 100644 --- a/crates/dav/src/calendar/delete.rs +++ b/crates/dav/src/calendar/delete.rs @@ -22,12 +22,12 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ +use store::write::BatchBuilder; +use trc::AddContext; +use types::{ acl::Acl, collection::{Collection, SyncCollection}, }; -use store::write::BatchBuilder; -use trc::AddContext; pub(crate) trait CalendarDeleteRequestHandler: Sync + Send { fn handle_calendar_delete_request( diff --git a/crates/dav/src/calendar/freebusy.rs b/crates/dav/src/calendar/freebusy.rs index 595dff4a..92dd3197 100644 --- a/crates/dav/src/calendar/freebusy.rs +++ b/crates/dav/src/calendar/freebusy.rs @@ -24,16 +24,16 @@ use dav_proto::{ use groupware::{cache::GroupwareCache, calendar::CalendarEvent}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use std::str::FromStr; use store::{ ahash::AHashMap, write::{now, serialize::rkyv_deserialize}, }; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait CalendarFreebusyRequestHandler: Sync + Send { fn handle_calendar_freebusy_request( diff --git a/crates/dav/src/calendar/get.rs b/crates/dav/src/calendar/get.rs index 2dd1bac6..428f671f 100644 --- a/crates/dav/src/calendar/get.rs +++ b/crates/dav/src/calendar/get.rs @@ -4,17 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::{Server, auth::AccessToken}; -use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime}; -use groupware::{cache::GroupwareCache, calendar::CalendarEvent}; -use http_proto::HttpResponse; -use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; -use trc::AddContext; - use crate::{ DavError, DavMethod, common::{ @@ -23,6 +12,16 @@ use crate::{ uri::DavUriResource, }, }; +use common::{Server, auth::AccessToken}; +use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime}; +use groupware::{cache::GroupwareCache, calendar::CalendarEvent}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait CalendarGetRequestHandler: Sync + Send { fn handle_calendar_get_request( diff --git a/crates/dav/src/calendar/mkcol.rs b/crates/dav/src/calendar/mkcol.rs index dc0f0517..0d938e60 100644 --- a/crates/dav/src/calendar/mkcol.rs +++ b/crates/dav/src/calendar/mkcol.rs @@ -4,6 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::proppatch::CalendarPropPatchRequestHandler; +use crate::{ + DavError, DavMethod, PropStatBuilder, + common::{ + ExtractETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; use common::{Server, auth::AccessToken}; use dav_proto::{ RequestHeaders, Return, @@ -15,20 +24,9 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::collection::{Collection, SyncCollection}; use store::write::BatchBuilder; use trc::AddContext; - -use crate::{ - DavError, DavMethod, PropStatBuilder, - common::{ - ExtractETag, - lock::{LockRequestHandler, ResourceState}, - uri::DavUriResource, - }, -}; - -use super::proppatch::CalendarPropPatchRequestHandler; +use types::collection::{Collection, SyncCollection}; pub(crate) trait CalendarMkColRequestHandler: Sync + Send { fn handle_calendar_mkcol_request( diff --git a/crates/dav/src/calendar/mod.rs b/crates/dav/src/calendar/mod.rs index 1af50c97..fabea3a7 100644 --- a/crates/dav/src/calendar/mod.rs +++ b/crates/dav/src/calendar/mod.rs @@ -15,16 +15,15 @@ pub mod scheduling; pub mod update; use crate::{DavError, DavErrorCondition}; -use common::IDX_UID; use common::{DavResources, Server}; use dav_proto::schema::{ property::{CalDavProperty, CalendarData, DavProperty, WebDavProperty}, response::CalCondition, }; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; use store::query::Filter; use trc::AddContext; +use types::{collection::Collection, field::CalendarField}; pub(crate) static CALENDAR_CONTAINER_PROPS: [DavProperty; 31] = [ DavProperty::WebDav(WebDavProperty::CreationDate), @@ -101,7 +100,7 @@ pub(crate) async fn assert_is_unique_uid( .filter( account_id, Collection::CalendarEvent, - vec![Filter::eq(IDX_UID, uid.as_bytes().to_vec())], + vec![Filter::eq(CalendarField::Uid, uid.as_bytes().to_vec())], ) .await .caused_by(trc::location!())?; diff --git a/crates/dav/src/calendar/proppatch.rs b/crates/dav/src/calendar/proppatch.rs index 28dca29e..f302676b 100644 --- a/crates/dav/src/calendar/proppatch.rs +++ b/crates/dav/src/calendar/proppatch.rs @@ -29,13 +29,13 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use std::str::FromStr; use store::write::BatchBuilder; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait CalendarPropPatchRequestHandler: Sync + Send { fn handle_calendar_proppatch_request( diff --git a/crates/dav/src/calendar/query.rs b/crates/dav/src/calendar/query.rs index ffcfdd92..1ae28a50 100644 --- a/crates/dav/src/calendar/query.rs +++ b/crates/dav/src/calendar/query.rs @@ -34,13 +34,13 @@ use dav_proto::{ use groupware::{cache::GroupwareCache, calendar::ArchivedCalendarEvent}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{acl::Acl, collection::SyncCollection}; use std::{fmt::Write, slice::Iter, str::FromStr}; use store::{ ahash::{AHashMap, AHashSet}, write::serialize::rkyv_deserialize, }; use trc::AddContext; +use types::{acl::Acl, collection::SyncCollection}; pub(crate) trait CalendarQueryRequestHandler: Sync + Send { fn handle_calendar_query_request( diff --git a/crates/dav/src/calendar/scheduling.rs b/crates/dav/src/calendar/scheduling.rs index e41839e4..d2df9609 100644 --- a/crates/dav/src/calendar/scheduling.rs +++ b/crates/dav/src/calendar/scheduling.rs @@ -32,9 +32,9 @@ use dav_proto::{ use groupware::{DestroyArchive, cache::GroupwareCache, calendar::CalendarScheduling}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::collection::{Collection, SyncCollection}; use store::{ahash::AHashMap, write::BatchBuilder}; use trc::AddContext; +use types::collection::{Collection, SyncCollection}; use utils::sanitize_email; pub(crate) trait CalendarSchedulingHandler: Sync + Send { diff --git a/crates/dav/src/calendar/update.rs b/crates/dav/src/calendar/update.rs index aab6ce5a..a9c825ae 100644 --- a/crates/dav/src/calendar/update.rs +++ b/crates/dav/src/calendar/update.rs @@ -33,13 +33,13 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use std::collections::HashSet; use store::write::{BatchBuilder, now}; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait CalendarUpdateRequestHandler: Sync + Send { fn handle_calendar_update_request( diff --git a/crates/dav/src/card/copy_move.rs b/crates/dav/src/card/copy_move.rs index 146b1f54..2af11f04 100644 --- a/crates/dav/src/card/copy_move.rs +++ b/crates/dav/src/card/copy_move.rs @@ -4,6 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::assert_is_unique_uid; +use crate::{ + DavError, DavMethod, + common::{ + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, + file::DavFileResource, +}; use common::{DavName, Server, auth::AccessToken}; use dav_proto::{Depth, RequestHeaders}; use groupware::{ @@ -13,23 +22,12 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ +use store::write::BatchBuilder; +use trc::AddContext; +use types::{ acl::Acl, collection::{Collection, SyncCollection, VanishedCollection}, }; -use store::write::BatchBuilder; -use trc::AddContext; - -use crate::{ - DavError, DavMethod, - common::{ - lock::{LockRequestHandler, ResourceState}, - uri::DavUriResource, - }, - file::DavFileResource, -}; - -use super::assert_is_unique_uid; pub(crate) trait CardCopyMoveRequestHandler: Sync + Send { fn handle_card_copy_move_request( diff --git a/crates/dav/src/card/delete.rs b/crates/dav/src/card/delete.rs index 9d2d85fa..39782124 100644 --- a/crates/dav/src/card/delete.rs +++ b/crates/dav/src/card/delete.rs @@ -4,6 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{ + DavError, DavMethod, + common::{ + ETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; use common::{Server, auth::AccessToken, sharing::EffectiveAcl}; use dav_proto::RequestHeaders; use groupware::{ @@ -13,20 +21,11 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use store::write::BatchBuilder; use trc::AddContext; - -use crate::{ - DavError, DavMethod, - common::{ - ETag, - lock::{LockRequestHandler, ResourceState}, - uri::DavUriResource, - }, +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, }; pub(crate) trait CardDeleteRequestHandler: Sync + Send { diff --git a/crates/dav/src/card/get.rs b/crates/dav/src/card/get.rs index 46579877..816f69d6 100644 --- a/crates/dav/src/card/get.rs +++ b/crates/dav/src/card/get.rs @@ -4,17 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::{Server, auth::AccessToken}; -use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime}; -use groupware::{cache::GroupwareCache, contact::ContactCard}; -use http_proto::HttpResponse; -use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; -use trc::AddContext; - use crate::{ DavError, DavMethod, common::{ @@ -23,6 +12,16 @@ use crate::{ uri::DavUriResource, }, }; +use common::{Server, auth::AccessToken}; +use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime}; +use groupware::{cache::GroupwareCache, contact::ContactCard}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait CardGetRequestHandler: Sync + Send { fn handle_card_get_request( diff --git a/crates/dav/src/card/mkcol.rs b/crates/dav/src/card/mkcol.rs index b38c03e9..527101e4 100644 --- a/crates/dav/src/card/mkcol.rs +++ b/crates/dav/src/card/mkcol.rs @@ -21,9 +21,9 @@ use dav_proto::{ use groupware::{cache::GroupwareCache, contact::AddressBook}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::collection::{Collection, SyncCollection}; use store::write::BatchBuilder; use trc::AddContext; +use types::collection::{Collection, SyncCollection}; pub(crate) trait CardMkColRequestHandler: Sync + Send { fn handle_card_mkcol_request( diff --git a/crates/dav/src/card/mod.rs b/crates/dav/src/card/mod.rs index cdf55f67..b966d2c3 100644 --- a/crates/dav/src/card/mod.rs +++ b/crates/dav/src/card/mod.rs @@ -4,18 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::IDX_UID; +use crate::{DavError, DavErrorCondition}; use common::{DavResources, Server}; use dav_proto::schema::{ property::{CardDavProperty, DavProperty, WebDavProperty}, response::CardCondition, }; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; use store::query::Filter; use trc::AddContext; - -use crate::{DavError, DavErrorCondition}; +use types::{collection::Collection, field::ContactField}; pub mod copy_move; pub mod delete; @@ -87,7 +85,7 @@ pub(crate) async fn assert_is_unique_uid( .filter( account_id, Collection::ContactCard, - vec![Filter::eq(IDX_UID, uid.as_bytes().to_vec())], + vec![Filter::eq(ContactField::Uid, uid.as_bytes().to_vec())], ) .await .caused_by(trc::location!())?; diff --git a/crates/dav/src/card/proppatch.rs b/crates/dav/src/card/proppatch.rs index 903b80c8..630d15f8 100644 --- a/crates/dav/src/card/proppatch.rs +++ b/crates/dav/src/card/proppatch.rs @@ -4,6 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{ + DavError, DavMethod, PropStatBuilder, + common::{ + ETag, ExtractETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; use common::{Server, auth::AccessToken}; use dav_proto::{ RequestHeaders, Return, @@ -20,20 +28,11 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use store::write::BatchBuilder; use trc::AddContext; - -use crate::{ - DavError, DavMethod, PropStatBuilder, - common::{ - ETag, ExtractETag, - lock::{LockRequestHandler, ResourceState}, - uri::DavUriResource, - }, +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, }; pub(crate) trait CardPropPatchRequestHandler: Sync + Send { diff --git a/crates/dav/src/card/query.rs b/crates/dav/src/card/query.rs index 3d5cf450..06d8db99 100644 --- a/crates/dav/src/card/query.rs +++ b/crates/dav/src/card/query.rs @@ -28,9 +28,9 @@ use dav_proto::{ use groupware::cache::GroupwareCache; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{acl::Acl, collection::SyncCollection}; use std::fmt::Write; use trc::AddContext; +use types::{acl::Acl, collection::SyncCollection}; pub(crate) trait CardQueryRequestHandler: Sync + Send { fn handle_card_query_request( diff --git a/crates/dav/src/card/update.rs b/crates/dav/src/card/update.rs index 457b74ba..bbcb543e 100644 --- a/crates/dav/src/card/update.rs +++ b/crates/dav/src/card/update.rs @@ -4,22 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use calcard::{Entry, Parser}; -use common::{DavName, Server, auth::AccessToken}; -use dav_proto::{ - RequestHeaders, Return, - schema::{property::Rfc1123DateTime, response::CardCondition}, -}; -use groupware::{cache::GroupwareCache, contact::ContactCard}; -use http_proto::HttpResponse; -use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; -use store::write::BatchBuilder; -use trc::AddContext; - +use super::assert_is_unique_uid; use crate::{ DavError, DavErrorCondition, DavMethod, common::{ @@ -30,8 +15,21 @@ use crate::{ file::DavFileResource, fix_percent_encoding, }; - -use super::assert_is_unique_uid; +use calcard::{Entry, Parser}; +use common::{DavName, Server, auth::AccessToken}; +use dav_proto::{ + RequestHeaders, Return, + schema::{property::Rfc1123DateTime, response::CardCondition}, +}; +use groupware::{cache::GroupwareCache, contact::ContactCard}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use store::write::BatchBuilder; +use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait CardUpdateRequestHandler: Sync + Send { fn handle_card_update_request( diff --git a/crates/dav/src/common/acl.rs b/crates/dav/src/common/acl.rs index d7c1ec18..aeb809a8 100644 --- a/crates/dav/src/common/acl.rs +++ b/crates/dav/src/common/acl.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::ArchivedResource; use crate::{ DavError, DavErrorCondition, DavResourceName, common::uri::DavUriResource, principal::propfind::PrincipalPropFind, @@ -22,18 +23,15 @@ use groupware::RFC_3986; use groupware::{cache::GroupwareCache, calendar::Calendar, contact::AddressBook, file::FileNode}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::Collection, - value::{AclGrant, ArchivedAclGrant}, -}; use rkyv::vec::ArchivedVec; use store::{ahash::AHashSet, roaring::RoaringBitmap, write::BatchBuilder}; use trc::AddContext; +use types::{ + acl::{Acl, AclGrant, ArchivedAclGrant}, + collection::Collection, +}; use utils::map::bitmap::Bitmap; -use super::ArchivedResource; - pub(crate) trait DavAclHandler: Sync + Send { fn handle_acl_request( &self, diff --git a/crates/dav/src/common/lock.rs b/crates/dav/src/common/lock.rs index 6931bc5f..036f0ecb 100644 --- a/crates/dav/src/common/lock.rs +++ b/crates/dav/src/common/lock.rs @@ -4,6 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::ETag; +use super::uri::{DavUriResource, OwnedUri, UriResource, Urn}; +use crate::{DavError, DavErrorCondition, DavMethod}; use common::KV_LOCK_DAV; use common::{Server, auth::AccessToken}; use dav_proto::schema::property::{ActiveLock, LockScope, WebDavProperty}; @@ -11,21 +14,16 @@ use dav_proto::schema::request::{DavPropertyValue, DeadProperty}; use dav_proto::schema::response::{BaseCondition, List, PropResponse}; use dav_proto::{Condition, Depth, Timeout}; use dav_proto::{RequestHeaders, schema::request::LockInfo}; - use groupware::cache::GroupwareCache; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; use std::collections::HashMap; use store::dispatch::lookup::KeyValue; use store::write::serialize::rkyv_deserialize; use store::write::{AlignedBytes, Archive, Archiver, now}; use store::{Serialize, U32_LEN}; use trc::AddContext; - -use super::ETag; -use super::uri::{DavUriResource, OwnedUri, UriResource, Urn}; -use crate::{DavError, DavErrorCondition, DavMethod}; +use types::collection::Collection; #[derive(Debug, Default, Clone)] pub struct ResourceState<'x> { diff --git a/crates/dav/src/common/mod.rs b/crates/dav/src/common/mod.rs index 950dbe25..ab494d59 100644 --- a/crates/dav/src/common/mod.rs +++ b/crates/dav/src/common/mod.rs @@ -27,10 +27,10 @@ use groupware::{ contact::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard}, file::{ArchivedFileNode, FileNode}, }; -use jmap_proto::types::{collection::Collection, property::Property, value::ArchivedAclGrant}; use propfind::PropFindItem; use rkyv::vec::ArchivedVec; use store::write::{AlignedBytes, Archive, BatchBuilder, Operation, ValueClass, ValueOp}; +use types::{acl::ArchivedAclGrant, collection::Collection, field::Field}; use uri::{OwnedUri, Urn}; pub mod acl; @@ -109,7 +109,7 @@ impl ETag for Archive { impl ExtractETag for BatchBuilder { fn etag(&self) -> Option { - let p_value = u8::from(Property::Value); + let p_value = u8::from(Field::ARCHIVE); for op in self.ops().iter().rev() { match op { Operation::Value { diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index ba564fa4..42d6e8af 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -56,10 +56,6 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use std::sync::Arc; use store::{ ahash::AHashMap, @@ -68,6 +64,10 @@ use store::{ write::{AlignedBytes, Archive}, }; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, +}; pub(crate) trait PropFindRequestHandler: Sync + Send { fn handle_propfind_request( @@ -1182,7 +1182,7 @@ async fn get( SyncType::From { id, seq } => { let changes = server .store() - .changes(account_id, sync_collection, Query::Since(id)) + .changes(account_id, sync_collection.into(), Query::Since(id)) .await .caused_by(trc::location!())?; let mut vanished: Vec = Vec::new(); @@ -1258,7 +1258,7 @@ async fn get( { vanished = server .store() - .vanished(account_id, vanished_collection, Query::Since(id)) + .vanished(account_id, vanished_collection.into(), Query::Since(id)) .await .caused_by(trc::location!())?; total_changes += vanished.len(); diff --git a/crates/dav/src/common/uri.rs b/crates/dav/src/common/uri.rs index 78bd718f..4ef0eba9 100644 --- a/crates/dav/src/common/uri.rs +++ b/crates/dav/src/common/uri.rs @@ -4,19 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - +use crate::{DavError, DavResourceName}; use common::{Server, auth::AccessToken}; - use directory::backend::internal::manage::ManageDirectory; - use groupware::cache::GroupwareCache; use http_proto::request::decode_path_element; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; +use std::fmt::Display; use trc::AddContext; - -use crate::{DavError, DavResourceName}; +use types::collection::Collection; #[derive(Debug)] pub(crate) struct UriResource { diff --git a/crates/dav/src/file/copy_move.rs b/crates/dav/src/file/copy_move.rs index 0ba6be61..02724080 100644 --- a/crates/dav/src/file/copy_move.rs +++ b/crates/dav/src/file/copy_move.rs @@ -21,16 +21,16 @@ use dav_proto::{Depth, RequestHeaders}; use groupware::{DestroyArchive, cache::GroupwareCache, file::FileNode}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection, VanishedCollection}, -}; use std::sync::Arc; use store::{ ahash::AHashMap, write::{BatchBuilder, now}, }; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection, VanishedCollection}, +}; pub(crate) trait FileCopyMoveRequestHandler: Sync + Send { fn handle_file_copy_move_request( diff --git a/crates/dav/src/file/delete.rs b/crates/dav/src/file/delete.rs index c6bb4aff..d9de9b5e 100644 --- a/crates/dav/src/file/delete.rs +++ b/crates/dav/src/file/delete.rs @@ -16,8 +16,8 @@ use dav_proto::RequestHeaders; use groupware::{DestroyArchive, cache::GroupwareCache}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{acl::Acl, collection::SyncCollection}; use trc::AddContext; +use types::{acl::Acl, collection::SyncCollection}; pub(crate) trait FileDeleteRequestHandler: Sync + Send { fn handle_file_delete_request( diff --git a/crates/dav/src/file/get.rs b/crates/dav/src/file/get.rs index 49cc24b3..7c7aef92 100644 --- a/crates/dav/src/file/get.rs +++ b/crates/dav/src/file/get.rs @@ -9,11 +9,11 @@ use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime}; use groupware::{cache::GroupwareCache, file::FileNode}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ +use trc::AddContext; +use types::{ acl::Acl, collection::{Collection, SyncCollection}, }; -use trc::AddContext; use crate::{ DavError, DavMethod, diff --git a/crates/dav/src/file/mkcol.rs b/crates/dav/src/file/mkcol.rs index eeae86ac..3c4027e8 100644 --- a/crates/dav/src/file/mkcol.rs +++ b/crates/dav/src/file/mkcol.rs @@ -23,12 +23,12 @@ use dav_proto::{ use groupware::{cache::GroupwareCache, file::FileNode}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ +use store::write::{BatchBuilder, now}; +use trc::AddContext; +use types::{ acl::Acl, collection::{Collection, SyncCollection}, }; -use store::write::{BatchBuilder, now}; -use trc::AddContext; pub(crate) trait FileMkColRequestHandler: Sync + Send { fn handle_file_mkcol_request( diff --git a/crates/dav/src/file/proppatch.rs b/crates/dav/src/file/proppatch.rs index 569ebb0f..bcd95bb2 100644 --- a/crates/dav/src/file/proppatch.rs +++ b/crates/dav/src/file/proppatch.rs @@ -4,6 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{ + DavError, DavMethod, PropStatBuilder, + common::{ + ETag, ExtractETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, + file::DavFileResource, +}; use common::{Server, auth::AccessToken, sharing::EffectiveAcl}; use dav_proto::{ RequestHeaders, Return, @@ -16,21 +25,11 @@ use dav_proto::{ use groupware::{cache::GroupwareCache, file::FileNode}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use store::write::BatchBuilder; use trc::AddContext; - -use crate::{ - DavError, DavMethod, PropStatBuilder, - common::{ - ETag, ExtractETag, - lock::{LockRequestHandler, ResourceState}, - uri::DavUriResource, - }, - file::DavFileResource, +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, }; pub(crate) trait FilePropPatchRequestHandler: Sync + Send { diff --git a/crates/dav/src/file/update.rs b/crates/dav/src/file/update.rs index 3f041649..9e0c5e70 100644 --- a/crates/dav/src/file/update.rs +++ b/crates/dav/src/file/update.rs @@ -24,13 +24,13 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, -}; use store::write::{BatchBuilder, now}; use trc::AddContext; -use utils::BlobHash; +use types::{ + acl::Acl, + blob_hash::BlobHash, + collection::{Collection, SyncCollection}, +}; pub(crate) trait FileUpdateRequestHandler: Sync + Send { fn handle_file_update_request( diff --git a/crates/dav/src/principal/matching.rs b/crates/dav/src/principal/matching.rs index d46a6fae..f86e3abf 100644 --- a/crates/dav/src/principal/matching.rs +++ b/crates/dav/src/principal/matching.rs @@ -4,6 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::propfind::PrincipalPropFind; +use crate::{ + DavError, + common::{ + DavQuery, DavQueryResource, + propfind::PropFindRequestHandler, + uri::{DavUriResource, UriResource}, + }, +}; use common::{Server, auth::AccessToken}; use dav_proto::{ RequestHeaders, @@ -15,19 +24,8 @@ use dav_proto::{ }; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; use store::roaring::RoaringBitmap; - -use crate::{ - DavError, - common::{ - DavQuery, DavQueryResource, - propfind::PropFindRequestHandler, - uri::{DavUriResource, UriResource}, - }, -}; - -use super::propfind::PrincipalPropFind; +use types::collection::Collection; pub(crate) trait PrincipalMatching: Sync + Send { fn handle_principal_match( diff --git a/crates/dav/src/principal/propfind.rs b/crates/dav/src/principal/propfind.rs index 35e729c1..9bd1f45f 100644 --- a/crates/dav/src/principal/propfind.rs +++ b/crates/dav/src/principal/propfind.rs @@ -23,9 +23,9 @@ use directory::{QueryParams, Type, backend::internal::manage::ManageDirectory}; use groupware::RFC_3986; use groupware::cache::GroupwareCache; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; use std::borrow::Cow; use trc::AddContext; +use types::collection::Collection; pub(crate) trait PrincipalPropFind: Sync + Send { fn prepare_principal_propfind_response( diff --git a/crates/dav/src/principal/propsearch.rs b/crates/dav/src/principal/propsearch.rs index 1a274d04..fbd6d9e8 100644 --- a/crates/dav/src/principal/propsearch.rs +++ b/crates/dav/src/principal/propsearch.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::propfind::PrincipalPropFind; use common::{ Server, auth::{AccessToken, AsTenantId}, @@ -16,11 +17,9 @@ use dav_proto::schema::{ use directory::{Type, backend::internal::manage::ManageDirectory}; use http_proto::HttpResponse; use hyper::StatusCode; -use jmap_proto::types::collection::Collection; use store::roaring::RoaringBitmap; use trc::AddContext; - -use super::propfind::PrincipalPropFind; +use types::collection::Collection; pub(crate) trait PrincipalPropSearch: Sync + Send { fn handle_principal_property_search( diff --git a/crates/dav/src/request.rs b/crates/dav/src/request.rs index 8b9e90ee..91fb1777 100644 --- a/crates/dav/src/request.rs +++ b/crates/dav/src/request.rs @@ -50,9 +50,9 @@ use dav_proto::{ use directory::Permission; use http_proto::{HttpRequest, HttpResponse, HttpSessionData, request::fetch_body}; use hyper::{StatusCode, header}; -use jmap_proto::types::collection::Collection; use std::{sync::Arc, time::Instant}; use trc::{EventType, LimitEvent, StoreEvent, WebDavEvent}; +use types::collection::Collection; pub trait DavRequestHandler: Sync + Send { fn handle_dav_request( diff --git a/crates/directory/Cargo.toml b/crates/directory/Cargo.toml index 591473a9..2a756499 100644 --- a/crates/directory/Cargo.toml +++ b/crates/directory/Cargo.toml @@ -10,7 +10,7 @@ proc_macros = { path = "../utils/proc-macros" } store = { path = "../store" } trc = { path = "../trc" } nlp = { path = "../nlp" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } smtp-proto = { version = "0.2" } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs index 7794e123..77e1a37c 100644 --- a/crates/directory/src/backend/internal/manage.rs +++ b/crates/directory/src/backend/internal/manage.rs @@ -15,7 +15,6 @@ use crate::{ }; use ahash::{AHashMap, AHashSet}; use compact_str::CompactString; -use jmap_proto::types::collection::Collection; use nlp::tokenizers::word::WordTokenizer; use store::{ Deserialize, IterateParams, Serialize, SerializeInfallible, Store, U32_LEN, ValueKey, @@ -27,6 +26,7 @@ use store::{ }, }; use trc::AddContext; +use types::collection::Collection; use utils::sanitize_email; #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] diff --git a/crates/email/Cargo.toml b/crates/email/Cargo.toml index 0db741ce..b6d1c079 100644 --- a/crates/email/Cargo.toml +++ b/crates/email/Cargo.toml @@ -9,7 +9,7 @@ utils = { path = "../utils" } nlp = { path = "../nlp" } store = { path = "../store" } trc = { path = "../trc" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } common = { path = "../common" } directory = { path = "../directory" } groupware = { path = "../groupware" } diff --git a/crates/email/src/cache/email.rs b/crates/email/src/cache/email.rs index 23f95cfa..506e8ef4 100644 --- a/crates/email/src/cache/email.rs +++ b/crates/email/src/cache/email.rs @@ -9,13 +9,13 @@ use common::{ MessageCache, MessageStoreCache, MessageUidCache, MessagesCache, Server, auth::AccessToken, sharing::EffectiveAcl, }; -use jmap_proto::types::{ +use store::{ahash::AHashMap, roaring::RoaringBitmap, write::Archive}; +use trc::AddContext; +use types::{ acl::Acl, collection::Collection, keyword::{Keyword, OTHER}, }; -use store::{ahash::AHashMap, roaring::RoaringBitmap, write::Archive}; -use trc::AddContext; use utils::map::bitmap::Bitmap; pub(crate) async fn update_email_cache( diff --git a/crates/email/src/cache/mailbox.rs b/crates/email/src/cache/mailbox.rs index 8d25824f..d7577d3c 100644 --- a/crates/email/src/cache/mailbox.rs +++ b/crates/email/src/cache/mailbox.rs @@ -9,9 +9,12 @@ use common::{ MailboxCache, MailboxesCache, MessageStoreCache, Server, auth::AccessToken, config::jmap::settings::SpecialUse, sharing::EffectiveAcl, }; -use jmap_proto::types::{acl::Acl, collection::Collection, value::AclGrant}; use store::{ahash::AHashMap, roaring::RoaringBitmap}; use trc::AddContext; +use types::{ + acl::{Acl, AclGrant}, + collection::Collection, +}; use utils::{map::bitmap::Bitmap, topological::TopologicalSort}; pub(crate) async fn update_mailbox_cache( diff --git a/crates/email/src/cache/mod.rs b/crates/email/src/cache/mod.rs index 40650472..771f35eb 100644 --- a/crates/email/src/cache/mod.rs +++ b/crates/email/src/cache/mod.rs @@ -4,18 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{collections::hash_map::Entry, sync::Arc, time::Instant}; - use common::{CacheSwap, MessageStoreCache, Server}; use email::{full_email_cache_build, update_email_cache}; -use jmap_proto::types::collection::SyncCollection; use mailbox::{full_mailbox_cache_build, update_mailbox_cache}; +use std::{collections::hash_map::Entry, sync::Arc, time::Instant}; use store::{ ahash::AHashMap, query::log::{Change, Query}, }; use tokio::sync::Semaphore; use trc::{AddContext, StoreEvent}; +use types::collection::SyncCollection; pub mod email; pub mod mailbox; @@ -70,7 +69,7 @@ impl MessageCacheFetch for Server { .data .changes( account_id, - SyncCollection::Email, + SyncCollection::Email.into(), Query::Since(cache.last_change_id), ) .await @@ -203,7 +202,7 @@ async fn full_cache_build( .core .storage .data - .get_last_change_id(account_id, SyncCollection::Email) + .get_last_change_id(account_id, SyncCollection::Email.into()) .await .caused_by(trc::location!())? .unwrap_or_default(); diff --git a/crates/email/src/identity/index.rs b/crates/email/src/identity/index.rs index 4ca3a8a7..c42d1fcb 100644 --- a/crates/email/src/identity/index.rs +++ b/crates/email/src/identity/index.rs @@ -4,15 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; -use jmap_proto::types::collection::SyncCollection; - use super::{ArchivedIdentity, Identity}; +use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; +use types::collection::SyncCollection; impl IndexableObject for Identity { fn index_values(&self) -> impl Iterator> { [IndexValue::LogItem { - sync_collection: SyncCollection::Identity.into(), + sync_collection: SyncCollection::Identity, prefix: None, }] .into_iter() @@ -22,7 +21,7 @@ impl IndexableObject for Identity { impl IndexableObject for &ArchivedIdentity { fn index_values(&self) -> impl Iterator> { [IndexValue::LogItem { - sync_collection: SyncCollection::Identity.into(), + sync_collection: SyncCollection::Identity, prefix: None, }] .into_iter() diff --git a/crates/email/src/mailbox/destroy.rs b/crates/email/src/mailbox/destroy.rs index 3b7a0a7e..815f3911 100644 --- a/crates/email/src/mailbox/destroy.rs +++ b/crates/email/src/mailbox/destroy.rs @@ -13,12 +13,9 @@ use common::{ Server, auth::AccessToken, sharing::EffectiveAcl, storage::index::ObjectIndexBuilder, }; use directory::Permission; -use jmap_proto::{ - error::set::{SetError, SetErrorType}, - types::{acl::Acl, collection::Collection, property::Property}, -}; use store::{roaring::RoaringBitmap, write::BatchBuilder}; use trc::AddContext; +use types::{acl::Acl, collection::Collection, field::MailboxField}; pub trait MailboxDestroy: Sync + Send { fn mailbox_destroy( @@ -27,7 +24,16 @@ pub trait MailboxDestroy: Sync + Send { document_id: u32, access_token: &AccessToken, remove_emails: bool, - ) -> impl Future, SetError>>> + Send; + ) -> impl Future, MailboxDestroyError>>> + Send; +} + +pub enum MailboxDestroyError { + CannotDestroy, + Forbidden, + HasChildren, + HasEmails, + NotFound, + AssertionFailed, } impl MailboxDestroy for Server { @@ -37,24 +43,20 @@ impl MailboxDestroy for Server { document_id: u32, access_token: &AccessToken, remove_emails: bool, - ) -> trc::Result, SetError>> { + ) -> trc::Result, MailboxDestroyError>> { // Internal folders cannot be deleted #[cfg(feature = "test_mode")] if [INBOX_ID, TRASH_ID].contains(&document_id) && !access_token.has_permission(Permission::DeleteSystemFolders) { - return Ok(Err(SetError::forbidden().with_description( - "You are not allowed to delete Inbox, Junk or Trash folders.", - ))); + return Ok(Err(MailboxDestroyError::CannotDestroy)); } #[cfg(not(feature = "test_mode"))] if [INBOX_ID, TRASH_ID, JUNK_ID].contains(&document_id) && !access_token.has_permission(Permission::DeleteSystemFolders) { - return Ok(Err(SetError::forbidden().with_description( - "You are not allowed to delete Inbox, Junk or Trash folders.", - ))); + return Ok(Err(MailboxDestroyError::CannotDestroy)); } // Verify that this mailbox does not have sub-mailboxes @@ -68,8 +70,7 @@ impl MailboxDestroy for Server { .iter() .any(|item| item.parent_id == document_id) { - return Ok(Err(SetError::new(SetErrorType::MailboxHasChild) - .with_description("Mailbox has at least one children."))); + return Ok(Err(MailboxDestroyError::HasChildren)); } // Verify that the mailbox is empty @@ -142,8 +143,7 @@ impl MailboxDestroy for Server { .await?; } } else { - return Ok(Err(SetError::new(SetErrorType::MailboxHasEmail) - .with_description("Mailbox is not empty."))); + return Ok(Err(MailboxDestroyError::HasEmails)); } } @@ -159,26 +159,22 @@ impl MailboxDestroy for Server { // Validate ACLs if access_token.is_shared(account_id) { let acl = mailbox.inner.acls.effective_acl(access_token); - if !acl.contains(Acl::Administer) { - if !acl.contains(Acl::Delete) { - return Ok(Err(SetError::forbidden() - .with_description("You are not allowed to delete this mailbox."))); - } else if remove_emails && !acl.contains(Acl::RemoveItems) { - return Ok(Err(SetError::forbidden().with_description( - "You are not allowed to delete emails from this mailbox.", - ))); - } + if !acl.contains(Acl::Administer) + && (!acl.contains(Acl::Delete) + || (remove_emails && !acl.contains(Acl::RemoveItems))) + { + return Ok(Err(MailboxDestroyError::Forbidden)); } } batch .with_account_id(account_id) .with_collection(Collection::Mailbox) .delete_document(document_id) - .clear(Property::EmailIds) + .clear(MailboxField::UidCounter) .custom(ObjectIndexBuilder::<_, ()>::new().with_current(mailbox)) .caused_by(trc::location!())?; } else { - return Ok(Err(SetError::not_found())); + return Ok(Err(MailboxDestroyError::NotFound)); }; if !batch.is_empty() { @@ -188,11 +184,9 @@ impl MailboxDestroy for Server { .and_then(|ids| ids.last_change_id(account_id)) { Ok(change_id) => Ok(Ok(Some(change_id))), - Err(err) if err.is_assertion_failure() => Ok(Err(SetError::forbidden() - .with_description(concat!( - "Another process modified a message in this mailbox ", - "while deleting it, please try again." - )))), + Err(err) if err.is_assertion_failure() => { + Ok(Err(MailboxDestroyError::AssertionFailed)) + } Err(err) => Err(err.caused_by(trc::location!())), } } else { diff --git a/crates/email/src/mailbox/index.rs b/crates/email/src/mailbox/index.rs index 991fbd57..0bf10a13 100644 --- a/crates/email/src/mailbox/index.rs +++ b/crates/email/src/mailbox/index.rs @@ -4,16 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; -use jmap_proto::types::{collection::SyncCollection, value::AclGrant}; - use super::{ArchivedMailbox, Mailbox}; +use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; +use types::{acl::AclGrant, collection::SyncCollection}; impl IndexableObject for Mailbox { fn index_values(&self) -> impl Iterator> { [ IndexValue::LogContainer { - sync_collection: SyncCollection::Email.into(), + sync_collection: SyncCollection::Email, }, IndexValue::Acl { value: (&self.acls).into(), @@ -27,7 +26,7 @@ impl IndexableObject for &ArchivedMailbox { fn index_values(&self) -> impl Iterator> { [ IndexValue::LogContainer { - sync_collection: SyncCollection::Email.into(), + sync_collection: SyncCollection::Email, }, IndexValue::Acl { value: self diff --git a/crates/email/src/mailbox/manage.rs b/crates/email/src/mailbox/manage.rs index df06ac50..e57d3ab4 100644 --- a/crates/email/src/mailbox/manage.rs +++ b/crates/email/src/mailbox/manage.rs @@ -7,10 +7,10 @@ use super::*; use crate::cache::MessageCacheFetch; use common::{Server, config::jmap::settings::SpecialUse, storage::index::ObjectIndexBuilder}; -use jmap_proto::types::collection::Collection; use std::future::Future; use store::write::BatchBuilder; use trc::AddContext; +use types::collection::Collection; pub trait MailboxFnc: Sync + Send { fn create_system_folders( diff --git a/crates/email/src/mailbox/mod.rs b/crates/email/src/mailbox/mod.rs index 3617f839..4510da3c 100644 --- a/crates/email/src/mailbox/mod.rs +++ b/crates/email/src/mailbox/mod.rs @@ -5,7 +5,7 @@ */ use common::config::jmap::settings::SpecialUse; -use jmap_proto::types::value::AclGrant; +use types::acl::AclGrant; pub mod destroy; pub mod index; diff --git a/crates/email/src/message/bayes.rs b/crates/email/src/message/bayes.rs index 4763a5af..722f9cf6 100644 --- a/crates/email/src/message/bayes.rs +++ b/crates/email/src/message/bayes.rs @@ -4,19 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::future::Future; - +use super::metadata::MessageMetadata; use common::Server; -use jmap_proto::types::{collection::Collection, property::Property}; use mail_parser::Message; use spam_filter::{ SpamFilterInput, analysis::init::SpamFilterInit, modules::bayes::BayesClassifier, }; +use std::future::Future; use store::write::{TaskQueueClass, now}; use trc::StoreEvent; -use utils::BlobHash; - -use super::metadata::MessageMetadata; +use types::{blob_hash::BlobHash, collection::Collection, field::EmailField}; pub trait EmailBayesTrain: Sync + Send { fn email_bayes_train( @@ -63,7 +60,7 @@ impl EmailBayesTrain for Server { account_id, Collection::Email, document_id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await? .ok_or_else(|| { diff --git a/crates/email/src/message/copy.rs b/crates/email/src/message/copy.rs index 8c27ff91..0c15d6b6 100644 --- a/crates/email/src/message/copy.rs +++ b/crates/email/src/message/copy.rs @@ -11,23 +11,20 @@ use super::{ }; use crate::mailbox::UidMailbox; use common::{Server, auth::ResourceToken, storage::index::ObjectIndexBuilder}; -use jmap_proto::{ - error::set::SetError, - types::{ - blob::BlobId, - collection::{Collection, SyncCollection}, - date::UTCDate, - id::Id, - keyword::Keyword, - property::Property, - }, -}; use mail_parser::{HeaderName, HeaderValue, parsers::fields::thread::thread_name}; -use store::{ - BlobClass, - write::{BatchBuilder, TaskQueueClass, ValueClass, now}, -}; +use store::write::{BatchBuilder, TaskQueueClass, ValueClass, now}; use trc::AddContext; +use types::{ + blob::{BlobClass, BlobId}, + collection::{Collection, SyncCollection}, + field::EmailField, + keyword::Keyword, +}; + +pub enum CopyMessageError { + NotFound, + OverQuota, +} pub trait EmailCopy: Sync + Send { #[allow(clippy::too_many_arguments)] @@ -38,9 +35,9 @@ pub trait EmailCopy: Sync + Send { resource_token: &ResourceToken, mailboxes: Vec, keywords: Vec, - received_at: Option, + received_at: Option, session_id: u64, - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; } impl EmailCopy for Server { @@ -52,9 +49,9 @@ impl EmailCopy for Server { resource_token: &ResourceToken, mailboxes: Vec, keywords: Vec, - received_at: Option, + received_at: Option, session_id: u64, - ) -> trc::Result> { + ) -> trc::Result> { // Obtain metadata let account_id = resource_token.account_id; let mut metadata = if let Some(metadata) = self @@ -62,7 +59,7 @@ impl EmailCopy for Server { from_account_id, Collection::Email, from_message_id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await? { @@ -70,10 +67,7 @@ impl EmailCopy for Server { .deserialize::() .caused_by(trc::location!())? } else { - return Ok(Err(SetError::not_found().with_description(format!( - "Message not found not found in account {}.", - Id::from(from_account_id) - )))); + return Ok(Err(CopyMessageError::NotFound)); }; // Check quota @@ -87,7 +81,7 @@ impl EmailCopy for Server { || err.matches(trc::EventType::Limit(trc::LimitEvent::TenantQuota)) { trc::error!(err.account_id(account_id).span_id(session_id)); - return Ok(Err(SetError::over_quota())); + return Ok(Err(CopyMessageError::OverQuota)); } else { return Err(err); } @@ -96,7 +90,7 @@ impl EmailCopy for Server { // Set receivedAt if let Some(received_at) = received_at { - metadata.received_at = received_at.timestamp() as u64; + metadata.received_at = received_at; } // Obtain threadId @@ -226,7 +220,8 @@ impl EmailCopy for Server { self.notify_task_queue(); // Update response - email.id = Id::from_parts(thread_id, document_id); + email.document_id = document_id; + email.thread_id = thread_id; email.change_id = change_id; email.blob_id = BlobId::new( blob_hash, diff --git a/crates/email/src/message/delete.rs b/crates/email/src/message/delete.rs index f2e94d8a..1c0fd5fb 100644 --- a/crates/email/src/message/delete.rs +++ b/crates/email/src/message/delete.rs @@ -8,8 +8,6 @@ use super::metadata::MessageData; use crate::{cache::MessageCacheFetch, mailbox::*, message::metadata::MessageMetadata}; use common::{KV_LOCK_PURGE_ACCOUNT, Server, storage::index::ObjectIndexBuilder}; use groupware::calendar::storage::ItipAutoExpunge; -use jmap_proto::types::collection::VanishedCollection; -use jmap_proto::types::{collection::Collection, property::Property}; use std::future::Future; use store::rand::prelude::SliceRandom; use store::write::key::DeserializeBigEndian; @@ -21,7 +19,10 @@ use store::{ }; use store::{IndexKey, IterateParams, SerializeInfallible, U32_LEN}; use trc::AddContext; -use utils::BlobHash; +#[cfg(feature = "enterprise")] +use types::blob_hash::BlobHash; +use types::collection::{Collection, VanishedCollection}; +use types::field::EmailField; pub trait EmailDeletion: Sync + Send { fn emails_tombstone( @@ -78,7 +79,7 @@ impl EmailDeletion for Server { .update_document(document_id) .custom(ObjectIndexBuilder::<_, ()>::new().with_current(metadata)) .caused_by(trc::location!())? - .tag(Property::MailboxIds, TagValue::Id(TOMBSTONE_ID)) + .tag(EmailField::MailboxIds, TagValue::Id(TOMBSTONE_ID)) .commit_point(); deleted_ids.insert(document_id); @@ -211,14 +212,14 @@ impl EmailDeletion for Server { account_id, collection: Collection::Email.into(), document_id: 0, - field: Property::ReceivedAt.into(), + field: EmailField::ReceivedAt.into(), key: 0u64.serialize(), }, IndexKey { account_id, collection: Collection::Email.into(), document_id: u32::MAX, - field: Property::ReceivedAt.into(), + field: EmailField::ReceivedAt.into(), key: now().saturating_sub(hold_period).serialize(), }, ) @@ -269,7 +270,7 @@ impl EmailDeletion for Server { account_id, collection: Collection::Email.into(), class: BitmapClass::Tag { - field: Property::MailboxIds.into(), + field: EmailField::MailboxIds.into(), value: TagValue::Id(TOMBSTONE_ID), }, document_id: 0, @@ -291,7 +292,7 @@ impl EmailDeletion for Server { self.core .storage .fts - .remove(account_id, Collection::Email.into(), &tombstoned_ids) + .remove(account_id, Collection::Email, &tombstoned_ids) .await?; // Obtain tenant id @@ -310,8 +311,8 @@ impl EmailDeletion for Server { batch .with_collection(Collection::Email) .delete_document(document_id) - .clear(Property::Value) - .untag(Property::MailboxIds, TagValue::Id(TOMBSTONE_ID)); + .clear(EmailField::Archive) + .untag(EmailField::MailboxIds, TagValue::Id(TOMBSTONE_ID)); // Remove message metadata if let Some(metadata_) = self @@ -322,7 +323,7 @@ impl EmailDeletion for Server { account_id, collection: Collection::Email.into(), document_id, - class: ValueClass::Property(Property::BodyStructure.into()), + class: ValueClass::Property(EmailField::Metadata.into()), }) .await? { diff --git a/crates/email/src/message/delivery.rs b/crates/email/src/message/delivery.rs index e3d19979..75b53221 100644 --- a/crates/email/src/message/delivery.rs +++ b/crates/email/src/message/delivery.rs @@ -4,18 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::ingest::{EmailIngest, IngestEmail, IngestSource}; +use crate::{mailbox::INBOX_ID, sieve::ingest::SieveScriptIngest}; use common::Server; - use directory::Permission; -use jmap_proto::types::{state::StateChange, type_state::DataType}; use mail_parser::MessageParser; use std::{borrow::Cow, future::Future}; use store::ahash::AHashMap; -use utils::BlobHash; - -use crate::{mailbox::INBOX_ID, sieve::ingest::SieveScriptIngest}; - -use super::ingest::{EmailIngest, IngestEmail, IngestSource}; +use types::{ + blob_hash::BlobHash, + type_state::{DataType, StateChange}, +}; #[derive(Debug)] pub struct IngestMessage { diff --git a/crates/email/src/message/index.rs b/crates/email/src/message/index.rs index 473bf88b..604ecbd0 100644 --- a/crates/email/src/message/index.rs +++ b/crates/email/src/message/index.rs @@ -12,7 +12,6 @@ use super::metadata::{ MessageMetadata, MessageMetadataPart, }; use common::storage::index::{IndexValue, IndexableObject, ObjectIndexBuilder}; -use jmap_proto::types::{collection::SyncCollection, property::Property}; use mail_parser::{ Addr, Address, ArchivedAddress, ArchivedHeaderName, ArchivedHeaderValue, Group, HeaderName, HeaderValue, @@ -29,7 +28,7 @@ use store::{ write::{Archiver, BatchBuilder, BlobOp, DirectoryClass}, }; use trc::AddContext; -use utils::BlobHash; +use types::{blob_hash::BlobHash, collection::SyncCollection, field::EmailField}; pub const MAX_MESSAGE_PARTS: usize = 1000; pub const MAX_ID_LENGTH: usize = 100; @@ -53,14 +52,14 @@ impl MessageMetadata { if set { // Serialize metadata batch - .index(Property::Size, self.size.serialize()) - .index(Property::ReceivedAt, (self.received_at).serialize()); + .index(EmailField::Size, self.size.serialize()) + .index(EmailField::ReceivedAt, (self.received_at).serialize()); } else { // Delete metadata batch - .clear(Property::BodyStructure) - .unindex(Property::Size, self.size.serialize()) - .unindex(Property::ReceivedAt, (self.received_at).serialize()); + .clear(EmailField::Metadata) + .unindex(EmailField::Size, self.size.serialize()) + .unindex(EmailField::ReceivedAt, (self.received_at).serialize()); } // Index properties @@ -76,9 +75,9 @@ impl MessageMetadata { if self.has_attachments { if set { - batch.tag(Property::HasAttachment, ()); + batch.tag(EmailField::HasAttachment, ()); } else { - batch.untag(Property::HasAttachment, ()); + batch.untag(EmailField::HasAttachment, ()); } } @@ -100,7 +99,7 @@ impl MessageMetadata { } if set { - batch.set(Property::BodyStructure, Archiver::new(self).serialize()?); + batch.set(EmailField::Metadata, Archiver::new(self).serialize()?); } Ok(()) @@ -119,9 +118,9 @@ impl MessageMetadata { // Add ids to inverted index if id.len() < MAX_ID_LENGTH { if set { - batch.index(Property::References, encode_message_id(id)); + batch.index(EmailField::References, encode_message_id(id)); } else { - batch.unindex(Property::References, encode_message_id(id)); + batch.unindex(EmailField::References, encode_message_id(id)); } } }); @@ -131,9 +130,9 @@ impl MessageMetadata { // Add ids to inverted index if id.len() < MAX_ID_LENGTH { if set { - batch.index(Property::References, id.serialize()); + batch.index(EmailField::References, id.serialize()); } else { - batch.unindex(Property::References, id.serialize()); + batch.unindex(EmailField::References, id.serialize()); } } }); @@ -161,9 +160,9 @@ impl MessageMetadata { // Add address to inverted index if set { - batch.index(u8::from(&property), sort_text.build()); + batch.index(property, sort_text.build()); } else { - batch.unindex(u8::from(&property), sort_text.build()); + batch.unindex(property, sort_text.build()); } seen_headers[header.name.id() as usize] = true; } @@ -173,9 +172,9 @@ impl MessageMetadata { if let HeaderValue::DateTime(datetime) = &header.value { let value = (datetime.to_timestamp() as u64).serialize(); if set { - batch.index(Property::SentAt, value); + batch.index(EmailField::SentAt, value); } else { - batch.unindex(Property::SentAt, value); + batch.unindex(EmailField::SentAt, value); } } seen_headers[header.name.id() as usize] = true; @@ -202,9 +201,9 @@ impl MessageMetadata { .serialize(); if set { - batch.index(Property::Subject, thread_name); + batch.index(EmailField::Subject, thread_name); } else { - batch.unindex(Property::Subject, thread_name); + batch.unindex(EmailField::Subject, thread_name); } seen_headers[header.name.id() as usize] = true; @@ -218,9 +217,9 @@ impl MessageMetadata { // Add subject to index if missing if !seen_headers[HeaderName::Subject.id() as usize] { if set { - batch.index(Property::Subject, "!".serialize()); + batch.index(EmailField::Subject, "!".serialize()); } else { - batch.unindex(Property::Subject, "!".serialize()); + batch.unindex(EmailField::Subject, "!".serialize()); } } } @@ -249,18 +248,18 @@ impl ArchivedMessageMetadata { if set { // Serialize metadata batch - .index(Property::Size, u32::from(self.size).serialize()) + .index(EmailField::Size, u32::from(self.size).serialize()) .index( - Property::ReceivedAt, + EmailField::ReceivedAt, u64::from(self.received_at).serialize(), ); } else { // Delete metadata batch - .clear(Property::BodyStructure) - .unindex(Property::Size, u32::from(self.size).serialize()) + .clear(EmailField::Metadata) + .unindex(EmailField::Size, u32::from(self.size).serialize()) .unindex( - Property::ReceivedAt, + EmailField::ReceivedAt, u64::from(self.received_at).serialize(), ); } @@ -278,9 +277,9 @@ impl ArchivedMessageMetadata { if self.has_attachments { if set { - batch.tag(Property::HasAttachment, ()); + batch.tag(EmailField::HasAttachment, ()); } else { - batch.untag(Property::HasAttachment, ()); + batch.untag(EmailField::HasAttachment, ()); } } @@ -311,9 +310,9 @@ impl ArchivedMessageMetadata { // Add ids to inverted index if id.len() < MAX_ID_LENGTH { if set { - batch.index(Property::References, encode_message_id(id)); + batch.index(EmailField::References, encode_message_id(id)); } else { - batch.unindex(Property::References, encode_message_id(id)); + batch.unindex(EmailField::References, encode_message_id(id)); } } }); @@ -325,9 +324,9 @@ impl ArchivedMessageMetadata { // Add ids to inverted index if id.len() < MAX_ID_LENGTH { if set { - batch.index(Property::References, id.serialize()); + batch.index(EmailField::References, id.serialize()); } else { - batch.unindex(Property::References, id.serialize()); + batch.unindex(EmailField::References, id.serialize()); } } }); @@ -358,9 +357,9 @@ impl ArchivedMessageMetadata { // Add address to inverted index if set { - batch.index(u8::from(&property), sort_text.build()); + batch.index(property, sort_text.build()); } else { - batch.unindex(u8::from(&property), sort_text.build()); + batch.unindex(property, sort_text.build()); } seen_headers[header.name.id() as usize] = true; } @@ -372,9 +371,9 @@ impl ArchivedMessageMetadata { as u64) .serialize(); if set { - batch.index(Property::SentAt, value); + batch.index(EmailField::SentAt, value); } else { - batch.unindex(Property::SentAt, value); + batch.unindex(EmailField::SentAt, value); } } seen_headers[header.name.id() as usize] = true; @@ -401,9 +400,9 @@ impl ArchivedMessageMetadata { .serialize(); if set { - batch.index(Property::Subject, thread_name); + batch.index(EmailField::Subject, thread_name); } else { - batch.unindex(Property::Subject, thread_name); + batch.unindex(EmailField::Subject, thread_name); } seen_headers[header.name.id() as usize] = true; @@ -417,9 +416,9 @@ impl ArchivedMessageMetadata { // Add subject to index if missing if !seen_headers[HeaderName::Subject.id() as usize] { if set { - batch.index(Property::Subject, "!".serialize()); + batch.index(EmailField::Subject, "!".serialize()); } else { - batch.unindex(Property::Subject, "!".serialize()); + batch.unindex(EmailField::Subject, "!".serialize()); } } } @@ -465,7 +464,7 @@ impl IndexMessage for BatchBuilder { ) -> trc::Result<&mut Self> { // Index size self.index( - Property::Size, + EmailField::Size, (message.raw_message.len() as u32).serialize(), ) .add( @@ -480,7 +479,7 @@ impl IndexMessage for BatchBuilder { } // Index receivedAt - self.index(Property::ReceivedAt, received_at.serialize()); + self.index(EmailField::ReceivedAt, received_at.serialize()); let mut has_attachments = false; let mut preview = None; @@ -549,7 +548,7 @@ impl IndexMessage for BatchBuilder { // Store and index hasAttachment property if has_attachments { - self.tag(Property::HasAttachment, ()); + self.tag(EmailField::HasAttachment, ()); } // Link blob @@ -566,7 +565,7 @@ impl IndexMessage for BatchBuilder { // Store message metadata self.set( - Property::BodyStructure, + EmailField::Metadata, Archiver::new(metadata) .serialize() .caused_by(trc::location!())?, @@ -580,15 +579,15 @@ impl IndexableObject for MessageData { fn index_values(&self) -> impl Iterator> { [ IndexValue::LogItem { - sync_collection: SyncCollection::Email.into(), + sync_collection: SyncCollection::Email, prefix: self.thread_id.into(), }, IndexValue::LogContainerProperty { - sync_collection: SyncCollection::Thread.into(), + sync_collection: SyncCollection::Thread, ids: vec![self.thread_id], }, IndexValue::LogContainerProperty { - sync_collection: SyncCollection::Email.into(), + sync_collection: SyncCollection::Email, ids: self.mailboxes.iter().map(|m| m.mailbox_id).collect(), }, ] @@ -600,15 +599,15 @@ impl IndexableObject for &ArchivedMessageData { fn index_values(&self) -> impl Iterator> { [ IndexValue::LogItem { - sync_collection: SyncCollection::Email.into(), + sync_collection: SyncCollection::Email, prefix: self.thread_id.to_native().into(), }, IndexValue::LogContainerProperty { - sync_collection: SyncCollection::Thread.into(), + sync_collection: SyncCollection::Thread, ids: vec![self.thread_id.to_native()], }, IndexValue::LogContainerProperty { - sync_collection: SyncCollection::Email.into(), + sync_collection: SyncCollection::Email, ids: self .mailboxes .iter() @@ -1037,38 +1036,38 @@ impl TrimTextValue for Vec { } } -pub fn property_from_header(header: &HeaderName) -> Property { +pub fn property_from_header(header: &HeaderName) -> EmailField { match header { - HeaderName::Subject => Property::Subject, - HeaderName::From => Property::From, - HeaderName::To => Property::To, - HeaderName::Cc => Property::Cc, - HeaderName::Date => Property::SentAt, - HeaderName::Bcc => Property::Bcc, - HeaderName::ReplyTo => Property::ReplyTo, - HeaderName::Sender => Property::Sender, - HeaderName::InReplyTo => Property::InReplyTo, - HeaderName::MessageId => Property::MessageId, - HeaderName::References => Property::References, - HeaderName::ResentMessageId => Property::EmailIds, + HeaderName::Subject => EmailField::Subject, + HeaderName::From => EmailField::From, + HeaderName::To => EmailField::To, + HeaderName::Cc => EmailField::Cc, + HeaderName::Date => EmailField::SentAt, + HeaderName::Bcc => EmailField::Bcc, + HeaderName::ReplyTo => EmailField::ReplyTo, + HeaderName::Sender => EmailField::Sender, + HeaderName::InReplyTo => EmailField::InReplyTo, + HeaderName::MessageId => EmailField::MessageId, + HeaderName::References => EmailField::References, + HeaderName::ResentMessageId => EmailField::EmailIds, _ => unreachable!(), } } -pub fn property_from_archived_header(header: &ArchivedHeaderName) -> Property { +pub fn property_from_archived_header(header: &ArchivedHeaderName) -> EmailField { match header { - ArchivedHeaderName::Subject => Property::Subject, - ArchivedHeaderName::From => Property::From, - ArchivedHeaderName::To => Property::To, - ArchivedHeaderName::Cc => Property::Cc, - ArchivedHeaderName::Date => Property::SentAt, - ArchivedHeaderName::Bcc => Property::Bcc, - ArchivedHeaderName::ReplyTo => Property::ReplyTo, - ArchivedHeaderName::Sender => Property::Sender, - ArchivedHeaderName::InReplyTo => Property::InReplyTo, - ArchivedHeaderName::MessageId => Property::MessageId, - ArchivedHeaderName::References => Property::References, - ArchivedHeaderName::ResentMessageId => Property::EmailIds, + ArchivedHeaderName::Subject => EmailField::Subject, + ArchivedHeaderName::From => EmailField::From, + ArchivedHeaderName::To => EmailField::To, + ArchivedHeaderName::Cc => EmailField::Cc, + ArchivedHeaderName::Date => EmailField::SentAt, + ArchivedHeaderName::Bcc => EmailField::Bcc, + ArchivedHeaderName::ReplyTo => EmailField::ReplyTo, + ArchivedHeaderName::Sender => EmailField::Sender, + ArchivedHeaderName::InReplyTo => EmailField::InReplyTo, + ArchivedHeaderName::MessageId => EmailField::MessageId, + ArchivedHeaderName::References => EmailField::References, + ArchivedHeaderName::ResentMessageId => EmailField::EmailIds, _ => unreachable!(), } } diff --git a/crates/email/src/message/ingest.rs b/crates/email/src/message/ingest.rs index 19f0b726..77193492 100644 --- a/crates/email/src/message/ingest.rs +++ b/crates/email/src/message/ingest.rs @@ -17,20 +17,12 @@ use crate::{ metadata::MessageData, }, }; -use common::{IDX_EMAIL, Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; +use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; use directory::Permission; use groupware::{ calendar::itip::{ItipIngest, ItipIngestError}, scheduling::{ItipError, ItipMessages}, }; -use jmap_proto::types::{ - blob::BlobId, - collection::{Collection, SyncCollection}, - id::Id, - keyword::Keyword, - property::Property, - value::{Object, Value}, -}; use mail_parser::{ Header, HeaderName, HeaderValue, Message, MessageParser, MimeHeaders, PartType, parsers::fields::thread::thread_name, @@ -45,7 +37,7 @@ use std::{ time::{Duration, Instant}, }; use store::{ - BlobClass, IndexKey, IndexKeyPrefix, IterateParams, U32_LEN, + IndexKey, IndexKeyPrefix, IterateParams, U32_LEN, ahash::AHashMap, query::Filter, roaring::RoaringBitmap, @@ -53,11 +45,18 @@ use store::{ }; use store::{SerializeInfallible, rand::Rng}; use trc::{AddContext, MessageIngestEvent}; +use types::{ + blob::{BlobClass, BlobId}, + collection::{Collection, SyncCollection}, + field::{ContactField, EmailField, MailboxField, PrincipalField}, + keyword::Keyword, +}; use utils::sanitize_email; #[derive(Default)] pub struct IngestedEmail { - pub id: Id, + pub document_id: u32, + pub thread_id: u32, pub change_id: u64, pub blob_id: BlobId, pub size: usize, @@ -219,7 +218,7 @@ impl EmailIngest for Server { .filter( account_id, Collection::ContactCard, - vec![Filter::eq(IDX_EMAIL, sender.into_bytes())], + vec![Filter::eq(ContactField::Email, sender.into_bytes())], ) .await .caused_by(trc::location!())? @@ -473,7 +472,8 @@ impl EmailIngest for Server { ); return Ok(IngestedEmail { - id: Id::default(), + document_id: 0, + thread_id: 0, change_id: u64::MAX, blob_id: BlobId::default(), imap_uids: Vec::new(), @@ -543,7 +543,12 @@ impl EmailIngest for Server { if do_encrypt && !message.is_encrypted() && let Some(encrypt_params_) = self - .get_archive_by_property(account_id, Collection::Principal, 0, Property::Parameters) + .get_archive_by_property( + account_id, + Collection::Principal, + 0, + PrincipalField::EncryptionKeys.into(), + ) .await .caused_by(trc::location!())? { @@ -682,7 +687,6 @@ impl EmailIngest for Server { .await .caused_by(trc::location!())? .last_change_id(account_id)?; - let id = Id::from_parts(thread_id, document_id); // Request FTS index self.notify_task_queue(); @@ -710,7 +714,8 @@ impl EmailIngest for Server { ); Ok(IngestedEmail { - id, + document_id, + thread_id, change_id, blob_id: BlobId { hash: blob_id.hash, @@ -758,14 +763,14 @@ impl EmailIngest for Server { account_id, collection: Collection::Email.into(), document_id: 0, - field: Property::Subject.into(), + field: EmailField::Subject.into(), key: thread_name.clone(), }, IndexKey { account_id, collection: Collection::Email.into(), document_id: u32::MAX, - field: Property::Subject.into(), + field: EmailField::Subject.into(), key: thread_name.clone(), }, ) @@ -802,14 +807,14 @@ impl EmailIngest for Server { account_id, collection: Collection::Email.into(), document_id: 0, - field: Property::References.into(), + field: EmailField::References.into(), key: references.first().unwrap().to_vec(), }, IndexKey { account_id, collection: Collection::Email.into(), document_id: u32::MAX, - field: Property::References.into(), + field: EmailField::References.into(), key: references.last().unwrap().to_vec(), }, ) @@ -952,7 +957,7 @@ impl EmailIngest for Server { .with_account_id(account_id) .with_collection(Collection::Mailbox) .update_document(mailbox_id) - .add_and_get(Property::EmailIds, 1); + .add_and_get(MailboxField::UidCounter, 1); self.core .storage .data @@ -973,13 +978,3 @@ impl IngestSource<'_> { matches!(self, Self::Smtp { .. }) } } - -impl From for Object { - fn from(email: IngestedEmail) -> Self { - Object::with_capacity(3) - .with_property(Property::Id, email.id) - .with_property(Property::ThreadId, Id::from(email.id.prefix_id())) - .with_property(Property::BlobId, email.blob_id) - .with_property(Property::Size, email.size) - } -} diff --git a/crates/email/src/message/metadata.rs b/crates/email/src/message/metadata.rs index ccbb887f..9eb4eb27 100644 --- a/crates/email/src/message/metadata.rs +++ b/crates/email/src/message/metadata.rs @@ -6,7 +6,6 @@ use crate::mailbox::{ArchivedUidMailbox, UidMailbox}; use common::storage::index::IndexableAndSerializableObject; -use jmap_proto::types::keyword::{ArchivedKeyword, Keyword}; use mail_parser::{ ArchivedContentType, ArchivedEncoding, ArchivedHeaderName, ArchivedHeaderValue, DateTime, Encoding, Header, HeaderName, HeaderValue, PartType, @@ -21,7 +20,10 @@ use rkyv::{ vec::ArchivedVec, }; use std::{borrow::Cow, collections::VecDeque}; -use utils::BlobHash; +use types::{ + blob_hash::BlobHash, + keyword::{ArchivedKeyword, Keyword}, +}; #[derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive, Debug, Default)] pub struct MessageData { diff --git a/crates/email/src/push/mod.rs b/crates/email/src/push/mod.rs index 97443f0e..568a3072 100644 --- a/crates/email/src/push/mod.rs +++ b/crates/email/src/push/mod.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_proto::types::type_state::DataType; +use types::type_state::DataType; use utils::map::bitmap::Bitmap; #[derive( diff --git a/crates/email/src/sieve/activate.rs b/crates/email/src/sieve/activate.rs index 941cbf3b..577377fb 100644 --- a/crates/email/src/sieve/activate.rs +++ b/crates/email/src/sieve/activate.rs @@ -5,9 +5,9 @@ */ use common::{Server, storage::index::ObjectIndexBuilder}; -use jmap_proto::types::{collection::Collection, property::Property}; use store::{query::Filter, write::BatchBuilder}; use trc::AddContext; +use types::{collection::Collection, field::SieveField}; use super::SieveScript; @@ -32,7 +32,7 @@ impl SieveScriptActivate for Server { .filter( account_id, Collection::SieveScript, - vec![Filter::eq(Property::IsActive, vec![1u8])], + vec![Filter::eq(SieveField::IsActive, vec![1u8])], ) .await? .results; @@ -65,7 +65,7 @@ impl SieveScriptActivate for Server { new_sieve.is_active = false; batch .update_document(document_id) - .clear(Property::EmailIds) + .clear(SieveField::Ids) .custom( ObjectIndexBuilder::new() .with_changes(new_sieve) diff --git a/crates/email/src/sieve/delete.rs b/crates/email/src/sieve/delete.rs index e2cbbd70..d21e5395 100644 --- a/crates/email/src/sieve/delete.rs +++ b/crates/email/src/sieve/delete.rs @@ -4,12 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::SieveScript; use common::{Server, auth::ResourceToken, storage::index::ObjectIndexBuilder}; -use jmap_proto::types::{collection::Collection, property::Property}; use store::write::BatchBuilder; use trc::AddContext; - -use super::SieveScript; +use types::{collection::Collection, field::SieveField}; pub trait SieveScriptDelete: Sync + Send { fn sieve_script_delete( @@ -54,7 +53,7 @@ impl SieveScriptDelete for Server { .with_account_id(account_id) .with_collection(Collection::SieveScript) .delete_document(document_id) - .clear(Property::EmailIds) + .clear(SieveField::Ids) .custom( ObjectIndexBuilder::<_, ()>::new() .with_current(obj) diff --git a/crates/email/src/sieve/index.rs b/crates/email/src/sieve/index.rs index 3d42bf7b..5d9f735f 100644 --- a/crates/email/src/sieve/index.rs +++ b/crates/email/src/sieve/index.rs @@ -4,20 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; -use jmap_proto::types::{collection::SyncCollection, property::Property}; - use super::{ArchivedSieveScript, SieveScript}; +use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; +use types::{collection::SyncCollection, field::SieveField}; impl IndexableObject for SieveScript { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: Property::Name.into(), + field: SieveField::Name.into(), value: self.name.as_str().to_lowercase().into(), }, IndexValue::Index { - field: Property::IsActive.into(), + field: SieveField::IsActive.into(), value: if self.is_active { &[1u8] } else { &[0u8] } .as_slice() .into(), @@ -26,7 +25,7 @@ impl IndexableObject for SieveScript { value: self.blob_hash.clone(), }, IndexValue::LogItem { - sync_collection: SyncCollection::SieveScript.into(), + sync_collection: SyncCollection::SieveScript, prefix: None, }, IndexValue::Quota { used: self.size }, @@ -45,11 +44,11 @@ impl IndexableObject for &ArchivedSieveScript { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: Property::Name.into(), + field: SieveField::Name.into(), value: self.name.to_lowercase().into(), }, IndexValue::Index { - field: Property::IsActive.into(), + field: SieveField::IsActive.into(), value: if self.is_active { &[1u8] } else { &[0u8] } .as_slice() .into(), @@ -58,7 +57,7 @@ impl IndexableObject for &ArchivedSieveScript { value: (&self.blob_hash).into(), }, IndexValue::LogItem { - sync_collection: SyncCollection::SieveScript.into(), + sync_collection: SyncCollection::SieveScript, prefix: None, }, IndexValue::Quota { diff --git a/crates/email/src/sieve/ingest.rs b/crates/email/src/sieve/ingest.rs index 4622154c..1b93297b 100644 --- a/crates/email/src/sieve/ingest.rs +++ b/crates/email/src/sieve/ingest.rs @@ -17,7 +17,6 @@ use common::{ Server, auth::AccessToken, config::jmap::settings::SpecialUse, scripts::plugins::PluginContext, }; use directory::{Permission, QueryParams}; -use jmap_proto::types::{collection::Collection, id::Id, keyword::Keyword, property::Property}; use mail_parser::MessageParser; use sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve}; use std::future::Future; @@ -30,6 +29,7 @@ use store::{ write::{AlignedBytes, Archive, ArchiveVersion, Archiver, BatchBuilder, BlobOp}, }; use trc::{AddContext, SieveEvent}; +use types::{collection::Collection, field::SieveField, id::Id, keyword::Keyword}; use utils::config::utils::ParseValue; struct SieveMessage<'x> { @@ -142,7 +142,8 @@ impl SieveScriptIngest for Server { did_file_into: false, }]; let mut ingested_message = IngestedEmail { - id: Id::default(), + document_id: 0, + thread_id: 0, change_id: u64::MAX, blob_id: Default::default(), size: raw_message.len(), @@ -560,7 +561,7 @@ impl SieveScriptIngest for Server { .filter( account_id, Collection::SieveScript, - vec![Filter::eq(Property::IsActive, vec![1u8])], + vec![Filter::eq(SieveField::IsActive, vec![1u8])], ) .await .caused_by(trc::location!())? @@ -591,7 +592,7 @@ impl SieveScriptIngest for Server { .filter( account_id, Collection::SieveScript, - vec![Filter::eq(Property::Name, name.serialize())], + vec![Filter::eq(SieveField::Name, name.serialize())], ) .await .caused_by(trc::location!())? @@ -693,9 +694,9 @@ impl SieveScriptIngest for Server { .with_account_id(account_id) .with_collection(Collection::SieveScript) .update_document(document_id) - .assert_value(Property::Value, &script_object) + .assert_value(SieveField::Archive, &script_object) .set( - Property::Value, + SieveField::Archive, new_archive.serialize().caused_by(trc::location!())?, ) .clear(BlobOp::Link { hash: blob_hash }) diff --git a/crates/email/src/sieve/mod.rs b/crates/email/src/sieve/mod.rs index 6cda013b..8451d3bf 100644 --- a/crates/email/src/sieve/mod.rs +++ b/crates/email/src/sieve/mod.rs @@ -4,13 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::sync::Arc; - use common::KV_SIEVE_ID; - use sieve::Sieve; +use std::sync::Arc; use store::{blake3, write::ArchiveVersion}; -use utils::BlobHash; +use types::blob_hash::BlobHash; pub mod activate; pub mod delete; diff --git a/crates/email/src/submission/index.rs b/crates/email/src/submission/index.rs index 30c35af6..851389c7 100644 --- a/crates/email/src/submission/index.rs +++ b/crates/email/src/submission/index.rs @@ -4,36 +4,35 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; -use jmap_proto::types::{collection::SyncCollection, property::Property}; - use super::{ArchivedEmailSubmission, EmailSubmission}; +use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; +use types::{collection::SyncCollection, field::EmailSubmissionField}; impl IndexableObject for EmailSubmission { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: Property::UndoStatus.into(), + field: EmailSubmissionField::UndoStatus.into(), value: self.undo_status.as_index().into(), }, IndexValue::Index { - field: Property::EmailId.into(), + field: EmailSubmissionField::EmailId.into(), value: self.email_id.into(), }, IndexValue::Index { - field: Property::ThreadId.into(), + field: EmailSubmissionField::ThreadId.into(), value: self.thread_id.into(), }, IndexValue::Index { - field: Property::IdentityId.into(), + field: EmailSubmissionField::IdentityId.into(), value: self.identity_id.into(), }, IndexValue::Index { - field: Property::SendAt.into(), + field: EmailSubmissionField::SendAt.into(), value: self.send_at.into(), }, IndexValue::LogItem { - sync_collection: SyncCollection::EmailSubmission.into(), + sync_collection: SyncCollection::EmailSubmission, prefix: None, }, ] @@ -45,27 +44,27 @@ impl IndexableObject for &ArchivedEmailSubmission { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: Property::UndoStatus.into(), + field: EmailSubmissionField::UndoStatus.into(), value: self.undo_status.as_index().into(), }, IndexValue::Index { - field: Property::EmailId.into(), + field: EmailSubmissionField::EmailId.into(), value: self.email_id.into(), }, IndexValue::Index { - field: Property::ThreadId.into(), + field: EmailSubmissionField::ThreadId.into(), value: self.thread_id.into(), }, IndexValue::Index { - field: Property::IdentityId.into(), + field: EmailSubmissionField::IdentityId.into(), value: self.identity_id.into(), }, IndexValue::Index { - field: Property::SendAt.into(), + field: EmailSubmissionField::SendAt.into(), value: self.send_at.into(), }, IndexValue::LogItem { - sync_collection: SyncCollection::EmailSubmission.into(), + sync_collection: SyncCollection::EmailSubmission, prefix: None, }, ] diff --git a/crates/groupware/Cargo.toml b/crates/groupware/Cargo.toml index a9991d37..480a989b 100644 --- a/crates/groupware/Cargo.toml +++ b/crates/groupware/Cargo.toml @@ -8,7 +8,7 @@ resolver = "2" utils = { path = "../utils" } store = { path = "../store" } common = { path = "../common" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } trc = { path = "../trc" } directory = { path = "../directory" } dav-proto = { path = "../dav-proto" } diff --git a/crates/groupware/src/cache/calcard.rs b/crates/groupware/src/cache/calcard.rs index 655192a9..860f5344 100644 --- a/crates/groupware/src/cache/calcard.rs +++ b/crates/groupware/src/cache/calcard.rs @@ -18,14 +18,14 @@ use common::{ DavName, DavPath, DavResource, DavResourceMetadata, DavResources, Server, auth::AccessToken, }; use directory::backend::internal::manage::ManageDirectory; -use jmap_proto::types::{ - collection::{Collection, SyncCollection}, - value::AclGrant, -}; use std::sync::Arc; use store::ahash::{AHashMap, AHashSet}; use tokio::sync::Semaphore; use trc::AddContext; +use types::{ + acl::AclGrant, + collection::{Collection, SyncCollection}, +}; use utils::map::bitmap::Bitmap; pub(super) async fn build_calcard_resources( @@ -41,7 +41,7 @@ pub(super) async fn build_calcard_resources( .core .storage .data - .get_last_change_id(account_id, sync_collection) + .get_last_change_id(account_id, sync_collection.into()) .await .caused_by(trc::location!())? .unwrap_or_default(); @@ -73,7 +73,7 @@ pub(super) async fn build_calcard_resources( .core .storage .data - .get_last_change_id(account_id, sync_collection) + .get_last_change_id(account_id, sync_collection.into()) .await .caused_by(trc::location!())? .unwrap_or_default(); @@ -183,7 +183,7 @@ pub(super) async fn build_scheduling_resources( .core .storage .data - .get_last_change_id(account_id, SyncCollection::CalendarScheduling) + .get_last_change_id(account_id, SyncCollection::CalendarScheduling.into()) .await .caused_by(trc::location!())? .unwrap_or_default(); diff --git a/crates/groupware/src/cache/file.rs b/crates/groupware/src/cache/file.rs index c27537a8..3d473d59 100644 --- a/crates/groupware/src/cache/file.rs +++ b/crates/groupware/src/cache/file.rs @@ -10,11 +10,6 @@ use crate::{ }; use common::{DavPath, DavResource, DavResourceMetadata, DavResources, Server}; use directory::backend::internal::manage::ManageDirectory; -use jmap_proto::types::{ - collection::{Collection, SyncCollection}, - property::Property, - value::AclGrant, -}; use std::sync::Arc; use store::{ Deserialize, IterateParams, U32_LEN, ValueKey, @@ -23,6 +18,11 @@ use store::{ }; use tokio::sync::Semaphore; use trc::AddContext; +use types::{ + acl::AclGrant, + collection::{Collection, SyncCollection}, + field::Field, +}; use utils::{map::bitmap::Bitmap, topological::TopologicalSort}; pub(super) async fn build_file_resources( @@ -34,7 +34,7 @@ pub(super) async fn build_file_resources( .core .storage .data - .get_last_change_id(account_id, SyncCollection::FileNode) + .get_last_change_id(account_id, SyncCollection::FileNode.into()) .await .caused_by(trc::location!())? .unwrap_or_default(); @@ -130,13 +130,13 @@ async fn fetch_files(server: &Server, account_id: u32) -> trc::Result impl Iterator> { @@ -27,7 +26,7 @@ impl IndexableObject for Calendar { + self.name.len() as u32, }, IndexValue::LogContainer { - sync_collection: SyncCollection::Calendar.into(), + sync_collection: SyncCollection::Calendar, }, ] .into_iter() @@ -52,7 +51,7 @@ impl IndexableObject for &ArchivedCalendar { + self.name.len() as u32, }, IndexValue::LogContainer { - sync_collection: SyncCollection::Calendar.into(), + sync_collection: SyncCollection::Calendar, }, ] .into_iter() @@ -69,7 +68,7 @@ impl IndexableObject for CalendarEvent { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: IDX_UID, + field: CalendarField::Uid.into(), value: self.data.event.uids().next().into(), }, IndexValue::Quota { @@ -79,7 +78,7 @@ impl IndexableObject for CalendarEvent { + self.size, }, IndexValue::LogItem { - sync_collection: SyncCollection::Calendar.into(), + sync_collection: SyncCollection::Calendar, prefix: None, }, ] @@ -91,7 +90,7 @@ impl IndexableObject for &ArchivedCalendarEvent { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: IDX_UID, + field: CalendarField::Uid.into(), value: self.data.event.uids().next().into(), }, IndexValue::Quota { @@ -101,7 +100,7 @@ impl IndexableObject for &ArchivedCalendarEvent { + self.size, }, IndexValue::LogItem { - sync_collection: SyncCollection::Calendar.into(), + sync_collection: SyncCollection::Calendar, prefix: None, }, ] @@ -120,11 +119,11 @@ impl IndexableObject for CalendarScheduling { [ IndexValue::Quota { used: self.size }, IndexValue::Index { - field: IDX_CREATED, + field: CalendarField::Created.into(), value: self.created.into(), }, IndexValue::LogItem { - sync_collection: SyncCollection::CalendarScheduling.into(), + sync_collection: SyncCollection::CalendarScheduling, prefix: None, }, ] @@ -139,11 +138,11 @@ impl IndexableObject for &ArchivedCalendarScheduling { used: self.size.to_native(), }, IndexValue::Index { - field: IDX_CREATED, + field: CalendarField::Created.into(), value: self.created.to_native().into(), }, IndexValue::LogItem { - sync_collection: SyncCollection::CalendarScheduling.into(), + sync_collection: SyncCollection::CalendarScheduling, prefix: None, }, ] diff --git a/crates/groupware/src/calendar/itip.rs b/crates/groupware/src/calendar/itip.rs index 12134942..38609ca3 100644 --- a/crates/groupware/src/calendar/itip.rs +++ b/crates/groupware/src/calendar/itip.rs @@ -24,18 +24,21 @@ use calcard::{ }, }; use common::{ - DavName, IDX_EMAIL, IDX_UID, Server, + DavName, Server, auth::{AccessToken, ResourceToken, oauth::GrantType}, config::groupware::CalendarTemplateVariable, i18n, }; -use jmap_proto::types::collection::Collection; use store::{ query::Filter, rand, write::{BatchBuilder, now}, }; use trc::AddContext; +use types::{ + collection::Collection, + field::{CalendarField, ContactField}, +}; use utils::{template::Variables, url_params::UrlParams}; pub enum ItipIngestError { @@ -142,7 +145,10 @@ impl ItipIngest for Server { .filter( account_id, Collection::CalendarEvent, - vec![Filter::eq(IDX_UID, itip_snapshots.uid.as_bytes().to_vec())], + vec![Filter::eq( + CalendarField::Uid, + itip_snapshots.uid.as_bytes().to_vec(), + )], ) .await .caused_by(trc::location!())? @@ -261,7 +267,7 @@ impl ItipIngest for Server { .filter( account_id, Collection::ContactCard, - vec![Filter::eq(IDX_EMAIL, sender.as_bytes().to_vec())], + vec![Filter::eq(ContactField::Email, sender.as_bytes().to_vec())], ) .await .caused_by(trc::location!())? diff --git a/crates/groupware/src/calendar/mod.rs b/crates/groupware/src/calendar/mod.rs index f0c70e64..0ef2d2e7 100644 --- a/crates/groupware/src/calendar/mod.rs +++ b/crates/groupware/src/calendar/mod.rs @@ -14,7 +14,7 @@ pub mod storage; use calcard::icalendar::ICalendar; use common::DavName; use dav_proto::schema::request::DeadProperty; -use jmap_proto::types::{acl::Acl, value::AclGrant}; +use types::acl::{Acl, AclGrant}; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, diff --git a/crates/groupware/src/calendar/storage.rs b/crates/groupware/src/calendar/storage.rs index 404af7f7..75b3d23f 100644 --- a/crates/groupware/src/calendar/storage.rs +++ b/crates/groupware/src/calendar/storage.rs @@ -4,14 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{ + ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent, CalendarPreferences, + alarm::CalendarAlarm, +}; use crate::{ DavResourceName, DestroyArchive, RFC_3986, calendar::{ArchivedCalendarScheduling, CalendarScheduling}, scheduling::{ItipMessages, event_cancel::itip_cancel}, }; use calcard::common::timezone::Tz; -use common::{IDX_CREATED, Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; -use jmap_proto::types::collection::{Collection, VanishedCollection}; +use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; use store::{ IndexKey, IterateParams, SerializeInfallible, U16_LEN, U32_LEN, U64_LEN, roaring::RoaringBitmap, @@ -22,10 +25,9 @@ use store::{ }, }; use trc::AddContext; - -use super::{ - ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent, CalendarPreferences, - alarm::CalendarAlarm, +use types::{ + collection::{Collection, VanishedCollection}, + field::CalendarField, }; pub trait ItipAutoExpunge: Sync + Send { @@ -47,14 +49,14 @@ impl ItipAutoExpunge for Server { account_id, collection: Collection::CalendarScheduling.into(), document_id: 0, - field: IDX_CREATED, + field: CalendarField::Created.into(), key: 0u64.serialize(), }, IndexKey { account_id, collection: Collection::CalendarScheduling.into(), document_id: u32::MAX, - field: IDX_CREATED, + field: CalendarField::Created.into(), key: now().saturating_sub(hold_period).serialize(), }, ) diff --git a/crates/groupware/src/contact/index.rs b/crates/groupware/src/contact/index.rs index 47a2bffb..e295542f 100644 --- a/crates/groupware/src/contact/index.rs +++ b/crates/groupware/src/contact/index.rs @@ -9,9 +9,8 @@ use calcard::vcard::VCardProperty; use common::storage::index::{ IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject, }; -use common::{IDX_EMAIL, IDX_UID}; -use jmap_proto::types::{collection::SyncCollection, value::AclGrant}; use std::collections::HashSet; +use types::{acl::AclGrant, collection::SyncCollection, field::ContactField}; use utils::sanitize_email; impl IndexableObject for AddressBook { @@ -27,7 +26,7 @@ impl IndexableObject for AddressBook { + self.name.len() as u32, }, IndexValue::LogContainer { - sync_collection: SyncCollection::AddressBook.into(), + sync_collection: SyncCollection::AddressBook, }, ] .into_iter() @@ -52,7 +51,7 @@ impl IndexableObject for &ArchivedAddressBook { + self.name.len() as u32, }, IndexValue::LogContainer { - sync_collection: SyncCollection::AddressBook.into(), + sync_collection: SyncCollection::AddressBook, }, ] .into_iter() @@ -69,11 +68,11 @@ impl IndexableObject for ContactCard { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: IDX_UID, + field: ContactField::Uid.into(), value: self.card.uid().into(), }, IndexValue::IndexList { - field: IDX_EMAIL, + field: ContactField::Email.into(), value: self .emails() .map(Into::into) @@ -88,7 +87,7 @@ impl IndexableObject for ContactCard { + self.size, }, IndexValue::LogItem { - sync_collection: SyncCollection::AddressBook.into(), + sync_collection: SyncCollection::AddressBook, prefix: None, }, ] @@ -100,11 +99,11 @@ impl IndexableObject for &ArchivedContactCard { fn index_values(&self) -> impl Iterator> { [ IndexValue::Index { - field: IDX_UID, + field: ContactField::Uid.into(), value: self.card.uid().into(), }, IndexValue::IndexList { - field: IDX_EMAIL, + field: ContactField::Email.into(), value: self .emails() .map(Into::into) @@ -119,7 +118,7 @@ impl IndexableObject for &ArchivedContactCard { + self.size, }, IndexValue::LogItem { - sync_collection: SyncCollection::AddressBook.into(), + sync_collection: SyncCollection::AddressBook, prefix: None, }, ] diff --git a/crates/groupware/src/contact/mod.rs b/crates/groupware/src/contact/mod.rs index 8642dd8a..cf819ca5 100644 --- a/crates/groupware/src/contact/mod.rs +++ b/crates/groupware/src/contact/mod.rs @@ -10,7 +10,7 @@ pub mod storage; use calcard::vcard::VCard; use common::DavName; use dav_proto::schema::request::DeadProperty; -use jmap_proto::types::{acl::Acl, value::AclGrant}; +use types::acl::{Acl, AclGrant}; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, diff --git a/crates/groupware/src/contact/storage.rs b/crates/groupware/src/contact/storage.rs index 8d1e76ec..caf29cfb 100644 --- a/crates/groupware/src/contact/storage.rs +++ b/crates/groupware/src/contact/storage.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard}; +use crate::DestroyArchive; use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; -use jmap_proto::types::collection::{Collection, VanishedCollection}; use store::write::{Archive, BatchBuilder, now}; use trc::AddContext; - -use crate::DestroyArchive; - -use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard}; +use types::collection::{Collection, VanishedCollection}; impl ContactCard { pub fn update<'x>( diff --git a/crates/groupware/src/file/index.rs b/crates/groupware/src/file/index.rs index 8ff34f85..3ac79539 100644 --- a/crates/groupware/src/file/index.rs +++ b/crates/groupware/src/file/index.rs @@ -6,7 +6,7 @@ use super::{ArchivedFileNode, FileNode}; use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject}; -use jmap_proto::types::{collection::SyncCollection, value::AclGrant}; +use types::{acl::AclGrant, collection::SyncCollection}; impl IndexableObject for FileNode { fn index_values(&self) -> impl Iterator> { @@ -18,7 +18,7 @@ impl IndexableObject for FileNode { }, IndexValue::LogItem { prefix: None, - sync_collection: SyncCollection::FileNode.into(), + sync_collection: SyncCollection::FileNode, }, IndexValue::Quota { used: self.size() }, ]); @@ -48,7 +48,7 @@ impl IndexableObject for &ArchivedFileNode { }, IndexValue::LogItem { prefix: None, - sync_collection: SyncCollection::FileNode.into(), + sync_collection: SyncCollection::FileNode, }, IndexValue::Quota { used: self.size() }, ]); diff --git a/crates/groupware/src/file/mod.rs b/crates/groupware/src/file/mod.rs index 31fda75e..447cfea7 100644 --- a/crates/groupware/src/file/mod.rs +++ b/crates/groupware/src/file/mod.rs @@ -8,8 +8,7 @@ pub mod index; pub mod storage; use dav_proto::schema::request::DeadProperty; -use jmap_proto::types::value::AclGrant; -use utils::BlobHash; +use types::{acl::AclGrant, blob_hash::BlobHash}; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, diff --git a/crates/groupware/src/file/storage.rs b/crates/groupware/src/file/storage.rs index 9ecd402b..9a6532b4 100644 --- a/crates/groupware/src/file/storage.rs +++ b/crates/groupware/src/file/storage.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{ArchivedFileNode, FileNode}; +use crate::DestroyArchive; use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; -use jmap_proto::types::collection::{Collection, VanishedCollection}; use store::write::{Archive, BatchBuilder, now}; use trc::AddContext; - -use crate::DestroyArchive; - -use super::{ArchivedFileNode, FileNode}; +use types::collection::{Collection, VanishedCollection}; impl FileNode { pub fn insert<'x>( diff --git a/crates/groupware/src/lib.rs b/crates/groupware/src/lib.rs index 602251cc..d59216ac 100644 --- a/crates/groupware/src/lib.rs +++ b/crates/groupware/src/lib.rs @@ -6,8 +6,8 @@ use calcard::common::timezone::Tz; use common::DavResources; -use jmap_proto::types::collection::{Collection, SyncCollection}; use percent_encoding::{AsciiSet, CONTROLS}; +use types::collection::{Collection, SyncCollection}; pub mod cache; pub mod calendar; diff --git a/crates/http/Cargo.toml b/crates/http/Cargo.toml index 91b37d09..7289ff0f 100644 --- a/crates/http/Cargo.toml +++ b/crates/http/Cargo.toml @@ -17,6 +17,7 @@ groupware = { path = "../groupware" } spam-filter = { path = "../spam-filter" } http_proto = { path = "../http-proto" } jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } directory = { path = "../directory" } services = { path = "../services" } smtp-proto = { version = "0.2" } diff --git a/crates/http/src/form/mod.rs b/crates/http/src/form/mod.rs index 1be59d7f..cf0fde45 100644 --- a/crates/http/src/form/mod.rs +++ b/crates/http/src/form/mod.rs @@ -30,7 +30,7 @@ use store::{ write::{BatchBuilder, BlobOp, now}, }; use trc::AddContext; -use utils::BlobHash; +use types::blob_hash::BlobHash; use x509_parser::nom::AsBytes; pub trait FormHandler: Sync + Send { diff --git a/crates/http/src/management/crypto.rs b/crates/http/src/management/crypto.rs index a11bfa47..4c5e4c97 100644 --- a/crates/http/src/management/crypto.rs +++ b/crates/http/src/management/crypto.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{future::Future, sync::Arc}; - use common::{Server, auth::AccessToken}; use directory::backend::internal::manage; use email::message::crypto::{ @@ -13,15 +11,16 @@ use email::message::crypto::{ EncryptionMethod, EncryptionParams, EncryptionType, try_parse_certs, }; use http_proto::*; -use jmap_proto::types::{collection::Collection, property::Property}; use mail_builder::encoders::base64::base64_encode_mime; use mail_parser::MessageParser; use serde_json::json; +use std::{future::Future, sync::Arc}; use store::{ Deserialize, Serialize, write::{AlignedBytes, Archive, Archiver, BatchBuilder}, }; use trc::AddContext; +use types::{collection::Collection, field::PrincipalField}; pub trait CryptoHandler: Sync + Send { fn handle_crypto_get( @@ -43,7 +42,7 @@ impl CryptoHandler for Server { access_token.primary_id(), Collection::Principal, 0, - Property::Parameters, + PrincipalField::EncryptionKeys.into(), ) .await? { @@ -96,7 +95,7 @@ impl CryptoHandler for Server { .with_account_id(access_token.primary_id()) .with_collection(Collection::Principal) .update_document(0) - .clear(Property::Parameters); + .clear(PrincipalField::EncryptionKeys); self.core.storage.data.write(batch.build_all()).await?; return Ok(JsonResponse::new(json!({ "data": (), @@ -146,7 +145,7 @@ impl CryptoHandler for Server { .with_account_id(access_token.primary_id()) .with_collection(Collection::Principal) .update_document(0) - .set(Property::Parameters, params); + .set(PrincipalField::EncryptionKeys, params); self.core.storage.data.write(batch.build_all()).await?; Ok(JsonResponse::new(json!({ diff --git a/crates/http/src/management/enterprise/undelete.rs b/crates/http/src/management/enterprise/undelete.rs index 2519a3c4..d28aa7e8 100644 --- a/crates/http/src/management/enterprise/undelete.rs +++ b/crates/http/src/management/enterprise/undelete.rs @@ -8,8 +8,6 @@ * */ -use std::str::FromStr; - use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use common::{Server, enterprise::undelete::DeletedBlob}; use directory::backend::internal::manage::ManageDirectory; @@ -18,13 +16,14 @@ use email::{ message::ingest::{EmailIngest, IngestEmail, IngestSource}, }; use hyper::Method; -use jmap_proto::types::collection::Collection; use mail_parser::{DateTime, MessageParser}; use serde_json::json; use std::future::Future; +use std::str::FromStr; use store::write::{BatchBuilder, BlobOp, ValueClass}; use trc::AddContext; -use utils::{BlobHash, url_params::UrlParams}; +use types::{blob_hash::BlobHash, collection::Collection}; +use utils::url_params::UrlParams; use http_proto::{request::decode_path_element, *}; diff --git a/crates/http/src/management/stores.rs b/crates/http/src/management/stores.rs index 15b76478..12804b21 100644 --- a/crates/http/src/management/stores.rs +++ b/crates/http/src/management/stores.rs @@ -19,7 +19,6 @@ use directory::{ use email::message::{ingest::EmailIngest, metadata::MessageData}; use http_proto::{request::decode_path_element, *}; use hyper::Method; -use jmap_proto::types::{collection::Collection, property::Property}; use serde_json::json; use services::task_manager::fts::FtsIndexTask; use std::future::Future; @@ -28,6 +27,10 @@ use store::{ write::{Archiver, BatchBuilder, ValueClass}, }; use trc::AddContext; +use types::{ + collection::Collection, + field::{EmailField, MailboxField}, +}; use utils::url_params::UrlParams; // SPDX-SnippetBegin @@ -354,7 +357,7 @@ pub async fn reset_imap_uids(server: &Server, account_id: u32) -> trc::Result<(u .with_changes(new_mailbox), ) .caused_by(trc::location!())? - .clear(Property::EmailIds); + .clear(MailboxField::UidCounter); server .store() .write(batch.build_all()) @@ -399,9 +402,9 @@ pub async fn reset_imap_uids(server: &Server, account_id: u32) -> trc::Result<(u .with_account_id(account_id) .with_collection(Collection::Email) .update_document(message_id) - .assert_value(ValueClass::Property(Property::Value.into()), &data) + .assert_value(ValueClass::Property(EmailField::Archive.into()), &data) .set( - Property::Value, + EmailField::Archive, Archiver::new(new_data) .serialize() .caused_by(trc::location!())?, diff --git a/crates/http/src/request.rs b/crates/http/src/request.rs index 3ccd9a5c..b074ed73 100644 --- a/crates/http/src/request.rs +++ b/crates/http/src/request.rs @@ -49,13 +49,11 @@ use jmap::{ blob::{download::BlobDownload, upload::BlobUpload}, websocket::upgrade::WebSocketUpgrade, }; -use jmap_proto::{ - request::{Request, capability::Session}, - types::{blob::BlobId, id::Id}, -}; +use jmap_proto::request::{Request, capability::Session}; use std::{net::IpAddr, sync::Arc}; use store::dispatch::lookup::KeyValue; use trc::SecurityEvent; +use types::{blob::BlobId, id::Id}; use utils::url_params::UrlParams; pub trait ParseHttp: Sync + Send { diff --git a/crates/imap-proto/Cargo.toml b/crates/imap-proto/Cargo.toml index 15175d58..41812d78 100644 --- a/crates/imap-proto/Cargo.toml +++ b/crates/imap-proto/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" resolver = "2" [dependencies] -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } store = { path = "../store" } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } ahash = { version = "0.8" } diff --git a/crates/imap-proto/src/lib.rs b/crates/imap-proto/src/lib.rs index 5465a2c4..693ae899 100644 --- a/crates/imap-proto/src/lib.rs +++ b/crates/imap-proto/src/lib.rs @@ -4,10 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::borrow::Cow; - -use jmap_proto::error::set::SetErrorType; use protocol::capability::Capability; +use std::borrow::Cow; pub mod parser; pub mod protocol; @@ -246,6 +244,7 @@ impl StatusResponse { } } +/* impl From for ResponseCode { fn from(value: SetErrorType) -> Self { match value { @@ -264,3 +263,4 @@ impl From for ResponseCode { } } } +*/ diff --git a/crates/imap-proto/src/protocol/acl.rs b/crates/imap-proto/src/protocol/acl.rs index afa56758..90fac3ab 100644 --- a/crates/imap-proto/src/protocol/acl.rs +++ b/crates/imap-proto/src/protocol/acl.rs @@ -33,13 +33,11 @@ */ -use std::fmt::Display; - -use jmap_proto::types::acl::Acl; - -use crate::utf7::utf7_encode; +use types::acl::Acl; use super::quoted_string; +use crate::utf7::utf7_encode; +use std::fmt::Display; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Rights { diff --git a/crates/imap-proto/src/protocol/mod.rs b/crates/imap-proto/src/protocol/mod.rs index c3585432..3a02bfd3 100644 --- a/crates/imap-proto/src/protocol/mod.rs +++ b/crates/imap-proto/src/protocol/mod.rs @@ -4,15 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{cmp::Ordering, fmt::Display}; - +use crate::{Command, ResponseCode, ResponseType, StatusResponse}; use ahash::AHashSet; use chrono::{DateTime, Utc}; - use compact_str::CompactString; -use jmap_proto::types::keyword::{ArchivedKeyword, Keyword}; - -use crate::{Command, ResponseCode, ResponseType, StatusResponse}; +use std::{cmp::Ordering, fmt::Display}; +use types::keyword::{ArchivedKeyword, Keyword}; pub mod acl; pub mod append; diff --git a/crates/imap/Cargo.toml b/crates/imap/Cargo.toml index 68b48972..e046282c 100644 --- a/crates/imap/Cargo.toml +++ b/crates/imap/Cargo.toml @@ -6,7 +6,7 @@ resolver = "2" [dependencies] imap_proto = { path = "../imap-proto" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } directory = { path = "../directory" } trc = { path = "../trc" } store = { path = "../store" } diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index e4f0cb25..3a7bd1a5 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -13,20 +13,19 @@ use common::{ listener::{SessionStream, limiter::InFlight}, sharing::EffectiveAcl, }; - use directory::backend::internal::manage::ManageDirectory; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess, mailbox::MailboxCacheAccess}, mailbox::INBOX_ID, }; use imap_proto::protocol::list::Attribute; -use jmap_proto::types::{acl::Acl, collection::Collection, id::Id, keyword::Keyword}; use parking_lot::Mutex; use std::{ collections::BTreeMap, sync::{Arc, atomic::Ordering}, }; use trc::AddContext; +use types::{acl::Acl, collection::Collection, id::Id, keyword::Keyword}; impl SessionData { pub async fn new( diff --git a/crates/imap/src/core/message.rs b/crates/imap/src/core/message.rs index 3a5f1ce3..4445558c 100644 --- a/crates/imap/src/core/message.rs +++ b/crates/imap/src/core/message.rs @@ -4,20 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{ + ImapUidToId, Mailbox, MailboxId, MailboxState, NextMailboxState, SelectedMailbox, SessionData, +}; +use crate::core::ImapId; use ahash::AHashMap; use common::listener::SessionStream; use email::cache::MessageCacheFetch; use imap_proto::protocol::{Sequence, expunge, select::Exists}; -use jmap_proto::types::{collection::Collection, property::Property}; use std::collections::BTreeMap; use store::{ValueKey, write::ValueClass}; use trc::AddContext; - -use crate::core::ImapId; - -use super::{ - ImapUidToId, Mailbox, MailboxId, MailboxState, NextMailboxState, SelectedMailbox, SessionData, -}; +use types::{collection::Collection, field::MailboxField}; impl SessionData { pub async fn fetch_messages( @@ -171,7 +169,7 @@ impl SessionData { account_id: mailbox.account_id, collection: Collection::Mailbox.into(), document_id: mailbox.mailbox_id, - class: ValueClass::Property(Property::EmailIds.into()), + class: ValueClass::Property(MailboxField::UidCounter.into()), }) .await .map(|v| (v + 1) as u32) diff --git a/crates/imap/src/op/acl.rs b/crates/imap/src/op/acl.rs index fab39f8b..5d6193f0 100644 --- a/crates/imap/src/op/acl.rs +++ b/crates/imap/src/op/acl.rs @@ -28,10 +28,13 @@ use imap_proto::{ }, receiver::Request, }; -use jmap_proto::types::{acl::Acl, collection::Collection, value::AclGrant}; use std::{sync::Arc, time::Instant}; use store::write::{AlignedBytes, Archive, BatchBuilder}; use trc::AddContext; +use types::{ + acl::{Acl, AclGrant}, + collection::Collection, +}; use utils::map::bitmap::Bitmap; impl Session { diff --git a/crates/imap/src/op/append.rs b/crates/imap/src/op/append.rs index a696e958..acadf04e 100644 --- a/crates/imap/src/op/append.rs +++ b/crates/imap/src/op/append.rs @@ -4,8 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{sync::Arc, time::Instant}; - +use super::{ImapContext, ToModSeq}; +use crate::{ + core::{ImapUidToId, MailboxId, SelectedMailbox, Session, SessionData}, + spawn_op, +}; +use common::listener::SessionStream; use directory::Permission; use email::message::ingest::{EmailIngest, IngestEmail, IngestSource}; use imap_proto::{ @@ -13,16 +17,13 @@ use imap_proto::{ protocol::{append::Arguments, select::HighestModSeq}, receiver::Request, }; - -use crate::{ - core::{ImapUidToId, MailboxId, SelectedMailbox, Session, SessionData}, - spawn_op, -}; -use common::listener::SessionStream; -use jmap_proto::types::{acl::Acl, keyword::Keyword, state::StateChange, type_state::DataType}; use mail_parser::MessageParser; - -use super::{ImapContext, ToModSeq}; +use std::{sync::Arc, time::Instant}; +use types::{ + acl::Acl, + keyword::Keyword, + type_state::{DataType, StateChange}, +}; impl Session { pub async fn handle_append(&mut self, request: Request) -> trc::Result<()> { @@ -119,7 +120,7 @@ impl SessionData { Ok(email) => { created_ids.push(ImapUidToId { uid: email.imap_uids[0], - id: email.id.document_id(), + id: email.document_id, }); last_change_id = Some(email.change_id); } diff --git a/crates/imap/src/op/copy_move.rs b/crates/imap/src/op/copy_move.rs index ee04fd97..2e3ff1c9 100644 --- a/crates/imap/src/op/copy_move.rs +++ b/crates/imap/src/op/copy_move.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::ImapContext; use crate::{ core::{MailboxId, SelectedMailbox, Session, SessionData}, spawn_op, @@ -14,29 +15,26 @@ use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, mailbox::{JUNK_ID, TRASH_ID, UidMailbox}, message::{ - bayes::EmailBayesTrain, copy::EmailCopy, ingest::EmailIngest, metadata::MessageData, + bayes::EmailBayesTrain, + copy::{CopyMessageError, EmailCopy}, + ingest::EmailIngest, + metadata::MessageData, }, }; use imap_proto::{ Command, ResponseCode, ResponseType, StatusResponse, protocol::copy_move::Arguments, receiver::Request, }; -use jmap_proto::{ - error::set::SetErrorType, - types::{ - acl::Acl, - collection::{Collection, VanishedCollection}, - state::StateChange, - type_state::DataType, - }, -}; use std::{sync::Arc, time::Instant}; use store::{ roaring::RoaringBitmap, write::{AlignedBytes, Archive, BatchBuilder, ValueClass}, }; - -use super::ImapContext; +use types::{ + acl::Acl, + collection::{Collection, VanishedCollection}, + type_state::{DataType, StateChange}, +}; impl Session { pub async fn handle_copy_move( @@ -399,12 +397,13 @@ impl SessionData { } } Err(err) => { - if err.type_ != SetErrorType::NotFound { - response.rtype = ResponseType::No; - response.code = Some(err.type_.into()); - if let Some(message) = err.description { - response.message = message; + match err { + CopyMessageError::OverQuota => { + response.rtype = ResponseType::No; + response.code = Some(ResponseCode::OverQuota); + response.message = "Mailbox quota exceeded".into(); } + CopyMessageError::NotFound => (), } continue; } diff --git a/crates/imap/src/op/create.rs b/crates/imap/src/op/create.rs index ca773584..90796bdd 100644 --- a/crates/imap/src/op/create.rs +++ b/crates/imap/src/op/create.rs @@ -19,10 +19,10 @@ use imap_proto::{ protocol::{create::Arguments, list::Attribute}, receiver::Request, }; -use jmap_proto::types::{acl::Acl, collection::Collection, id::Id}; use std::time::Instant; use store::write::BatchBuilder; use trc::AddContext; +use types::{acl::Acl, collection::Collection, id::Id}; impl Session { pub async fn handle_create(&mut self, requests: Vec>) -> trc::Result<()> { diff --git a/crates/imap/src/op/delete.rs b/crates/imap/src/op/delete.rs index 4deac9b0..ab39630f 100644 --- a/crates/imap/src/op/delete.rs +++ b/crates/imap/src/op/delete.rs @@ -4,20 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use super::ImapContext; use crate::{ core::{Session, SessionData}, spawn_op, }; use common::listener::SessionStream; use directory::Permission; -use email::mailbox::destroy::MailboxDestroy; +use email::mailbox::destroy::{MailboxDestroy, MailboxDestroyError}; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::delete::Arguments, receiver::Request, }; - -use super::ImapContext; +use std::time::Instant; impl Session { pub async fn handle_delete(&mut self, requests: Vec>) -> trc::Result<()> { @@ -80,10 +78,29 @@ impl SessionData { .await .imap_ctx(&arguments.tag, trc::location!())? { + let (code, message) = match err { + MailboxDestroyError::CannotDestroy => { + (ResponseCode::NoPerm, "You cannot delete system mailboxes") + } + MailboxDestroyError::Forbidden => ( + ResponseCode::NoPerm, + "You do not have enough permissions to delete this mailbox", + ), + MailboxDestroyError::HasChildren => { + (ResponseCode::HasChildren, "Mailbox has children") + } + MailboxDestroyError::HasEmails => (ResponseCode::HasChildren, "Mailbox has emails"), + MailboxDestroyError::NotFound => (ResponseCode::NonExistent, "Mailbox not found"), + MailboxDestroyError::AssertionFailed => ( + ResponseCode::Cannot, + "Another process is accessing this mailbox", + ), + }; + return Err(trc::ImapEvent::Error .into_err() - .details(err.description.unwrap_or("Delete failed".into())) - .code(ResponseCode::from(err.type_)) + .details(message) + .code(code) .id(arguments.tag)); } diff --git a/crates/imap/src/op/expunge.rs b/crates/imap/src/op/expunge.rs index 1c3f6975..8c3c2883 100644 --- a/crates/imap/src/op/expunge.rs +++ b/crates/imap/src/op/expunge.rs @@ -19,18 +19,18 @@ use imap_proto::{ parser::parse_sequence_set, receiver::{Request, Token}, }; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, VanishedCollection}, - keyword::Keyword, - property::Property, -}; use std::{sync::Arc, time::Instant}; use store::{ roaring::RoaringBitmap, write::{BatchBuilder, TagValue}, }; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, VanishedCollection}, + field::EmailField, + keyword::Keyword, +}; impl Session { pub async fn handle_expunge( @@ -194,7 +194,7 @@ impl SessionData { batch .custom(ObjectIndexBuilder::<_, ()>::new().with_current(metadata)) .caused_by(trc::location!())? - .tag(Property::MailboxIds, TagValue::Id(TOMBSTONE_ID)) + .tag(EmailField::MailboxIds, TagValue::Id(TOMBSTONE_ID)) .commit_point(); } else { // Untag message from this mailbox and remove Deleted flag diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs index fa351705..2f02bf71 100644 --- a/crates/imap/src/op/fetch.rs +++ b/crates/imap/src/op/fetch.rs @@ -32,13 +32,6 @@ use imap_proto::{ }, receiver::Request, }; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection, VanishedCollection}, - id::Id, - keyword::Keyword, - property::Property, -}; use mail_parser::{ ArchivedAddress, ArchivedHeaderName, ArchivedHeaderValue, core::rkyv::ArchivedGetHeader, }; @@ -48,6 +41,13 @@ use store::{ rkyv::rend::u16_le, write::BatchBuilder, }; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection, VanishedCollection}, + field::EmailField, + id::Id, + keyword::Keyword, +}; impl Session { pub async fn handle_fetch(&mut self, requests: Vec>) -> trc::Result<()> { @@ -156,7 +156,7 @@ impl SessionData { .store() .changes( account_id, - SyncCollection::Email, + SyncCollection::Email.into(), Query::from_modseq(changed_since), ) .await @@ -192,7 +192,7 @@ impl SessionData { .store() .vanished::<(u32, u32)>( account_id, - VanishedCollection::Email, + VanishedCollection::Email.into(), Query::from_modseq(changed_since), ) .await @@ -328,7 +328,7 @@ impl SessionData { account_id, Collection::Email, id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await .imap_ctx(&arguments.tag, trc::location!())?, diff --git a/crates/imap/src/op/idle.rs b/crates/imap/src/op/idle.rs index 9085113a..bc0058c0 100644 --- a/crates/imap/src/op/idle.rs +++ b/crates/imap/src/op/idle.rs @@ -20,11 +20,11 @@ use imap_proto::{ }, receiver::Request, }; -use jmap_proto::types::{collection::SyncCollection, type_state::DataType}; use std::{sync::Arc, time::Instant}; use store::query::log::Query; use tokio::io::AsyncReadExt; use trc::AddContext; +use types::{collection::SyncCollection, type_state::DataType}; use utils::map::bitmap::Bitmap; impl Session { @@ -204,7 +204,7 @@ impl SessionData { .store() .changes( mailbox.id.account_id, - SyncCollection::Email, + SyncCollection::Email.into(), Query::Since(modseq), ) .await diff --git a/crates/imap/src/op/rename.rs b/crates/imap/src/op/rename.rs index f06f2b46..45154ac8 100644 --- a/crates/imap/src/op/rename.rs +++ b/crates/imap/src/op/rename.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - use crate::{ core::{Session, SessionData}, spawn_op, @@ -15,9 +13,10 @@ use directory::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::rename::Arguments, receiver::Request, }; -use jmap_proto::types::{acl::Acl, collection::Collection}; +use std::time::Instant; use store::write::BatchBuilder; use trc::AddContext; +use types::{acl::Acl, collection::Collection}; use super::ImapContext; diff --git a/crates/imap/src/op/search.rs b/crates/imap/src/op/search.rs index a425dacd..e19ece14 100644 --- a/crates/imap/src/op/search.rs +++ b/crates/imap/src/op/search.rs @@ -20,12 +20,6 @@ use imap_proto::{ }, receiver::Request, }; -use jmap_proto::types::{ - collection::{Collection, SyncCollection}, - id::Id, - keyword::Keyword, - property::Property, -}; use mail_parser::HeaderName; use nlp::language::Language; use std::{sync::Arc, time::Instant}; @@ -38,6 +32,12 @@ use store::{ }; use tokio::sync::watch; use trc::AddContext; +use types::{ + collection::{Collection, SyncCollection}, + field::EmailField, + id::Id, + keyword::Keyword, +}; impl Session { pub async fn handle_search( @@ -155,25 +155,25 @@ impl SessionData { sort.into_iter() .map(|item| match item.sort { search::Sort::Arrival => { - query::Comparator::field(Property::ReceivedAt, item.ascending) + query::Comparator::field(EmailField::ReceivedAt, item.ascending) } search::Sort::Cc => { - query::Comparator::field(Property::Cc, item.ascending) + query::Comparator::field(EmailField::Cc, item.ascending) } search::Sort::Date => { - query::Comparator::field(Property::SentAt, item.ascending) + query::Comparator::field(EmailField::SentAt, item.ascending) } search::Sort::From | search::Sort::DisplayFrom => { - query::Comparator::field(Property::From, item.ascending) + query::Comparator::field(EmailField::From, item.ascending) } search::Sort::Size => { - query::Comparator::field(Property::Size, item.ascending) + query::Comparator::field(EmailField::Size, item.ascending) } search::Sort::Subject => { - query::Comparator::field(Property::Subject, item.ascending) + query::Comparator::field(EmailField::Subject, item.ascending) } search::Sort::To | search::Sort::DisplayTo => { - query::Comparator::field(Property::To, item.ascending) + query::Comparator::field(EmailField::To, item.ascending) } }) .collect::>(), @@ -464,7 +464,7 @@ impl SessionData { } search::Filter::Before(date) => { filters.push(query::Filter::lt( - Property::ReceivedAt, + EmailField::ReceivedAt, (date as u64).serialize(), )); } @@ -491,16 +491,16 @@ impl SessionData { ))); } search::Filter::Larger(size) => { - filters.push(query::Filter::gt(Property::Size, size.serialize())); + filters.push(query::Filter::gt(EmailField::Size, size.serialize())); } search::Filter::On(date) => { filters.push(query::Filter::And); filters.push(query::Filter::ge( - Property::ReceivedAt, + EmailField::ReceivedAt, (date as u64).serialize(), )); filters.push(query::Filter::lt( - Property::ReceivedAt, + EmailField::ReceivedAt, ((date + 86400) as u64).serialize(), )); filters.push(query::Filter::End); @@ -512,36 +512,36 @@ impl SessionData { } search::Filter::SentBefore(date) => { filters.push(query::Filter::lt( - Property::SentAt, + EmailField::SentAt, (date as u64).serialize(), )); } search::Filter::SentOn(date) => { filters.push(query::Filter::And); filters.push(query::Filter::ge( - Property::SentAt, + EmailField::SentAt, (date as u64).serialize(), )); filters.push(query::Filter::lt( - Property::SentAt, + EmailField::SentAt, ((date + 86400) as u64).serialize(), )); filters.push(query::Filter::End); } search::Filter::SentSince(date) => { filters.push(query::Filter::ge( - Property::SentAt, + EmailField::SentAt, (date as u64).serialize(), )); } search::Filter::Since(date) => { filters.push(query::Filter::ge( - Property::ReceivedAt, + EmailField::ReceivedAt, (date as u64).serialize(), )); } search::Filter::Smaller(size) => { - filters.push(query::Filter::lt(Property::Size, size.serialize())); + filters.push(query::Filter::lt(EmailField::Size, size.serialize())); } search::Filter::Unanswered => { filters.push(query::Filter::is_in_set(RoaringBitmap::from_iter( @@ -603,7 +603,7 @@ impl SessionData { filters.push(query::Filter::is_in_set(self.get_recent(&mailbox.id))); filters.push(query::Filter::Not); filters.push(query::Filter::is_in_bitmap( - Property::Keywords, + EmailField::Keywords, Keyword::Seen, )); filters.push(query::Filter::End); @@ -616,13 +616,13 @@ impl SessionData { } search::Filter::Older(secs) => { filters.push(query::Filter::le( - Property::ReceivedAt, + EmailField::ReceivedAt, now().saturating_sub(secs as u64).serialize(), )); } search::Filter::Younger(secs) => { filters.push(query::Filter::ge( - Property::ReceivedAt, + EmailField::ReceivedAt, now().saturating_sub(secs as u64).serialize(), )); } @@ -633,7 +633,7 @@ impl SessionData { .store() .changes( mailbox.id.account_id, - SyncCollection::Email, + SyncCollection::Email.into(), Query::from_modseq(modseq), ) .await? diff --git a/crates/imap/src/op/select.rs b/crates/imap/src/op/select.rs index 42210b12..5f15e519 100644 --- a/crates/imap/src/op/select.rs +++ b/crates/imap/src/op/select.rs @@ -4,8 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{sync::Arc, time::Instant}; - +use super::{ImapContext, ToModSeq}; +use crate::core::{SavedSearch, SelectedMailbox, Session, State}; +use common::listener::SessionStream; use directory::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, @@ -16,12 +17,8 @@ use imap_proto::{ }, receiver::Request, }; - -use crate::core::{SavedSearch, SelectedMailbox, Session, State}; -use common::listener::SessionStream; -use jmap_proto::types::id::Id; - -use super::{ImapContext, ToModSeq}; +use std::{sync::Arc, time::Instant}; +use types::id::Id; impl Session { pub async fn handle_select(&mut self, request: Request) -> trc::Result<()> { diff --git a/crates/imap/src/op/status.rs b/crates/imap/src/op/status.rs index 067ee13e..f2b3d97e 100644 --- a/crates/imap/src/op/status.rs +++ b/crates/imap/src/op/status.rs @@ -19,13 +19,13 @@ use imap_proto::{ protocol::status::{Status, StatusItem, StatusItemType}, receiver::Request, }; -use jmap_proto::types::{collection::Collection, id::Id, keyword::Keyword, property::Property}; use std::time::Instant; use store::{Deserialize, U32_LEN}; use store::{ IndexKeyPrefix, IterateParams, roaring::RoaringBitmap, write::key::DeserializeBigEndian, }; use trc::AddContext; +use types::{collection::Collection, field::EmailField, id::Id, keyword::Keyword}; impl Session { pub async fn handle_status(&mut self, requests: Vec>) -> trc::Result<()> { @@ -302,12 +302,12 @@ impl SessionData { IndexKeyPrefix { account_id, collection: Collection::Email.into(), - field: Property::Size.into(), + field: EmailField::Size.into(), }, IndexKeyPrefix { account_id, collection: Collection::Email.into(), - field: u8::from(Property::Size) + 1, + field: u8::from(EmailField::Size) + 1, }, ) .ascending() diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs index f96b5af5..61b51ef4 100644 --- a/crates/imap/src/op/store.rs +++ b/crates/imap/src/op/store.rs @@ -22,17 +22,17 @@ use imap_proto::{ }, receiver::Request, }; -use jmap_proto::types::{ - acl::Acl, - collection::{Collection, SyncCollection}, - keyword::Keyword, -}; use std::{sync::Arc, time::Instant}; use store::{ query::log::{Change, Query}, write::{BatchBuilder, ValueClass}, }; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection}, + keyword::Keyword, +}; impl Session { pub async fn handle_store( @@ -114,7 +114,7 @@ impl SessionData { .store() .changes( account_id, - SyncCollection::Email, + SyncCollection::Email.into(), Query::from_modseq(unchanged_since), ) .await diff --git a/crates/imap/src/op/subscribe.rs b/crates/imap/src/op/subscribe.rs index e49eaab2..95cd88f9 100644 --- a/crates/imap/src/op/subscribe.rs +++ b/crates/imap/src/op/subscribe.rs @@ -4,20 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use super::ImapContext; use crate::{ core::{Session, SessionData}, spawn_op, }; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; - use directory::Permission; use imap_proto::{Command, ResponseCode, StatusResponse, receiver::Request}; -use jmap_proto::types::collection::Collection; +use std::time::Instant; use store::write::BatchBuilder; - -use super::ImapContext; +use types::collection::Collection; impl Session { pub async fn handle_subscribe( diff --git a/crates/jmap-proto/Cargo.toml b/crates/jmap-proto/Cargo.toml index b51f464b..1b9656da 100644 --- a/crates/jmap-proto/Cargo.toml +++ b/crates/jmap-proto/Cargo.toml @@ -7,6 +7,7 @@ resolver = "2" [dependencies] store = { path = "../store" } utils = { path = "../utils" } +types = { path = "../types" } trc = { path = "../trc" } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } fast-float = "0.2.0" diff --git a/crates/jmap-proto/src/error/set.rs b/crates/jmap-proto/src/error/set.rs index 99ab37ff..d5497d66 100644 --- a/crates/jmap-proto/src/error/set.rs +++ b/crates/jmap-proto/src/error/set.rs @@ -6,7 +6,9 @@ use std::borrow::Cow; -use crate::types::{id::Id, property::Property}; +use types::id::Id; + +use crate::types::property::Property; #[derive(Debug, Clone, serde::Serialize)] pub struct SetError { diff --git a/crates/jmap-proto/src/method/changes.rs b/crates/jmap-proto/src/method/changes.rs index 1117ac1d..ebf36bfe 100644 --- a/crates/jmap-proto/src/method/changes.rs +++ b/crates/jmap-proto/src/method/changes.rs @@ -4,13 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use compact_str::format_compact; - use crate::{ parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::{RequestProperty, method::MethodObject}, - types::{id::Id, property::Property, state::State}, + types::{property::Property, state::State}, }; +use compact_str::format_compact; +use types::id::Id; #[derive(Debug, Clone)] pub struct ChangesRequest { diff --git a/crates/jmap-proto/src/method/copy.rs b/crates/jmap-proto/src/method/copy.rs index 666b9a41..e07caaae 100644 --- a/crates/jmap-proto/src/method/copy.rs +++ b/crates/jmap-proto/src/method/copy.rs @@ -4,21 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use compact_str::format_compact; -use serde::Serialize; -use utils::map::vec_map::VecMap; - use crate::{ error::set::SetError, parser::{JsonObjectParser, Token, json::Parser}, request::{RequestProperty, method::MethodObject, reference::MaybeReference}, types::{ - blob::BlobId, - id::Id, state::State, value::{Object, SetValue, Value}, }, }; +use compact_str::format_compact; +use serde::Serialize; +use types::{blob::BlobId, id::Id}; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone)] pub struct CopyRequest { diff --git a/crates/jmap-proto/src/method/get.rs b/crates/jmap-proto/src/method/get.rs index 16a1e364..cfe62ac0 100644 --- a/crates/jmap-proto/src/method/get.rs +++ b/crates/jmap-proto/src/method/get.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use compact_str::format_compact; - use crate::{ object::{blob, email}, parser::{JsonObjectParser, Token, json::Parser}, @@ -16,13 +14,13 @@ use crate::{ }, types::{ any_id::AnyId, - blob::BlobId, - id::Id, property::Property, state::State, value::{Object, Value}, }, }; +use compact_str::format_compact; +use types::{blob::BlobId, id::Id}; #[derive(Debug, Clone)] pub struct GetRequest { diff --git a/crates/jmap-proto/src/method/import.rs b/crates/jmap-proto/src/method/import.rs index 35e8be10..4701c519 100644 --- a/crates/jmap-proto/src/method/import.rs +++ b/crates/jmap-proto/src/method/import.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use utils::map::vec_map::VecMap; - use crate::{ error::set::SetError, parser::{JsonObjectParser, Token, json::Parser}, @@ -15,15 +13,14 @@ use crate::{ }, response::Response, types::{ - blob::BlobId, date::UTCDate, - id::Id, - keyword::Keyword, property::Property, state::State, value::{Object, SetValueMap, Value}, }, }; +use types::{blob::BlobId, id::Id, keyword::Keyword}; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone)] pub struct ImportEmailRequest { diff --git a/crates/jmap-proto/src/method/lookup.rs b/crates/jmap-proto/src/method/lookup.rs index 6584135a..530c5983 100644 --- a/crates/jmap-proto/src/method/lookup.rs +++ b/crates/jmap-proto/src/method/lookup.rs @@ -4,13 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use utils::map::vec_map::VecMap; - use crate::{ parser::{JsonObjectParser, Token, json::Parser}, request::RequestProperty, - types::{MaybeUnparsable, blob::BlobId, id::Id, type_state::DataType}, + types::MaybeUnparsable, }; +use types::{blob::BlobId, id::Id, type_state::DataType}; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone)] pub struct BlobLookupRequest { diff --git a/crates/jmap-proto/src/method/parse.rs b/crates/jmap-proto/src/method/parse.rs index ffd4690c..afe9f01c 100644 --- a/crates/jmap-proto/src/method/parse.rs +++ b/crates/jmap-proto/src/method/parse.rs @@ -4,18 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use utils::map::vec_map::VecMap; - use crate::{ parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::RequestProperty, types::{ - blob::BlobId, - id::Id, property::Property, value::{Object, Value}, }, }; +use types::{blob::BlobId, id::Id}; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone)] pub struct ParseEmailRequest { diff --git a/crates/jmap-proto/src/method/query.rs b/crates/jmap-proto/src/method/query.rs index cc618e05..2a154347 100644 --- a/crates/jmap-proto/src/method/query.rs +++ b/crates/jmap-proto/src/method/query.rs @@ -4,17 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use compact_str::format_compact; -use store::fts::{FilterItem, FilterType, FtsFilter}; - use crate::{ object::{email, mailbox}, parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::{RequestProperty, RequestPropertyParser, method::MethodObject}, - types::{date::UTCDate, id::Id, keyword::Keyword, state::State}, + types::{date::UTCDate, state::State}, }; +use compact_str::format_compact; +use std::fmt::Display; +use store::fts::{FilterItem, FilterType, FtsFilter}; +use types::{id::Id, keyword::Keyword}; #[derive(Debug, Clone)] pub struct QueryRequest { diff --git a/crates/jmap-proto/src/method/query_changes.rs b/crates/jmap-proto/src/method/query_changes.rs index 470a06c4..344a82b4 100644 --- a/crates/jmap-proto/src/method/query_changes.rs +++ b/crates/jmap-proto/src/method/query_changes.rs @@ -4,15 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use compact_str::format_compact; - +use super::query::{Comparator, Filter, RequestArguments, parse_filter, parse_sort}; use crate::{ parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::{RequestProperty, RequestPropertyParser, method::MethodObject}, - types::{id::Id, state::State}, + types::state::State, }; - -use super::query::{Comparator, Filter, RequestArguments, parse_filter, parse_sort}; +use compact_str::format_compact; +use types::id::Id; #[derive(Debug, Clone)] pub struct QueryChangesRequest { diff --git a/crates/jmap-proto/src/method/search_snippet.rs b/crates/jmap-proto/src/method/search_snippet.rs index 77e5cd8e..da9bb693 100644 --- a/crates/jmap-proto/src/method/search_snippet.rs +++ b/crates/jmap-proto/src/method/search_snippet.rs @@ -4,16 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::query::{Filter, parse_filter}; use crate::{ parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::{ RequestProperty, reference::{MaybeReference, ResultReference}, }, - types::id::Id, }; - -use super::query::{Filter, parse_filter}; +use types::id::Id; #[derive(Debug, Clone)] pub struct GetSearchSnippetRequest { diff --git a/crates/jmap-proto/src/method/set.rs b/crates/jmap-proto/src/method/set.rs index 59d80ddb..7897e553 100644 --- a/crates/jmap-proto/src/method/set.rs +++ b/crates/jmap-proto/src/method/set.rs @@ -4,10 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use ahash::AHashMap; -use compact_str::format_compact; -use utils::map::{bitmap::Bitmap, vec_map::VecMap}; - +use super::ahash_is_empty; use crate::{ error::set::{InvalidProperty, SetError}, object::{email_submission, mailbox, sieve}, @@ -19,19 +16,17 @@ use crate::{ }, response::Response, types::{ - acl::Acl, any_id::AnyId, - blob::BlobId, date::UTCDate, - id::Id, - keyword::Keyword, property::{HeaderForm, ObjectProperty, Property, SetProperty}, state::State, value::{Object, SetValue, SetValueMap, Value}, }, }; - -use super::ahash_is_empty; +use ahash::AHashMap; +use compact_str::format_compact; +use types::{acl::Acl, blob::BlobId, id::Id, keyword::Keyword}; +use utils::map::{bitmap::Bitmap, vec_map::VecMap}; #[derive(Debug, Clone)] pub struct SetRequest { diff --git a/crates/jmap-proto/src/method/upload.rs b/crates/jmap-proto/src/method/upload.rs index 5c392473..781df334 100644 --- a/crates/jmap-proto/src/method/upload.rs +++ b/crates/jmap-proto/src/method/upload.rs @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use ahash::AHashMap; -use mail_parser::decoders::base64::base64_decode; -use utils::map::vec_map::VecMap; - +use super::ahash_is_empty; use crate::{ error::set::SetError, parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::{RequestProperty, reference::MaybeReference}, response::Response, - types::{blob::BlobId, id::Id}, }; - -use super::ahash_is_empty; +use ahash::AHashMap; +use mail_parser::decoders::base64::base64_decode; +use types::{blob::BlobId, id::Id}; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone)] pub struct BlobUploadRequest { diff --git a/crates/jmap-proto/src/method/validate.rs b/crates/jmap-proto/src/method/validate.rs index b29d0e6f..8db57fd1 100644 --- a/crates/jmap-proto/src/method/validate.rs +++ b/crates/jmap-proto/src/method/validate.rs @@ -4,14 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use serde::Serialize; - use crate::{ error::set::SetError, parser::{JsonObjectParser, Token, json::Parser}, request::RequestProperty, - types::{blob::BlobId, id::Id}, }; +use serde::Serialize; +use types::{blob::BlobId, id::Id}; #[derive(Debug, Clone)] pub struct ValidateSieveScriptRequest { diff --git a/crates/jmap-proto/src/object/email_submission.rs b/crates/jmap-proto/src/object/email_submission.rs index 2445467c..3b6ea20f 100644 --- a/crates/jmap-proto/src/object/email_submission.rs +++ b/crates/jmap-proto/src/object/email_submission.rs @@ -4,16 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use utils::map::vec_map::VecMap; - use crate::{ parser::{JsonObjectParser, json::Parser}, request::{RequestProperty, RequestPropertyParser, reference::MaybeReference}, - types::{ - id::Id, - value::{Object, SetValue}, - }, + types::value::{Object, SetValue}, }; +use types::id::Id; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone, Default)] pub struct SetArguments { diff --git a/crates/jmap-proto/src/object/mod.rs b/crates/jmap-proto/src/object/mod.rs index c668c2a3..9a3ffa7d 100644 --- a/crates/jmap-proto/src/object/mod.rs +++ b/crates/jmap-proto/src/object/mod.rs @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::types::{ + property::Property, + value::{Object, Value}, +}; use std::sync::Arc; - +use types::id::Id; use utils::{ erased_serde, json::{JsonPointerItem, JsonQueryable}, }; -use crate::types::{ - id::Id, - property::Property, - value::{Object, Value}, -}; - pub mod blob; pub mod email; pub mod email_submission; diff --git a/crates/jmap-proto/src/object/sieve.rs b/crates/jmap-proto/src/object/sieve.rs index 0ee89711..ce4a29b2 100644 --- a/crates/jmap-proto/src/object/sieve.rs +++ b/crates/jmap-proto/src/object/sieve.rs @@ -4,10 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use types::id::Id; + use crate::{ parser::json::Parser, request::{RequestProperty, RequestPropertyParser, reference::MaybeReference}, - types::id::Id, }; #[derive(Debug, Clone, Default)] diff --git a/crates/jmap-proto/src/request/capability.rs b/crates/jmap-proto/src/request/capability.rs index 93acca04..bcd6f5ca 100644 --- a/crates/jmap-proto/src/request/capability.rs +++ b/crates/jmap-proto/src/request/capability.rs @@ -4,14 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use compact_str::CompactString; -use utils::map::vec_map::VecMap; - use crate::{ parser::{JsonObjectParser, json::Parser}, response::serialize::serialize_hex, - types::{id::Id, type_state::DataType}, }; +use compact_str::CompactString; +use types::{id::Id, type_state::DataType}; +use utils::map::vec_map::VecMap; #[derive(Debug, Clone, serde::Serialize)] pub struct Session { diff --git a/crates/jmap-proto/src/request/reference.rs b/crates/jmap-proto/src/request/reference.rs index 234ff03c..21cf9596 100644 --- a/crates/jmap-proto/src/request/reference.rs +++ b/crates/jmap-proto/src/request/reference.rs @@ -5,13 +5,13 @@ */ use std::fmt::Display; - -use crate::{ - parser::{JsonObjectParser, Token, json::Parser}, - types::{id::Id, pointer::JSONPointer}, -}; +use types::id::Id; use super::method::MethodName; +use crate::{ + parser::{JsonObjectParser, Token, json::Parser}, + types::pointer::JSONPointer, +}; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub struct ResultReference { diff --git a/crates/jmap-proto/src/request/websocket.rs b/crates/jmap-proto/src/request/websocket.rs index 3e201cc8..dc7e720c 100644 --- a/crates/jmap-proto/src/request/websocket.rs +++ b/crates/jmap-proto/src/request/websocket.rs @@ -4,19 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{borrow::Cow, collections::HashMap}; - +use super::{Request, RequestProperty}; use crate::{ error::request::{RequestError, RequestErrorType, RequestLimitError}, parser::{JsonObjectParser, Token, json::Parser}, request::Call, response::{Response, ResponseMethod, serialize::serialize_hex}, - types::{any_id::AnyId, id::Id, state::State, type_state::DataType}, + types::{any_id::AnyId, state::State}, }; +use std::{borrow::Cow, collections::HashMap}; +use types::{id::Id, type_state::DataType}; use utils::map::vec_map::VecMap; -use super::{Request, RequestProperty}; - #[derive(Debug)] pub struct WebSocketRequest { pub id: Option, diff --git a/crates/jmap-proto/src/response/references.rs b/crates/jmap-proto/src/response/references.rs index caebbc2d..f5f44038 100644 --- a/crates/jmap-proto/src/response/references.rs +++ b/crates/jmap-proto/src/response/references.rs @@ -4,11 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::collections::HashMap; - -use compact_str::format_compact; -use utils::map::vec_map::VecMap; - +use super::{Response, ResponseMethod}; use crate::{ error::set::SetError, method::{copy::CopyResponse, set::SetResponse, upload::DataSourceObject}, @@ -18,13 +14,14 @@ use crate::{ }, types::{ any_id::AnyId, - id::Id, property::Property, value::{MaybePatchValue, Object, SetValue, Value}, }, }; - -use super::{Response, ResponseMethod}; +use compact_str::format_compact; +use std::collections::HashMap; +use types::id::Id; +use utils::map::vec_map::VecMap; enum EvalResult { Properties(Vec), @@ -535,17 +532,17 @@ impl EvalResult { #[cfg(test)] mod tests { - use std::collections::HashMap; + use types::id::Id; use crate::{ request::{Request, RequestMethod}, response::Response, types::{ - id::Id, property::Property, value::{SetValue, Value}, }, }; + use std::collections::HashMap; #[test] fn eval_references() { diff --git a/crates/jmap-proto/src/response/status.rs b/crates/jmap-proto/src/response/status.rs index 7cd8e3c5..8b51d89b 100644 --- a/crates/jmap-proto/src/response/status.rs +++ b/crates/jmap-proto/src/response/status.rs @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::types::state::State; +use types::{id::Id, type_state::DataType}; use utils::map::vec_map::VecMap; -use crate::types::{id::Id, state::State, type_state::DataType}; - #[derive(serde::Serialize, serde::Deserialize, Debug)] pub enum StateChangeType { StateChange, diff --git a/crates/jmap-proto/src/types/acl.rs b/crates/jmap-proto/src/types/acl.rs index 960ac977..6b3a45fb 100644 --- a/crates/jmap-proto/src/types/acl.rs +++ b/crates/jmap-proto/src/types/acl.rs @@ -4,45 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::{self, Display}; - -use utils::map::bitmap::BitmapItem; - use crate::parser::{JsonObjectParser, json::Parser}; - -#[derive( - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Copy, -)] -#[rkyv(compare(PartialEq), derive(Debug))] -#[repr(u8)] -pub enum Acl { - Read = 0, - Modify = 1, - Delete = 2, - ReadItems = 3, - AddItems = 4, - ModifyItems = 5, - RemoveItems = 6, - CreateChild = 7, - Administer = 8, - Submit = 9, - SchedulingReadFreeBusy = 10, - SchedulingInvite = 11, - SchedulingReply = 12, - ModifyItemsOwn = 13, - ModifyPrivateProperties = 14, - None = 15, -} +use types::acl::Acl; impl JsonObjectParser for Acl { fn parse(parser: &mut Parser<'_>) -> trc::Result @@ -76,80 +39,3 @@ impl JsonObjectParser for Acl { } } } - -impl Acl { - fn as_str(&self) -> &'static str { - match self { - Acl::Read => "read", - Acl::Modify => "modify", - Acl::Delete => "delete", - Acl::ReadItems => "readItems", - Acl::AddItems => "addItems", - Acl::ModifyItems => "modifyItems", - Acl::RemoveItems => "removeItems", - Acl::CreateChild => "createChild", - Acl::Administer => "administer", - Acl::Submit => "submit", - Acl::ModifyItemsOwn => "modifyItemsOwn", - Acl::ModifyPrivateProperties => "modifyPrivateProperties", - Acl::None => "", - Acl::SchedulingReadFreeBusy => "schedulingReadFreeBusy", - Acl::SchedulingInvite => "schedulingInvite", - Acl::SchedulingReply => "schedulingReply", - } - } -} - -impl Display for Acl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl serde::Serialize for Acl { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.as_str()) - } -} - -impl BitmapItem for Acl { - fn max() -> u64 { - Acl::None as u64 - } - - fn is_valid(&self) -> bool { - !matches!(self, Acl::None) - } -} - -impl From for u64 { - fn from(value: Acl) -> Self { - value as u64 - } -} - -impl From for Acl { - fn from(value: u64) -> Self { - match value { - 0 => Acl::Read, - 1 => Acl::Modify, - 2 => Acl::Delete, - 3 => Acl::ReadItems, - 4 => Acl::AddItems, - 5 => Acl::ModifyItems, - 6 => Acl::RemoveItems, - 7 => Acl::CreateChild, - 8 => Acl::Administer, - 9 => Acl::Submit, - 10 => Acl::SchedulingReadFreeBusy, - 11 => Acl::SchedulingInvite, - 12 => Acl::SchedulingReply, - 13 => Acl::ModifyItemsOwn, - 14 => Acl::ModifyPrivateProperties, - _ => Acl::None, - } - } -} diff --git a/crates/jmap-proto/src/types/any_id.rs b/crates/jmap-proto/src/types/any_id.rs index 75fee732..f8fa9eb4 100644 --- a/crates/jmap-proto/src/types/any_id.rs +++ b/crates/jmap-proto/src/types/any_id.rs @@ -4,12 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::value::Value; use crate::{ parser::{JsonObjectParser, json::Parser}, request::reference::MaybeReference, }; - -use super::{blob::BlobId, id::Id, value::Value}; +use types::{blob::BlobId, id::Id}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum AnyId { diff --git a/crates/jmap-proto/src/types/blob.rs b/crates/jmap-proto/src/types/blob.rs index e63e2664..23b839f3 100644 --- a/crates/jmap-proto/src/types/blob.rs +++ b/crates/jmap-proto/src/types/blob.rs @@ -4,35 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::borrow::Borrow; - -use store::BlobClass; -use utils::{ - BlobHash, - codec::{ - base32_custom::{Base32Reader, Base32Writer}, - leb128::{Leb128Iterator, Leb128Writer}, - }, -}; - use crate::parser::{JsonObjectParser, base32::JsonBase32Reader, json::Parser}; - -const B_LINKED: u8 = 0x10; -const B_RESERVED: u8 = 0x20; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct BlobId { - pub hash: BlobHash, - pub class: BlobClass, - pub section: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct BlobSection { - pub offset_start: usize, - pub size: usize, - pub encoding: u8, -} +use types::blob::BlobId; impl JsonObjectParser for BlobId { fn parse(parser: &mut Parser<'_>) -> trc::Result @@ -43,156 +16,3 @@ impl JsonObjectParser for BlobId { BlobId::from_iter(&mut it).ok_or_else(|| it.error()) } } - -impl BlobId { - pub fn new(hash: BlobHash, class: BlobClass) -> Self { - BlobId { - hash, - class, - section: None, - } - } - - pub fn new_section( - hash: BlobHash, - class: BlobClass, - offset_start: usize, - offset_end: usize, - encoding: impl Into, - ) -> Self { - BlobId { - hash, - class, - section: BlobSection { - offset_start, - size: offset_end - offset_start, - encoding: encoding.into(), - } - .into(), - } - } - - pub fn with_section_size(mut self, size: usize) -> Self { - self.section.get_or_insert_with(Default::default).size = size; - self - } - - pub fn from_base32(value: impl AsRef<[u8]>) -> Option { - BlobId::from_iter(&mut Base32Reader::new(value.as_ref())) - } - - #[allow(clippy::should_implement_trait)] - pub fn from_iter(it: &mut T) -> Option - where - T: Iterator + Leb128Iterator, - U: Borrow, - { - let class = *it.next()?.borrow(); - let encoding = class & 0x0F; - - let mut hash = BlobHash::default(); - for byte in hash.as_mut().iter_mut() { - *byte = *it.next()?.borrow(); - } - - let account_id: u32 = it.next_leb128()?; - - BlobId { - hash, - class: if (class & B_LINKED) != 0 { - BlobClass::Linked { - account_id, - collection: *it.next()?.borrow(), - document_id: it.next_leb128()?, - } - } else { - BlobClass::Reserved { - account_id, - expires: it.next_leb128()?, - } - }, - section: if encoding != 0 { - BlobSection { - offset_start: it.next_leb128()?, - size: it.next_leb128()?, - encoding: encoding - 1, - } - .into() - } else { - None - }, - } - .into() - } - - fn serialize_as(&self, writer: &mut impl Leb128Writer) { - let marker = self - .section - .as_ref() - .map_or(0, |section| section.encoding + 1) - | if matches!( - self, - BlobId { - class: BlobClass::Linked { .. }, - .. - } - ) { - B_LINKED - } else { - B_RESERVED - }; - - let _ = writer.write(&[marker]); - let _ = writer.write(self.hash.as_ref()); - - match &self.class { - BlobClass::Reserved { - account_id, - expires, - } => { - let _ = writer.write_leb128(*account_id); - let _ = writer.write_leb128(*expires); - } - BlobClass::Linked { - account_id, - collection, - document_id, - } => { - let _ = writer.write_leb128(*account_id); - let _ = writer.write(&[*collection]); - let _ = writer.write_leb128(*document_id); - } - } - - if let Some(section) = &self.section { - let _ = writer.write_leb128(section.offset_start); - let _ = writer.write_leb128(section.size); - } - } - - pub fn start_offset(&self) -> usize { - if let Some(section) = &self.section { - section.offset_start - } else { - 0 - } - } -} - -impl serde::Serialize for BlobId { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.to_string().as_str()) - } -} - -impl std::fmt::Display for BlobId { - #[allow(clippy::unused_io_amount)] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut writer = Base32Writer::with_capacity(std::mem::size_of::() * 2); - self.serialize_as(&mut writer); - f.write_str(&writer.finalize()) - } -} diff --git a/crates/jmap-proto/src/types/id.rs b/crates/jmap-proto/src/types/id.rs index 61daac48..b0f54795 100644 --- a/crates/jmap-proto/src/types/id.rs +++ b/crates/jmap-proto/src/types/id.rs @@ -4,24 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::ops::Deref; - -use utils::codec::base32_custom::{BASE32_ALPHABET, BASE32_INVERSE}; - use crate::parser::{JsonObjectParser, json::Parser}; - -use super::DocumentId; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] -pub struct Id { - id: u64, -} - -impl Default for Id { - fn default() -> Self { - Id { id: u64::MAX } - } -} +use types::id::Id; +use utils::codec::base32_custom::BASE32_INVERSE; impl JsonObjectParser for Id { fn parse(parser: &mut Parser<'_>) -> trc::Result @@ -39,192 +24,7 @@ impl JsonObjectParser for Id { } } - Ok(Id { id }) - } -} - -impl Id { - pub fn new(id: u64) -> Self { - Self { id } - } - - pub fn from_bytes(bytes: &[u8]) -> Option { - let mut id = 0; - - for &ch in bytes { - let i = BASE32_INVERSE[ch as usize]; - if i != u8::MAX { - id = (id << 5) | i as u64; - } else { - return None; - } - } - - Id { id }.into() - } - - pub fn singleton() -> Self { - Self::new(20080258862541) - } - - // From https://github.com/archer884/crockford by J/A - // License: MIT/Apache 2.0 - pub fn as_string(&self) -> String { - match self.id { - 0 => "a".to_string(), - mut n => { - // Used for the initial shift. - const QUAD_SHIFT: usize = 60; - const QUAD_RESET: usize = 4; - - // Used for all subsequent shifts. - const FIVE_SHIFT: usize = 59; - const FIVE_RESET: usize = 5; - - // After we clear the four most significant bits, the four least significant bits will be - // replaced with 0001. We can then know to stop once the four most significant bits are, - // likewise, 0001. - const STOP_BIT: u64 = 1 << QUAD_SHIFT; - - let mut buf = String::with_capacity(7); - - // Start by getting the most significant four bits. We get four here because these would be - // leftovers when starting from the least significant bits. In either case, tag the four least - // significant bits with our stop bit. - match (n >> QUAD_SHIFT) as usize { - // Eat leading zero-bits. This should not be done if the first four bits were non-zero. - // Additionally, we *must* do this in increments of five bits. - 0 => { - n <<= QUAD_RESET; - n |= 1; - n <<= n.leading_zeros() / 5 * 5; - } - - // Write value of first four bytes. - i => { - n <<= QUAD_RESET; - n |= 1; - buf.push(char::from(BASE32_ALPHABET[i])); - } - } - - // From now until we reach the stop bit, take the five most significant bits and then shift - // left by five bits. - while n != STOP_BIT { - buf.push(char::from(BASE32_ALPHABET[(n >> FIVE_SHIFT) as usize])); - n <<= FIVE_RESET; - } - - buf - } - } - } - - pub fn from_parts(prefix_id: DocumentId, doc_id: DocumentId) -> Id { - Id { - id: ((prefix_id as u64) << 32) | doc_id as u64, - } - } - - pub fn id(&self) -> u64 { - self.id - } - - pub fn document_id(&self) -> DocumentId { - (self.id & 0xFFFFFFFF) as DocumentId - } - - pub fn prefix_id(&self) -> DocumentId { - (self.id >> 32) as DocumentId - } - - pub fn is_singleton(&self) -> bool { - self.id == 20080258862541 - } - - pub fn is_valid(&self) -> bool { - self.id != u64::MAX - } -} - -impl From for Id { - fn from(id: u64) -> Self { - Id { id } - } -} - -impl From for Id { - fn from(id: u32) -> Self { - Id { id: id as u64 } - } -} - -impl From for u64 { - fn from(id: Id) -> Self { - id.id - } -} - -impl From<&Id> for u64 { - fn from(id: &Id) -> Self { - id.id - } -} - -impl From<(u32, u32)> for Id { - fn from(id: (u32, u32)) -> Self { - Id::from_parts(id.0, id.1) - } -} - -impl Deref for Id { - type Target = u64; - - fn deref(&self) -> &Self::Target { - &self.id - } -} - -impl AsRef for Id { - fn as_ref(&self) -> &u64 { - &self.id - } -} - -impl From for u32 { - fn from(id: Id) -> Self { - id.document_id() - } -} - -impl From for String { - fn from(id: Id) -> Self { - id.as_string() - } -} - -impl serde::Serialize for Id { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.as_string().as_str()) - } -} - -impl<'de> serde::Deserialize<'de> for Id { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Id::from_bytes(<&str>::deserialize(deserializer)?.as_bytes()) - .ok_or_else(|| serde::de::Error::custom("invalid JMAP ID")) - } -} - -impl std::fmt::Display for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.as_string()) + Ok(Id::new(id)) } } @@ -239,7 +39,7 @@ mod tests { 1, 10, 1000, - Id::singleton().id, + Id::singleton().id(), u64::MAX / 2, u64::MAX - 1, u64::MAX, diff --git a/crates/jmap-proto/src/types/keyword.rs b/crates/jmap-proto/src/types/keyword.rs index 4581e4bc..4882b013 100644 --- a/crates/jmap-proto/src/types/keyword.rs +++ b/crates/jmap-proto/src/types/keyword.rs @@ -4,68 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use store::{Serialize, write::TagValue}; - use crate::parser::{JsonObjectParser, json::Parser}; - -pub const SEEN: usize = 0; -pub const DRAFT: usize = 1; -pub const FLAGGED: usize = 2; -pub const ANSWERED: usize = 3; -pub const RECENT: usize = 4; -pub const IMPORTANT: usize = 5; -pub const PHISHING: usize = 6; -pub const JUNK: usize = 7; -pub const NOTJUNK: usize = 8; -pub const DELETED: usize = 9; -pub const FORWARDED: usize = 10; -pub const MDN_SENT: usize = 11; -pub const OTHER: usize = 12; - -#[derive( - rkyv::Serialize, - rkyv::Deserialize, - rkyv::Archive, - Debug, - Clone, - PartialEq, - Eq, - Hash, - Default, - serde::Serialize, -)] -#[serde(untagged)] -#[rkyv(derive(PartialEq), compare(PartialEq))] -pub enum Keyword { - #[serde(rename(serialize = "$seen"))] - Seen, - #[serde(rename(serialize = "$draft"))] - Draft, - #[serde(rename(serialize = "$flagged"))] - Flagged, - #[serde(rename(serialize = "$answered"))] - Answered, - #[default] - #[serde(rename(serialize = "$recent"))] - Recent, - #[serde(rename(serialize = "$important"))] - Important, - #[serde(rename(serialize = "$phishing"))] - Phishing, - #[serde(rename(serialize = "$junk"))] - Junk, - #[serde(rename(serialize = "$notjunk"))] - NotJunk, - #[serde(rename(serialize = "$deleted"))] - Deleted, - #[serde(rename(serialize = "$forwarded"))] - Forwarded, - #[serde(rename(serialize = "$mdnsent"))] - MdnSent, - Other(String), -} +use types::keyword::Keyword; impl JsonObjectParser for Keyword { fn parse(parser: &mut Parser<'_>) -> trc::Result @@ -116,227 +56,3 @@ impl JsonObjectParser for Keyword { } } } - -impl> From for Keyword { - fn from(value: T) -> Self { - let value = value.as_ref(); - if value - .as_bytes() - .first() - .is_some_and(|&ch| [b'$', b'\\'].contains(&ch)) - { - let mut hash = 0; - let mut shift = 0; - - for &ch in value.as_bytes().iter().skip(1) { - if shift < 128 { - hash |= (ch.to_ascii_lowercase() as u128) << shift; - shift += 8; - } else { - break; - } - } - - match hash { - 0x6e65_6573 => return Keyword::Seen, - 0x0074_6661_7264 => return Keyword::Draft, - 0x0064_6567_6761_6c66 => return Keyword::Flagged, - 0x6465_7265_7773_6e61 => return Keyword::Answered, - 0x746e_6563_6572 => return Keyword::Recent, - 0x0074_6e61_7472_6f70_6d69 => return Keyword::Important, - 0x676e_6968_7369_6870 => return Keyword::Phishing, - 0x6b6e_756a => return Keyword::Junk, - 0x006b_6e75_6a74_6f6e => return Keyword::NotJunk, - 0x0064_6574_656c_6564 => return Keyword::Deleted, - 0x0064_6564_7261_7772_6f66 => return Keyword::Forwarded, - 0x0074_6e65_736e_646d => return Keyword::MdnSent, - _ => (), - } - } - - Keyword::Other(String::from(value)) - } -} - -impl Display for Keyword { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Keyword::Seen => write!(f, "$seen"), - Keyword::Draft => write!(f, "$draft"), - Keyword::Flagged => write!(f, "$flagged"), - Keyword::Answered => write!(f, "$answered"), - Keyword::Recent => write!(f, "$recent"), - Keyword::Important => write!(f, "$important"), - Keyword::Phishing => write!(f, "$phishing"), - Keyword::Junk => write!(f, "$junk"), - Keyword::NotJunk => write!(f, "$notjunk"), - Keyword::Deleted => write!(f, "$deleted"), - Keyword::Forwarded => write!(f, "$forwarded"), - Keyword::MdnSent => write!(f, "$mdnsent"), - Keyword::Other(s) => write!(f, "{}", s), - } - } -} - -impl Display for ArchivedKeyword { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ArchivedKeyword::Seen => write!(f, "$seen"), - ArchivedKeyword::Draft => write!(f, "$draft"), - ArchivedKeyword::Flagged => write!(f, "$flagged"), - ArchivedKeyword::Answered => write!(f, "$answered"), - ArchivedKeyword::Recent => write!(f, "$recent"), - ArchivedKeyword::Important => write!(f, "$important"), - ArchivedKeyword::Phishing => write!(f, "$phishing"), - ArchivedKeyword::Junk => write!(f, "$junk"), - ArchivedKeyword::NotJunk => write!(f, "$notjunk"), - ArchivedKeyword::Deleted => write!(f, "$deleted"), - ArchivedKeyword::Forwarded => write!(f, "$forwarded"), - ArchivedKeyword::MdnSent => write!(f, "$mdnsent"), - ArchivedKeyword::Other(s) => write!(f, "{}", s), - } - } -} - -impl Serialize for Keyword { - fn serialize(&self) -> trc::Result> { - Ok(match self { - Keyword::Seen => vec![SEEN as u8], - Keyword::Draft => vec![DRAFT as u8], - Keyword::Flagged => vec![FLAGGED as u8], - Keyword::Answered => vec![ANSWERED as u8], - Keyword::Recent => vec![RECENT as u8], - Keyword::Important => vec![IMPORTANT as u8], - Keyword::Phishing => vec![PHISHING as u8], - Keyword::Junk => vec![JUNK as u8], - Keyword::NotJunk => vec![NOTJUNK as u8], - Keyword::Deleted => vec![DELETED as u8], - Keyword::Forwarded => vec![FORWARDED as u8], - Keyword::MdnSent => vec![MDN_SENT as u8], - Keyword::Other(string) => string.as_bytes().to_vec(), - }) - } -} - -impl Keyword { - pub fn id(&self) -> Result { - match self { - Keyword::Seen => Ok(SEEN as u32), - Keyword::Draft => Ok(DRAFT as u32), - Keyword::Flagged => Ok(FLAGGED as u32), - Keyword::Answered => Ok(ANSWERED as u32), - Keyword::Recent => Ok(RECENT as u32), - Keyword::Important => Ok(IMPORTANT as u32), - Keyword::Phishing => Ok(PHISHING as u32), - Keyword::Junk => Ok(JUNK as u32), - Keyword::NotJunk => Ok(NOTJUNK as u32), - Keyword::Deleted => Ok(DELETED as u32), - Keyword::Forwarded => Ok(FORWARDED as u32), - Keyword::MdnSent => Ok(MDN_SENT as u32), - Keyword::Other(string) => Err(string.as_str()), - } - } - - pub fn into_id(self) -> Result { - match self { - Keyword::Seen => Ok(SEEN as u32), - Keyword::Draft => Ok(DRAFT as u32), - Keyword::Flagged => Ok(FLAGGED as u32), - Keyword::Answered => Ok(ANSWERED as u32), - Keyword::Recent => Ok(RECENT as u32), - Keyword::Important => Ok(IMPORTANT as u32), - Keyword::Phishing => Ok(PHISHING as u32), - Keyword::Junk => Ok(JUNK as u32), - Keyword::NotJunk => Ok(NOTJUNK as u32), - Keyword::Deleted => Ok(DELETED as u32), - Keyword::Forwarded => Ok(FORWARDED as u32), - Keyword::MdnSent => Ok(MDN_SENT as u32), - Keyword::Other(string) => Err(string), - } - } - - pub fn try_from_id(id: usize) -> Result { - match id { - SEEN => Ok(Keyword::Seen), - DRAFT => Ok(Keyword::Draft), - FLAGGED => Ok(Keyword::Flagged), - ANSWERED => Ok(Keyword::Answered), - RECENT => Ok(Keyword::Recent), - IMPORTANT => Ok(Keyword::Important), - PHISHING => Ok(Keyword::Phishing), - JUNK => Ok(Keyword::Junk), - NOTJUNK => Ok(Keyword::NotJunk), - DELETED => Ok(Keyword::Deleted), - FORWARDED => Ok(Keyword::Forwarded), - MDN_SENT => Ok(Keyword::MdnSent), - _ => Err(id), - } - } -} - -impl ArchivedKeyword { - pub fn id(&self) -> Result { - match self { - ArchivedKeyword::Seen => Ok(SEEN as u32), - ArchivedKeyword::Draft => Ok(DRAFT as u32), - ArchivedKeyword::Flagged => Ok(FLAGGED as u32), - ArchivedKeyword::Answered => Ok(ANSWERED as u32), - ArchivedKeyword::Recent => Ok(RECENT as u32), - ArchivedKeyword::Important => Ok(IMPORTANT as u32), - ArchivedKeyword::Phishing => Ok(PHISHING as u32), - ArchivedKeyword::Junk => Ok(JUNK as u32), - ArchivedKeyword::NotJunk => Ok(NOTJUNK as u32), - ArchivedKeyword::Deleted => Ok(DELETED as u32), - ArchivedKeyword::Forwarded => Ok(FORWARDED as u32), - ArchivedKeyword::MdnSent => Ok(MDN_SENT as u32), - ArchivedKeyword::Other(string) => Err(string.as_str()), - } - } -} - -impl From for TagValue { - fn from(value: Keyword) -> Self { - match value.into_id() { - Ok(id) => TagValue::Id(id), - Err(string) => TagValue::Text(string.as_bytes().to_vec()), - } - } -} - -impl From<&Keyword> for TagValue { - fn from(value: &Keyword) -> Self { - match value.id() { - Ok(id) => TagValue::Id(id), - Err(string) => TagValue::Text(string.as_bytes().to_vec()), - } - } -} - -impl From<&ArchivedKeyword> for TagValue { - fn from(value: &ArchivedKeyword) -> Self { - match value.id() { - Ok(id) => TagValue::Id(id), - Err(string) => TagValue::Text(string.as_bytes().to_vec()), - } - } -} - -impl From<&ArchivedKeyword> for Keyword { - fn from(value: &ArchivedKeyword) -> Self { - match value { - ArchivedKeyword::Seen => Keyword::Seen, - ArchivedKeyword::Draft => Keyword::Draft, - ArchivedKeyword::Flagged => Keyword::Flagged, - ArchivedKeyword::Answered => Keyword::Answered, - ArchivedKeyword::Recent => Keyword::Recent, - ArchivedKeyword::Important => Keyword::Important, - ArchivedKeyword::Phishing => Keyword::Phishing, - ArchivedKeyword::Junk => Keyword::Junk, - ArchivedKeyword::NotJunk => Keyword::NotJunk, - ArchivedKeyword::Deleted => Keyword::Deleted, - ArchivedKeyword::Forwarded => Keyword::Forwarded, - ArchivedKeyword::MdnSent => Keyword::MdnSent, - ArchivedKeyword::Other(string) => Keyword::Other(string.as_str().into()), - } - } -} diff --git a/crates/jmap-proto/src/types/mod.rs b/crates/jmap-proto/src/types/mod.rs index a1db04c2..c78fe86c 100644 --- a/crates/jmap-proto/src/types/mod.rs +++ b/crates/jmap-proto/src/types/mod.rs @@ -9,7 +9,6 @@ use crate::parser::{JsonObjectParser, json::Parser}; pub mod acl; pub mod any_id; pub mod blob; -pub mod collection; pub mod date; pub mod id; pub mod keyword; @@ -19,9 +18,6 @@ pub mod state; pub mod type_state; pub mod value; -pub type DocumentId = u32; -pub type ChangeId = u64; - #[derive(Debug, Clone, PartialEq, Eq)] pub enum MaybeUnparsable { Value(V), diff --git a/crates/jmap-proto/src/types/property.rs b/crates/jmap-proto/src/types/property.rs index 50e2a0aa..1426799c 100644 --- a/crates/jmap-proto/src/types/property.rs +++ b/crates/jmap-proto/src/types/property.rs @@ -4,15 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::{Display, Formatter}; - +use super::value::Value; +use crate::parser::{JsonObjectParser, json::Parser}; use mail_parser::HeaderName; use serde::Serialize; -use store::write::ValueClass; - -use crate::parser::{JsonObjectParser, json::Parser}; - -use super::{acl::Acl, id::Id, keyword::Keyword, value::Value}; +use std::fmt::{Display, Formatter}; +use types::{acl::Acl, id::Id, keyword::Keyword}; #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum Property { @@ -1073,124 +1070,6 @@ impl Display for HeaderForm { } } -impl From<&Property> for u8 { - fn from(value: &Property) -> Self { - match value { - Property::IsActive => 0, - Property::IsEnabled => 1, - Property::IsSubscribed => 2, - Property::Keys => 3, - Property::Keywords => 4, - Property::Language => 5, - Property::Location => 6, - Property::MailboxIds => 7, - Property::MayDelete => 8, - Property::MdnBlobIds => 9, - Property::Members => 10, - Property::MessageId => 11, - Property::MyRights => 12, - Property::Name => 13, - Property::ParentId => 14, - Property::PartId => 15, - Property::Picture => 16, - Property::Preview => 17, - Property::Quota => 18, - Property::ReceivedAt => 19, - Property::References => 20, - Property::ReplyTo => 21, - Property::Role => 22, - Property::Secret => 23, - Property::SendAt => 24, - Property::Sender => 25, - Property::SentAt => 26, - Property::Size => 27, - Property::SortOrder => 28, - Property::Subject => 29, - Property::SubParts => 30, - Property::TextBody => 31, - Property::TextSignature => 32, - Property::ThreadId => 33, - Property::Timezone => 34, - Property::To => 35, - Property::ToDate => 36, - Property::TotalEmails => 37, - Property::TotalThreads => 38, - Property::Type => 39, - Property::Types => 40, - Property::UndoStatus => 41, - Property::UnreadEmails => 42, - Property::UnreadThreads => 43, - Property::Url => 44, - Property::VerificationCode => 45, - Property::Parameters => 46, - Property::Addresses => 47, - Property::P256dh => 48, - Property::Auth => 49, - Property::Value => 50, - Property::SmtpReply => 51, - Property::Delivered => 52, - Property::Displayed => 53, - Property::MailFrom => 54, - Property::RcptTo => 55, - Property::IsEncodingProblem => 56, - Property::IsTruncated => 57, - Property::MayReadItems => 58, - Property::MayAddItems => 59, - Property::MayRemoveItems => 60, - Property::MaySetSeen => 61, - Property::MaySetKeywords => 62, - Property::MayCreateChild => 63, - Property::MayRename => 64, - Property::MaySubmit => 65, - Property::Acl => 66, - Property::Aliases => 67, - Property::Attachments => 68, - Property::Bcc => 69, - Property::BlobId => 70, - Property::BodyStructure => 71, - Property::BodyValues => 72, - Property::Capabilities => 73, - Property::Cc => 74, - Property::Charset => 75, - Property::Cid => 76, - Property::DeliveryStatus => 77, - Property::Description => 78, - Property::DeviceClientId => 79, - Property::Disposition => 80, - Property::DsnBlobIds => 81, - Property::Email => 82, - Property::EmailId => 83, - Property::EmailIds => 84, - Property::Envelope => 85, - Property::Expires => 86, - Property::From => 87, - Property::FromDate => 88, - Property::HasAttachment => 89, - Property::Header(_) => 90, - Property::Headers => 91, - Property::HtmlBody => 92, - Property::HtmlSignature => 93, - Property::Id => 94, - Property::IdentityId => 95, - Property::InReplyTo => 96, - Property::_T(_) => 97, - Property::ResourceType => 98, - Property::Used => 99, - Property::HardLimit => 100, - Property::WarnLimit => 101, - Property::SoftLimit => 102, - Property::Scope => 103, - Property::Digest(_) | Property::Data(_) => unreachable!("invalid property"), - } - } -} - -impl From for u8 { - fn from(value: Property) -> Self { - (&value).into() - } -} - impl Property { pub fn from_header(header: &HeaderName) -> Self { match header { @@ -1225,9 +1104,3 @@ impl AsRef for Property { self } } - -impl From for ValueClass { - fn from(value: Property) -> Self { - ValueClass::Property(value.into()) - } -} diff --git a/crates/jmap-proto/src/types/state.rs b/crates/jmap-proto/src/types/state.rs index 45e80c7a..d515eced 100644 --- a/crates/jmap-proto/src/types/state.rs +++ b/crates/jmap-proto/src/types/state.rs @@ -4,17 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use utils::{ - codec::{ - base32_custom::Base32Writer, - leb128::{Leb128Iterator, Leb128Writer}, - }, - map::bitmap::Bitmap, -}; - use crate::parser::{JsonObjectParser, base32::JsonBase32Reader, json::Parser}; - -use super::{ChangeId, type_state::DataType}; +use types::ChangeId; +use utils::codec::{ + base32_custom::Base32Writer, + leb128::{Leb128Iterator, Leb128Writer}, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct JMAPIntermediateState { @@ -31,36 +26,6 @@ pub enum State { Intermediate(JMAPIntermediateState), } -#[derive(Debug, Clone, Copy)] -pub struct StateChange { - pub account_id: u32, - pub change_id: u64, - pub types: Bitmap, -} - -impl StateChange { - pub fn new(account_id: u32, change_id: u64) -> Self { - Self { - account_id, - change_id, - types: Default::default(), - } - } - - pub fn set_change(&mut self, type_state: DataType) { - self.types.insert(type_state); - } - - pub fn with_change(mut self, type_state: DataType) -> Self { - self.set_change(type_state); - self - } - - pub fn has_changes(&self) -> bool { - !self.types.is_empty() - } -} - impl From for State { fn from(change_id: ChangeId) -> Self { State::Exact(change_id) @@ -194,10 +159,9 @@ impl std::fmt::Display for State { #[cfg(test)] mod tests { - - use crate::{parser::json::Parser, types::ChangeId}; - use super::State; + use crate::parser::json::Parser; + use types::ChangeId; #[test] fn test_state_id() { diff --git a/crates/jmap-proto/src/types/type_state.rs b/crates/jmap-proto/src/types/type_state.rs index dba4c847..077f4629 100644 --- a/crates/jmap-proto/src/types/type_state.rs +++ b/crates/jmap-proto/src/types/type_state.rs @@ -4,102 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use serde::Serialize; -use utils::map::bitmap::{BitmapItem, ShortId}; - use crate::parser::{JsonObjectParser, json::Parser}; - -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize)] -#[repr(u8)] -pub enum DataType { - #[serde(rename = "Email")] - Email = 0, - #[serde(rename = "EmailDelivery")] - EmailDelivery = 1, - #[serde(rename = "EmailSubmission")] - EmailSubmission = 2, - #[serde(rename = "Mailbox")] - Mailbox = 3, - #[serde(rename = "Thread")] - Thread = 4, - #[serde(rename = "Identity")] - Identity = 5, - #[serde(rename = "Core")] - Core = 6, - #[serde(rename = "PushSubscription")] - PushSubscription = 7, - #[serde(rename = "SearchSnippet")] - SearchSnippet = 8, - #[serde(rename = "VacationResponse")] - VacationResponse = 9, - #[serde(rename = "MDN")] - Mdn = 10, - #[serde(rename = "Quota")] - Quota = 11, - #[serde(rename = "SieveScript")] - SieveScript = 12, - #[serde(rename = "Calendar")] - Calendar = 13, - #[serde(rename = "CalendarEvent")] - CalendarEvent = 14, - #[serde(rename = "CalendarEventNotification")] - CalendarEventNotification = 15, - #[serde(rename = "AddressBook")] - AddressBook = 16, - #[serde(rename = "ContactCard")] - ContactCard = 17, - #[serde(rename = "FileNode")] - FileNode = 18, - None = 19, -} - -impl BitmapItem for DataType { - fn max() -> u64 { - DataType::None as u64 - } - - fn is_valid(&self) -> bool { - !matches!(self, DataType::None) - } -} - -impl From for DataType { - fn from(value: u64) -> Self { - match value { - 0 => DataType::Email, - 1 => DataType::EmailDelivery, - 2 => DataType::EmailSubmission, - 3 => DataType::Mailbox, - 4 => DataType::Thread, - 5 => DataType::Identity, - 6 => DataType::Core, - 7 => DataType::PushSubscription, - 8 => DataType::SearchSnippet, - 9 => DataType::VacationResponse, - 10 => DataType::Mdn, - 11 => DataType::Quota, - 12 => DataType::SieveScript, - 13 => DataType::Calendar, - 14 => DataType::CalendarEvent, - 15 => DataType::CalendarEventNotification, - 16 => DataType::AddressBook, - 17 => DataType::ContactCard, - 18 => DataType::FileNode, - _ => { - debug_assert!(false, "Invalid type_state value: {}", value); - DataType::None - } - } - } -} - -impl From for u64 { - fn from(type_state: DataType) -> u64 { - type_state as u64 - } -} +use types::type_state::DataType; impl JsonObjectParser for DataType { fn parse(parser: &mut Parser<'_>) -> trc::Result @@ -136,100 +42,3 @@ impl JsonObjectParser for DataType { } } } - -impl TryFrom<&str> for DataType { - type Error = (); - - fn try_from(value: &str) -> Result { - let mut hash = 0; - let mut shift = 0; - - for &ch in value.as_bytes() { - if shift < 128 { - hash |= (ch as u128) << shift; - shift += 8; - } else { - return Err(()); - } - } - - match hash { - 0x006c_6961_6d45 => Ok(DataType::Email), - 0x0079_7265_7669_6c65_446c_6961_6d45 => Ok(DataType::EmailDelivery), - 0x006e_6f69_7373_696d_6275_536c_6961_6d45 => Ok(DataType::EmailSubmission), - 0x0078_6f62_6c69_614d => Ok(DataType::Mailbox), - 0x6461_6572_6854 => Ok(DataType::Thread), - 0x7974_6974_6e65_6449 => Ok(DataType::Identity), - 0x6572_6f43 => Ok(DataType::Core), - 0x6e6f_6974_7069_7263_7362_7553_6873_7550 => Ok(DataType::PushSubscription), - 0x0074_6570_7069_6e53_6863_7261_6553 => Ok(DataType::SearchSnippet), - 0x6573_6e6f_7073_6552_6e6f_6974_6163_6156 => Ok(DataType::VacationResponse), - 0x004e_444d => Ok(DataType::Mdn), - 0x0061_746f_7551 => Ok(DataType::Quota), - 0x0074_7069_7263_5365_7665_6953 => Ok(DataType::SieveScript), - _ => Err(()), - } - } -} - -impl DataType { - pub fn try_from_id(value: ShortId, is_container: bool) -> Option { - match (value.0, is_container) { - (0, false) => DataType::Email.into(), - (0, true) => DataType::Mailbox.into(), - (1, _) => DataType::Thread.into(), - (2, true) => DataType::Calendar.into(), - (2, false) => DataType::CalendarEvent.into(), - (3, true) => DataType::AddressBook.into(), - (3, false) => DataType::ContactCard.into(), - (4, _) => DataType::FileNode.into(), - (5, _) => DataType::Identity.into(), - (6, _) => DataType::EmailSubmission.into(), - (7, _) => DataType::SieveScript.into(), - _ => None, - } - } -} - -impl DataType { - pub fn as_str(&self) -> &'static str { - match self { - DataType::Email => "Email", - DataType::EmailDelivery => "EmailDelivery", - DataType::EmailSubmission => "EmailSubmission", - DataType::Mailbox => "Mailbox", - DataType::Thread => "Thread", - DataType::Identity => "Identity", - DataType::Core => "Core", - DataType::PushSubscription => "PushSubscription", - DataType::SearchSnippet => "SearchSnippet", - DataType::VacationResponse => "VacationResponse", - DataType::Mdn => "MDN", - DataType::Quota => "Quota", - DataType::SieveScript => "SieveScript", - DataType::Calendar => "Calendar", - DataType::CalendarEvent => "CalendarEvent", - DataType::CalendarEventNotification => "CalendarEventNotification", - DataType::AddressBook => "AddressBook", - DataType::ContactCard => "ContactCard", - DataType::FileNode => "FileNode", - DataType::None => "", - } - } -} - -impl Display for DataType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl<'de> serde::Deserialize<'de> for DataType { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - DataType::try_from(<&str>::deserialize(deserializer)?) - .map_err(|_| serde::de::Error::custom("invalid JMAP data type")) - } -} diff --git a/crates/jmap-proto/src/types/value.rs b/crates/jmap-proto/src/types/value.rs index 3de2d9e1..ed1dcb16 100644 --- a/crates/jmap-proto/src/types/value.rs +++ b/crates/jmap-proto/src/types/value.rs @@ -4,29 +4,23 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{borrow::Cow, fmt::Display}; - -use mail_parser::{Addr, DateTime, Group}; -use rkyv::{option::ArchivedOption, string::ArchivedString}; -use serde::Serialize; -use utils::{ - json::{JsonPointerItem, JsonQueryable}, - map::{bitmap::Bitmap, vec_map::VecMap}, +use super::{ + any_id::AnyId, + date::UTCDate, + property::{HeaderForm, IntoProperty, ObjectProperty, Property}, }; - use crate::{ parser::{Ignore, JsonObjectParser, Token, json::Parser}, request::reference::{MaybeReference, ResultReference}, }; - -use super::{ - acl::Acl, - any_id::AnyId, - blob::BlobId, - date::UTCDate, - id::Id, - keyword::Keyword, - property::{HeaderForm, IntoProperty, ObjectProperty, Property}, +use mail_parser::{Addr, DateTime, Group}; +use rkyv::{option::ArchivedOption, string::ArchivedString}; +use serde::Serialize; +use std::{borrow::Cow, fmt::Display}; +use types::{acl::AclGrant, blob::BlobId, id::Id, keyword::Keyword}; +use utils::{ + json::{JsonPointerItem, JsonQueryable}, + map::vec_map::VecMap, }; #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] @@ -50,23 +44,6 @@ pub enum Value { #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] pub struct Object(pub VecMap); -#[derive( - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, - Debug, - Clone, - PartialEq, - Eq, - Serialize, - Default, -)] -#[rkyv(compare(PartialEq), derive(Debug))] -pub struct AclGrant { - pub account_id: u32, - pub grants: Bitmap, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum SetValue { Value(Value), @@ -615,12 +592,3 @@ impl JsonQueryable for Value { } } } - -impl From<&ArchivedAclGrant> for AclGrant { - fn from(value: &ArchivedAclGrant) -> Self { - Self { - account_id: u32::from(value.account_id), - grants: (&value.grants).into(), - } - } -} diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index 8c262930..461ed2c0 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -9,6 +9,7 @@ store = { path = "../store" } nlp = { path = "../nlp" } http_proto = { path = "../http-proto" } jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } smtp = { path = "../smtp" } utils = { path = "../utils" } common = { path = "../common" } diff --git a/crates/jmap/src/api/auth.rs b/crates/jmap/src/api/auth.rs new file mode 100644 index 00000000..b365526e --- /dev/null +++ b/crates/jmap/src/api/auth.rs @@ -0,0 +1,360 @@ +use common::{Server, auth::AccessToken}; +use directory::{Permission, QueryParams, backend::internal::manage::ManageDirectory}; +use jmap_proto::{ + error::set::SetError, + request::RequestMethod, + types::{ + property::Property, + value::{MaybePatchValue, Value}, + }, +}; +use types::{ + acl::{Acl, AclGrant}, + collection::Collection, + id::Id, +}; +use utils::map::bitmap::Bitmap; + +pub trait JmapAcl { + fn acl_get( + &self, + value: &[AclGrant], + access_token: &AccessToken, + account_id: u32, + ) -> impl Future + Send; + fn acl_set( + &self, + changes: &mut Vec, + current: Option<&[AclGrant]>, + acl_changes: MaybePatchValue, + ) -> impl Future> + Send; + fn map_acl_set( + &self, + acl_set: Vec, + ) -> impl Future, SetError>> + Send; + fn map_acl_patch( + &self, + acl_patch: Vec, + ) -> impl Future), SetError>> + Send; +} + +pub trait JmapAuthorization { + fn assert_is_member(&self, account_id: Id) -> trc::Result<&Self>; + fn assert_has_jmap_permission(&self, request: &RequestMethod) -> trc::Result<()>; + fn assert_has_access(&self, to_account_id: Id, to_collection: Collection) + -> trc::Result<&Self>; +} + +impl JmapAcl for Server { + async fn acl_get( + &self, + value: &[AclGrant], + access_token: &AccessToken, + account_id: u32, + ) -> Value { + if access_token.is_member(account_id) + || value.iter().any(|item| { + access_token.is_member(item.account_id) && item.grants.contains(Acl::Administer) + }) + { + let mut acl_obj = jmap_proto::types::value::Object::with_capacity(value.len() / 2); + for item in value { + if let Some(name) = self + .store() + .get_principal(item.account_id) + .await + .unwrap_or_default() + { + acl_obj.append( + Property::_T(name.name), + item.grants + .map(|acl_item| Value::Text(acl_item.to_string())) + .collect::>(), + ); + } + } + + Value::Object(acl_obj) + } else { + Value::Null + } + } + + async fn acl_set( + &self, + changes: &mut Vec, + current: Option<&[AclGrant]>, + acl_changes: MaybePatchValue, + ) -> Result<(), SetError> { + match acl_changes { + MaybePatchValue::Value(Value::List(values)) => { + *changes = self.map_acl_set(values).await?; + } + MaybePatchValue::Patch(patch) => { + let (mut patch, is_update) = self.map_acl_patch(patch).await?; + if let Some(changes_) = current { + *changes = changes_.to_vec(); + } + + if let Some(is_set) = is_update { + if !patch.grants.is_empty() { + if let Some(acl_item) = changes + .iter_mut() + .find(|item| item.account_id == patch.account_id) + { + let item = patch.grants.pop().unwrap(); + if is_set { + acl_item.grants.insert(item); + } else { + acl_item.grants.remove(item); + if acl_item.grants.is_empty() { + changes.retain(|item| item.account_id != patch.account_id); + } + } + } else if is_set { + changes.push(patch); + } + } + } else if !patch.grants.is_empty() { + if let Some(acl_item) = changes + .iter_mut() + .find(|item| item.account_id == patch.account_id) + { + acl_item.grants = patch.grants; + } else { + changes.push(patch); + } + } else { + changes.retain(|item| item.account_id != patch.account_id); + } + } + _ => { + return Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description("Invalid ACL property.")); + } + } + Ok(()) + } + + async fn map_acl_set(&self, acl_set: Vec) -> Result, SetError> { + let mut acls = Vec::with_capacity(acl_set.len() / 2); + for item in acl_set.chunks_exact(2) { + if let (Value::Text(account_name), Value::UnsignedInt(grants)) = (&item[0], &item[1]) { + match self + .core + .storage + .directory + .query(QueryParams::name(account_name).with_return_member_of(false)) + .await + { + Ok(Some(principal)) => { + acls.push(AclGrant { + account_id: principal.id(), + grants: Bitmap::from(*grants), + }); + } + Ok(None) => { + return Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description(format!("Account {account_name} does not exist."))); + } + _ => { + return Err(SetError::forbidden() + .with_property(Property::Acl) + .with_description("Temporary server failure during lookup")); + } + } + } else { + return Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description("Invalid ACL value found.")); + } + } + + Ok(acls) + } + + async fn map_acl_patch( + &self, + acl_patch: Vec, + ) -> Result<(AclGrant, Option), SetError> { + if let (Value::Text(account_name), Value::UnsignedInt(grants)) = + (&acl_patch[0], &acl_patch[1]) + { + match self + .core + .storage + .directory + .query(QueryParams::name(account_name).with_return_member_of(false)) + .await + { + Ok(Some(principal)) => Ok(( + AclGrant { + account_id: principal.id(), + grants: Bitmap::from(*grants), + }, + acl_patch.get(2).map(|v| v.as_bool().unwrap_or(false)), + )), + Ok(None) => Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description(format!("Account {account_name} does not exist."))), + _ => Err(SetError::forbidden() + .with_property(Property::Acl) + .with_description("Temporary server failure during lookup")), + } + } else { + Err(SetError::invalid_properties() + .with_property(Property::Acl) + .with_description("Invalid ACL value found.")) + } + } +} + +impl JmapAuthorization for AccessToken { + fn assert_is_member(&self, account_id: Id) -> trc::Result<&Self> { + if self.is_member(account_id.document_id()) { + Ok(self) + } else { + Err(trc::JmapEvent::Forbidden + .into_err() + .details(format!("You are not an owner of account {}", account_id))) + } + } + + fn assert_has_access( + &self, + to_account_id: Id, + to_collection: Collection, + ) -> trc::Result<&Self> { + if self.has_access(to_account_id.document_id(), to_collection) { + Ok(self) + } else { + Err(trc::JmapEvent::Forbidden.into_err().details(format!( + "You do not have access to account {}", + to_account_id + ))) + } + } + + fn assert_has_jmap_permission(&self, request: &RequestMethod) -> trc::Result<()> { + let permission = match request { + RequestMethod::Get(m) => match &m.arguments { + jmap_proto::method::get::RequestArguments::Email(_) => Permission::JmapEmailGet, + jmap_proto::method::get::RequestArguments::Mailbox => Permission::JmapMailboxGet, + jmap_proto::method::get::RequestArguments::Thread => Permission::JmapThreadGet, + jmap_proto::method::get::RequestArguments::Identity => Permission::JmapIdentityGet, + jmap_proto::method::get::RequestArguments::EmailSubmission => { + Permission::JmapEmailSubmissionGet + } + jmap_proto::method::get::RequestArguments::PushSubscription => { + Permission::JmapPushSubscriptionGet + } + jmap_proto::method::get::RequestArguments::SieveScript => { + Permission::JmapSieveScriptGet + } + jmap_proto::method::get::RequestArguments::VacationResponse => { + Permission::JmapVacationResponseGet + } + jmap_proto::method::get::RequestArguments::Principal => { + Permission::JmapPrincipalGet + } + jmap_proto::method::get::RequestArguments::Quota => Permission::JmapQuotaGet, + jmap_proto::method::get::RequestArguments::Blob(_) => Permission::JmapBlobGet, + }, + RequestMethod::Set(m) => match &m.arguments { + jmap_proto::method::set::RequestArguments::Email => Permission::JmapEmailSet, + jmap_proto::method::set::RequestArguments::Mailbox(_) => Permission::JmapMailboxSet, + jmap_proto::method::set::RequestArguments::Identity => Permission::JmapIdentitySet, + jmap_proto::method::set::RequestArguments::EmailSubmission(_) => { + Permission::JmapEmailSubmissionSet + } + jmap_proto::method::set::RequestArguments::PushSubscription => { + Permission::JmapPushSubscriptionSet + } + jmap_proto::method::set::RequestArguments::SieveScript(_) => { + Permission::JmapSieveScriptSet + } + jmap_proto::method::set::RequestArguments::VacationResponse => { + Permission::JmapVacationResponseSet + } + }, + RequestMethod::Changes(m) => match m.arguments { + jmap_proto::method::changes::RequestArguments::Email => { + Permission::JmapEmailChanges + } + jmap_proto::method::changes::RequestArguments::Mailbox => { + Permission::JmapMailboxChanges + } + jmap_proto::method::changes::RequestArguments::Thread => { + Permission::JmapThreadChanges + } + jmap_proto::method::changes::RequestArguments::Identity => { + Permission::JmapIdentityChanges + } + jmap_proto::method::changes::RequestArguments::EmailSubmission => { + Permission::JmapEmailSubmissionChanges + } + jmap_proto::method::changes::RequestArguments::Quota => { + Permission::JmapQuotaChanges + } + }, + RequestMethod::Copy(m) => match m.arguments { + jmap_proto::method::copy::RequestArguments::Email => Permission::JmapEmailCopy, + }, + RequestMethod::CopyBlob(_) => Permission::JmapBlobCopy, + RequestMethod::ImportEmail(_) => Permission::JmapEmailImport, + RequestMethod::ParseEmail(_) => Permission::JmapEmailParse, + RequestMethod::QueryChanges(m) => match m.arguments { + jmap_proto::method::query::RequestArguments::Email(_) => { + Permission::JmapEmailQueryChanges + } + jmap_proto::method::query::RequestArguments::Mailbox(_) => { + Permission::JmapMailboxQueryChanges + } + jmap_proto::method::query::RequestArguments::EmailSubmission => { + Permission::JmapEmailSubmissionQueryChanges + } + jmap_proto::method::query::RequestArguments::SieveScript => { + Permission::JmapSieveScriptQueryChanges + } + jmap_proto::method::query::RequestArguments::Principal => { + Permission::JmapPrincipalQueryChanges + } + jmap_proto::method::query::RequestArguments::Quota => { + Permission::JmapQuotaQueryChanges + } + }, + RequestMethod::Query(m) => match m.arguments { + jmap_proto::method::query::RequestArguments::Email(_) => Permission::JmapEmailQuery, + jmap_proto::method::query::RequestArguments::Mailbox(_) => { + Permission::JmapMailboxQuery + } + jmap_proto::method::query::RequestArguments::EmailSubmission => { + Permission::JmapEmailSubmissionQuery + } + jmap_proto::method::query::RequestArguments::SieveScript => { + Permission::JmapSieveScriptQuery + } + jmap_proto::method::query::RequestArguments::Principal => { + Permission::JmapPrincipalQuery + } + jmap_proto::method::query::RequestArguments::Quota => Permission::JmapQuotaQuery, + }, + RequestMethod::SearchSnippet(_) => Permission::JmapSearchSnippet, + RequestMethod::ValidateScript(_) => Permission::JmapSieveScriptValidate, + RequestMethod::LookupBlob(_) => Permission::JmapBlobLookup, + RequestMethod::UploadBlob(_) => Permission::JmapBlobUpload, + RequestMethod::Echo(_) => Permission::JmapEcho, + RequestMethod::Error(_) => return Ok(()), + }; + + if self.has_permission(permission) { + Ok(()) + } else { + Err(trc::JmapEvent::Forbidden + .into_err() + .details("You are not authorized to perform this action")) + } + } +} diff --git a/crates/jmap/src/api/event_source.rs b/crates/jmap/src/api/event_source.rs index f6891b8d..c857a1fb 100644 --- a/crates/jmap/src/api/event_source.rs +++ b/crates/jmap/src/api/event_source.rs @@ -4,22 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; - use common::{LONG_1D_SLUMBER, Server, auth::AccessToken}; use http_body_util::{StreamBody, combinators::BoxBody}; +use http_proto::*; use hyper::{ StatusCode, body::{Bytes, Frame}, }; -use jmap_proto::{response::status::StateChangeResponse, types::type_state::DataType}; -use utils::map::bitmap::Bitmap; - -use http_proto::*; +use jmap_proto::response::status::StateChangeResponse; use std::future::Future; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use types::type_state::DataType; +use utils::map::bitmap::Bitmap; struct Ping { interval: Duration, diff --git a/crates/jmap/src/api/mod.rs b/crates/jmap/src/api/mod.rs index ea460fd1..8d2db484 100644 --- a/crates/jmap/src/api/mod.rs +++ b/crates/jmap/src/api/mod.rs @@ -14,6 +14,7 @@ use jmap_proto::{ use crate::blob::UploadResponse; +pub mod auth; pub mod event_source; pub mod request; pub mod session; diff --git a/crates/jmap/src/api/request.rs b/crates/jmap/src/api/request.rs index 60f7a33e..cbcd8c1e 100644 --- a/crates/jmap/src/api/request.rs +++ b/crates/jmap/src/api/request.rs @@ -6,20 +6,8 @@ use std::{sync::Arc, time::Instant}; -use common::{Server, auth::AccessToken}; -use http_proto::HttpSessionData; -use jmap_proto::{ - method::{ - get, query, - set::{self}, - }, - request::{Call, Request, RequestMethod, method::MethodName}, - response::{Response, ResponseMethod}, - types::collection::Collection, -}; -use trc::JmapEvent; - use crate::{ + api::auth::JmapAuthorization, blob::{copy::BlobCopy, get::BlobOperations, upload::BlobUpload}, changes::{get::ChangesLookup, query::QueryChanges}, email::{ @@ -39,6 +27,18 @@ use crate::{ thread::get::ThreadGet, vacation::{get::VacationResponseGet, set::VacationResponseSet}, }; +use common::{Server, auth::AccessToken}; +use http_proto::HttpSessionData; +use jmap_proto::{ + method::{ + get, query, + set::{self}, + }, + request::{Call, Request, RequestMethod, method::MethodName}, + response::{Response, ResponseMethod}, +}; +use trc::JmapEvent; +use types::collection::Collection; use std::future::Future; diff --git a/crates/jmap/src/api/session.rs b/crates/jmap/src/api/session.rs index 80f0d63f..ee24d50f 100644 --- a/crates/jmap/src/api/session.rs +++ b/crates/jmap/src/api/session.rs @@ -8,12 +8,10 @@ use std::sync::Arc; use common::{Server, auth::AccessToken}; use directory::backend::internal::manage::ManageDirectory; -use jmap_proto::{ - request::capability::{Capability, Session}, - types::{acl::Acl, collection::Collection, id::Id}, -}; +use jmap_proto::request::capability::{Capability, Session}; use std::future::Future; use trc::AddContext; +use types::{acl::Acl, collection::Collection, id::Id}; pub trait SessionHandler: Sync + Send { fn handle_session_resource( diff --git a/crates/jmap/src/blob/copy.rs b/crates/jmap/src/blob/copy.rs index 220aafef..f150913c 100644 --- a/crates/jmap/src/blob/copy.rs +++ b/crates/jmap/src/blob/copy.rs @@ -4,23 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::download::BlobDownload; use common::{Server, auth::AccessToken}; use jmap_proto::{ error::set::{SetError, SetErrorType}, method::copy::{CopyBlobRequest, CopyBlobResponse}, - types::blob::BlobId, }; -use trc::AddContext; - use std::future::Future; use store::{ - BlobClass, SerializeInfallible, + SerializeInfallible, write::{BatchBuilder, BlobOp, now}, }; +use trc::AddContext; +use types::blob::{BlobClass, BlobId}; use utils::map::vec_map::VecMap; -use super::download::BlobDownload; - pub trait BlobCopy: Sync + Send { fn blob_copy( &self, diff --git a/crates/jmap/src/blob/download.rs b/crates/jmap/src/blob/download.rs index 2cd64ca8..e1101267 100644 --- a/crates/jmap/src/blob/download.rs +++ b/crates/jmap/src/blob/download.rs @@ -7,12 +7,13 @@ use common::{Server, auth::AccessToken}; use email::cache::MessageCacheFetch; use email::cache::email::MessageCacheAccess; -use jmap_proto::types::{acl::Acl, blob::BlobId, collection::Collection}; use std::future::Future; use std::ops::Range; -use store::BlobClass; use trc::AddContext; -use utils::BlobHash; +use types::acl::Acl; +use types::blob::{BlobClass, BlobId}; +use types::blob_hash::BlobHash; +use types::collection::Collection; pub trait BlobDownload: Sync + Send { fn blob_download( diff --git a/crates/jmap/src/blob/get.rs b/crates/jmap/src/blob/get.rs index ae15169e..ec7772f9 100644 --- a/crates/jmap/src/blob/get.rs +++ b/crates/jmap/src/blob/get.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::download::BlobDownload; use common::{Server, auth::AccessToken}; use email::message::metadata::MessageData; use jmap_proto::{ @@ -14,23 +15,17 @@ use jmap_proto::{ object::blob::GetArguments, types::{ MaybeUnparsable, - collection::Collection, - id::Id, property::{DataProperty, DigestProperty, Property}, - type_state::DataType, value::{Object, Value}, }, }; use mail_builder::encoders::base64::base64_encode; use sha1::{Digest, Sha1}; use sha2::{Sha256, Sha512}; -use store::BlobClass; -use trc::AddContext; -use utils::map::vec_map::VecMap; - use std::future::Future; - -use super::download::BlobDownload; +use trc::AddContext; +use types::{blob::BlobClass, collection::Collection, id::Id, type_state::DataType}; +use utils::map::vec_map::VecMap; pub trait BlobOperations: Sync + Send { fn blob_get( diff --git a/crates/jmap/src/blob/mod.rs b/crates/jmap/src/blob/mod.rs index a5d19e27..d409834a 100644 --- a/crates/jmap/src/blob/mod.rs +++ b/crates/jmap/src/blob/mod.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_proto::types::{blob::BlobId, id::Id}; +use types::{blob::BlobId, id::Id}; pub mod copy; pub mod download; diff --git a/crates/jmap/src/blob/upload.rs b/crates/jmap/src/blob/upload.rs index f01bd749..fca01878 100644 --- a/crates/jmap/src/blob/upload.rs +++ b/crates/jmap/src/blob/upload.rs @@ -6,6 +6,7 @@ use std::sync::Arc; +use super::{UploadResponse, download::BlobDownload}; use common::{Server, auth::AccessToken}; use directory::Permission; use jmap_proto::{ @@ -14,13 +15,10 @@ use jmap_proto::{ BlobUploadRequest, BlobUploadResponse, BlobUploadResponseObject, DataSourceObject, }, request::reference::MaybeReference, - types::id::Id, }; - -use trc::AddContext; - -use super::{UploadResponse, download::BlobDownload}; use std::future::Future; +use trc::AddContext; +use types::id::Id; #[cfg(feature = "test_mode")] pub static DISABLE_UPLOAD_QUOTA: std::sync::atomic::AtomicBool = diff --git a/crates/jmap/src/changes/get.rs b/crates/jmap/src/changes/get.rs index 2add9d2a..8b11238c 100644 --- a/crates/jmap/src/changes/get.rs +++ b/crates/jmap/src/changes/get.rs @@ -4,17 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::api::auth::JmapAuthorization; use common::{Server, auth::AccessToken}; use jmap_proto::{ method::changes::{ChangesRequest, ChangesResponse, RequestArguments}, - types::{ - collection::{Collection, SyncCollection}, - property::Property, - state::State, - }, + types::{property::Property, state::State}, }; use std::future::Future; use store::query::log::{Change, Query}; +use types::collection::{Collection, SyncCollection}; pub trait ChangesLookup: Sync + Send { fn changes( @@ -86,7 +84,7 @@ impl ChangesLookup for Server { State::Initial => { let changelog = self .store() - .changes(account_id, collection, Query::All) + .changes(account_id, collection.into(), Query::All) .await?; if changelog.changes.is_empty() && changelog.from_change_id == 0 { return Ok(response); @@ -97,7 +95,7 @@ impl ChangesLookup for Server { State::Exact(change_id) => ( 0, self.store() - .changes(account_id, collection, Query::Since(*change_id)) + .changes(account_id, collection.into(), Query::Since(*change_id)) .await?, ), State::Intermediate(intermediate_state) => { @@ -105,7 +103,7 @@ impl ChangesLookup for Server { .store() .changes( account_id, - collection, + collection.into(), Query::RangeInclusive(intermediate_state.from_id, intermediate_state.to_id), ) .await?; @@ -119,7 +117,7 @@ impl ChangesLookup for Server { self.store() .changes( account_id, - collection, + collection.into(), Query::Since(intermediate_state.to_id), ) .await?, diff --git a/crates/jmap/src/changes/state.rs b/crates/jmap/src/changes/state.rs index 0772e55e..9becac3a 100644 --- a/crates/jmap/src/changes/state.rs +++ b/crates/jmap/src/changes/state.rs @@ -5,9 +5,10 @@ */ use common::{MessageStoreCache, Server}; -use jmap_proto::types::{collection::SyncCollection, state::State}; +use jmap_proto::types::state::State; use std::future::Future; use trc::AddContext; +use types::collection::SyncCollection; pub trait StateManager: Sync + Send { fn get_state( @@ -35,7 +36,7 @@ impl StateManager for Server { self.core .storage .data - .get_last_change_id(account_id, collection) + .get_last_change_id(account_id, collection.into()) .await .caused_by(trc::location!()) .map(State::from) diff --git a/crates/jmap/src/email/body.rs b/crates/jmap/src/email/body.rs index a2e14244..3d379211 100644 --- a/crates/jmap/src/email/body.rs +++ b/crates/jmap/src/email/body.rs @@ -6,11 +6,11 @@ use email::message::metadata::{ArchivedMessageMetadataContents, ArchivedMetadataPartType}; use jmap_proto::types::{ - blob::BlobId, property::Property, value::{Object, Value}, }; use mail_parser::{ArchivedHeaderValue, HeaderValue, MessagePart, MimeHeaders, PartType}; +use types::blob::BlobId; use super::headers::HeaderToValue; diff --git a/crates/jmap/src/email/copy.rs b/crates/jmap/src/email/copy.rs index 07a23768..80d7bfca 100644 --- a/crates/jmap/src/email/copy.rs +++ b/crates/jmap/src/email/copy.rs @@ -4,11 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::changes::state::MessageCacheState; +use crate::{changes::state::MessageCacheState, email::ingested_into_object}; use common::{Server, auth::AccessToken}; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess, mailbox::MailboxCacheAccess}, - message::copy::EmailCopy, + message::copy::{CopyMessageError, EmailCopy}, }; use http_proto::HttpSessionData; use jmap_proto::{ @@ -24,13 +24,13 @@ use jmap_proto::{ }, response::references::EvalObjectReferences, types::{ - acl::Acl, property::Property, value::{MaybePatchValue, Value}, }, }; use std::future::Future; use trc::AddContext; +use types::acl::Acl; use utils::map::vec_map::VecMap; pub trait JmapEmailCopy: Sync + Send { @@ -214,16 +214,23 @@ impl JmapEmailCopy for Server { &resource_token, mailboxes, keywords, - received_at, + received_at.map(|dt| dt.timestamp() as u64), session.session_id, ) .await? { Ok(email) => { - response.created.append(id, email.into()); + response.created.append(id, ingested_into_object(email)); } Err(err) => { - response.not_created.append(id, err); + response.not_created.append( + id, + match err { + CopyMessageError::NotFound => SetError::not_found() + .with_description("Message not found not found in account."), + CopyMessageError::OverQuota => SetError::over_quota(), + }, + ); } } diff --git a/crates/jmap/src/email/get.rs b/crates/jmap/src/email/get.rs index 59a60590..82e08e9e 100644 --- a/crates/jmap/src/email/get.rs +++ b/crates/jmap/src/email/get.rs @@ -20,20 +20,22 @@ use jmap_proto::{ method::get::{GetRequest, GetResponse}, object::email::GetArguments, types::{ - acl::Acl, - blob::BlobId, - collection::Collection, date::UTCDate, - id::Id, property::{HeaderForm, Property}, value::{Object, Value}, }, }; use mail_parser::{ArchivedHeaderName, HeaderValue, core::rkyv::ArchivedGetHeader}; use std::{borrow::Cow, future::Future}; -use store::BlobClass; use trc::{AddContext, StoreEvent}; -use utils::BlobHash; +use types::{ + acl::Acl, + blob::{BlobClass, BlobId}, + blob_hash::BlobHash, + collection::Collection, + field::EmailField, + id::Id, +}; pub trait EmailGet: Sync + Send { fn email_get( @@ -151,7 +153,7 @@ impl EmailGet for Server { account_id, Collection::Email, id.document_id(), - &Property::BodyStructure, + EmailField::Metadata.into(), ) .await? { diff --git a/crates/jmap/src/email/import.rs b/crates/jmap/src/email/import.rs index 3e7e8fbb..518b8acf 100644 --- a/crates/jmap/src/email/import.rs +++ b/crates/jmap/src/email/import.rs @@ -4,7 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{blob::download::BlobDownload, changes::state::MessageCacheState}; +use crate::{ + blob::download::BlobDownload, changes::state::MessageCacheState, email::ingested_into_object, +}; use common::{Server, auth::AccessToken}; use email::{ cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}, @@ -14,10 +16,11 @@ use http_proto::HttpSessionData; use jmap_proto::{ error::set::{SetError, SetErrorType}, method::import::{ImportEmailRequest, ImportEmailResponse}, - types::{acl::Acl, id::Id, property::Property, state::State}, + types::{property::Property, state::State}, }; use mail_parser::MessageParser; use std::future::Future; +use types::{acl::Acl, id::Id}; use utils::map::vec_map::VecMap; pub trait EmailImport: Sync + Send { @@ -145,7 +148,7 @@ impl EmailImport for Server { .await { Ok(email) => { - response.created.append(id, email.into()); + response.created.append(id, ingested_into_object(email)); } Err(mut err) => match err.as_ref() { trc::EventType::Limit(trc::LimitEvent::Quota) => { diff --git a/crates/jmap/src/email/mod.rs b/crates/jmap/src/email/mod.rs index b7d28aec..ec5dc104 100644 --- a/crates/jmap/src/email/mod.rs +++ b/crates/jmap/src/email/mod.rs @@ -4,6 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use email::message::ingest::IngestedEmail; +use jmap_proto::types::{ + property::Property, + value::{Object, Value}, +}; +use types::id::Id; + pub mod body; pub mod copy; pub mod get; @@ -13,3 +20,14 @@ pub mod parse; pub mod query; pub mod set; pub mod snippet; + +fn ingested_into_object(email: IngestedEmail) -> Object { + Object::with_capacity(3) + .with_property( + Property::Id, + Id::from_parts(email.thread_id, email.document_id), + ) + .with_property(Property::ThreadId, Id::from(email.thread_id)) + .with_property(Property::BlobId, email.blob_id) + .with_property(Property::Size, email.size) +} diff --git a/crates/jmap/src/email/query.rs b/crates/jmap/src/email/query.rs index 6ab350da..3f342dc7 100644 --- a/crates/jmap/src/email/query.rs +++ b/crates/jmap/src/email/query.rs @@ -10,7 +10,6 @@ use email::cache::{MessageCacheFetch, email::MessageCacheAccess}; use jmap_proto::{ method::query::{Comparator, Filter, QueryRequest, QueryResponse, SortProperty}, object::email::QueryArguments, - types::{acl::Acl, collection::Collection, keyword::Keyword, property::Property}, }; use mail_parser::HeaderName; use nlp::language::Language; @@ -23,6 +22,7 @@ use store::{ roaring::RoaringBitmap, }; use trc::AddContext; +use types::{acl::Acl, collection::Collection, field::EmailField, keyword::Keyword}; pub trait EmailQuery: Sync + Send { fn email_query( @@ -203,17 +203,15 @@ impl EmailQuery for Server { filters.push(query::Filter::End); filters.push(query::Filter::End); } - Filter::Before(date) => { - filters.push(query::Filter::lt(Property::ReceivedAt, date.serialize())) - } - Filter::After(date) => { - filters.push(query::Filter::gt(Property::ReceivedAt, date.serialize())) - } + Filter::Before(date) => filters + .push(query::Filter::lt(EmailField::ReceivedAt, date.serialize())), + Filter::After(date) => filters + .push(query::Filter::gt(EmailField::ReceivedAt, date.serialize())), Filter::MinSize(size) => { - filters.push(query::Filter::ge(Property::Size, size.serialize())) + filters.push(query::Filter::ge(EmailField::Size, size.serialize())) } Filter::MaxSize(size) => { - filters.push(query::Filter::lt(Property::Size, size.serialize())) + filters.push(query::Filter::lt(EmailField::Size, size.serialize())) } Filter::AllInThreadHaveKeyword(keyword) => { filters.push(query::Filter::is_in_set(thread_keywords( @@ -258,7 +256,8 @@ impl EmailQuery for Server { if !has_attach { filters.push(query::Filter::Not); } - filters.push(query::Filter::is_in_bitmap(Property::HasAttachment, ())); + filters + .push(query::Filter::is_in_bitmap(EmailField::HasAttachment, ())); if !has_attach { filters.push(query::Filter::End); } @@ -273,10 +272,10 @@ impl EmailQuery for Server { filters.push(query::Filter::is_in_set(set)); } Filter::SentBefore(date) => { - filters.push(query::Filter::lt(Property::SentAt, date.serialize())) + filters.push(query::Filter::lt(EmailField::SentAt, date.serialize())) } Filter::SentAfter(date) => { - filters.push(query::Filter::gt(Property::SentAt, date.serialize())) + filters.push(query::Filter::gt(EmailField::SentAt, date.serialize())) } Filter::InThread(id) => { filters.push(query::Filter::is_in_set(RoaringBitmap::from_iter( @@ -317,22 +316,22 @@ impl EmailQuery for Server { { comparators.push(match comparator.property { SortProperty::ReceivedAt => { - query::Comparator::field(Property::ReceivedAt, comparator.is_ascending) + query::Comparator::field(EmailField::ReceivedAt, comparator.is_ascending) } SortProperty::Size => { - query::Comparator::field(Property::Size, comparator.is_ascending) + query::Comparator::field(EmailField::Size, comparator.is_ascending) } SortProperty::From => { - query::Comparator::field(Property::From, comparator.is_ascending) + query::Comparator::field(EmailField::From, comparator.is_ascending) } SortProperty::To => { - query::Comparator::field(Property::To, comparator.is_ascending) + query::Comparator::field(EmailField::To, comparator.is_ascending) } SortProperty::Subject => { - query::Comparator::field(Property::Subject, comparator.is_ascending) + query::Comparator::field(EmailField::Subject, comparator.is_ascending) } SortProperty::SentAt => { - query::Comparator::field(Property::SentAt, comparator.is_ascending) + query::Comparator::field(EmailField::SentAt, comparator.is_ascending) } SortProperty::HasKeyword => query::Comparator::set( RoaringBitmap::from_iter( @@ -360,7 +359,7 @@ impl EmailQuery for Server { ), // Non-standard SortProperty::Cc => { - query::Comparator::field(Property::Cc, comparator.is_ascending) + query::Comparator::field(EmailField::Cc, comparator.is_ascending) } other => { diff --git a/crates/jmap/src/email/set.rs b/crates/jmap/src/email/set.rs index 21ed7061..f138672e 100644 --- a/crates/jmap/src/email/set.rs +++ b/crates/jmap/src/email/set.rs @@ -7,7 +7,10 @@ use std::{borrow::Cow, collections::HashMap}; use super::headers::{BuildHeader, ValueToHeader}; -use crate::{JmapMethods, blob::download::BlobDownload, changes::state::MessageCacheState}; +use crate::{ + JmapMethods, blob::download::BlobDownload, changes::state::MessageCacheState, + email::ingested_into_object, +}; use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess, mailbox::MailboxCacheAccess}, @@ -24,12 +27,8 @@ use jmap_proto::{ method::set::{RequestArguments, SetRequest, SetResponse}, response::references::EvalObjectReferences, types::{ - acl::Acl, - collection::{Collection, SyncCollection, VanishedCollection}, - keyword::Keyword, property::Property, - state::{State, StateChange}, - type_state::DataType, + state::State, value::{MaybePatchValue, SetValue, Value}, }, }; @@ -45,6 +44,12 @@ use mail_parser::MessageParser; use std::future::Future; use store::{ahash::AHashMap, roaring::RoaringBitmap, write::BatchBuilder}; use trc::AddContext; +use types::{ + acl::Acl, + collection::{Collection, SyncCollection, VanishedCollection}, + keyword::Keyword, + type_state::{DataType, StateChange}, +}; pub trait EmailSet: Sync + Send { fn email_set( @@ -734,7 +739,7 @@ impl EmailSet for Server { { Ok(message) => { last_change_id = message.change_id.into(); - response.created.insert(id, message.into()); + response.created.insert(id, ingested_into_object(message)); } Err(err) if err.matches(trc::EventType::Limit(trc::LimitEvent::Quota)) => { response.not_created.append( diff --git a/crates/jmap/src/email/snippet.rs b/crates/jmap/src/email/snippet.rs index 2f4d9c97..d132aa03 100644 --- a/crates/jmap/src/email/snippet.rs +++ b/crates/jmap/src/email/snippet.rs @@ -10,12 +10,9 @@ use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, message::metadata::{ArchivedMetadataPartType, DecodedPartContent, MessageMetadata}, }; -use jmap_proto::{ - method::{ - query::Filter, - search_snippet::{GetSearchSnippetRequest, GetSearchSnippetResponse, SearchSnippet}, - }, - types::{acl::Acl, collection::Collection, property::Property}, +use jmap_proto::method::{ + query::Filter, + search_snippet::{GetSearchSnippetRequest, GetSearchSnippetResponse, SearchSnippet}, }; use mail_parser::{ ArchivedHeaderName, core::rkyv::ArchivedGetHeader, decoders::html::html_to_text, @@ -24,7 +21,7 @@ use nlp::language::{Language, search_snippet::generate_snippet, stemmer::Stemmer use std::future::Future; use store::backend::MAX_TOKEN_LENGTH; use trc::AddContext; -use utils::BlobHash; +use types::{acl::Acl, blob_hash::BlobHash, collection::Collection, field::EmailField}; pub trait EmailSearchSnippet: Sync + Send { fn email_search_snippet( @@ -126,7 +123,7 @@ impl EmailSearchSnippet for Server { account_id, Collection::Email, document_id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await? { diff --git a/crates/jmap/src/identity/get.rs b/crates/jmap/src/identity/get.rs index 71bd02a8..d03b1b4d 100644 --- a/crates/jmap/src/identity/get.rs +++ b/crates/jmap/src/identity/get.rs @@ -10,7 +10,6 @@ use email::identity::{ArchivedEmailAddress, Identity}; use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - collection::{Collection, SyncCollection}, property::Property, value::{Object, Value}, }, @@ -21,6 +20,7 @@ use store::{ write::BatchBuilder, }; use trc::AddContext; +use types::collection::{Collection, SyncCollection}; use utils::sanitize_email; use crate::changes::state::StateManager; diff --git a/crates/jmap/src/identity/set.rs b/crates/jmap/src/identity/set.rs index f2e8b84c..30430607 100644 --- a/crates/jmap/src/identity/set.rs +++ b/crates/jmap/src/identity/set.rs @@ -12,7 +12,6 @@ use jmap_proto::{ method::set::{RequestArguments, SetRequest, SetResponse}, response::references::EvalObjectReferences, types::{ - collection::{Collection, SyncCollection}, property::Property, state::State, value::{MaybePatchValue, Value}, @@ -21,6 +20,10 @@ use jmap_proto::{ use std::future::Future; use store::write::BatchBuilder; use trc::AddContext; +use types::{ + collection::{Collection, SyncCollection}, + field::Field, +}; use utils::sanitize_email; pub trait IdentitySet: Sync + Send { @@ -160,7 +163,7 @@ impl IdentitySet for Server { .with_account_id(account_id) .with_collection(Collection::Identity) .delete_document(document_id) - .clear(Property::Value) + .clear(Field::ARCHIVE) .log_item_delete(SyncCollection::Identity, None) .commit_point(); response.destroyed.push(id); diff --git a/crates/jmap/src/lib.rs b/crates/jmap/src/lib.rs index 497919f8..eb14a24d 100644 --- a/crates/jmap/src/lib.rs +++ b/crates/jmap/src/lib.rs @@ -12,7 +12,7 @@ use jmap_proto::{ query::{QueryRequest, QueryResponse}, set::{SetRequest, SetResponse}, }, - types::{collection::Collection, state::State}, + types::state::State, }; use std::{fmt::Display, future::Future}; use store::{ @@ -21,6 +21,7 @@ use store::{ roaring::RoaringBitmap, }; use trc::AddContext; +use types::collection::Collection; pub mod api; pub mod blob; diff --git a/crates/jmap/src/mailbox/get.rs b/crates/jmap/src/mailbox/get.rs index 36759802..99539588 100644 --- a/crates/jmap/src/mailbox/get.rs +++ b/crates/jmap/src/mailbox/get.rs @@ -9,14 +9,15 @@ use email::cache::{MessageCacheFetch, email::MessageCacheAccess, mailbox::Mailbo use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - acl::Acl, - keyword::Keyword, property::Property, value::{Object, Value}, }, }; use std::future::Future; use store::ahash::AHashSet; +use types::{acl::Acl, keyword::Keyword}; + +use crate::api::auth::JmapAcl; pub trait MailboxGet: Sync + Send { fn mailbox_get( diff --git a/crates/jmap/src/mailbox/query.rs b/crates/jmap/src/mailbox/query.rs index 92505fdb..47d486d8 100644 --- a/crates/jmap/src/mailbox/query.rs +++ b/crates/jmap/src/mailbox/query.rs @@ -10,7 +10,6 @@ use email::cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}; use jmap_proto::{ method::query::{Comparator, Filter, QueryRequest, QueryResponse, SortProperty}, object::mailbox::QueryArguments, - types::{acl::Acl, collection::Collection}, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -20,6 +19,7 @@ use store::{ query::{self}, roaring::RoaringBitmap, }; +use types::{acl::Acl, collection::Collection}; pub trait MailboxQuery: Sync + Send { fn mailbox_query( diff --git a/crates/jmap/src/mailbox/set.rs b/crates/jmap/src/mailbox/set.rs index c7121775..379c15ef 100644 --- a/crates/jmap/src/mailbox/set.rs +++ b/crates/jmap/src/mailbox/set.rs @@ -4,40 +4,40 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{JmapMethods, changes::state::MessageCacheState}; +use crate::{JmapMethods, api::auth::JmapAcl, changes::state::MessageCacheState}; use common::{ Server, auth::AccessToken, config::jmap::settings::SpecialUse, sharing::EffectiveAcl, storage::index::ObjectIndexBuilder, }; +#[allow(unused_imports)] +use email::mailbox::{INBOX_ID, JUNK_ID, TRASH_ID, UidMailbox}; use email::{ cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}, - mailbox::{Mailbox, destroy::MailboxDestroy}, + mailbox::{ + Mailbox, + destroy::{MailboxDestroy, MailboxDestroyError}, + }, }; use jmap_proto::{ - error::set::SetError, + error::set::{SetError, SetErrorType}, method::set::{SetRequest, SetResponse}, object::mailbox::SetArguments, response::references::EvalObjectReferences, types::{ - acl::Acl, - collection::Collection, - id::Id, property::Property, state::State, value::{MaybePatchValue, Object, SetValue, Value}, }, }; +use std::future::Future; use store::{ roaring::RoaringBitmap, write::{Archive, BatchBuilder, assert::AssertValue}, }; use trc::AddContext; +use types::{acl::Acl, collection::Collection, field::MailboxField, id::Id}; use utils::config::utils::ParseValue; -#[allow(unused_imports)] -use email::mailbox::{INBOX_ID, JUNK_ID, TRASH_ID, UidMailbox}; -use std::future::Future; - pub struct SetContext<'x> { account_id: u32, access_token: &'x AccessToken, @@ -98,7 +98,7 @@ impl MailboxSet for Server { if parent_id > 0 { batch .update_document(parent_id - 1) - .assert_value(Property::Value, AssertValue::Some); + .assert_value(MailboxField::Archive, AssertValue::Some); } let document_id = self @@ -189,7 +189,7 @@ impl MailboxSet for Server { if parent_id > 0 { batch .update_document(parent_id - 1) - .assert_value(Property::Value, AssertValue::Some); + .assert_value(MailboxField::Archive, AssertValue::Some); } batch @@ -255,7 +255,31 @@ impl MailboxSet for Server { ctx.response.destroyed.push(id); } Err(err) => { - ctx.response.not_destroyed.append(id, err); + ctx.response.not_destroyed.append( + id, + match err { + MailboxDestroyError::CannotDestroy => SetError::forbidden() + .with_description( + "You are not allowed to delete Inbox, Junk or Trash folders.", + ), + MailboxDestroyError::Forbidden => SetError::forbidden() + .with_description("You are not allowed to delete this mailbox."), + MailboxDestroyError::HasChildren => { + SetError::new(SetErrorType::MailboxHasChild) + .with_description("Mailbox has at least one children.") + } + MailboxDestroyError::HasEmails => { + SetError::new(SetErrorType::MailboxHasEmail) + .with_description("Mailbox is not empty.") + } + MailboxDestroyError::NotFound => SetError::not_found(), + MailboxDestroyError::AssertionFailed => SetError::forbidden() + .with_description(concat!( + "Another process modified a message in this mailbox ", + "while deleting it, please try again." + )), + }, + ); } } } diff --git a/crates/jmap/src/principal/get.rs b/crates/jmap/src/principal/get.rs index e9d4abd6..88cd43c7 100644 --- a/crates/jmap/src/principal/get.rs +++ b/crates/jmap/src/principal/get.rs @@ -9,13 +9,13 @@ use directory::QueryParams; use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - collection::Collection, property::Property, state::State, value::{Object, Value}, }, }; use std::future::Future; +use types::collection::Collection; pub trait PrincipalGet: Sync + Send { fn principal_get( diff --git a/crates/jmap/src/principal/query.rs b/crates/jmap/src/principal/query.rs index dc5cf5ab..c69dd630 100644 --- a/crates/jmap/src/principal/query.rs +++ b/crates/jmap/src/principal/query.rs @@ -10,10 +10,11 @@ use directory::QueryParams; use http_proto::HttpSessionData; use jmap_proto::{ method::query::{Filter, QueryRequest, QueryResponse, RequestArguments}, - types::{collection::Collection, state::State}, + types::state::State, }; use std::future::Future; use store::{query::ResultSet, roaring::RoaringBitmap}; +use types::collection::Collection; pub trait PrincipalQuery: Sync + Send { fn principal_query( @@ -32,7 +33,7 @@ impl PrincipalQuery for Server { let account_id = request.account_id.document_id(); let mut result_set = ResultSet { account_id, - collection: Collection::Principal.into(), + collection: Collection::Principal, results: RoaringBitmap::new(), }; let mut is_set = true; diff --git a/crates/jmap/src/push/get.rs b/crates/jmap/src/push/get.rs index a2112938..8ed46cb0 100644 --- a/crates/jmap/src/push/get.rs +++ b/crates/jmap/src/push/get.rs @@ -12,21 +12,20 @@ use common::{ use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - collection::Collection, date::UTCDate, property::Property, value::{Object, Value}, }, }; +use std::future::Future; use store::{ BitmapKey, ValueKey, write::{AlignedBytes, Archive, ValueClass, now}, }; use trc::{AddContext, ServerEvent}; +use types::{collection::Collection, field::Field}; use utils::map::bitmap::Bitmap; -use std::future::Future; - pub trait PushSubscriptionFetch: Sync + Send { fn push_subscription_get( &self, @@ -167,7 +166,7 @@ impl PushSubscriptionFetch for Server { account_id, collection: Collection::PushSubscription.into(), document_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await? .ok_or_else(|| { diff --git a/crates/jmap/src/push/set.rs b/crates/jmap/src/push/set.rs index 634b9ce1..89d3eb44 100644 --- a/crates/jmap/src/push/set.rs +++ b/crates/jmap/src/push/set.rs @@ -13,10 +13,8 @@ use jmap_proto::{ method::set::{RequestArguments, SetRequest, SetResponse}, response::references::EvalObjectReferences, types::{ - collection::Collection, date::UTCDate, property::Property, - type_state::DataType, value::{MaybePatchValue, Object, Value}, }, }; @@ -28,6 +26,7 @@ use store::{ write::{Archiver, BatchBuilder, now}, }; use trc::AddContext; +use types::{collection::Collection, field::Field, type_state::DataType}; use utils::map::bitmap::Bitmap; const EXPIRES_MAX: i64 = 7 * 24 * 3600; // 7 days @@ -111,7 +110,7 @@ impl PushSubscriptionSet for Server { .with_collection(Collection::PushSubscription) .create_document(document_id) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(push) .serialize() .caused_by(trc::location!())?, @@ -163,7 +162,7 @@ impl PushSubscriptionSet for Server { .with_collection(Collection::PushSubscription) .update_document(document_id) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(push) .serialize() .caused_by(trc::location!())?, @@ -181,7 +180,7 @@ impl PushSubscriptionSet for Server { .with_account_id(account_id) .with_collection(Collection::PushSubscription) .delete_document(document_id) - .clear(Property::Value) + .clear(Field::ARCHIVE) .commit_point(); response.destroyed.push(id); } else { diff --git a/crates/jmap/src/quota/get.rs b/crates/jmap/src/quota/get.rs index c687e931..0fa17a85 100644 --- a/crates/jmap/src/quota/get.rs +++ b/crates/jmap/src/quota/get.rs @@ -8,14 +8,13 @@ use common::{Server, auth::AccessToken}; use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - id::Id, property::Property, state::State, - type_state::DataType, value::{Object, Value}, }, }; use std::future::Future; +use types::{id::Id, type_state::DataType}; pub trait QuotaGet: Sync + Send { fn quota_get( diff --git a/crates/jmap/src/quota/query.rs b/crates/jmap/src/quota/query.rs index 5787f2bf..856821fb 100644 --- a/crates/jmap/src/quota/query.rs +++ b/crates/jmap/src/quota/query.rs @@ -7,9 +7,10 @@ use common::{Server, auth::AccessToken}; use jmap_proto::{ method::query::{QueryRequest, QueryResponse, RequestArguments}, - types::{id::Id, state::State}, + types::state::State, }; use std::future::Future; +use types::id::Id; pub trait QuotaQuery: Sync + Send { fn quota_query( diff --git a/crates/jmap/src/sieve/get.rs b/crates/jmap/src/sieve/get.rs index cdc8c277..3c5c0a65 100644 --- a/crates/jmap/src/sieve/get.rs +++ b/crates/jmap/src/sieve/get.rs @@ -4,23 +4,22 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::changes::state::StateManager; use common::Server; use email::sieve::SieveScript; use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - blob::{BlobId, BlobSection}, - collection::{Collection, SyncCollection}, property::Property, value::{Object, Value}, }, }; -use store::BlobClass; -use trc::AddContext; - -use crate::changes::state::StateManager; - use std::future::Future; +use trc::AddContext; +use types::{ + blob::{BlobClass, BlobId, BlobSection}, + collection::{Collection, SyncCollection}, +}; pub trait SieveScriptGet: Sync + Send { fn sieve_script_get( diff --git a/crates/jmap/src/sieve/query.rs b/crates/jmap/src/sieve/query.rs index be148244..79f159a0 100644 --- a/crates/jmap/src/sieve/query.rs +++ b/crates/jmap/src/sieve/query.rs @@ -4,20 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{JmapMethods, changes::state::StateManager}; use common::Server; -use jmap_proto::{ - method::query::{ - Comparator, Filter, QueryRequest, QueryResponse, RequestArguments, SortProperty, - }, - types::{ - collection::{Collection, SyncCollection}, - property::Property, - }, +use jmap_proto::method::query::{ + Comparator, Filter, QueryRequest, QueryResponse, RequestArguments, SortProperty, }; use std::future::Future; use store::query::{self}; - -use crate::{JmapMethods, changes::state::StateManager}; +use types::{ + collection::{Collection, SyncCollection}, + field::SieveField, +}; pub trait SieveScriptQuery: Sync + Send { fn sieve_script_query( @@ -36,10 +33,13 @@ impl SieveScriptQuery for Server { for cond in std::mem::take(&mut request.filter) { match cond { - Filter::Name(name) => filters.push(query::Filter::contains(Property::Name, &name)), - Filter::IsActive(is_active) => { - filters.push(query::Filter::eq(Property::IsActive, vec![is_active as u8])) + Filter::Name(name) => { + filters.push(query::Filter::contains(SieveField::Name, &name)) } + Filter::IsActive(is_active) => filters.push(query::Filter::eq( + SieveField::IsActive, + vec![is_active as u8], + )), Filter::And | Filter::Or | Filter::Not | Filter::Close => { filters.push(cond.into()); } @@ -74,10 +74,10 @@ impl SieveScriptQuery for Server { { comparators.push(match comparator.property { SortProperty::Name => { - query::Comparator::field(Property::Name, comparator.is_ascending) + query::Comparator::field(SieveField::Name, comparator.is_ascending) } SortProperty::IsActive => { - query::Comparator::field(Property::IsActive, comparator.is_ascending) + query::Comparator::field(SieveField::IsActive, comparator.is_ascending) } other => { return Err(trc::JmapEvent::UnsupportedSort diff --git a/crates/jmap/src/sieve/set.rs b/crates/jmap/src/sieve/set.rs index 384259fa..67c28319 100644 --- a/crates/jmap/src/sieve/set.rs +++ b/crates/jmap/src/sieve/set.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{JmapMethods, blob::download::BlobDownload, changes::state::StateManager}; use common::{ Server, auth::{AccessToken, ResourceToken}, @@ -20,9 +21,6 @@ use jmap_proto::{ request::reference::MaybeReference, response::references::EvalObjectReferences, types::{ - blob::{BlobId, BlobSection}, - collection::{Collection, SyncCollection}, - id::Id, property::Property, state::State, value::{MaybePatchValue, Object, SetValue, Value}, @@ -30,16 +28,20 @@ use jmap_proto::{ }; use rand::distr::Alphanumeric; use sieve::compiler::ErrorType; +use std::future::Future; use store::{ - BlobClass, Serialize, + Serialize, query::Filter, rand::{Rng, rng}, write::{Archive, Archiver, BatchBuilder}, }; use trc::AddContext; - -use crate::{JmapMethods, blob::download::BlobDownload, changes::state::StateManager}; -use std::future::Future; +use types::{ + blob::{BlobClass, BlobId, BlobSection}, + collection::{Collection, SyncCollection}, + field::SieveField, + id::Id, +}; pub struct SetContext<'x> { resource_token: ResourceToken, @@ -395,7 +397,7 @@ impl SieveScriptSet for Server { .filter( ctx.resource_token.account_id, Collection::SieveScript, - vec![Filter::eq(Property::Name, value.as_bytes().to_vec())], + vec![Filter::eq(SieveField::Name, value.as_bytes().to_vec())], ) .await? .results diff --git a/crates/jmap/src/submission/get.rs b/crates/jmap/src/submission/get.rs index fee42c36..00d69cc8 100644 --- a/crates/jmap/src/submission/get.rs +++ b/crates/jmap/src/submission/get.rs @@ -11,9 +11,7 @@ use email::submission::{ use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, types::{ - collection::{Collection, SyncCollection}, date::UTCDate, - id::Id, property::Property, value::{Object, Value}, }, @@ -23,6 +21,10 @@ use smtp_proto::ArchivedResponse; use std::future::Future; use store::rkyv::option::ArchivedOption; use trc::AddContext; +use types::{ + collection::{Collection, SyncCollection}, + id::Id, +}; use utils::map::vec_map::VecMap; use crate::changes::state::StateManager; diff --git a/crates/jmap/src/submission/query.rs b/crates/jmap/src/submission/query.rs index cb3af17e..90f96cc3 100644 --- a/crates/jmap/src/submission/query.rs +++ b/crates/jmap/src/submission/query.rs @@ -4,24 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{JmapMethods, changes::state::StateManager}; use common::Server; use email::submission::UndoStatus; -use jmap_proto::{ - method::query::{ - Comparator, Filter, QueryRequest, QueryResponse, RequestArguments, SortProperty, - }, - types::{ - collection::{Collection, SyncCollection}, - property::Property, - }, +use jmap_proto::method::query::{ + Comparator, Filter, QueryRequest, QueryResponse, RequestArguments, SortProperty, }; use std::future::Future; use store::{ SerializeInfallible, query::{self}, }; - -use crate::{JmapMethods, changes::state::StateManager}; +use types::{ + collection::{Collection, SyncCollection}, + field::EmailSubmissionField, +}; pub trait EmailSubmissionQuery: Sync + Send { fn email_submission_query( @@ -44,7 +41,7 @@ impl EmailSubmissionQuery for Server { filters.push(query::Filter::Or); for id in ids { filters.push(query::Filter::eq( - Property::IdentityId, + EmailSubmissionField::IdentityId, id.document_id().serialize(), )); } @@ -53,7 +50,10 @@ impl EmailSubmissionQuery for Server { Filter::EmailIds(ids) => { filters.push(query::Filter::Or); for id in ids { - filters.push(query::Filter::eq(Property::EmailId, id.id().serialize())); + filters.push(query::Filter::eq( + EmailSubmissionField::EmailId, + id.id().serialize(), + )); } filters.push(query::Filter::End); } @@ -61,25 +61,25 @@ impl EmailSubmissionQuery for Server { filters.push(query::Filter::Or); for id in ids { filters.push(query::Filter::eq( - Property::ThreadId, + EmailSubmissionField::ThreadId, id.document_id().serialize(), )); } filters.push(query::Filter::End); } Filter::UndoStatus(undo_status) => filters.push(query::Filter::eq( - Property::UndoStatus, + EmailSubmissionField::UndoStatus, UndoStatus::parse(&undo_status) .unwrap_or(UndoStatus::Pending) .as_index() .serialize(), )), Filter::Before(before) => filters.push(query::Filter::lt( - Property::SendAt, + EmailSubmissionField::SendAt, (before.timestamp() as u64).serialize(), )), Filter::After(after) => filters.push(query::Filter::gt( - Property::SendAt, + EmailSubmissionField::SendAt, (after.timestamp() as u64).serialize(), )), Filter::And | Filter::Or | Filter::Not | Filter::Close => { @@ -115,15 +115,18 @@ impl EmailSubmissionQuery for Server { .unwrap_or_else(|| vec![Comparator::descending(SortProperty::SentAt)]) { comparators.push(match comparator.property { - SortProperty::EmailId => { - query::Comparator::field(Property::EmailId, comparator.is_ascending) - } - SortProperty::ThreadId => { - query::Comparator::field(Property::ThreadId, comparator.is_ascending) - } - SortProperty::SentAt => { - query::Comparator::field(Property::SendAt, comparator.is_ascending) - } + SortProperty::EmailId => query::Comparator::field( + EmailSubmissionField::EmailId, + comparator.is_ascending, + ), + SortProperty::ThreadId => query::Comparator::field( + EmailSubmissionField::ThreadId, + comparator.is_ascending, + ), + SortProperty::SentAt => query::Comparator::field( + EmailSubmissionField::SendAt, + comparator.is_ascending, + ), other => { return Err(trc::JmapEvent::UnsupportedSort .into_err() diff --git a/crates/jmap/src/submission/set.rs b/crates/jmap/src/submission/set.rs index 6d1142d4..636cc917 100644 --- a/crates/jmap/src/submission/set.rs +++ b/crates/jmap/src/submission/set.rs @@ -27,8 +27,6 @@ use jmap_proto::{ }, response::references::EvalObjectReferences, types::{ - collection::Collection, - id::Id, property::Property, state::State, value::{MaybePatchValue, Object, SetValue, Value}, @@ -44,7 +42,8 @@ use std::{borrow::Cow, future::Future}; use std::{collections::HashMap, sync::Arc, time::Duration}; use store::write::{BatchBuilder, now}; use trc::AddContext; -use utils::{BlobHash, map::vec_map::VecMap, sanitize_email}; +use types::{blob_hash::BlobHash, collection::Collection, field::EmailField, id::Id}; +use utils::{map::vec_map::VecMap, sanitize_email}; pub trait EmailSubmissionSet: Sync + Send { fn email_submission_set( @@ -487,7 +486,7 @@ impl EmailSubmissionSet for Server { account_id, Collection::Email, submission.email_id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await? { diff --git a/crates/jmap/src/thread/get.rs b/crates/jmap/src/thread/get.rs index ac9cc0ed..481cbdd5 100644 --- a/crates/jmap/src/thread/get.rs +++ b/crates/jmap/src/thread/get.rs @@ -9,12 +9,7 @@ use common::Server; use email::cache::MessageCacheFetch; use jmap_proto::{ method::get::{GetRequest, GetResponse, RequestArguments}, - types::{ - collection::{Collection, SyncCollection}, - id::Id, - property::Property, - value::Object, - }, + types::{property::Property, value::Object}, }; use std::future::Future; use store::{ @@ -23,6 +18,11 @@ use store::{ roaring::RoaringBitmap, }; use trc::AddContext; +use types::{ + collection::{Collection, SyncCollection}, + field::EmailField, + id::Id, +}; pub trait ThreadGet: Sync + Send { fn thread_get( @@ -87,7 +87,7 @@ impl ThreadGet for Server { .data .sort( ResultSet::new(account_id, Collection::Email, document_ids), - vec![Comparator::ascending(Property::ReceivedAt)], + vec![Comparator::ascending(EmailField::ReceivedAt)], Pagination::new(doc_count, 0, None, 0), ) .await diff --git a/crates/jmap/src/vacation/get.rs b/crates/jmap/src/vacation/get.rs index e8cd324b..7cce5000 100644 --- a/crates/jmap/src/vacation/get.rs +++ b/crates/jmap/src/vacation/get.rs @@ -11,9 +11,7 @@ use jmap_proto::{ request::reference::MaybeReference, types::{ any_id::AnyId, - collection::{Collection, SyncCollection}, date::UTCDate, - id::Id, property::Property, value::{Object, Value}, }, @@ -21,6 +19,11 @@ use jmap_proto::{ use std::future::Future; use store::query::Filter; use trc::AddContext; +use types::{ + collection::{Collection, SyncCollection}, + field::SieveField, + id::Id, +}; use crate::{JmapMethods, changes::state::StateManager}; @@ -160,7 +163,7 @@ impl VacationResponseGet for Server { self.filter( account_id, Collection::SieveScript, - vec![Filter::eq(Property::Name, "vacation".as_bytes().to_vec())], + vec![Filter::eq(SieveField::Name, "vacation".as_bytes().to_vec())], ) .await .map(|r| r.results.min()) diff --git a/crates/jmap/src/vacation/set.rs b/crates/jmap/src/vacation/set.rs index 06bae1a4..acbe2efa 100644 --- a/crates/jmap/src/vacation/set.rs +++ b/crates/jmap/src/vacation/set.rs @@ -15,9 +15,7 @@ use jmap_proto::{ method::set::{RequestArguments, SetRequest, SetResponse}, response::references::EvalObjectReferences, types::{ - collection::{Collection, SyncCollection}, date::UTCDate, - id::Id, property::Property, value::{MaybePatchValue, Object, Value}, }, @@ -31,6 +29,10 @@ use store::{ write::{Archiver, BatchBuilder}, }; use trc::AddContext; +use types::{ + collection::{Collection, SyncCollection}, + id::Id, +}; pub trait VacationResponseSet: Sync + Send { fn vacation_response_set( diff --git a/crates/jmap/src/websocket/stream.rs b/crates/jmap/src/websocket/stream.rs index bdfb6a5f..96fb1a29 100644 --- a/crates/jmap/src/websocket/stream.rs +++ b/crates/jmap/src/websocket/stream.rs @@ -4,8 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{sync::Arc, time::Instant}; - +use crate::api::{ToRequestError, request::RequestHandler}; use common::{Server, auth::AccessToken}; use futures_util::{SinkExt, StreamExt}; use http_proto::HttpSessionData; @@ -16,16 +15,15 @@ use jmap_proto::{ request::websocket::{ WebSocketMessage, WebSocketRequestError, WebSocketResponse, WebSocketStateChange, }, - types::type_state::DataType, }; +use std::future::Future; +use std::{sync::Arc, time::Instant}; use tokio_tungstenite::WebSocketStream; use trc::JmapEvent; use tungstenite::Message; +use types::type_state::DataType; use utils::map::bitmap::Bitmap; -use crate::api::{ToRequestError, request::RequestHandler}; -use std::future::Future; - pub trait WebSocketHandler: Sync + Send { fn handle_websocket_stream( &self, diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml index 0f47b287..cebe49b3 100644 --- a/crates/main/Cargo.toml +++ b/crates/main/Cargo.toml @@ -18,7 +18,7 @@ path = "src/main.rs" [dependencies] store = { path = "../store" } jmap = { path = "../jmap" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } smtp = { path = "../smtp" } imap = { path = "../imap" } pop3 = { path = "../pop3" } diff --git a/crates/managesieve/Cargo.toml b/crates/managesieve/Cargo.toml index 130506d0..536886db 100644 --- a/crates/managesieve/Cargo.toml +++ b/crates/managesieve/Cargo.toml @@ -7,6 +7,7 @@ resolver = "2" [dependencies] imap_proto = { path = "../imap-proto" } imap = { path = "../imap" } +types = { path = "../types" } jmap_proto = { path = "../jmap-proto" } directory = { path = "../directory" } common = { path = "../common" } diff --git a/crates/managesieve/src/core/client.rs b/crates/managesieve/src/core/client.rs index 213a8d5d..61bcbe49 100644 --- a/crates/managesieve/src/core/client.rs +++ b/crates/managesieve/src/core/client.rs @@ -4,17 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{Command, ResponseCode, SerializeResponse, Session, State}; use common::{ KV_RATE_LIMIT_IMAP, listener::{SessionResult, SessionStream}, }; use imap_proto::receiver::{self, Request}; -use jmap_proto::types::{collection::Collection, property::Property}; use store::query::Filter; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use trc::{AddContext, SecurityEvent}; - -use super::{Command, ResponseCode, SerializeResponse, Session, State}; +use types::{collection::Collection, field::SieveField}; impl Session { pub async fn ingest(&mut self, bytes: &[u8]) -> SessionResult { @@ -278,7 +277,10 @@ impl Session { .filter( account_id, Collection::SieveScript, - vec![Filter::eq(Property::Name, name.to_lowercase().into_bytes())], + vec![Filter::eq( + SieveField::Name, + name.to_lowercase().into_bytes(), + )], ) .await .caused_by(trc::location!()) diff --git a/crates/managesieve/src/op/capability.rs b/crates/managesieve/src/op/capability.rs index d2b1fc37..e91463ed 100644 --- a/crates/managesieve/src/op/capability.rs +++ b/crates/managesieve/src/op/capability.rs @@ -4,12 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use crate::core::{Session, StatusResponse}; use common::listener::SessionStream; use jmap_proto::request::capability::Capabilities; - -use crate::core::{Session, StatusResponse}; +use std::time::Instant; impl Session { pub async fn handle_capability(&self, message: &'static str) -> trc::Result> { diff --git a/crates/managesieve/src/op/getscript.rs b/crates/managesieve/src/op/getscript.rs index 15b51e2b..111e6029 100644 --- a/crates/managesieve/src/op/getscript.rs +++ b/crates/managesieve/src/op/getscript.rs @@ -4,17 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::listener::SessionStream; use directory::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; -use jmap_proto::types::{blob::BlobSection, collection::Collection}; +use std::time::Instant; use trc::AddContext; -use utils::BlobHash; - -use crate::core::{Command, ResponseCode, Session, StatusResponse}; +use types::{blob::BlobSection, blob_hash::BlobHash, collection::Collection}; impl Session { pub async fn handle_getscript(&mut self, request: Request) -> trc::Result> { diff --git a/crates/managesieve/src/op/listscripts.rs b/crates/managesieve/src/op/listscripts.rs index 8a4f3756..69f937d1 100644 --- a/crates/managesieve/src/op/listscripts.rs +++ b/crates/managesieve/src/op/listscripts.rs @@ -4,15 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use crate::core::{Session, StatusResponse}; use common::listener::SessionStream; use directory::Permission; use email::sieve::SieveScript; -use jmap_proto::types::collection::Collection; +use std::time::Instant; use trc::AddContext; - -use crate::core::{Session, StatusResponse}; +use types::collection::Collection; impl Session { pub async fn handle_listscripts(&mut self) -> trc::Result> { diff --git a/crates/managesieve/src/op/mod.rs b/crates/managesieve/src/op/mod.rs index 4d04cccc..3bc8ff3e 100644 --- a/crates/managesieve/src/op/mod.rs +++ b/crates/managesieve/src/op/mod.rs @@ -4,11 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::core::{Session, State, StatusResponse}; use common::listener::SessionStream; use directory::Permission; -use crate::core::{Session, State, StatusResponse}; - pub mod authenticate; pub mod capability; pub mod checkscript; diff --git a/crates/managesieve/src/op/putscript.rs b/crates/managesieve/src/op/putscript.rs index e5c77b30..826796cd 100644 --- a/crates/managesieve/src/op/putscript.rs +++ b/crates/managesieve/src/op/putscript.rs @@ -9,7 +9,6 @@ use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; use directory::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; -use jmap_proto::types::{collection::Collection, property::Property}; use sieve::compiler::ErrorType; use std::time::Instant; use store::{ @@ -18,6 +17,7 @@ use store::{ write::{Archiver, BatchBuilder}, }; use trc::AddContext; +use types::{collection::Collection, field::SieveField}; impl Session { pub async fn handle_putscript(&mut self, request: Request) -> trc::Result> { @@ -226,7 +226,10 @@ impl Session { .filter( account_id, Collection::SieveScript, - vec![Filter::eq(Property::Name, name.to_lowercase().into_bytes())], + vec![Filter::eq( + SieveField::Name, + name.to_lowercase().into_bytes(), + )], ) .await .caused_by(trc::location!())? diff --git a/crates/managesieve/src/op/renamescript.rs b/crates/managesieve/src/op/renamescript.rs index da5dc554..c2358a51 100644 --- a/crates/managesieve/src/op/renamescript.rs +++ b/crates/managesieve/src/op/renamescript.rs @@ -4,17 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; use directory::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; -use jmap_proto::types::collection::Collection; +use std::time::Instant; use store::write::BatchBuilder; use trc::AddContext; - -use crate::core::{Command, ResponseCode, Session, StatusResponse}; +use types::collection::Collection; impl Session { pub async fn handle_renamescript(&mut self, request: Request) -> trc::Result> { diff --git a/crates/migration/Cargo.toml b/crates/migration/Cargo.toml index 6406e21d..e500ad65 100644 --- a/crates/migration/Cargo.toml +++ b/crates/migration/Cargo.toml @@ -9,7 +9,7 @@ utils = { path = "../utils" } nlp = { path = "../nlp" } store = { path = "../store" } trc = { path = "../trc" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } common = { path = "../common" } email = { path = "../email" } directory = { path = "../directory" } diff --git a/crates/migration/src/calendar.rs b/crates/migration/src/calendar.rs index 6e3641c4..6ba925b7 100644 --- a/crates/migration/src/calendar.rs +++ b/crates/migration/src/calendar.rs @@ -10,13 +10,13 @@ use dav_proto::schema::request::DeadProperty; use groupware::calendar::{ AlarmDelta, CalendarEvent, CalendarEventData, ComponentTimeRange, UserProperties, }; -use jmap_proto::types::{collection::Collection, property::Property}; use store::{ Serialize, rand::{self, seq::SliceRandom}, write::{Archiver, BatchBuilder, serialize::rkyv_deserialize}, }; use trc::AddContext; +use types::{collection::Collection, field::Field}; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, @@ -117,7 +117,7 @@ pub(crate) async fn migrate_calendar_events(server: &Server) -> trc::Result<()> .with_collection(Collection::CalendarEvent) .update_document(document_id) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(new_event) .serialize() .caused_by(trc::location!())?, diff --git a/crates/migration/src/email.rs b/crates/migration/src/email.rs index 9a67a3e7..d70699c3 100644 --- a/crates/migration/src/email.rs +++ b/crates/migration/src/email.rs @@ -16,7 +16,6 @@ use email::{ }, }, }; -use jmap_proto::types::{collection::Collection, keyword::*, property::Property}; use mail_parser::{ Address, Attribute, ContentType, DateTime, Encoding, Header, HeaderName, HeaderValue, Received, }; @@ -31,7 +30,18 @@ use store::{ }, }; use trc::AddContext; -use utils::{BlobHash, codec::leb128::Leb128Iterator}; +use types::{ + blob_hash::BlobHash, + collection::Collection, + field::{EmailField, Field}, + keyword::*, +}; +use utils::codec::leb128::Leb128Iterator; + +const FIELD_KEYWORDS: u8 = 4; +const FIELD_THREAD_ID: u8 = 33; +const FIELD_CID: u8 = 76; +const FIELD_MAILBOX_IDS: u8 = 7; const BM_MARKER: u8 = 1 << 7; @@ -52,7 +62,7 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res account_id, collection: Collection::Email.into(), class: BitmapClass::Tag { - field: Property::MailboxIds.into(), + field: FIELD_MAILBOX_IDS, value: TagValue::Id(TOMBSTONE_ID), }, document_id: 0, @@ -65,12 +75,12 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res let mut did_migrate = false; // Obtain mailboxes - for (message_id, uid_mailbox) in get_properties::( + for (message_id, uid_mailbox) in get_properties::( server, account_id, Collection::Email, &(), - Property::MailboxIds, + FIELD_MAILBOX_IDS, ) .await .caused_by(trc::location!())? @@ -79,29 +89,19 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res } // Obtain keywords - for (message_id, keywords) in get_properties::( - server, - account_id, - Collection::Email, - &(), - Property::Keywords, - ) - .await - .caused_by(trc::location!())? + for (message_id, keywords) in + get_properties::(server, account_id, Collection::Email, &(), FIELD_KEYWORDS) + .await + .caused_by(trc::location!())? { message_data.entry(message_id).or_default().keywords = keywords.0; } // Obtain threadIds - for (message_id, thread_id) in get_properties::( - server, - account_id, - Collection::Email, - &(), - Property::ThreadId, - ) - .await - .caused_by(trc::location!())? + for (message_id, thread_id) in + get_properties::(server, account_id, Collection::Email, &(), FIELD_THREAD_ID) + .await + .caused_by(trc::location!())? { message_data.entry(message_id).or_default().thread_id = thread_id; } @@ -117,13 +117,13 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res .update_document(message_id); for mailbox in &data.mailboxes { - batch.untag(Property::MailboxIds, TagValue::Id(mailbox.mailbox_id)); + batch.untag(EmailField::MailboxIds, TagValue::Id(mailbox.mailbox_id)); } did_migrate = true; batch.set( - Property::Value, + Field::ARCHIVE, Archiver::new(data) .serialize() .caused_by(trc::location!())?, @@ -144,7 +144,7 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res account_id, collection: Collection::Email.into(), document_id: message_id, - class: ValueClass::Property(Property::BodyStructure.into()), + class: ValueClass::Property(EmailField::Metadata.into()), }) .await { @@ -161,14 +161,14 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res if matches!(header.name, HeaderName::MessageId) { header.value.visit_text(|id| { if id.len() < MAX_ID_LENGTH { - batch.index(Property::References, encode_message_id(id)); + batch.index(EmailField::References, encode_message_id(id)); } }); } } batch.set( - Property::BodyStructure, + EmailField::Metadata, Archiver::new(metadata) .serialize() .caused_by(trc::location!())?, @@ -190,7 +190,7 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res account_id, collection: Collection::Email.into(), document_id: message_id, - class: ValueClass::Property(Property::BodyStructure.into()), + class: ValueClass::Property(EmailField::Metadata.into()), }) .await .is_err() @@ -205,10 +205,7 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res } // Delete keyword bitmaps - for field in [ - u8::from(Property::Keywords), - u8::from(Property::Keywords) | BM_MARKER, - ] { + for field in [FIELD_KEYWORDS, FIELD_KEYWORDS | BM_MARKER] { server .store() .delete_range( @@ -243,7 +240,7 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res key: KeySerializer::new(U64_LEN) .write(account_id) .write(u8::from(Collection::Email)) - .write(u8::from(Property::MessageId)) + .write(u8::from(EmailField::MessageId)) .finalize(), }, AnyKey { @@ -251,7 +248,7 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res key: KeySerializer::new(U64_LEN) .write(account_id) .write(u8::from(Collection::Email)) - .write(u8::from(Property::MessageId)) + .write(u8::from(EmailField::MessageId)) .write(&[u8::MAX; 8][..]) .finalize(), }, @@ -261,12 +258,11 @@ pub(crate) async fn migrate_emails(server: &Server, account_id: u32) -> trc::Res // Delete values for property in [ - Property::MailboxIds, - Property::Keywords, - Property::ThreadId, - Property::Cid, + FIELD_MAILBOX_IDS, + FIELD_KEYWORDS, + FIELD_THREAD_ID, + FIELD_CID, ] { - let property: u8 = property.into(); server .store() .delete_range( diff --git a/crates/migration/src/encryption.rs b/crates/migration/src/encryption.rs index 1cca9364..ea7955f0 100644 --- a/crates/migration/src/encryption.rs +++ b/crates/migration/src/encryption.rs @@ -6,12 +6,12 @@ use common::Server; use email::message::crypto::EncryptionParams; -use jmap_proto::types::{collection::Collection, property::Property}; use store::{ Deserialize, Serialize, ValueKey, write::{AlignedBytes, Archive, Archiver, BatchBuilder, ValueClass}, }; use trc::AddContext; +use types::{collection::Collection, field::PrincipalField}; pub(crate) async fn migrate_encryption_params( server: &Server, @@ -23,7 +23,7 @@ pub(crate) async fn migrate_encryption_params( account_id, collection: Collection::Principal.into(), document_id: 0, - class: ValueClass::Property(Property::Parameters.into()), + class: ValueClass::from(PrincipalField::EncryptionKeys), }) .await { @@ -34,7 +34,7 @@ pub(crate) async fn migrate_encryption_params( .with_collection(Collection::Principal) .update_document(0) .set( - Property::Parameters, + PrincipalField::EncryptionKeys, Archiver::new(legacy.0) .serialize() .caused_by(trc::location!())?, @@ -55,7 +55,7 @@ pub(crate) async fn migrate_encryption_params( account_id, collection: Collection::Principal.into(), document_id: 0, - class: ValueClass::Property(Property::Parameters.into()), + class: ValueClass::from(PrincipalField::EncryptionKeys), }) .await .is_err() diff --git a/crates/migration/src/identity.rs b/crates/migration/src/identity.rs index dae91d84..ff0a3e30 100644 --- a/crates/migration/src/identity.rs +++ b/crates/migration/src/identity.rs @@ -5,15 +5,15 @@ */ use super::object::Object; -use crate::object::FromLegacy; +use crate::object::{FromLegacy, Property, Value}; use common::Server; use email::identity::{EmailAddress, Identity}; -use jmap_proto::types::{collection::Collection, property::Property, value::Value}; use store::{ Serialize, ValueKey, write::{AlignedBytes, Archive, Archiver, BatchBuilder, ValueClass}, }; use trc::AddContext; +use types::{collection::Collection, field::Field}; pub(crate) async fn migrate_identities(server: &Server, account_id: u32) -> trc::Result { // Obtain email ids @@ -35,7 +35,7 @@ pub(crate) async fn migrate_identities(server: &Server, account_id: u32) -> trc: account_id, collection: Collection::Identity.into(), document_id: identity_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await { @@ -46,7 +46,7 @@ pub(crate) async fn migrate_identities(server: &Server, account_id: u32) -> trc: .with_collection(Collection::Identity) .update_document(identity_id) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(Identity::from_legacy(legacy)) .serialize() .caused_by(trc::location!())?, @@ -68,7 +68,7 @@ pub(crate) async fn migrate_identities(server: &Server, account_id: u32) -> trc: account_id, collection: Collection::Identity.into(), document_id: identity_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await .is_err() @@ -141,7 +141,7 @@ fn convert_email_addresses(value: &Value) -> Option> { name: None, email: String::new(), }; - for (key, value) in &obj.0 { + for (key, value) in &obj.properties { match (key, value) { (Property::Email, Value::Text(value)) => { addr.email = value.to_string(); diff --git a/crates/migration/src/lib.rs b/crates/migration/src/lib.rs index 742db2ea..d16a3dec 100644 --- a/crates/migration/src/lib.rs +++ b/crates/migration/src/lib.rs @@ -13,7 +13,6 @@ use changelog::reset_changelog; use common::{ DATABASE_SCHEMA_VERSION, KV_LOCK_HOUSEKEEPER, Server, manager::boot::DEFAULT_SETTINGS, }; -use jmap_proto::types::{collection::Collection, property::Property}; use principal::{migrate_principal, migrate_principals}; use report::migrate_reports; use std::time::Duration; @@ -25,6 +24,7 @@ use store::{ write::{AnyClass, AnyKey, BatchBuilder, ValueClass, key::DeserializeBigEndian}, }; use trc::AddContext; +use types::collection::Collection; pub mod calendar; pub mod changelog; @@ -411,19 +411,17 @@ async fn is_new_install(server: &Server) -> trc::Result { Ok(true) } -async fn get_properties( +async fn get_properties( server: &Server, account_id: u32, collection: Collection, iterate: &I, - property: P, + property: u8, ) -> trc::Result> where I: DocumentSet + Send + Sync, - P: AsRef + Sync + Send, U: Deserialize + 'static, { - let property: u8 = property.as_ref().into(); let collection: u8 = collection.into(); let expected_results = iterate.len(); let mut results = Vec::with_capacity(expected_results); diff --git a/crates/migration/src/mailbox.rs b/crates/migration/src/mailbox.rs index 4d103be0..48b18c18 100644 --- a/crates/migration/src/mailbox.rs +++ b/crates/migration/src/mailbox.rs @@ -5,10 +5,9 @@ */ use super::object::Object; -use crate::object::FromLegacy; +use crate::object::{FromLegacy, Property, Value}; use common::{Server, config::jmap::settings::SpecialUse}; use email::mailbox::Mailbox; -use jmap_proto::types::{collection::Collection, property::Property, value::Value}; use store::{ SUBSPACE_BITMAP_TAG, SUBSPACE_BITMAP_TEXT, SUBSPACE_INDEXES, Serialize, U64_LEN, ValueKey, rand, @@ -17,6 +16,7 @@ use store::{ }, }; use trc::AddContext; +use types::{collection::Collection, field::Field}; use utils::config::utils::ParseValue; pub(crate) async fn migrate_mailboxes(server: &Server, account_id: u32) -> trc::Result { @@ -39,7 +39,7 @@ pub(crate) async fn migrate_mailboxes(server: &Server, account_id: u32) -> trc:: account_id, collection: Collection::Mailbox.into(), document_id: mailbox_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await { @@ -50,7 +50,7 @@ pub(crate) async fn migrate_mailboxes(server: &Server, account_id: u32) -> trc:: .with_collection(Collection::Mailbox) .update_document(mailbox_id) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(Mailbox::from_legacy(legacy)) .serialize() .caused_by(trc::location!())?, @@ -71,7 +71,7 @@ pub(crate) async fn migrate_mailboxes(server: &Server, account_id: u32) -> trc:: account_id, collection: Collection::Mailbox.into(), document_id: mailbox_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await .is_err() diff --git a/crates/migration/src/object.rs b/crates/migration/src/object.rs index af5a713b..0919b494 100644 --- a/crates/migration/src/object.rs +++ b/crates/migration/src/object.rs @@ -4,27 +4,126 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_proto::types::{ - blob::BlobId, - date::UTCDate, - id::Id, - keyword::*, - property::{HeaderForm, HeaderProperty, Property}, - value::{AclGrant, Value}, -}; use std::slice::Iter; use store::{Deserialize, U64_LEN}; +use types::{acl::AclGrant, blob::BlobId, id::Id, keyword::*}; use utils::{ codec::leb128::Leb128Iterator, map::{bitmap::Bitmap, vec_map::VecMap}, }; -#[derive(Debug, Clone, Default, serde::Serialize, PartialEq, Eq)] -#[serde(transparent)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Object { pub properties: VecMap, } +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum Property { + Acl, + Aliases, + Attachments, + Bcc, + BlobId, + BodyStructure, + BodyValues, + Capabilities, + Cc, + Charset, + Cid, + DeliveryStatus, + Description, + DeviceClientId, + Disposition, + DsnBlobIds, + Email, + EmailId, + EmailIds, + Envelope, + Expires, + From, + FromDate, + HasAttachment, + Headers, + HtmlBody, + HtmlSignature, + Id, + IdentityId, + InReplyTo, + IsActive, + IsEnabled, + IsSubscribed, + Keys, + Keywords, + Language, + Location, + MailboxIds, + MayDelete, + MdnBlobIds, + Members, + MessageId, + MyRights, + Name, + ParentId, + PartId, + Picture, + Preview, + Quota, + ReceivedAt, + References, + ReplyTo, + Role, + Secret, + SendAt, + Sender, + SentAt, + Size, + SortOrder, + Subject, + SubParts, + TextBody, + TextSignature, + ThreadId, + Timezone, + To, + ToDate, + TotalEmails, + TotalThreads, + Type, + Types, + UndoStatus, + UnreadEmails, + UnreadThreads, + Url, + VerificationCode, + Addresses, + P256dh, + Auth, + Value, + SmtpReply, + Delivered, + Displayed, + MailFrom, + RcptTo, + Parameters, + IsEncodingProblem, + IsTruncated, + MayReadItems, + MayAddItems, + MayRemoveItems, + MaySetSeen, + MaySetKeywords, + MayCreateChild, + MayRename, + MaySubmit, + ResourceType, + Used, + HardLimit, + WarnLimit, + SoftLimit, + Scope, + _T(String), +} + impl Object { pub fn with_capacity(capacity: usize) -> Self { Self { @@ -54,6 +153,36 @@ impl Object { } } +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub enum Value { + Text(String), + UnsignedInt(u64), + Bool(bool), + Id(Id), + Date(UTCDate), + BlobId(BlobId), + Keyword(Keyword), + List(Vec), + Object(Object), + Acl(Vec), + Blob(Vec), + #[default] + Null, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +pub struct UTCDate { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, + pub tz_before_gmt: bool, + pub tz_hour: u8, + pub tz_minute: u8, +} + const TEXT: u8 = 0; const UNSIGNED_INT: u8 = 1; const BOOL_TRUE: u8 = 2; @@ -131,9 +260,7 @@ impl DeserializeFrom for Value { } Some(Value::List(items)) } - OBJECT => Some(Value::Object(jmap_proto::types::value::Object( - Object::deserialize_from(bytes)?.properties, - ))), + OBJECT => Some(Value::Object(Object::deserialize_from(bytes)?)), BLOB => Some(Value::Blob(Vec::deserialize_from(bytes)?)), ACL => { let len = bytes.next_leb128()?; @@ -304,11 +431,6 @@ impl DeserializeFrom for Property { 87 => Some(Property::From), 88 => Some(Property::FromDate), 89 => Some(Property::HasAttachment), - 90 => Some(Property::Header(HeaderProperty { - form: HeaderForm::Raw, - header: String::new(), - all: false, - })), // Never serialized 91 => Some(Property::Headers), 92 => Some(Property::HtmlBody), 93 => Some(Property::HtmlSignature), @@ -334,3 +456,194 @@ pub trait FromLegacy { pub trait TryFromLegacy: Sized { fn try_from_legacy(legacy: Object) -> Option; } + +impl Value { + pub fn try_unwrap_id(self) -> Option { + match self { + Value::Id(id) => id.into(), + _ => None, + } + } + + pub fn try_unwrap_bool(self) -> Option { + match self { + Value::Bool(b) => b.into(), + _ => None, + } + } + + pub fn try_unwrap_keyword(self) -> Option { + match self { + Value::Keyword(k) => k.into(), + _ => None, + } + } + + pub fn try_unwrap_string(self) -> Option { + match self { + Value::Text(s) => Some(s), + _ => None, + } + } + + pub fn try_unwrap_object(self) -> Option> { + match self { + Value::Object(o) => Some(o), + _ => None, + } + } + + pub fn try_unwrap_list(self) -> Option> { + match self { + Value::List(l) => Some(l), + _ => None, + } + } + + pub fn try_unwrap_date(self) -> Option { + match self { + Value::Date(d) => Some(d), + _ => None, + } + } + + pub fn try_unwrap_blob_id(self) -> Option { + match self { + Value::BlobId(b) => Some(b), + _ => None, + } + } + + pub fn try_unwrap_uint(self) -> Option { + match self { + Value::UnsignedInt(u) => Some(u), + _ => None, + } + } + + pub fn as_string(&self) -> Option<&str> { + match self { + Value::Text(s) => Some(s), + _ => None, + } + } + + pub fn as_id(&self) -> Option<&Id> { + match self { + Value::Id(id) => Some(id), + _ => None, + } + } + + pub fn as_blob_id(&self) -> Option<&BlobId> { + match self { + Value::BlobId(id) => Some(id), + _ => None, + } + } + + pub fn as_list(&self) -> Option<&Vec> { + match self { + Value::List(l) => Some(l), + _ => None, + } + } + + pub fn as_acl(&self) -> Option<&Vec> { + match self { + Value::Acl(l) => Some(l), + _ => None, + } + } + + pub fn as_uint(&self) -> Option { + match self { + Value::UnsignedInt(u) => Some(*u), + Value::Id(id) => Some(*id.as_ref()), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match self { + Value::Bool(b) => Some(*b), + _ => None, + } + } + + pub fn as_date(&self) -> Option<&UTCDate> { + match self { + Value::Date(d) => Some(d), + _ => None, + } + } + + pub fn as_obj(&self) -> Option<&Object> { + match self { + Value::Object(o) => Some(o), + _ => None, + } + } + + pub fn as_obj_mut(&mut self) -> Option<&mut Object> { + match self { + Value::Object(o) => Some(o), + _ => None, + } + } + + pub fn try_cast_uint(&self) -> Option { + match self { + Value::UnsignedInt(u) => Some(*u), + Value::Id(id) => Some(id.id()), + Value::Bool(b) => Some(*b as u64), + _ => None, + } + } +} + +impl UTCDate { + pub fn from_timestamp(timestamp: i64) -> Self { + // Ported from http://howardhinnant.github.io/date_algorithms.html#civil_from_days + let (z, seconds) = ((timestamp / 86400) + 719468, timestamp % 86400); + let era: i64 = (if z >= 0 { z } else { z - 146096 }) / 146097; + let doe: u64 = (z - era * 146097) as u64; // [0, 146096] + let yoe: u64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] + let y: i64 = (yoe as i64) + era * 400; + let doy: u64 = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] + let mp = (5 * doy + 2) / 153; // [0, 11] + let d: u64 = doy - (153 * mp + 2) / 5 + 1; // [1, 31] + let m: u64 = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12] + let (h, mn, s) = (seconds / 3600, (seconds / 60) % 60, seconds % 60); + + UTCDate { + year: (y + i64::from(m <= 2)) as u16, + month: m as u8, + day: d as u8, + hour: h as u8, + minute: mn as u8, + second: s as u8, + tz_before_gmt: false, + tz_hour: 0, + tz_minute: 0, + } + } + + pub fn timestamp(&self) -> i64 { + // Ported from https://github.com/protocolbuffers/upb/blob/22182e6e/upb/json_decode.c#L982-L992 + let month = self.month as u32; + let year_base = 4800; /* Before min year, multiple of 400. */ + let m_adj = month.wrapping_sub(3); /* March-based month. */ + let carry = i64::from(m_adj > month); + let adjust = if carry > 0 { 12 } else { 0 }; + let y_adj = self.year as i64 + year_base - carry; + let month_days = ((m_adj.wrapping_add(adjust)) * 62719 + 769) / 2048; + let leap_days = y_adj / 4 - y_adj / 100 + y_adj / 400; + (y_adj * 365 + leap_days + month_days as i64 + (self.day as i64 - 1) - 2472632) * 86400 + + self.hour as i64 * 3600 + + self.minute as i64 * 60 + + self.second as i64 + + ((self.tz_hour as i64 * 3600 + self.tz_minute as i64 * 60) + * if self.tz_before_gmt { 1 } else { -1 }) + } +} diff --git a/crates/migration/src/principal.rs b/crates/migration/src/principal.rs index 40686a62..805d5655 100644 --- a/crates/migration/src/principal.rs +++ b/crates/migration/src/principal.rs @@ -10,7 +10,6 @@ use directory::{ Type, backend::internal::{PrincipalField, PrincipalSet}, }; -use jmap_proto::types::collection::Collection; use nlp::tokenizers::word::WordTokenizer; use std::{slice::Iter, time::Instant}; use store::{ @@ -21,6 +20,7 @@ use store::{ write::{AlignedBytes, Archive, Archiver, BatchBuilder, DirectoryClass, ValueClass}, }; use trc::AddContext; +use types::collection::Collection; use utils::codec::leb128::Leb128Iterator; use crate::{ diff --git a/crates/migration/src/push.rs b/crates/migration/src/push.rs index 867323f5..443afd61 100644 --- a/crates/migration/src/push.rs +++ b/crates/migration/src/push.rs @@ -5,18 +5,16 @@ */ use super::object::Object; -use crate::object::FromLegacy; +use crate::object::{FromLegacy, Property, Value}; use base64::{Engine, engine::general_purpose}; use common::Server; use email::push::{Keys, PushSubscription}; -use jmap_proto::types::{ - collection::Collection, property::Property, type_state::DataType, value::Value, -}; use store::{ Serialize, ValueKey, write::{AlignedBytes, Archive, Archiver, BatchBuilder, ValueClass}, }; use trc::AddContext; +use types::{collection::Collection, field::Field, type_state::DataType}; pub(crate) async fn migrate_push_subscriptions( server: &Server, @@ -41,7 +39,7 @@ pub(crate) async fn migrate_push_subscriptions( account_id, collection: Collection::PushSubscription.into(), document_id: push_subscription_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await { @@ -52,7 +50,7 @@ pub(crate) async fn migrate_push_subscriptions( .with_collection(Collection::PushSubscription) .update_document(push_subscription_id) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(PushSubscription::from_legacy(legacy)) .serialize() .caused_by(trc::location!())?, @@ -73,7 +71,7 @@ pub(crate) async fn migrate_push_subscriptions( account_id, collection: Collection::PushSubscription.into(), document_id: push_subscription_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await .is_err() @@ -159,7 +157,7 @@ fn convert_keys(value: &Value) -> Option { auth: Default::default(), }; if let Value::Object(obj) = value { - for (key, value) in &obj.0 { + for (key, value) in &obj.properties { match (key, value) { (Property::Auth, Value::Text(value)) => { addr.auth = general_purpose::URL_SAFE.decode(value).unwrap_or_default(); diff --git a/crates/migration/src/queue.rs b/crates/migration/src/queue.rs index 679bad92..0dd64997 100644 --- a/crates/migration/src/queue.rs +++ b/crates/migration/src/queue.rs @@ -24,7 +24,7 @@ use store::{ }, }; use trc::AddContext; -use utils::BlobHash; +use types::blob_hash::BlobHash; pub(crate) async fn migrate_queue_v011(server: &Server) -> trc::Result<()> { let mut count = 0; diff --git a/crates/migration/src/sieve.rs b/crates/migration/src/sieve.rs index 27961fb0..41e4b6fa 100644 --- a/crates/migration/src/sieve.rs +++ b/crates/migration/src/sieve.rs @@ -5,10 +5,9 @@ */ use super::object::Object; -use crate::object::TryFromLegacy; +use crate::object::{Property, TryFromLegacy, Value}; use common::Server; use email::sieve::{SieveScript, VacationResponse}; -use jmap_proto::types::{collection::Collection, property::Property, value::Value}; use store::{ SUBSPACE_BITMAP_TEXT, SUBSPACE_INDEXES, SUBSPACE_PROPERTY, Serialize, U64_LEN, ValueKey, write::{ @@ -16,6 +15,10 @@ use store::{ }, }; use trc::{AddContext, StoreEvent}; +use types::{ + collection::Collection, + field::{Field, SieveField}, +}; pub(crate) async fn migrate_sieve(server: &Server, account_id: u32) -> trc::Result { // Obtain email ids @@ -62,7 +65,7 @@ pub(crate) async fn migrate_sieve(server: &Server, account_id: u32) -> trc::Resu account_id, collection: Collection::SieveScript.into(), document_id: script_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await { @@ -74,16 +77,16 @@ pub(crate) async fn migrate_sieve(server: &Server, account_id: u32) -> trc::Resu .with_collection(Collection::SieveScript) .update_document(script_id) .index( - Property::IsActive, + SieveField::IsActive, if script.is_active { vec![1u8] } else { vec![0u8] }, ) - .index(Property::Name, script.name.to_lowercase()) + .index(SieveField::Name, script.name.to_lowercase()) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(script) .serialize() .caused_by(trc::location!())?, @@ -111,7 +114,7 @@ pub(crate) async fn migrate_sieve(server: &Server, account_id: u32) -> trc::Resu account_id, collection: Collection::SieveScript.into(), document_id: script_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await .is_err() @@ -134,7 +137,7 @@ pub(crate) async fn migrate_sieve(server: &Server, account_id: u32) -> trc::Resu key: KeySerializer::new(U64_LEN) .write(account_id) .write(u8::from(Collection::SieveScript)) - .write(u8::from(Property::EmailIds)) + .write(u8::from(SieveField::Ids)) .finalize(), }, AnyKey { @@ -142,7 +145,7 @@ pub(crate) async fn migrate_sieve(server: &Server, account_id: u32) -> trc::Resu key: KeySerializer::new(U64_LEN) .write(account_id) .write(u8::from(Collection::SieveScript)) - .write(u8::from(Property::EmailIds)) + .write(u8::from(SieveField::Ids)) .write(&[u8::MAX; 8][..]) .finalize(), }, diff --git a/crates/migration/src/submission.rs b/crates/migration/src/submission.rs index a81a3134..8d30db39 100644 --- a/crates/migration/src/submission.rs +++ b/crates/migration/src/submission.rs @@ -5,12 +5,11 @@ */ use super::object::Object; -use crate::object::FromLegacy; +use crate::object::{FromLegacy, Property, Value}; use common::Server; use email::submission::{ Address, Delivered, DeliveryStatus, EmailSubmission, Envelope, UndoStatus, }; -use jmap_proto::types::{collection::Collection, property::Property, value::Value}; use store::{ SUBSPACE_BITMAP_TAG, SUBSPACE_BITMAP_TEXT, SUBSPACE_INDEXES, Serialize, SerializeInfallible, U64_LEN, ValueKey, @@ -19,6 +18,10 @@ use store::{ }, }; use trc::AddContext; +use types::{ + collection::Collection, + field::{EmailSubmissionField, Field}, +}; use utils::map::vec_map::VecMap; pub(crate) async fn migrate_email_submissions( @@ -69,7 +72,7 @@ pub(crate) async fn migrate_email_submissions( account_id, collection: Collection::EmailSubmission.into(), document_id: email_submission_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await { @@ -80,13 +83,13 @@ pub(crate) async fn migrate_email_submissions( .with_account_id(account_id) .with_collection(Collection::EmailSubmission) .update_document(email_submission_id) - .index(Property::UndoStatus, es.undo_status.as_index()) - .index(Property::EmailId, es.email_id.serialize()) - .index(Property::ThreadId, es.thread_id.serialize()) - .index(Property::IdentityId, es.identity_id.serialize()) - .index(Property::SendAt, es.send_at.serialize()) + .index(EmailSubmissionField::UndoStatus, es.undo_status.as_index()) + .index(EmailSubmissionField::EmailId, es.email_id.serialize()) + .index(EmailSubmissionField::ThreadId, es.thread_id.serialize()) + .index(EmailSubmissionField::IdentityId, es.identity_id.serialize()) + .index(EmailSubmissionField::SendAt, es.send_at.serialize()) .set( - Property::Value, + Field::ARCHIVE, Archiver::new(es).serialize().caused_by(trc::location!())?, ); did_migrate = true; @@ -105,7 +108,7 @@ pub(crate) async fn migrate_email_submissions( account_id, collection: Collection::EmailSubmission.into(), document_id: email_submission_id, - class: ValueClass::Property(Property::Value.into()), + class: ValueClass::Property(Field::ARCHIVE.into()), }) .await .is_err() @@ -174,7 +177,7 @@ fn convert_delivery_status(value: &Value) -> VecMap { if let Value::List(list) = value { for value in list { if let Value::Object(obj) = value { - for (k, v) in obj.0.iter() { + for (k, v) in obj.properties.iter() { if let (Property::_T(k), Value::Object(v)) = (k, v) { let mut delivery_status = DeliveryStatus { smtp_reply: String::new(), @@ -182,7 +185,7 @@ fn convert_delivery_status(value: &Value) -> VecMap { displayed: false, }; - for (property, value) in &v.0 { + for (property, value) in &v.properties { match (property, value) { (Property::Delivered, Value::Text(v)) => match v.as_str() { "queued" => delivery_status.delivered = Delivered::Queued, @@ -215,7 +218,7 @@ fn convert_envelope(value: &Value) -> Envelope { }; if let Value::Object(obj) = value { - for (property, value) in &obj.0 { + for (property, value) in &obj.properties { match (property, value) { (Property::MailFrom, _) => { envelope.mail_from = convert_envelope_address(value).unwrap_or_default(); @@ -246,7 +249,7 @@ fn convert_envelope_address(envelope: &Value) -> Option
{ email: email.to_string(), parameters: None, }; - for (k, v) in params.0.iter() { + for (k, v) in params.properties.iter() { if let Property::_T(k) = &k && !k.is_empty() { diff --git a/crates/migration/src/tasks.rs b/crates/migration/src/tasks.rs index 361ed8a8..0ad610b1 100644 --- a/crates/migration/src/tasks.rs +++ b/crates/migration/src/tasks.rs @@ -10,7 +10,7 @@ use store::{ write::{AnyClass, BatchBuilder, TaskQueueClass, ValueClass, key::DeserializeBigEndian, now}, }; use trc::AddContext; -use utils::BlobHash; +use types::blob_hash::BlobHash; pub(crate) async fn migrate_tasks_v011(server: &Server) -> trc::Result<()> { let from_key = ValueKey:: { diff --git a/crates/migration/src/threads.rs b/crates/migration/src/threads.rs index d353b2ed..8d079bf3 100644 --- a/crates/migration/src/threads.rs +++ b/crates/migration/src/threads.rs @@ -5,12 +5,12 @@ */ use common::Server; -use jmap_proto::types::collection::Collection; use store::{ SUBSPACE_BITMAP_ID, U64_LEN, write::{AnyKey, key::KeySerializer}, }; use trc::AddContext; +use types::collection::Collection; pub(crate) async fn migrate_threads(server: &Server, account_id: u32) -> trc::Result { // Obtain email ids diff --git a/crates/pop3/Cargo.toml b/crates/pop3/Cargo.toml index ac25f52c..3f2d5227 100644 --- a/crates/pop3/Cargo.toml +++ b/crates/pop3/Cargo.toml @@ -11,7 +11,7 @@ directory = { path = "../directory" } imap = { path = "../imap" } utils = { path = "../utils" } trc = { path = "../trc" } -jmap_proto = { path = "../jmap-proto" } +types = { path = "../types" } email = { path = "../email" } mail-parser = { version = "0.11", features = ["full_encoding"] } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } diff --git a/crates/pop3/src/mailbox.rs b/crates/pop3/src/mailbox.rs index 59bd72aa..4f76daad 100644 --- a/crates/pop3/src/mailbox.rs +++ b/crates/pop3/src/mailbox.rs @@ -4,21 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::collections::BTreeMap; - +use crate::Session; use common::{config::jmap::settings::SpecialUse, listener::SessionStream}; use email::{ cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}, mailbox::INBOX_ID, }; -use jmap_proto::types::{collection::Collection, property::Property}; +use std::collections::BTreeMap; use store::{ IndexKey, IterateParams, SerializeInfallible, U32_LEN, ahash::AHashMap, write::key::DeserializeBigEndian, }; use trc::AddContext; - -use crate::Session; +use types::{collection::Collection, field::EmailField}; #[derive(Default)] pub struct Mailbox { @@ -66,14 +64,14 @@ impl Session { account_id, collection: Collection::Email.into(), document_id: 0, - field: Property::Size.into(), + field: EmailField::Size.into(), key: 0u32.serialize(), }, IndexKey { account_id, collection: Collection::Email.into(), document_id: u32::MAX, - field: Property::Size.into(), + field: EmailField::Size.into(), key: u32::MAX.serialize(), }, ) diff --git a/crates/pop3/src/op/fetch.rs b/crates/pop3/src/op/fetch.rs index 9c31ee03..850a2257 100644 --- a/crates/pop3/src/op/fetch.rs +++ b/crates/pop3/src/op/fetch.rs @@ -4,15 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use crate::{Session, protocol::response::Response}; use common::listener::SessionStream; use directory::Permission; use email::message::metadata::MessageMetadata; -use jmap_proto::types::{collection::Collection, property::Property}; +use std::time::Instant; use trc::AddContext; - -use crate::{Session, protocol::response::Response}; +use types::{collection::Collection, field::EmailField}; impl Session { pub async fn handle_fetch(&mut self, msg: u32, lines: Option) -> trc::Result<()> { @@ -30,7 +28,7 @@ impl Session { mailbox.account_id, Collection::Email, message.id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await .caused_by(trc::location!())? diff --git a/crates/services/Cargo.toml b/crates/services/Cargo.toml index 5121590b..d838a53f 100644 --- a/crates/services/Cargo.toml +++ b/crates/services/Cargo.toml @@ -12,6 +12,7 @@ trc = { path = "../trc" } email = { path = "../email" } smtp = { path = "../smtp" } groupware = { path = "../groupware" } +types = { path = "../types" } jmap_proto = { path = "../jmap-proto" } directory = { path = "../directory" } smtp-proto = { version = "0.2", features = ["rkyv", "serde"] } diff --git a/crates/services/src/broadcast/mod.rs b/crates/services/src/broadcast/mod.rs index 3e87699a..e90b2a24 100644 --- a/crates/services/src/broadcast/mod.rs +++ b/crates/services/src/broadcast/mod.rs @@ -4,10 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::borrow::Borrow; - use common::ipc::BroadcastEvent; -use jmap_proto::types::state::StateChange; +use std::borrow::Borrow; +use types::type_state::StateChange; use utils::{ codec::leb128::{Leb128Iterator, Leb128Writer}, map::bitmap::Bitmap, diff --git a/crates/services/src/state_manager/ece.rs b/crates/services/src/state_manager/ece.rs index 52f1dc3c..155d986e 100644 --- a/crates/services/src/state_manager/ece.rs +++ b/crates/services/src/state_manager/ece.rs @@ -83,7 +83,7 @@ pub fn ece_encrypt( // Split into records let rs = ECE_WEBPUSH_DEFAULT_RS as usize - ECE_TAG_LENGTH; let mut min_num_records = data.len() / (rs - 1); - if data.len() % (rs - 1) != 0 { + if !data.len().is_multiple_of(rs - 1) { min_num_records += 1; } let mut pad_length = std::cmp::max(pad_length, min_num_records); @@ -123,7 +123,7 @@ pub fn ece_encrypt( data_share = data.len(); } else if extra_data > 0 { let mut extra_share = extra_data / (records_remaining - 1); - if extra_data % (records_remaining - 1) != 0 { + if !extra_data.is_multiple_of(records_remaining - 1) { extra_share += 1; } data_share += extra_share; diff --git a/crates/services/src/state_manager/http.rs b/crates/services/src/state_manager/http.rs index 484bbc5a..fea74b26 100644 --- a/crates/services/src/state_manager/http.rs +++ b/crates/services/src/state_manager/http.rs @@ -4,17 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - +use super::{Event, PushServer, ece::ece_encrypt}; use base64::Engine; use common::ipc::EncryptionKeys; - -use jmap_proto::{response::status::StateChangeResponse, types::id::Id}; +use jmap_proto::response::status::StateChangeResponse; use reqwest::header::{CONTENT_ENCODING, CONTENT_TYPE}; +use std::time::{Duration, Instant}; use tokio::sync::mpsc; use trc::PushSubscriptionEvent; - -use super::{Event, PushServer, ece::ece_encrypt}; +use types::id::Id; impl PushServer { pub fn send(&mut self, id: Id, push_tx: mpsc::Sender, push_timeout: Duration) { diff --git a/crates/services/src/state_manager/manager.rs b/crates/services/src/state_manager/manager.rs index 711d3ee0..1cfe6560 100644 --- a/crates/services/src/state_manager/manager.rs +++ b/crates/services/src/state_manager/manager.rs @@ -4,26 +4,27 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - sync::Arc, - time::{Instant, SystemTime}, +use super::{ + Event, PURGE_EVERY, PushUpdate, SEND_TIMEOUT, Subscriber, SubscriberId, SubscriberType, + push::spawn_push_manager, }; - use common::{ Inner, core::BuildServer, ipc::{BroadcastEvent, PushSubscription, StateEvent, UpdateSubscription}, }; -use jmap_proto::types::{id::Id, state::StateChange, type_state::DataType}; +use std::{ + sync::Arc, + time::{Instant, SystemTime}, +}; use store::{ahash::AHashMap, rand}; use tokio::sync::mpsc; use trc::ServerEvent; -use utils::map::bitmap::Bitmap; - -use super::{ - Event, PURGE_EVERY, PushUpdate, SEND_TIMEOUT, Subscriber, SubscriberId, SubscriberType, - push::spawn_push_manager, +use types::{ + id::Id, + type_state::{DataType, StateChange}, }; +use utils::map::bitmap::Bitmap; #[allow(clippy::unwrap_or_default)] pub fn spawn_state_manager(inner: Arc, mut change_rx: mpsc::Receiver) { diff --git a/crates/services/src/state_manager/mod.rs b/crates/services/src/state_manager/mod.rs index 78933084..7a50f16a 100644 --- a/crates/services/src/state_manager/mod.rs +++ b/crates/services/src/state_manager/mod.rs @@ -9,12 +9,13 @@ pub mod http; pub mod manager; pub mod push; -use std::time::{Duration, Instant}; - use common::ipc::EncryptionKeys; - -use jmap_proto::types::{id::Id, state::StateChange, type_state::DataType}; +use std::time::{Duration, Instant}; use tokio::sync::mpsc; +use types::{ + id::Id, + type_state::{DataType, StateChange}, +}; use utils::map::bitmap::Bitmap; #[derive(Debug)] diff --git a/crates/services/src/state_manager/push.rs b/crates/services/src/state_manager/push.rs index 8291e3f2..705441dd 100644 --- a/crates/services/src/state_manager/push.rs +++ b/crates/services/src/state_manager/push.rs @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{Event, PushServer, PushUpdate, http::http_request}; +use common::{IPC_CHANNEL_BUFFER, Inner, LONG_1Y_SLUMBER, core::BuildServer}; use std::{ collections::hash_map::Entry, sync::Arc, time::{Duration, Instant}, }; - -use common::{IPC_CHANNEL_BUFFER, Inner, LONG_1Y_SLUMBER, core::BuildServer}; -use jmap_proto::types::id::Id; use store::ahash::{AHashMap, AHashSet}; use tokio::sync::mpsc; use trc::PushSubscriptionEvent; - -use super::{Event, PushServer, PushUpdate, http::http_request}; +use types::id::Id; pub fn spawn_push_manager(inner: Arc) -> mpsc::Sender { let (push_tx_, mut push_rx) = mpsc::channel::(IPC_CHANNEL_BUFFER); diff --git a/crates/services/src/task_manager/alarm.rs b/crates/services/src/task_manager/alarm.rs index 34d48396..e14630ac 100644 --- a/crates/services/src/task_manager/alarm.rs +++ b/crates/services/src/task_manager/alarm.rs @@ -19,7 +19,6 @@ use common::{ }; use directory::Permission; use groupware::calendar::{ArchivedCalendarEvent, CalendarEvent, alarm::CalendarAlarm}; -use jmap_proto::types::collection::Collection; use mail_builder::{ MessageBuilder, headers::{HeaderType, content_type::ContentType}, @@ -31,6 +30,7 @@ use smtp_proto::{MailFrom, RcptTo}; use std::{str::FromStr, sync::Arc, time::Duration}; use store::write::{BatchBuilder, now}; use trc::{AddContext, TaskQueueEvent}; +use types::collection::Collection; use utils::{sanitize_email, template::Variables}; pub trait SendAlarmTask: Sync + Send { diff --git a/crates/services/src/task_manager/bayes.rs b/crates/services/src/task_manager/bayes.rs index 0e28ee50..474bb7dc 100644 --- a/crates/services/src/task_manager/bayes.rs +++ b/crates/services/src/task_manager/bayes.rs @@ -4,16 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use super::Task; use common::Server; use email::message::bayes::EmailBayesTrain; -use jmap_proto::types::collection::Collection; use mail_parser::MessageParser; +use std::time::Instant; use trc::{SpamEvent, TaskQueueEvent}; -use utils::BlobHash; - -use super::Task; +use types::{blob_hash::BlobHash, collection::Collection}; pub trait BayesTrainTask: Sync + Send { fn bayes_train( diff --git a/crates/services/src/task_manager/fts.rs b/crates/services/src/task_manager/fts.rs index 8967b1ed..6fd87355 100644 --- a/crates/services/src/task_manager/fts.rs +++ b/crates/services/src/task_manager/fts.rs @@ -4,12 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - +use super::Task; use common::Server; use directory::{Type, backend::internal::manage::ManageDirectory}; use email::message::{index::IndexMessageText, metadata::MessageMetadata}; -use jmap_proto::types::{collection::Collection, property::Property}; +use std::time::Instant; use store::{ IterateParams, SerializeInfallible, U32_LEN, ValueKey, ahash::AHashMap, @@ -18,9 +17,11 @@ use store::{ write::{BatchBuilder, BlobOp, TaskQueueClass, ValueClass, key::DeserializeBigEndian, now}, }; use trc::{AddContext, MessageIngestEvent, TaskQueueEvent}; -use utils::{BLOB_HASH_LEN, BlobHash}; - -use super::Task; +use types::{ + blob_hash::{BLOB_HASH_LEN, BlobHash}, + collection::Collection, + field::EmailField, +}; pub trait FtsIndexTask: Sync + Send { fn fts_index(&self, task: &Task, hash: &BlobHash) -> impl Future + Send; @@ -56,7 +57,7 @@ impl FtsIndexTask for Server { task.account_id, Collection::Email, task.document_id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await { diff --git a/crates/services/src/task_manager/mod.rs b/crates/services/src/task_manager/mod.rs index dbd8147e..2ea8371c 100644 --- a/crates/services/src/task_manager/mod.rs +++ b/crates/services/src/task_manager/mod.rs @@ -31,8 +31,8 @@ use store::{ }; use tokio::sync::{mpsc, watch}; use trc::TaskQueueEvent; +use types::blob_hash::{BLOB_HASH_LEN, BlobHash}; use utils::snowflake::SnowflakeIdGenerator; -use utils::{BLOB_HASH_LEN, BlobHash}; pub mod alarm; pub mod bayes; diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index ce705cb7..b41c73ff 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -13,6 +13,7 @@ resolver = "2" [dependencies] store = { path = "../store" } +types = { path = "../types" } utils = { path = "../utils" } nlp = { path = "../nlp" } directory = { path = "../directory" } diff --git a/crates/smtp/src/queue/mod.rs b/crates/smtp/src/queue/mod.rs index 903d9ca3..991440a2 100644 --- a/crates/smtp/src/queue/mod.rs +++ b/crates/smtp/src/queue/mod.rs @@ -16,7 +16,7 @@ use std::{ time::{Duration, Instant, SystemTime}, }; use store::write::now; -use utils::BlobHash; +use types::blob_hash::BlobHash; pub mod dsn; pub mod manager; diff --git a/crates/smtp/src/queue/spool.rs b/crates/smtp/src/queue/spool.rs index 24edf739..8a107f37 100644 --- a/crates/smtp/src/queue/spool.rs +++ b/crates/smtp/src/queue/spool.rs @@ -27,7 +27,7 @@ use store::write::{ }; use store::{Deserialize, IterateParams, Serialize, SerializeInfallible, U64_LEN, ValueKey}; use trc::{AddContext, ServerEvent}; -use utils::BlobHash; +use types::blob_hash::BlobHash; pub const LOCK_EXPIRY: u64 = 10 * 60; // 10 minutes pub const QUEUE_REFRESH: u64 = 5 * 60; // 5 minutes diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 8daa0498..47da614f 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -6,6 +6,7 @@ resolver = "2" [dependencies] utils = { path = "../utils" } +types = { path = "../types" } nlp = { path = "../nlp" } trc = { path = "../trc" } rocksdb = { version = "0.24", optional = true, features = ["multi-threaded-cf"] } diff --git a/crates/store/src/backend/elastic/index.rs b/crates/store/src/backend/elastic/index.rs index 02833eef..6fa8b297 100644 --- a/crates/store/src/backend/elastic/index.rs +++ b/crates/store/src/backend/elastic/index.rs @@ -9,6 +9,7 @@ use std::{borrow::Cow, fmt::Display}; use elasticsearch::{DeleteByQueryParts, IndexParts}; use serde::{Deserialize, Serialize}; use serde_json::json; +use types::collection::Collection; use crate::{ backend::elastic::INDEX_NAMES, @@ -53,7 +54,7 @@ impl ElasticSearchStore { pub async fn fts_remove( &self, account_id: u32, - collection: u8, + collection: Collection, document_ids: &impl DocumentSet, ) -> trc::Result<()> { let document_ids = document_ids.iterate().collect::>(); diff --git a/crates/store/src/backend/foundationdb/blob.rs b/crates/store/src/backend/foundationdb/blob.rs index bfda8352..459d24e7 100644 --- a/crates/store/src/backend/foundationdb/blob.rs +++ b/crates/store/src/backend/foundationdb/blob.rs @@ -12,7 +12,7 @@ use crate::{ }; use std::ops::Range; use trc::AddContext; -use utils::BLOB_HASH_LEN; +use types::blob_hash::BLOB_HASH_LEN; impl FdbStore { pub(crate) async fn get_blob( @@ -101,7 +101,7 @@ impl FdbStore { const N_CHUNKS: usize = (1 << 5) - 1; let last_chunk = std::cmp::max( (data.len() / MAX_VALUE_SIZE) - + if data.len() % MAX_VALUE_SIZE > 0 { + + if !data.len().is_multiple_of(MAX_VALUE_SIZE) { 1 } else { 0 diff --git a/crates/store/src/backend/foundationdb/write.rs b/crates/store/src/backend/foundationdb/write.rs index c9fd3850..b3c8eddb 100644 --- a/crates/store/src/backend/foundationdb/write.rs +++ b/crates/store/src/backend/foundationdb/write.rs @@ -72,7 +72,7 @@ impl FdbStore { Operation::Collection { collection: collection_, } => { - collection = *collection_; + collection = u8::from(*collection_); } Operation::DocumentId { document_id: document_id_, @@ -193,7 +193,7 @@ impl FdbStore { Operation::Log { collection, set } => { let key = LogKey { account_id, - collection: *collection, + collection: u8::from(*collection), change_id, } .serialize(WITH_SUBSPACE); diff --git a/crates/store/src/backend/mysql/write.rs b/crates/store/src/backend/mysql/write.rs index fe20c827..13854c8a 100644 --- a/crates/store/src/backend/mysql/write.rs +++ b/crates/store/src/backend/mysql/write.rs @@ -117,7 +117,7 @@ impl MysqlStore { Operation::Collection { collection: collection_, } => { - collection = *collection_; + collection = u8::from(*collection_); } Operation::DocumentId { document_id: document_id_, @@ -314,7 +314,7 @@ impl MysqlStore { Operation::Log { collection, set } => { let key = LogKey { account_id, - collection: *collection, + collection: u8::from(*collection), change_id, } .serialize(0); diff --git a/crates/store/src/backend/postgres/write.rs b/crates/store/src/backend/postgres/write.rs index 4faa07a9..11baccf7 100644 --- a/crates/store/src/backend/postgres/write.rs +++ b/crates/store/src/backend/postgres/write.rs @@ -119,7 +119,7 @@ impl PostgresStore { Operation::Collection { collection: collection_, } => { - collection = *collection_; + collection = u8::from(*collection_); } Operation::DocumentId { document_id: document_id_, @@ -315,7 +315,7 @@ impl PostgresStore { Operation::Log { collection, set } => { let key = LogKey { account_id, - collection: *collection, + collection: u8::from(*collection), change_id, } .serialize(0); diff --git a/crates/store/src/backend/rocksdb/write.rs b/crates/store/src/backend/rocksdb/write.rs index 8b7d0638..3d249954 100644 --- a/crates/store/src/backend/rocksdb/write.rs +++ b/crates/store/src/backend/rocksdb/write.rs @@ -179,7 +179,7 @@ impl RocksDBTransaction<'_, '_> { Operation::Collection { collection: collection_, } => { - collection = *collection_; + collection = u8::from(*collection_); } Operation::DocumentId { document_id: document_id_, @@ -261,7 +261,7 @@ impl RocksDBTransaction<'_, '_> { Operation::Log { collection, set } => { let key = LogKey { account_id, - collection: *collection, + collection: u8::from(*collection), change_id, } .serialize(0); diff --git a/crates/store/src/backend/sqlite/lookup.rs b/crates/store/src/backend/sqlite/lookup.rs index 276bf52e..e51b0b54 100644 --- a/crates/store/src/backend/sqlite/lookup.rs +++ b/crates/store/src/backend/sqlite/lookup.rs @@ -21,7 +21,7 @@ impl SqliteStore { let mut s = conn.prepare_cached(query).map_err(into_error)?; let params = params_ .iter() - .map(|v| v as &(dyn rusqlite::types::ToSql)) + .map(|v| v as &dyn rusqlite::types::ToSql) .collect::>(); match T::query_type() { diff --git a/crates/store/src/backend/sqlite/write.rs b/crates/store/src/backend/sqlite/write.rs index 4fea537b..a7632631 100644 --- a/crates/store/src/backend/sqlite/write.rs +++ b/crates/store/src/backend/sqlite/write.rs @@ -62,7 +62,7 @@ impl SqliteStore { Operation::Collection { collection: collection_, } => { - collection = *collection_; + collection = u8::from(*collection_); } Operation::DocumentId { document_id: document_id_, @@ -230,7 +230,7 @@ impl SqliteStore { Operation::Log { collection, set } => { let key = LogKey { account_id, - collection: *collection, + collection: u8::from(*collection), change_id, } .serialize(0); diff --git a/crates/store/src/dispatch/fts.rs b/crates/store/src/dispatch/fts.rs index d18c874e..6ea403bc 100644 --- a/crates/store/src/dispatch/fts.rs +++ b/crates/store/src/dispatch/fts.rs @@ -8,6 +8,7 @@ use std::fmt::Display; use roaring::RoaringBitmap; use trc::AddContext; +use types::collection::Collection; use crate::{ FtsStore, @@ -32,7 +33,7 @@ impl FtsStore { pub async fn query + Display + Clone + std::fmt::Debug>( &self, account_id: u32, - collection: impl Into, + collection: Collection, filters: Vec>, ) -> trc::Result { match self { @@ -48,7 +49,7 @@ impl FtsStore { pub async fn remove( &self, account_id: u32, - collection: u8, + collection: Collection, document_ids: &impl DocumentSet, ) -> trc::Result<()> { match self { diff --git a/crates/store/src/dispatch/store.rs b/crates/store/src/dispatch/store.rs index 8b398048..ed2284b3 100644 --- a/crates/store/src/dispatch/store.rs +++ b/crates/store/src/dispatch/store.rs @@ -12,6 +12,7 @@ use std::{ use compact_str::ToCompactString; use roaring::RoaringBitmap; use trc::{AddContext, StoreEvent}; +use types::collection::Collection; use crate::{ BitmapKey, Deserialize, IterateParams, Key, QueryResult, SUBSPACE_BITMAP_ID, @@ -236,7 +237,7 @@ impl Store { pub async fn assign_document_ids( &self, account_id: u32, - collection: impl Into, + collection: Collection, num_ids: u64, ) -> trc::Result { // Increment UID next @@ -593,8 +594,6 @@ impl Store { #[cfg(feature = "test_mode")] pub async fn blob_expire_all(&self) { - use utils::{BLOB_HASH_LEN, BlobHash}; - use crate::{U64_LEN, write::BlobOp}; // Delete all temporary hashes @@ -603,7 +602,7 @@ impl Store { collection: 0, document_id: 0, class: ValueClass::Blob(BlobOp::Reserve { - hash: BlobHash::default(), + hash: types::blob_hash::BlobHash::default(), until: 0, }), }; @@ -612,7 +611,7 @@ impl Store { collection: 0, document_id: 0, class: ValueClass::Blob(BlobOp::Reserve { - hash: BlobHash::default(), + hash: types::blob_hash::BlobHash::default(), until: 0, }), }; @@ -629,8 +628,9 @@ impl Store { batch.any_op(Operation::Value { class: ValueClass::Blob(BlobOp::Reserve { - hash: BlobHash::try_from_hash_slice( - key.get(U32_LEN..U32_LEN + BLOB_HASH_LEN).unwrap(), + hash: types::blob_hash::BlobHash::try_from_hash_slice( + key.get(U32_LEN..U32_LEN + types::blob_hash::BLOB_HASH_LEN) + .unwrap(), ) .unwrap(), until: key diff --git a/crates/store/src/fts/index.rs b/crates/store/src/fts/index.rs index fe4fba3d..30a1d7d8 100644 --- a/crates/store/src/fts/index.rs +++ b/crates/store/src/fts/index.rs @@ -16,6 +16,7 @@ use nlp::{ tokenizers::word::WordTokenizer, }; use trc::AddContext; +use types::collection::Collection; use crate::{ IterateParams, SerializeInfallible, Store, U32_LEN, ValueKey, @@ -49,7 +50,7 @@ pub struct FtsDocument<'x, T: Into + Display + std::fmt::Debug> { pub(crate) parts: Vec>, pub(crate) default_language: Language, pub(crate) account_id: u32, - pub(crate) collection: u8, + pub(crate) collection: Collection, pub(crate) document_id: u32, } @@ -60,7 +61,7 @@ impl<'x, T: Into + Display + std::fmt::Debug> FtsDocument<'x, T> { default_language, account_id: 0, document_id: 0, - collection: 0, + collection: Collection::None, } } @@ -74,8 +75,8 @@ impl<'x, T: Into + Display + std::fmt::Debug> FtsDocument<'x, T> { self } - pub fn with_collection(mut self, collection: impl Into) -> Self { - self.collection = collection.into(); + pub fn with_collection(mut self, collection: Collection) -> Self { + self.collection = collection; self } @@ -238,7 +239,7 @@ impl Store { pub async fn fts_remove( &self, account_id: u32, - collection: u8, + collection: Collection, document_ids: &impl DocumentSet, ) -> trc::Result<()> { // Find keys to delete @@ -247,7 +248,7 @@ impl Store { IterateParams::new( ValueKey { account_id, - collection, + collection: collection as u8, document_id: 0, class: ValueClass::FtsIndex(BitmapHash { hash: [0; 8], @@ -256,7 +257,7 @@ impl Store { }, ValueKey { account_id: account_id + 1, - collection, + collection: collection as u8, document_id: 0, class: ValueClass::FtsIndex(BitmapHash { hash: [0; 8], diff --git a/crates/store/src/fts/query.rs b/crates/store/src/fts/query.rs index 5074e809..2a3ef84a 100644 --- a/crates/store/src/fts/query.rs +++ b/crates/store/src/fts/query.rs @@ -4,24 +4,22 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - fmt::Display, - ops::{BitAndAssign, BitOrAssign, BitXorAssign}, -}; - -use ahash::AHashMap; -use nlp::language::stemmer::Stemmer; -use roaring::RoaringBitmap; -use trc::AddContext; - +use super::postings::SerializedPostings; use crate::{ BitmapKey, IterateParams, Store, U32_LEN, ValueKey, backend::MAX_TOKEN_LENGTH, fts::FtsFilter, write::{BitmapHash, ValueClass, hash::TokenType, key::DeserializeBigEndian}, }; - -use super::postings::SerializedPostings; +use ahash::AHashMap; +use nlp::language::stemmer::Stemmer; +use roaring::RoaringBitmap; +use std::{ + fmt::Display, + ops::{BitAndAssign, BitOrAssign, BitXorAssign}, +}; +use trc::AddContext; +use types::collection::Collection; struct State { pub op: FtsTokenized, @@ -50,11 +48,9 @@ impl Store { pub async fn fts_query + Display + Clone + std::fmt::Debug>( &self, account_id: u32, - collection: impl Into, + collection: Collection, filters: Vec>, ) -> trc::Result { - let collection = collection.into(); - // Tokenize text let mut tokenized_filters = Vec::with_capacity(filters.len()); let mut token_count = AHashMap::new(); @@ -125,6 +121,7 @@ impl Store { let mut stack = Vec::new(); let mut token_cache = AHashMap::with_capacity(token_count.len()); let mut filters = tokenized_filters.into_iter().peekable(); + let collection = u8::from(collection); while let Some(filter) = filters.next() { let mut result = match filter { diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index 1c0e74b5..36eb22df 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -92,28 +92,6 @@ pub const U64_LEN: usize = std::mem::size_of::(); pub const U32_LEN: usize = std::mem::size_of::(); pub const U16_LEN: usize = std::mem::size_of::(); -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum BlobClass { - Reserved { - account_id: u32, - expires: u64, - }, - Linked { - account_id: u32, - collection: u8, - document_id: u32, - }, -} - -impl Default for BlobClass { - fn default() -> Self { - BlobClass::Reserved { - account_id: 0, - expires: 0, - } - } -} - pub const SUBSPACE_ACL: u8 = b'a'; pub const SUBSPACE_BITMAP_ID: u8 = b'b'; pub const SUBSPACE_BITMAP_TAG: u8 = b'c'; diff --git a/crates/store/src/query/acl.rs b/crates/store/src/query/acl.rs index 9b597ba4..4fbe123b 100644 --- a/crates/store/src/query/acl.rs +++ b/crates/store/src/query/acl.rs @@ -6,6 +6,7 @@ use ahash::AHashSet; use trc::AddContext; +use types::collection::Collection; use crate::{ Deserialize, IterateParams, Store, U32_LEN, ValueKey, @@ -26,7 +27,7 @@ pub enum AclQuery { #[derive(Debug)] pub struct AclItem { pub to_account_id: u32, - pub to_collection: u8, + pub to_collection: Collection, pub to_document_id: u32, pub permissions: u64, } @@ -114,7 +115,7 @@ impl Store { // Remove permissions let mut batch = BatchBuilder::new(); batch.with_account_id(account_id); - let mut last_collection = u8::MAX; + let mut last_collection = Collection::None; for (revoke_account_id, acl_item) in delete_keys.into_iter() { if batch.is_large_batch() { self.write(batch.build_all()) @@ -122,7 +123,7 @@ impl Store { .caused_by(trc::location!())?; batch = BatchBuilder::new(); batch.with_account_id(account_id); - last_collection = u8::MAX; + last_collection = Collection::None; } if acl_item.to_collection != last_collection { batch.with_collection(acl_item.to_collection); @@ -146,8 +147,9 @@ impl Deserialize for AclItem { fn deserialize(bytes: &[u8]) -> trc::Result { Ok(AclItem { to_account_id: bytes.deserialize_be_u32(U32_LEN)?, - to_collection: *bytes + to_collection: bytes .get(U32_LEN * 2) + .map(|b| Collection::from(*b)) .ok_or_else(|| trc::StoreEvent::DataCorruption.caused_by(trc::location!()))?, to_document_id: bytes.deserialize_be_u32((U32_LEN * 2) + 1)?, permissions: 0, diff --git a/crates/store/src/query/filter.rs b/crates/store/src/query/filter.rs index c7ccb253..2fd00e50 100644 --- a/crates/store/src/query/filter.rs +++ b/crates/store/src/query/filter.rs @@ -10,6 +10,7 @@ use ahash::HashSet; use nlp::tokenizers::word::WordTokenizer; use roaring::RoaringBitmap; use trc::AddContext; +use types::collection::Collection; use crate::{ BitmapKey, IndexKey, IndexKeyPrefix, IterateParams, Key, Store, U32_LEN, @@ -27,14 +28,15 @@ impl Store { pub async fn filter( &self, account_id: u32, - collection: impl Into + Sync + Send, + collection_: Collection, filters: Vec, ) -> trc::Result { - let collection = collection.into(); + let collection = u8::from(collection_); + if filters.is_empty() { return Ok(ResultSet { account_id, - collection, + collection: collection_, results: self .get_bitmap(BitmapKey::document_ids(account_id, collection)) .await @@ -164,7 +166,7 @@ impl Store { Ok(ResultSet { account_id, - collection, + collection: collection_, results: state.bm.unwrap_or_default(), }) } diff --git a/crates/store/src/query/log.rs b/crates/store/src/query/log.rs index efef5413..20a94d2d 100644 --- a/crates/store/src/query/log.rs +++ b/crates/store/src/query/log.rs @@ -5,9 +5,13 @@ */ use trc::AddContext; +use types::collection::{SyncCollection, VanishedCollection}; use utils::codec::leb128::Leb128Iterator; -use crate::{IterateParams, LogKey, Store, U32_LEN, U64_LEN, write::key::DeserializeBigEndian}; +use crate::{ + IterateParams, LogKey, Store, U32_LEN, U64_LEN, + write::{LogCollection, key::DeserializeBigEndian}, +}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Change { @@ -59,10 +63,11 @@ impl Store { pub async fn changes( &self, account_id: u32, - collection: impl Into + Sync + Send, + collection: LogCollection, query: Query, ) -> trc::Result { - let collection = collection.into(); + let collection = u8::from(collection); + let (is_inclusive, from_change_id, to_change_id) = match query { Query::All => (true, 0, u64::MAX), Query::Since(change_id) => (false, change_id, u64::MAX), @@ -129,10 +134,10 @@ impl Store { pub async fn vanished( &self, account_id: u32, - collection: impl Into + Sync + Send, + collection: LogCollection, query: Query, ) -> trc::Result> { - let collection = collection.into(); + let collection = u8::from(collection); let (is_inclusive, from_change_id, to_change_id) = match query { Query::All => (true, 0, u64::MAX), Query::Since(change_id) => (false, change_id, u64::MAX), @@ -185,10 +190,9 @@ impl Store { pub async fn get_last_change_id( &self, account_id: u32, - collection: impl Into + Sync + Send, + collection: LogCollection, ) -> trc::Result> { - let collection = collection.into(); - + let collection = u8::from(collection); let from_key = LogKey { account_id, collection, @@ -219,6 +223,18 @@ impl Store { } } +impl From for LogCollection { + fn from(value: VanishedCollection) -> Self { + LogCollection::Vanished(value) + } +} + +impl From for LogCollection { + fn from(value: SyncCollection) -> Self { + LogCollection::Sync(value) + } +} + impl Changes { pub fn deserialize(&mut self, bytes: &[u8]) -> Option<(bool, bool)> { let mut bytes_it = bytes.iter(); diff --git a/crates/store/src/query/mod.rs b/crates/store/src/query/mod.rs index a44e908c..570f2d70 100644 --- a/crates/store/src/query/mod.rs +++ b/crates/store/src/query/mod.rs @@ -10,6 +10,7 @@ pub mod log; pub mod sort; use roaring::RoaringBitmap; +use types::collection::Collection; use crate::{ BitmapKey, IterateParams, Key, @@ -56,7 +57,7 @@ pub enum Comparator { #[derive(Debug)] pub struct ResultSet { pub account_id: u32, - pub collection: u8, + pub collection: Collection, pub results: RoaringBitmap, } @@ -67,10 +68,10 @@ pub struct SortedResultSet { } impl ResultSet { - pub fn new(account_id: u32, collection: impl Into, results: RoaringBitmap) -> Self { + pub fn new(account_id: u32, collection: Collection, results: RoaringBitmap) -> Self { ResultSet { account_id, - collection: collection.into(), + collection, results, } } diff --git a/crates/store/src/query/sort.rs b/crates/store/src/query/sort.rs index 7d56f763..37dfc3a1 100644 --- a/crates/store/src/query/sort.rs +++ b/crates/store/src/query/sort.rs @@ -4,14 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::cmp::Ordering; - -use ahash::{AHashMap, AHashSet}; -use trc::AddContext; - -use crate::{IndexKeyPrefix, IterateParams, Store, U32_LEN, write::key::DeserializeBigEndian}; - use super::{Comparator, ResultSet, SortedResultSet}; +use crate::{IndexKeyPrefix, IterateParams, Store, U32_LEN, write::key::DeserializeBigEndian}; +use ahash::{AHashMap, AHashSet}; +use std::cmp::Ordering; +use trc::AddContext; #[derive(Debug)] pub struct Pagination<'x> { @@ -50,17 +47,18 @@ impl Store { match comparators.pop().unwrap() { Comparator::Field { field, ascending } => { let mut results = result_set.results; + let collection = u8::from(result_set.collection); self.iterate( IterateParams::new( IndexKeyPrefix { account_id: result_set.account_id, - collection: result_set.collection, + collection, field, }, IndexKeyPrefix { account_id: result_set.account_id, - collection: result_set.collection, + collection, field: field + 1, }, ) @@ -144,17 +142,18 @@ impl Store { let mut prev_data = vec![]; let mut has_grouped_ids = false; let mut idx = 0; + let collection = u8::from(result_set.collection); self.iterate( IterateParams::new( IndexKeyPrefix { account_id: result_set.account_id, - collection: result_set.collection, + collection, field, }, IndexKeyPrefix { account_id: result_set.account_id, - collection: result_set.collection, + collection, field: field + 1, }, ) diff --git a/crates/store/src/write/batch.rs b/crates/store/src/write/batch.rs index f52b17aa..a7b81ca6 100644 --- a/crates/store/src/write/batch.rs +++ b/crates/store/src/write/batch.rs @@ -8,8 +8,15 @@ use super::{ Batch, BatchBuilder, BitmapClass, ChangedCollection, IntoOperations, Operation, TagValue, ValueClass, ValueOp, assert::ToAssertValue, log::VanishedItem, }; -use crate::{SerializeInfallible, U32_LEN, write::MergeFn}; -use utils::map::{bitmap::ShortId, vec_map::VecMap}; +use crate::{ + SerializeInfallible, U32_LEN, + write::{LogCollection, MergeFn}, +}; +use types::{ + collection::{Collection, SyncCollection, VanishedCollection}, + field::FieldType, +}; +use utils::map::vec_map::VecMap; impl BatchBuilder { pub fn new() -> Self { @@ -38,8 +45,7 @@ impl BatchBuilder { self } - pub fn with_collection(&mut self, collection: impl Into) -> &mut Self { - let collection = collection.into(); + pub fn with_collection(&mut self, collection: Collection) -> &mut Self { let collection_ = Some(collection); if collection_ != self.current_collection { self.current_collection = collection_; @@ -95,7 +101,7 @@ impl BatchBuilder { self } - pub fn index(&mut self, field: impl Into, value: impl Into>) -> &mut Self { + pub fn index(&mut self, field: impl FieldType, value: impl Into>) -> &mut Self { let field = field.into(); let value = value.into(); let value_len = value.len(); @@ -110,7 +116,7 @@ impl BatchBuilder { self } - pub fn unindex(&mut self, field: impl Into, value: impl Into>) -> &mut Self { + pub fn unindex(&mut self, field: impl FieldType, value: impl Into>) -> &mut Self { let field = field.into(); let value = value.into(); let value_len = value.len(); @@ -125,7 +131,7 @@ impl BatchBuilder { self } - pub fn tag(&mut self, field: impl Into, value: impl Into) -> &mut Self { + pub fn tag(&mut self, field: impl FieldType, value: impl Into) -> &mut Self { let value = value.into(); let value_len = value.serialized_size(); self.ops.push(Operation::Bitmap { @@ -140,7 +146,7 @@ impl BatchBuilder { self } - pub fn untag(&mut self, field: impl Into, value: impl Into) -> &mut Self { + pub fn untag(&mut self, field: impl FieldType, value: impl Into) -> &mut Self { let value = value.into(); let value_len = value.serialized_size(); self.ops.push(Operation::Bitmap { @@ -261,12 +267,16 @@ impl BatchBuilder { self } - pub fn log_item_insert(&mut self, collection: impl Into, prefix: Option) -> &mut Self { + pub fn log_item_insert( + &mut self, + collection: SyncCollection, + prefix: Option, + ) -> &mut Self { if let (Some(account_id), Some(document_id)) = (self.current_account_id, self.current_document_id) { self.changes.get_mut_or_insert(account_id).log_item_insert( - collection.into(), + collection, prefix, document_id, ); @@ -274,12 +284,16 @@ impl BatchBuilder { self } - pub fn log_item_update(&mut self, collection: impl Into, prefix: Option) -> &mut Self { + pub fn log_item_update( + &mut self, + collection: SyncCollection, + prefix: Option, + ) -> &mut Self { if let (Some(account_id), Some(document_id)) = (self.current_account_id, self.current_document_id) { self.changes.get_mut_or_insert(account_id).log_item_update( - collection.into(), + collection, prefix, document_id, ); @@ -287,12 +301,16 @@ impl BatchBuilder { self } - pub fn log_item_delete(&mut self, collection: impl Into, prefix: Option) -> &mut Self { + pub fn log_item_delete( + &mut self, + collection: SyncCollection, + prefix: Option, + ) -> &mut Self { if let (Some(account_id), Some(document_id)) = (self.current_account_id, self.current_document_id) { self.changes.get_mut_or_insert(account_id).log_item_delete( - collection.into(), + collection, prefix, document_id, ); @@ -300,62 +318,60 @@ impl BatchBuilder { self } - pub fn log_container_insert(&mut self, collection: impl Into) -> &mut Self { + pub fn log_container_insert(&mut self, collection: SyncCollection) -> &mut Self { if let (Some(account_id), Some(document_id)) = (self.current_account_id, self.current_document_id) { self.changes .get_mut_or_insert(account_id) - .log_container_insert(collection.into(), document_id); + .log_container_insert(collection, document_id); } self } - pub fn log_container_update(&mut self, collection: impl Into) -> &mut Self { + pub fn log_container_update(&mut self, collection: SyncCollection) -> &mut Self { if let (Some(account_id), Some(document_id)) = (self.current_account_id, self.current_document_id) { self.changes .get_mut_or_insert(account_id) - .log_container_update(collection.into(), document_id); + .log_container_update(collection, document_id); } self } - pub fn log_container_delete(&mut self, collection: impl Into) -> &mut Self { + pub fn log_container_delete(&mut self, collection: SyncCollection) -> &mut Self { if let (Some(account_id), Some(document_id)) = (self.current_account_id, self.current_document_id) { self.changes .get_mut_or_insert(account_id) - .log_container_delete(collection.into(), document_id); + .log_container_delete(collection, document_id); } self } pub fn log_container_property_change( &mut self, - collection: impl Into, + collection: SyncCollection, document_id: u32, ) -> &mut Self { if let Some(account_id) = self.current_account_id { self.changes .get_mut_or_insert(account_id) - .log_container_property_update(collection.into(), document_id); + .log_container_property_update(collection, document_id); } self } pub fn log_vanished_item( &mut self, - collection: impl Into, + collection: VanishedCollection, item: impl Into, ) -> &mut Self { if let Some(account_id) = self.current_account_id { let item = item.into(); self.batch_size += item.serialized_size(); - let collection = collection.into(); - debug_assert!(collection > 200); self.changes .get_mut_or_insert(account_id) .log_vanished_item(collection, item); @@ -372,14 +388,14 @@ impl BatchBuilder { for (collection, changes) in changelog.changes.into_iter() { let cc = self.changed_collections.get_mut_or_insert(account_id); if changes.has_container_changes() { - cc.changed_containers.insert(ShortId(collection)); + cc.changed_containers.insert(collection); } if changes.has_item_changes() { - cc.changed_items.insert(ShortId(collection)); + cc.changed_items.insert(collection); } self.ops.push(Operation::Log { - collection, + collection: LogCollection::Sync(collection), set: changes.serialize(), }); } @@ -387,7 +403,7 @@ impl BatchBuilder { // Serialize vanished items for (collection, vanished) in changelog.vanished.into_iter() { self.ops.push(Operation::Log { - collection, + collection: LogCollection::Vanished(collection), set: vanished.serialize(), }); } diff --git a/crates/store/src/write/blob.rs b/crates/store/src/write/blob.rs index 72b52718..bfbeb946 100644 --- a/crates/store/src/write/blob.rs +++ b/crates/store/src/write/blob.rs @@ -4,17 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::{BlobOp, Operation, ValueClass, ValueOp, key::DeserializeBigEndian, now}; +use crate::{ + BlobStore, Deserialize, IterateParams, Store, U32_LEN, U64_LEN, ValueKey, write::BatchBuilder, +}; use ahash::AHashSet; use trc::AddContext; -use utils::{BLOB_HASH_LEN, BlobHash}; - -use crate::{ - BlobClass, BlobStore, Deserialize, IterateParams, Store, U32_LEN, U64_LEN, ValueKey, - write::BatchBuilder, +use types::{ + blob::BlobClass, + blob_hash::{BLOB_HASH_LEN, BlobHash}, + collection::Collection, }; -use super::{BlobOp, Operation, ValueClass, ValueOp, key::DeserializeBigEndian, now}; - #[derive(Debug, PartialEq, Eq)] pub struct BlobQuota { pub bytes: usize, @@ -266,7 +267,7 @@ impl Store { if document_id != u32::MAX && key.deserialize_be_u32(BLOB_HASH_LEN)? == account_id { delete_keys.push(( - key[BLOB_HASH_LEN + U32_LEN], + Collection::from(key[BLOB_HASH_LEN + U32_LEN]), document_id, BlobOp::Link { hash: BlobHash::try_from_hash_slice( @@ -288,7 +289,7 @@ impl Store { // Unlink blobs let mut batch = BatchBuilder::new(); batch.with_account_id(account_id); - let mut last_collection = u8::MAX; + let mut last_collection = Collection::None; for (collection, document_id, op) in delete_keys.into_iter() { if batch.is_large_batch() { self.write(batch.build_all()) @@ -296,7 +297,7 @@ impl Store { .caused_by(trc::location!())?; batch = BatchBuilder::new(); batch.with_account_id(account_id); - last_collection = u8::MAX; + last_collection = Collection::None; } if collection != last_collection { batch.with_collection(collection); diff --git a/crates/store/src/write/key.rs b/crates/store/src/write/key.rs index 5ebda851..07d4ab1c 100644 --- a/crates/store/src/write/key.rs +++ b/crates/store/src/write/key.rs @@ -5,7 +5,8 @@ */ use std::convert::TryInto; -use utils::{BLOB_HASH_LEN, codec::leb128::Leb128_}; +use types::blob_hash::BLOB_HASH_LEN; +use utils::codec::leb128::Leb128_; use crate::{ BitmapKey, Deserialize, IndexKey, IndexKeyPrefix, Key, LogKey, SUBSPACE_ACL, diff --git a/crates/store/src/write/log.rs b/crates/store/src/write/log.rs index 264475f5..d15e10a1 100644 --- a/crates/store/src/write/log.rs +++ b/crates/store/src/write/log.rs @@ -6,14 +6,15 @@ use crate::{SerializeInfallible, U64_LEN}; use ahash::AHashSet; +use types::collection::{SyncCollection, VanishedCollection}; use utils::{codec::leb128::Leb128Vec, map::vec_map::VecMap}; use super::key::KeySerializer; #[derive(Default, Debug)] pub(crate) struct ChangeLogBuilder { - pub changes: VecMap, - pub vanished: VecMap, + pub changes: VecMap, + pub vanished: VecMap, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -39,8 +40,8 @@ pub struct Changes { } impl ChangeLogBuilder { - pub fn log_container_insert(&mut self, collection: impl Into, document_id: u32) { - let changes = self.changes.get_mut_or_insert(collection.into()); + pub fn log_container_insert(&mut self, collection: SyncCollection, document_id: u32) { + let changes = self.changes.get_mut_or_insert(collection); if changes.container_deletes.remove(&document_id) { changes.container_updates.insert(document_id); } else { @@ -50,12 +51,12 @@ impl ChangeLogBuilder { pub fn log_item_insert( &mut self, - collection: impl Into, + collection: SyncCollection, prefix: Option, document_id: u32, ) { let id = build_id(prefix, document_id); - let changes = self.changes.get_mut_or_insert(collection.into()); + let changes = self.changes.get_mut_or_insert(collection); if changes.item_deletes.remove(&id) { changes.item_updates.insert(id); } else { @@ -63,34 +64,34 @@ impl ChangeLogBuilder { } } - pub fn log_container_update(&mut self, collection: impl Into, document_id: u32) { + pub fn log_container_update(&mut self, collection: SyncCollection, document_id: u32) { self.changes - .get_mut_or_insert(collection.into()) + .get_mut_or_insert(collection) .container_updates .insert(document_id); } - pub fn log_container_property_update(&mut self, collection: impl Into, document_id: u32) { + pub fn log_container_property_update(&mut self, collection: SyncCollection, document_id: u32) { self.changes - .get_mut_or_insert(collection.into()) + .get_mut_or_insert(collection) .container_property_changes .insert(document_id); } pub fn log_item_update( &mut self, - collection: impl Into, + collection: SyncCollection, prefix: Option, document_id: u32, ) { self.changes - .get_mut_or_insert(collection.into()) + .get_mut_or_insert(collection) .item_updates .insert(build_id(prefix, document_id)); } - pub fn log_container_delete(&mut self, collection: impl Into, document_id: u32) { - let changes = self.changes.get_mut_or_insert(collection.into()); + pub fn log_container_delete(&mut self, collection: SyncCollection, document_id: u32) { + let changes = self.changes.get_mut_or_insert(collection); let id = document_id; changes.container_updates.remove(&id); changes.container_property_changes.remove(&id); @@ -99,19 +100,23 @@ impl ChangeLogBuilder { pub fn log_item_delete( &mut self, - collection: impl Into, + collection: SyncCollection, prefix: Option, document_id: u32, ) { - let changes = self.changes.get_mut_or_insert(collection.into()); + let changes = self.changes.get_mut_or_insert(collection); let id = build_id(prefix, document_id); changes.item_updates.remove(&id); changes.item_deletes.insert(id); } - pub fn log_vanished_item(&mut self, collection: impl Into, item: impl Into) { + pub fn log_vanished_item( + &mut self, + collection: VanishedCollection, + item: impl Into, + ) { self.vanished - .get_mut_or_insert(collection.into()) + .get_mut_or_insert(collection) .0 .push(item.into()); } diff --git a/crates/store/src/write/mod.rs b/crates/store/src/write/mod.rs index b8600599..31aa623f 100644 --- a/crates/store/src/write/mod.rs +++ b/crates/store/src/write/mod.rs @@ -4,25 +4,24 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use self::assert::AssertValue; +use crate::backend::MAX_TOKEN_LENGTH; +use log::ChangeLogBuilder; +use nlp::tokenizers::word::WordTokenizer; +use rkyv::util::AlignedVec; use std::{ collections::HashSet, time::{Duration, SystemTime}, }; - -use log::ChangeLogBuilder; -use nlp::tokenizers::word::WordTokenizer; -use rkyv::util::AlignedVec; -use utils::{ - BlobHash, - map::{ - bitmap::{Bitmap, ShortId}, - vec_map::VecMap, +use types::{ + blob_hash::BlobHash, + collection::{Collection, SyncCollection, VanishedCollection}, + field::{ + CalendarField, ContactField, EmailField, EmailSubmissionField, Field, MailboxField, + PrincipalField, SieveField, }, }; - -use crate::{BlobClass, backend::MAX_TOKEN_LENGTH}; - -use self::assert::AssertValue; +use utils::map::{bitmap::Bitmap, vec_map::VecMap}; pub mod assert; pub mod batch; @@ -105,7 +104,7 @@ pub struct Batch<'x> { #[derive(Debug)] pub struct BatchBuilder { current_account_id: Option, - current_collection: Option, + current_collection: Option, current_document_id: Option, changes: VecMap, changed_collections: VecMap, @@ -118,8 +117,8 @@ pub struct BatchBuilder { #[derive(Debug, Default)] pub struct ChangedCollection { - pub changed_containers: Bitmap, - pub changed_items: Bitmap, + pub changed_containers: Bitmap, + pub changed_items: Bitmap, } #[derive(Debug, PartialEq, Eq, Hash)] @@ -128,7 +127,7 @@ pub enum Operation { account_id: u32, }, Collection { - collection: u8, + collection: Collection, }, DocumentId { document_id: u32, @@ -151,11 +150,17 @@ pub enum Operation { set: bool, }, Log { - collection: u8, + collection: LogCollection, set: Vec, }, } +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum LogCollection { + Sync(SyncCollection), + Vanished(VanishedCollection), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum BitmapClass { DocumentIds, @@ -405,29 +410,6 @@ impl BitmapClass { } } -impl AsRef for BlobClass { - fn as_ref(&self) -> &BlobClass { - self - } -} - -impl BlobClass { - pub fn account_id(&self) -> u32 { - match self { - BlobClass::Reserved { account_id, .. } | BlobClass::Linked { account_id, .. } => { - *account_id - } - } - } - - pub fn is_valid(&self) -> bool { - match self { - BlobClass::Reserved { expires, .. } => *expires > now(), - BlobClass::Linked { .. } => true, - } - } -} - impl AssignedIds { pub fn push_counter_id(&mut self, id: i64) { self.ids.push(AssignedId::Counter(id)); @@ -515,6 +497,15 @@ impl ArchiveVersion { } } +impl From for u8 { + fn from(value: LogCollection) -> Self { + match value { + LogCollection::Sync(col) => col as u8, + LogCollection::Vanished(col) => col as u8, + } + } +} + impl std::fmt::Debug for MergeFn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MergeFn") @@ -536,3 +527,51 @@ impl std::hash::Hash for MergeFn { self.fnc_id.hash(state); } } + +impl From for ValueClass { + fn from(value: ContactField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: CalendarField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: EmailField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: MailboxField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: PrincipalField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: SieveField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: EmailSubmissionField) -> Self { + ValueClass::Property(value.into()) + } +} + +impl From for ValueClass { + fn from(value: Field) -> Self { + ValueClass::Property(value.into()) + } +} diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml new file mode 100644 index 00000000..b8f32367 --- /dev/null +++ b/crates/types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "types" +version = "0.13.3" +edition = "2024" +resolver = "2" + +[dependencies] +utils = { path = "../utils" } +trc = { path = "../trc" } +hashify = "0.2" +serde = { version = "1.0", features = ["derive"]} +rkyv = { version = "0.8.10", features = ["little_endian"] } +compact_str = { version = "0.9.0", features = ["rkyv", "serde"] } +blake3 = "1.3.3" + + +[features] +test_mode = [] + diff --git a/crates/types/src/acl.rs b/crates/types/src/acl.rs new file mode 100644 index 00000000..d3559536 --- /dev/null +++ b/crates/types/src/acl.rs @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::fmt::{self, Display}; +use utils::map::bitmap::{Bitmap, BitmapItem}; + +#[derive( + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Copy, +)] +#[rkyv(compare(PartialEq), derive(Debug))] +#[repr(u8)] +pub enum Acl { + Read = 0, + Modify = 1, + Delete = 2, + ReadItems = 3, + AddItems = 4, + ModifyItems = 5, + RemoveItems = 6, + CreateChild = 7, + Administer = 8, + Submit = 9, + SchedulingReadFreeBusy = 10, + SchedulingInvite = 11, + SchedulingReply = 12, + ModifyItemsOwn = 13, + ModifyPrivateProperties = 14, + None = 15, +} + +#[derive( + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, + Debug, + Clone, + PartialEq, + Eq, + serde::Serialize, + Default, +)] +#[rkyv(compare(PartialEq), derive(Debug))] +pub struct AclGrant { + pub account_id: u32, + pub grants: Bitmap, +} + +impl Acl { + fn as_str(&self) -> &'static str { + match self { + Acl::Read => "read", + Acl::Modify => "modify", + Acl::Delete => "delete", + Acl::ReadItems => "readItems", + Acl::AddItems => "addItems", + Acl::ModifyItems => "modifyItems", + Acl::RemoveItems => "removeItems", + Acl::CreateChild => "createChild", + Acl::Administer => "administer", + Acl::Submit => "submit", + Acl::ModifyItemsOwn => "modifyItemsOwn", + Acl::ModifyPrivateProperties => "modifyPrivateProperties", + Acl::None => "", + Acl::SchedulingReadFreeBusy => "schedulingReadFreeBusy", + Acl::SchedulingInvite => "schedulingInvite", + Acl::SchedulingReply => "schedulingReply", + } + } +} + +impl Display for Acl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl serde::Serialize for Acl { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl BitmapItem for Acl { + fn max() -> u64 { + Acl::None as u64 + } + + fn is_valid(&self) -> bool { + !matches!(self, Acl::None) + } +} + +impl From for u64 { + fn from(value: Acl) -> Self { + value as u64 + } +} + +impl From for Acl { + fn from(value: u64) -> Self { + match value { + 0 => Acl::Read, + 1 => Acl::Modify, + 2 => Acl::Delete, + 3 => Acl::ReadItems, + 4 => Acl::AddItems, + 5 => Acl::ModifyItems, + 6 => Acl::RemoveItems, + 7 => Acl::CreateChild, + 8 => Acl::Administer, + 9 => Acl::Submit, + 10 => Acl::SchedulingReadFreeBusy, + 11 => Acl::SchedulingInvite, + 12 => Acl::SchedulingReply, + 13 => Acl::ModifyItemsOwn, + 14 => Acl::ModifyPrivateProperties, + _ => Acl::None, + } + } +} + +impl From<&ArchivedAclGrant> for AclGrant { + fn from(value: &ArchivedAclGrant) -> Self { + Self { + account_id: u32::from(value.account_id), + grants: (&value.grants).into(), + } + } +} diff --git a/crates/types/src/blob.rs b/crates/types/src/blob.rs new file mode 100644 index 00000000..04b8e34d --- /dev/null +++ b/crates/types/src/blob.rs @@ -0,0 +1,233 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::{borrow::Borrow, time::SystemTime}; +use utils::codec::{ + base32_custom::{Base32Reader, Base32Writer}, + leb128::{Leb128Iterator, Leb128Writer}, +}; + +use crate::blob_hash::BlobHash; + +const B_LINKED: u8 = 0x10; +const B_RESERVED: u8 = 0x20; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum BlobClass { + Reserved { + account_id: u32, + expires: u64, + }, + Linked { + account_id: u32, + collection: u8, + document_id: u32, + }, +} + +impl Default for BlobClass { + fn default() -> Self { + BlobClass::Reserved { + account_id: 0, + expires: 0, + } + } +} + +impl AsRef for BlobClass { + fn as_ref(&self) -> &BlobClass { + self + } +} + +impl BlobClass { + pub fn account_id(&self) -> u32 { + match self { + BlobClass::Reserved { account_id, .. } | BlobClass::Linked { account_id, .. } => { + *account_id + } + } + } + + pub fn is_valid(&self) -> bool { + match self { + BlobClass::Reserved { expires, .. } => { + *expires + > SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map_or(0, |d| d.as_secs()) + } + BlobClass::Linked { .. } => true, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct BlobId { + pub hash: BlobHash, + pub class: BlobClass, + pub section: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +pub struct BlobSection { + pub offset_start: usize, + pub size: usize, + pub encoding: u8, +} + +impl BlobId { + pub fn new(hash: BlobHash, class: BlobClass) -> Self { + BlobId { + hash, + class, + section: None, + } + } + + pub fn new_section( + hash: BlobHash, + class: BlobClass, + offset_start: usize, + offset_end: usize, + encoding: impl Into, + ) -> Self { + BlobId { + hash, + class, + section: BlobSection { + offset_start, + size: offset_end - offset_start, + encoding: encoding.into(), + } + .into(), + } + } + + pub fn with_section_size(mut self, size: usize) -> Self { + self.section.get_or_insert_with(Default::default).size = size; + self + } + + pub fn from_base32(value: impl AsRef<[u8]>) -> Option { + BlobId::from_iter(&mut Base32Reader::new(value.as_ref())) + } + + #[allow(clippy::should_implement_trait)] + pub fn from_iter(it: &mut T) -> Option + where + T: Iterator + Leb128Iterator, + U: Borrow, + { + let class = *it.next()?.borrow(); + let encoding = class & 0x0F; + + let mut hash = BlobHash::default(); + for byte in hash.as_mut().iter_mut() { + *byte = *it.next()?.borrow(); + } + + let account_id: u32 = it.next_leb128()?; + + BlobId { + hash, + class: if (class & B_LINKED) != 0 { + BlobClass::Linked { + account_id, + collection: *it.next()?.borrow(), + document_id: it.next_leb128()?, + } + } else { + BlobClass::Reserved { + account_id, + expires: it.next_leb128()?, + } + }, + section: if encoding != 0 { + BlobSection { + offset_start: it.next_leb128()?, + size: it.next_leb128()?, + encoding: encoding - 1, + } + .into() + } else { + None + }, + } + .into() + } + + fn serialize_as(&self, writer: &mut impl Leb128Writer) { + let marker = self + .section + .as_ref() + .map_or(0, |section| section.encoding + 1) + | if matches!( + self, + BlobId { + class: BlobClass::Linked { .. }, + .. + } + ) { + B_LINKED + } else { + B_RESERVED + }; + + let _ = writer.write(&[marker]); + let _ = writer.write(self.hash.as_ref()); + + match &self.class { + BlobClass::Reserved { + account_id, + expires, + } => { + let _ = writer.write_leb128(*account_id); + let _ = writer.write_leb128(*expires); + } + BlobClass::Linked { + account_id, + collection, + document_id, + } => { + let _ = writer.write_leb128(*account_id); + let _ = writer.write(&[*collection]); + let _ = writer.write_leb128(*document_id); + } + } + + if let Some(section) = &self.section { + let _ = writer.write_leb128(section.offset_start); + let _ = writer.write_leb128(section.size); + } + } + + pub fn start_offset(&self) -> usize { + if let Some(section) = &self.section { + section.offset_start + } else { + 0 + } + } +} + +impl serde::Serialize for BlobId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} + +impl std::fmt::Display for BlobId { + #[allow(clippy::unused_io_amount)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut writer = Base32Writer::with_capacity(std::mem::size_of::() * 2); + self.serialize_as(&mut writer); + f.write_str(&writer.finalize()) + } +} diff --git a/crates/types/src/blob_hash.rs b/crates/types/src/blob_hash.rs new file mode 100644 index 00000000..8a06c8f9 --- /dev/null +++ b/crates/types/src/blob_hash.rs @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +pub const BLOB_HASH_LEN: usize = 32; + +#[derive( + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + serde::Serialize, + serde::Deserialize, +)] +#[rkyv(derive(Debug))] +#[repr(transparent)] +pub struct BlobHash(pub [u8; BLOB_HASH_LEN]); + +impl BlobHash { + pub fn new_max() -> Self { + BlobHash([u8::MAX; BLOB_HASH_LEN]) + } + + pub fn generate(value: impl AsRef<[u8]>) -> Self { + BlobHash(blake3::hash(value.as_ref()).into()) + } + + pub fn try_from_hash_slice(value: &[u8]) -> Result { + value.try_into().map(BlobHash) + } + + pub fn as_slice(&self) -> &[u8] { + self.0.as_ref() + } + + pub fn to_hex(&self) -> String { + let mut hex = String::with_capacity(BLOB_HASH_LEN * 2); + for byte in self.0.iter() { + hex.push_str(&format!("{:02x}", byte)); + } + hex + } +} + +impl From<&ArchivedBlobHash> for BlobHash { + fn from(value: &ArchivedBlobHash) -> Self { + BlobHash(value.0) + } +} + +impl AsRef for BlobHash { + fn as_ref(&self) -> &BlobHash { + self + } +} + +impl From for Vec { + fn from(value: BlobHash) -> Self { + value.0.to_vec() + } +} + +impl AsRef<[u8]> for BlobHash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsMut<[u8]> for BlobHash { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} diff --git a/crates/jmap-proto/src/types/collection.rs b/crates/types/src/collection.rs similarity index 92% rename from crates/jmap-proto/src/types/collection.rs rename to crates/types/src/collection.rs index 82d0a290..8291dd3f 100644 --- a/crates/jmap-proto/src/types/collection.rs +++ b/crates/types/src/collection.rs @@ -197,6 +197,23 @@ impl From for SyncCollection { } } +impl From for SyncCollection { + fn from(v: u64) -> Self { + match v { + 0 => SyncCollection::Email, + 1 => SyncCollection::Thread, + 2 => SyncCollection::Calendar, + 3 => SyncCollection::AddressBook, + 4 => SyncCollection::FileNode, + 5 => SyncCollection::Identity, + 6 => SyncCollection::EmailSubmission, + 7 => SyncCollection::SieveScript, + 8 => SyncCollection::CalendarScheduling, + _ => SyncCollection::None, + } + } +} + impl From for Collection { fn from(v: u64) -> Self { match v { @@ -231,6 +248,12 @@ impl From for u8 { } } +impl From for u64 { + fn from(v: SyncCollection) -> Self { + v as u64 + } +} + impl From for u8 { fn from(v: VanishedCollection) -> Self { v as u8 @@ -327,6 +350,16 @@ impl BitmapItem for Collection { } } +impl BitmapItem for SyncCollection { + fn max() -> u64 { + SyncCollection::None as u64 + } + + fn is_valid(&self) -> bool { + !matches!(self, SyncCollection::None) + } +} + impl SyncCollection { pub fn as_str(&self) -> &'static str { match self { diff --git a/crates/types/src/field.rs b/crates/types/src/field.rs new file mode 100644 index 00000000..db1df17c --- /dev/null +++ b/crates/types/src/field.rs @@ -0,0 +1,234 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +const ARCHIVE_FIELD: u8 = 50; + +pub trait FieldType: Into + Copy + std::fmt::Debug + PartialEq + Eq {} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Field(u8); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum ContactField { + Uid, + Email, + Archive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum CalendarField { + Uid, + Created, + Archive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum EmailField { + Archive, + Metadata, + Size, + Subject, + References, + MailboxIds, + ReceivedAt, + SentAt, + HasAttachment, + From, + To, + Cc, + Bcc, + ReplyTo, + Sender, + InReplyTo, + MessageId, + EmailIds, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum MailboxField { + UidCounter, + Archive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum SieveField { + Name, + IsActive, + Ids, + Archive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum EmailSubmissionField { + Archive, + UndoStatus, + EmailId, + ThreadId, + IdentityId, + SendAt, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum PrincipalField { + Archive, + EncryptionKeys, +} + +impl From for u8 { + fn from(value: ContactField) -> Self { + match value { + ContactField::Uid => 0, + ContactField::Email => 1, + ContactField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: CalendarField) -> Self { + match value { + CalendarField::Uid => 0, + CalendarField::Created => 2, + CalendarField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: EmailField) -> Self { + match value { + EmailField::Size => 27, + EmailField::Metadata => 71, + EmailField::Subject => 29, + EmailField::References => 20, + EmailField::MailboxIds => 7, + EmailField::ReceivedAt => 19, + EmailField::MessageId => 11, + EmailField::ReplyTo => 21, + EmailField::Sender => 25, + EmailField::SentAt => 26, + EmailField::To => 35, + EmailField::Bcc => 69, + EmailField::Cc => 74, + EmailField::EmailIds => 84, + EmailField::From => 87, + EmailField::HasAttachment => 89, + EmailField::InReplyTo => 96, + EmailField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: MailboxField) -> Self { + match value { + MailboxField::UidCounter => 84, + MailboxField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: SieveField) -> Self { + match value { + SieveField::Name => 13, + SieveField::IsActive => 0, + SieveField::Ids => 84, + SieveField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: EmailSubmissionField) -> Self { + match value { + EmailSubmissionField::UndoStatus => 41, + EmailSubmissionField::EmailId => 83, + EmailSubmissionField::ThreadId => 33, + EmailSubmissionField::IdentityId => 95, + EmailSubmissionField::SendAt => 24, + EmailSubmissionField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: PrincipalField) -> Self { + match value { + PrincipalField::EncryptionKeys => 46, + PrincipalField::Archive => ARCHIVE_FIELD, + } + } +} + +impl From for u8 { + fn from(value: Field) -> Self { + value.0 + } +} + +impl From for Field { + fn from(value: ContactField) -> Self { + Field(u8::from(value)) + } +} + +impl From for Field { + fn from(value: CalendarField) -> Self { + Field(u8::from(value)) + } +} + +impl From for Field { + fn from(value: EmailField) -> Self { + Field(u8::from(value)) + } +} + +impl From for Field { + fn from(value: MailboxField) -> Self { + Field(u8::from(value)) + } +} + +impl From for Field { + fn from(value: PrincipalField) -> Self { + Field(u8::from(value)) + } +} + +impl From for Field { + fn from(value: SieveField) -> Self { + Field(u8::from(value)) + } +} + +impl From for Field { + fn from(value: EmailSubmissionField) -> Self { + Field(u8::from(value)) + } +} + +impl Field { + pub const ARCHIVE: Field = Field(ARCHIVE_FIELD); +} + +impl FieldType for Field {} +impl FieldType for ContactField {} +impl FieldType for CalendarField {} +impl FieldType for EmailField {} +impl FieldType for MailboxField {} +impl FieldType for PrincipalField {} +impl FieldType for SieveField {} +impl FieldType for EmailSubmissionField {} diff --git a/crates/types/src/id.rs b/crates/types/src/id.rs new file mode 100644 index 00000000..3b4d9bf6 --- /dev/null +++ b/crates/types/src/id.rs @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::DocumentId; +use std::ops::Deref; +use utils::codec::base32_custom::{BASE32_ALPHABET, BASE32_INVERSE}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] +#[repr(transparent)] +pub struct Id(u64); + +impl Default for Id { + fn default() -> Self { + Id(u64::MAX) + } +} + +impl Id { + pub fn new(id: u64) -> Self { + Self(id) + } + + pub fn from_bytes(bytes: &[u8]) -> Option { + let mut id = 0; + + for &ch in bytes { + let i = BASE32_INVERSE[ch as usize]; + if i != u8::MAX { + id = (id << 5) | i as u64; + } else { + return None; + } + } + + Id(id).into() + } + + pub fn singleton() -> Self { + Self::new(20080258862541) + } + + // From https://github.com/archer884/crockford by J/A + // License: MIT/Apache 2.0 + pub fn as_string(&self) -> String { + match self.0 { + 0 => "a".to_string(), + mut n => { + // Used for the initial shift. + const QUAD_SHIFT: usize = 60; + const QUAD_RESET: usize = 4; + + // Used for all subsequent shifts. + const FIVE_SHIFT: usize = 59; + const FIVE_RESET: usize = 5; + + // After we clear the four most significant bits, the four least significant bits will be + // replaced with 0001. We can then know to stop once the four most significant bits are, + // likewise, 0001. + const STOP_BIT: u64 = 1 << QUAD_SHIFT; + + let mut buf = String::with_capacity(7); + + // Start by getting the most significant four bits. We get four here because these would be + // leftovers when starting from the least significant bits. In either case, tag the four least + // significant bits with our stop bit. + match (n >> QUAD_SHIFT) as usize { + // Eat leading zero-bits. This should not be done if the first four bits were non-zero. + // Additionally, we *must* do this in increments of five bits. + 0 => { + n <<= QUAD_RESET; + n |= 1; + n <<= n.leading_zeros() / 5 * 5; + } + + // Write value of first four bytes. + i => { + n <<= QUAD_RESET; + n |= 1; + buf.push(char::from(BASE32_ALPHABET[i])); + } + } + + // From now until we reach the stop bit, take the five most significant bits and then shift + // left by five bits. + while n != STOP_BIT { + buf.push(char::from(BASE32_ALPHABET[(n >> FIVE_SHIFT) as usize])); + n <<= FIVE_RESET; + } + + buf + } + } + } + + pub fn from_parts(prefix_id: DocumentId, doc_id: DocumentId) -> Id { + Id(((prefix_id as u64) << 32) | doc_id as u64) + } + + pub fn id(&self) -> u64 { + self.0 + } + + pub fn document_id(&self) -> DocumentId { + (self.0 & 0xFFFFFFFF) as DocumentId + } + + pub fn prefix_id(&self) -> DocumentId { + (self.0 >> 32) as DocumentId + } + + pub fn is_singleton(&self) -> bool { + self.0 == 20080258862541 + } + + pub fn is_valid(&self) -> bool { + self.0 != u64::MAX + } +} + +impl From for Id { + fn from(id: u64) -> Self { + Id(id) + } +} + +impl From for Id { + fn from(id: u32) -> Self { + Id(id as u64) + } +} + +impl From for u64 { + fn from(id: Id) -> Self { + id.0 + } +} + +impl From<&Id> for u64 { + fn from(id: &Id) -> Self { + id.0 + } +} + +impl From<(u32, u32)> for Id { + fn from(id: (u32, u32)) -> Self { + Id::from_parts(id.0, id.1) + } +} + +impl Deref for Id { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for Id { + fn as_ref(&self) -> &u64 { + &self.0 + } +} + +impl From for u32 { + fn from(id: Id) -> Self { + id.document_id() + } +} + +impl From for String { + fn from(id: Id) -> Self { + id.as_string() + } +} + +impl serde::Serialize for Id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_string().as_str()) + } +} + +impl<'de> serde::Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Id::from_bytes(<&str>::deserialize(deserializer)?.as_bytes()) + .ok_or_else(|| serde::de::Error::custom("invalid JMAP ID")) + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.as_string()) + } +} diff --git a/crates/types/src/keyword.rs b/crates/types/src/keyword.rs new file mode 100644 index 00000000..a7702f6a --- /dev/null +++ b/crates/types/src/keyword.rs @@ -0,0 +1,288 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::fmt::Display; + +pub const SEEN: usize = 0; +pub const DRAFT: usize = 1; +pub const FLAGGED: usize = 2; +pub const ANSWERED: usize = 3; +pub const RECENT: usize = 4; +pub const IMPORTANT: usize = 5; +pub const PHISHING: usize = 6; +pub const JUNK: usize = 7; +pub const NOTJUNK: usize = 8; +pub const DELETED: usize = 9; +pub const FORWARDED: usize = 10; +pub const MDN_SENT: usize = 11; +pub const OTHER: usize = 12; + +#[derive( + rkyv::Serialize, + rkyv::Deserialize, + rkyv::Archive, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Default, + serde::Serialize, +)] +#[serde(untagged)] +#[rkyv(derive(PartialEq), compare(PartialEq))] +pub enum Keyword { + #[serde(rename(serialize = "$seen"))] + Seen, + #[serde(rename(serialize = "$draft"))] + Draft, + #[serde(rename(serialize = "$flagged"))] + Flagged, + #[serde(rename(serialize = "$answered"))] + Answered, + #[default] + #[serde(rename(serialize = "$recent"))] + Recent, + #[serde(rename(serialize = "$important"))] + Important, + #[serde(rename(serialize = "$phishing"))] + Phishing, + #[serde(rename(serialize = "$junk"))] + Junk, + #[serde(rename(serialize = "$notjunk"))] + NotJunk, + #[serde(rename(serialize = "$deleted"))] + Deleted, + #[serde(rename(serialize = "$forwarded"))] + Forwarded, + #[serde(rename(serialize = "$mdnsent"))] + MdnSent, + Other(String), +} + +impl> From for Keyword { + fn from(value: T) -> Self { + let value = value.as_ref(); + if value + .as_bytes() + .first() + .is_some_and(|&ch| [b'$', b'\\'].contains(&ch)) + { + let mut hash = 0; + let mut shift = 0; + + for &ch in value.as_bytes().iter().skip(1) { + if shift < 128 { + hash |= (ch.to_ascii_lowercase() as u128) << shift; + shift += 8; + } else { + break; + } + } + + match hash { + 0x6e65_6573 => return Keyword::Seen, + 0x0074_6661_7264 => return Keyword::Draft, + 0x0064_6567_6761_6c66 => return Keyword::Flagged, + 0x6465_7265_7773_6e61 => return Keyword::Answered, + 0x746e_6563_6572 => return Keyword::Recent, + 0x0074_6e61_7472_6f70_6d69 => return Keyword::Important, + 0x676e_6968_7369_6870 => return Keyword::Phishing, + 0x6b6e_756a => return Keyword::Junk, + 0x006b_6e75_6a74_6f6e => return Keyword::NotJunk, + 0x0064_6574_656c_6564 => return Keyword::Deleted, + 0x0064_6564_7261_7772_6f66 => return Keyword::Forwarded, + 0x0074_6e65_736e_646d => return Keyword::MdnSent, + _ => (), + } + } + + Keyword::Other(String::from(value)) + } +} + +impl Display for Keyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Keyword::Seen => write!(f, "$seen"), + Keyword::Draft => write!(f, "$draft"), + Keyword::Flagged => write!(f, "$flagged"), + Keyword::Answered => write!(f, "$answered"), + Keyword::Recent => write!(f, "$recent"), + Keyword::Important => write!(f, "$important"), + Keyword::Phishing => write!(f, "$phishing"), + Keyword::Junk => write!(f, "$junk"), + Keyword::NotJunk => write!(f, "$notjunk"), + Keyword::Deleted => write!(f, "$deleted"), + Keyword::Forwarded => write!(f, "$forwarded"), + Keyword::MdnSent => write!(f, "$mdnsent"), + Keyword::Other(s) => write!(f, "{}", s), + } + } +} + +impl Display for ArchivedKeyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArchivedKeyword::Seen => write!(f, "$seen"), + ArchivedKeyword::Draft => write!(f, "$draft"), + ArchivedKeyword::Flagged => write!(f, "$flagged"), + ArchivedKeyword::Answered => write!(f, "$answered"), + ArchivedKeyword::Recent => write!(f, "$recent"), + ArchivedKeyword::Important => write!(f, "$important"), + ArchivedKeyword::Phishing => write!(f, "$phishing"), + ArchivedKeyword::Junk => write!(f, "$junk"), + ArchivedKeyword::NotJunk => write!(f, "$notjunk"), + ArchivedKeyword::Deleted => write!(f, "$deleted"), + ArchivedKeyword::Forwarded => write!(f, "$forwarded"), + ArchivedKeyword::MdnSent => write!(f, "$mdnsent"), + ArchivedKeyword::Other(s) => write!(f, "{}", s), + } + } +} + +impl From for Vec { + fn from(keyword: Keyword) -> Self { + match keyword { + Keyword::Seen => vec![SEEN as u8], + Keyword::Draft => vec![DRAFT as u8], + Keyword::Flagged => vec![FLAGGED as u8], + Keyword::Answered => vec![ANSWERED as u8], + Keyword::Recent => vec![RECENT as u8], + Keyword::Important => vec![IMPORTANT as u8], + Keyword::Phishing => vec![PHISHING as u8], + Keyword::Junk => vec![JUNK as u8], + Keyword::NotJunk => vec![NOTJUNK as u8], + Keyword::Deleted => vec![DELETED as u8], + Keyword::Forwarded => vec![FORWARDED as u8], + Keyword::MdnSent => vec![MDN_SENT as u8], + Keyword::Other(string) => string.as_bytes().to_vec(), + } + } +} + +impl Keyword { + pub fn id(&self) -> Result { + match self { + Keyword::Seen => Ok(SEEN as u32), + Keyword::Draft => Ok(DRAFT as u32), + Keyword::Flagged => Ok(FLAGGED as u32), + Keyword::Answered => Ok(ANSWERED as u32), + Keyword::Recent => Ok(RECENT as u32), + Keyword::Important => Ok(IMPORTANT as u32), + Keyword::Phishing => Ok(PHISHING as u32), + Keyword::Junk => Ok(JUNK as u32), + Keyword::NotJunk => Ok(NOTJUNK as u32), + Keyword::Deleted => Ok(DELETED as u32), + Keyword::Forwarded => Ok(FORWARDED as u32), + Keyword::MdnSent => Ok(MDN_SENT as u32), + Keyword::Other(string) => Err(string.as_str()), + } + } + + pub fn into_id(self) -> Result { + match self { + Keyword::Seen => Ok(SEEN as u32), + Keyword::Draft => Ok(DRAFT as u32), + Keyword::Flagged => Ok(FLAGGED as u32), + Keyword::Answered => Ok(ANSWERED as u32), + Keyword::Recent => Ok(RECENT as u32), + Keyword::Important => Ok(IMPORTANT as u32), + Keyword::Phishing => Ok(PHISHING as u32), + Keyword::Junk => Ok(JUNK as u32), + Keyword::NotJunk => Ok(NOTJUNK as u32), + Keyword::Deleted => Ok(DELETED as u32), + Keyword::Forwarded => Ok(FORWARDED as u32), + Keyword::MdnSent => Ok(MDN_SENT as u32), + Keyword::Other(string) => Err(string), + } + } + + pub fn try_from_id(id: usize) -> Result { + match id { + SEEN => Ok(Keyword::Seen), + DRAFT => Ok(Keyword::Draft), + FLAGGED => Ok(Keyword::Flagged), + ANSWERED => Ok(Keyword::Answered), + RECENT => Ok(Keyword::Recent), + IMPORTANT => Ok(Keyword::Important), + PHISHING => Ok(Keyword::Phishing), + JUNK => Ok(Keyword::Junk), + NOTJUNK => Ok(Keyword::NotJunk), + DELETED => Ok(Keyword::Deleted), + FORWARDED => Ok(Keyword::Forwarded), + MDN_SENT => Ok(Keyword::MdnSent), + _ => Err(id), + } + } +} + +impl ArchivedKeyword { + pub fn id(&self) -> Result { + match self { + ArchivedKeyword::Seen => Ok(SEEN as u32), + ArchivedKeyword::Draft => Ok(DRAFT as u32), + ArchivedKeyword::Flagged => Ok(FLAGGED as u32), + ArchivedKeyword::Answered => Ok(ANSWERED as u32), + ArchivedKeyword::Recent => Ok(RECENT as u32), + ArchivedKeyword::Important => Ok(IMPORTANT as u32), + ArchivedKeyword::Phishing => Ok(PHISHING as u32), + ArchivedKeyword::Junk => Ok(JUNK as u32), + ArchivedKeyword::NotJunk => Ok(NOTJUNK as u32), + ArchivedKeyword::Deleted => Ok(DELETED as u32), + ArchivedKeyword::Forwarded => Ok(FORWARDED as u32), + ArchivedKeyword::MdnSent => Ok(MDN_SENT as u32), + ArchivedKeyword::Other(string) => Err(string.as_str()), + } + } +} + +/*impl From for TagValue { + fn from(value: Keyword) -> Self { + match value.into_id() { + Ok(id) => TagValue::Id(id), + Err(string) => TagValue::Text(string.as_bytes().to_vec()), + } + } +} + +impl From<&Keyword> for TagValue { + fn from(value: &Keyword) -> Self { + match value.id() { + Ok(id) => TagValue::Id(id), + Err(string) => TagValue::Text(string.as_bytes().to_vec()), + } + } +} + +impl From<&ArchivedKeyword> for TagValue { + fn from(value: &ArchivedKeyword) -> Self { + match value.id() { + Ok(id) => TagValue::Id(id), + Err(string) => TagValue::Text(string.as_bytes().to_vec()), + } + } +}*/ + +impl From<&ArchivedKeyword> for Keyword { + fn from(value: &ArchivedKeyword) -> Self { + match value { + ArchivedKeyword::Seen => Keyword::Seen, + ArchivedKeyword::Draft => Keyword::Draft, + ArchivedKeyword::Flagged => Keyword::Flagged, + ArchivedKeyword::Answered => Keyword::Answered, + ArchivedKeyword::Recent => Keyword::Recent, + ArchivedKeyword::Important => Keyword::Important, + ArchivedKeyword::Phishing => Keyword::Phishing, + ArchivedKeyword::Junk => Keyword::Junk, + ArchivedKeyword::NotJunk => Keyword::NotJunk, + ArchivedKeyword::Deleted => Keyword::Deleted, + ArchivedKeyword::Forwarded => Keyword::Forwarded, + ArchivedKeyword::MdnSent => Keyword::MdnSent, + ArchivedKeyword::Other(string) => Keyword::Other(string.as_str().into()), + } + } +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs new file mode 100644 index 00000000..5d5a9b2f --- /dev/null +++ b/crates/types/src/lib.rs @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +pub mod acl; +pub mod blob; +pub mod blob_hash; +pub mod collection; +pub mod field; +pub mod id; +pub mod keyword; +pub mod semver; +pub mod type_state; + +pub type DocumentId = u32; +pub type ChangeId = u64; diff --git a/crates/types/src/semver.rs b/crates/types/src/semver.rs new file mode 100644 index 00000000..e266f4a9 --- /dev/null +++ b/crates/types/src/semver.rs @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::fmt::Display; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Semver(u64); + +impl Semver { + pub fn current() -> Self { + env!("CARGO_PKG_VERSION").try_into().unwrap() + } + + pub fn new(major: u16, minor: u16, patch: u16) -> Self { + let mut version: u64 = 0; + version |= (major as u64) << 32; + version |= (minor as u64) << 16; + version |= patch as u64; + Semver(version) + } + + pub fn unpack(&self) -> (u16, u16, u16) { + let version = self.0; + let major = ((version >> 32) & 0xFFFF) as u16; + let minor = ((version >> 16) & 0xFFFF) as u16; + let patch = (version & 0xFFFF) as u16; + (major, minor, patch) + } + + pub fn major(&self) -> u16 { + (self.0 >> 32) as u16 + } + + pub fn minor(&self) -> u16 { + (self.0 >> 16) as u16 + } + + pub fn patch(&self) -> u16 { + self.0 as u16 + } + + pub fn is_valid(&self) -> bool { + self.0 > 0 + } +} + +impl AsRef for Semver { + fn as_ref(&self) -> &u64 { + &self.0 + } +} + +impl From for Semver { + fn from(value: u64) -> Self { + Semver(value) + } +} + +impl TryFrom<&str> for Semver { + type Error = (); + + fn try_from(value: &str) -> Result { + let mut parts = value.splitn(3, '.'); + let major = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let minor = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let patch = parts.next().ok_or(())?.parse().map_err(|_| ())?; + Ok(Semver::new(major, minor, patch)) + } +} + +impl Display for Semver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (major, minor, patch) = self.unpack(); + write!(f, "{major}.{minor}.{patch}") + } +} diff --git a/crates/types/src/type_state.rs b/crates/types/src/type_state.rs new file mode 100644 index 00000000..a1efb38e --- /dev/null +++ b/crates/types/src/type_state.rs @@ -0,0 +1,228 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use serde::Serialize; +use std::fmt::Display; +use utils::map::bitmap::{Bitmap, BitmapItem}; + +use crate::collection::SyncCollection; + +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize)] +#[repr(u8)] +pub enum DataType { + #[serde(rename = "Email")] + Email = 0, + #[serde(rename = "EmailDelivery")] + EmailDelivery = 1, + #[serde(rename = "EmailSubmission")] + EmailSubmission = 2, + #[serde(rename = "Mailbox")] + Mailbox = 3, + #[serde(rename = "Thread")] + Thread = 4, + #[serde(rename = "Identity")] + Identity = 5, + #[serde(rename = "Core")] + Core = 6, + #[serde(rename = "PushSubscription")] + PushSubscription = 7, + #[serde(rename = "SearchSnippet")] + SearchSnippet = 8, + #[serde(rename = "VacationResponse")] + VacationResponse = 9, + #[serde(rename = "MDN")] + Mdn = 10, + #[serde(rename = "Quota")] + Quota = 11, + #[serde(rename = "SieveScript")] + SieveScript = 12, + #[serde(rename = "Calendar")] + Calendar = 13, + #[serde(rename = "CalendarEvent")] + CalendarEvent = 14, + #[serde(rename = "CalendarEventNotification")] + CalendarEventNotification = 15, + #[serde(rename = "AddressBook")] + AddressBook = 16, + #[serde(rename = "ContactCard")] + ContactCard = 17, + #[serde(rename = "FileNode")] + FileNode = 18, + None = 19, +} + +#[derive(Debug, Clone, Copy)] +pub struct StateChange { + pub account_id: u32, + pub change_id: u64, + pub types: Bitmap, +} + +impl StateChange { + pub fn new(account_id: u32, change_id: u64) -> Self { + Self { + account_id, + change_id, + types: Default::default(), + } + } + + pub fn set_change(&mut self, type_state: DataType) { + self.types.insert(type_state); + } + + pub fn with_change(mut self, type_state: DataType) -> Self { + self.set_change(type_state); + self + } + + pub fn has_changes(&self) -> bool { + !self.types.is_empty() + } +} + +impl BitmapItem for DataType { + fn max() -> u64 { + DataType::None as u64 + } + + fn is_valid(&self) -> bool { + !matches!(self, DataType::None) + } +} + +impl From for DataType { + fn from(value: u64) -> Self { + match value { + 0 => DataType::Email, + 1 => DataType::EmailDelivery, + 2 => DataType::EmailSubmission, + 3 => DataType::Mailbox, + 4 => DataType::Thread, + 5 => DataType::Identity, + 6 => DataType::Core, + 7 => DataType::PushSubscription, + 8 => DataType::SearchSnippet, + 9 => DataType::VacationResponse, + 10 => DataType::Mdn, + 11 => DataType::Quota, + 12 => DataType::SieveScript, + 13 => DataType::Calendar, + 14 => DataType::CalendarEvent, + 15 => DataType::CalendarEventNotification, + 16 => DataType::AddressBook, + 17 => DataType::ContactCard, + 18 => DataType::FileNode, + _ => { + debug_assert!(false, "Invalid type_state value: {}", value); + DataType::None + } + } + } +} + +impl From for u64 { + fn from(type_state: DataType) -> u64 { + type_state as u64 + } +} + +impl TryFrom<&str> for DataType { + type Error = (); + + fn try_from(value: &str) -> Result { + let mut hash = 0; + let mut shift = 0; + + for &ch in value.as_bytes() { + if shift < 128 { + hash |= (ch as u128) << shift; + shift += 8; + } else { + return Err(()); + } + } + + match hash { + 0x006c_6961_6d45 => Ok(DataType::Email), + 0x0079_7265_7669_6c65_446c_6961_6d45 => Ok(DataType::EmailDelivery), + 0x006e_6f69_7373_696d_6275_536c_6961_6d45 => Ok(DataType::EmailSubmission), + 0x0078_6f62_6c69_614d => Ok(DataType::Mailbox), + 0x6461_6572_6854 => Ok(DataType::Thread), + 0x7974_6974_6e65_6449 => Ok(DataType::Identity), + 0x6572_6f43 => Ok(DataType::Core), + 0x6e6f_6974_7069_7263_7362_7553_6873_7550 => Ok(DataType::PushSubscription), + 0x0074_6570_7069_6e53_6863_7261_6553 => Ok(DataType::SearchSnippet), + 0x6573_6e6f_7073_6552_6e6f_6974_6163_6156 => Ok(DataType::VacationResponse), + 0x004e_444d => Ok(DataType::Mdn), + 0x0061_746f_7551 => Ok(DataType::Quota), + 0x0074_7069_7263_5365_7665_6953 => Ok(DataType::SieveScript), + _ => Err(()), + } + } +} + +impl DataType { + pub fn try_from_sync(value: SyncCollection, is_container: bool) -> Option { + match (value, is_container) { + (SyncCollection::Email, false) => DataType::Email.into(), + (SyncCollection::Email, true) => DataType::Mailbox.into(), + (SyncCollection::Thread, _) => DataType::Thread.into(), + (SyncCollection::Calendar, true) => DataType::Calendar.into(), + (SyncCollection::Calendar, false) => DataType::CalendarEvent.into(), + (SyncCollection::AddressBook, true) => DataType::AddressBook.into(), + (SyncCollection::AddressBook, false) => DataType::ContactCard.into(), + (SyncCollection::FileNode, _) => DataType::FileNode.into(), + (SyncCollection::Identity, _) => DataType::Identity.into(), + (SyncCollection::EmailSubmission, _) => DataType::EmailSubmission.into(), + (SyncCollection::SieveScript, _) => DataType::SieveScript.into(), + _ => None, + } + } +} + +impl DataType { + pub fn as_str(&self) -> &'static str { + match self { + DataType::Email => "Email", + DataType::EmailDelivery => "EmailDelivery", + DataType::EmailSubmission => "EmailSubmission", + DataType::Mailbox => "Mailbox", + DataType::Thread => "Thread", + DataType::Identity => "Identity", + DataType::Core => "Core", + DataType::PushSubscription => "PushSubscription", + DataType::SearchSnippet => "SearchSnippet", + DataType::VacationResponse => "VacationResponse", + DataType::Mdn => "MDN", + DataType::Quota => "Quota", + DataType::SieveScript => "SieveScript", + DataType::Calendar => "Calendar", + DataType::CalendarEvent => "CalendarEvent", + DataType::CalendarEventNotification => "CalendarEventNotification", + DataType::AddressBook => "AddressBook", + DataType::ContactCard => "ContactCard", + DataType::FileNode => "FileNode", + DataType::None => "", + } + } +} + +impl Display for DataType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl<'de> serde::Deserialize<'de> for DataType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + DataType::try_from(<&str>::deserialize(deserializer)?) + .map_err(|_| serde::de::Error::custom("invalid JMAP data type")) + } +} diff --git a/crates/utils/src/cache.rs b/crates/utils/src/cache.rs index 8e28ce81..7b41177c 100644 --- a/crates/utils/src/cache.rs +++ b/crates/utils/src/cache.rs @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::config::Config; +use mail_auth::{MX, ResolverCache, Txt}; +use quick_cache::{ + Equivalent, Weighter, + sync::{DefaultLifecycle, PlaceholderGuard}, +}; use std::{ borrow::Borrow, hash::Hash, @@ -12,14 +18,6 @@ use std::{ time::{Duration, Instant}, }; -use mail_auth::{MX, ResolverCache, Txt}; -use quick_cache::{ - Equivalent, Weighter, - sync::{DefaultLifecycle, PlaceholderGuard}, -}; - -use crate::config::Config; - pub struct Cache( quick_cache::sync::Cache, ); diff --git a/crates/utils/src/codec/base32_custom.rs b/crates/utils/src/codec/base32_custom.rs index 5053d2b2..cfc78c44 100644 --- a/crates/utils/src/codec/base32_custom.rs +++ b/crates/utils/src/codec/base32_custom.rs @@ -92,7 +92,7 @@ impl Base32Writer { } pub fn finalize(mut self) -> String { - if self.pos % 5 != 0 { + if !self.pos.is_multiple_of(5) { self.push_byte(0, true); } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index f06ae7ec..4fa7981d 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{fmt::Display, sync::Arc}; - pub mod bimap; pub mod cache; pub mod codec; @@ -26,85 +24,11 @@ use rustls::{ client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, }; use rustls_pki_types::TrustAnchor; +use std::sync::Arc; pub use downcast_rs; pub use erased_serde; -pub const BLOB_HASH_LEN: usize = 32; - -#[derive( - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, - Clone, - Debug, - Default, - PartialEq, - Eq, - Hash, - serde::Serialize, - serde::Deserialize, -)] -#[rkyv(derive(Debug))] -#[repr(transparent)] -pub struct BlobHash(pub [u8; BLOB_HASH_LEN]); - -impl BlobHash { - pub fn new_max() -> Self { - BlobHash([u8::MAX; BLOB_HASH_LEN]) - } - - pub fn generate(value: impl AsRef<[u8]>) -> Self { - BlobHash(blake3::hash(value.as_ref()).into()) - } - - pub fn try_from_hash_slice(value: &[u8]) -> Result { - value.try_into().map(BlobHash) - } - - pub fn as_slice(&self) -> &[u8] { - self.0.as_ref() - } - - pub fn to_hex(&self) -> String { - let mut hex = String::with_capacity(BLOB_HASH_LEN * 2); - for byte in self.0.iter() { - hex.push_str(&format!("{:02x}", byte)); - } - hex - } -} - -impl From<&ArchivedBlobHash> for BlobHash { - fn from(value: &ArchivedBlobHash) -> Self { - BlobHash(value.0) - } -} - -impl AsRef for BlobHash { - fn as_ref(&self) -> &BlobHash { - self - } -} - -impl From for Vec { - fn from(value: BlobHash) -> Self { - value.0.to_vec() - } -} - -impl AsRef<[u8]> for BlobHash { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsMut<[u8]> for BlobHash { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut() - } -} - pub trait HttpLimitResponse: Sync + Send { fn bytes_with_limit( self, @@ -136,79 +60,6 @@ impl HttpLimitResponse for Response { } } -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct Semver(u64); - -impl Semver { - pub fn current() -> Self { - env!("CARGO_PKG_VERSION").try_into().unwrap() - } - - pub fn new(major: u16, minor: u16, patch: u16) -> Self { - let mut version: u64 = 0; - version |= (major as u64) << 32; - version |= (minor as u64) << 16; - version |= patch as u64; - Semver(version) - } - - pub fn unpack(&self) -> (u16, u16, u16) { - let version = self.0; - let major = ((version >> 32) & 0xFFFF) as u16; - let minor = ((version >> 16) & 0xFFFF) as u16; - let patch = (version & 0xFFFF) as u16; - (major, minor, patch) - } - - pub fn major(&self) -> u16 { - (self.0 >> 32) as u16 - } - - pub fn minor(&self) -> u16 { - (self.0 >> 16) as u16 - } - - pub fn patch(&self) -> u16 { - self.0 as u16 - } - - pub fn is_valid(&self) -> bool { - self.0 > 0 - } -} - -impl AsRef for Semver { - fn as_ref(&self) -> &u64 { - &self.0 - } -} - -impl From for Semver { - fn from(value: u64) -> Self { - Semver(value) - } -} - -impl TryFrom<&str> for Semver { - type Error = (); - - fn try_from(value: &str) -> Result { - let mut parts = value.splitn(3, '.'); - let major = parts.next().ok_or(())?.parse().map_err(|_| ())?; - let minor = parts.next().ok_or(())?.parse().map_err(|_| ())?; - let patch = parts.next().ok_or(())?.parse().map_err(|_| ())?; - Ok(Semver::new(major, minor, patch)) - } -} - -impl Display for Semver { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let (major, minor, patch) = self.unpack(); - write!(f, "{major}.{minor}.{patch}") - } -} - pub trait UnwrapFailure { fn failed(self, action: &str) -> T; } diff --git a/crates/utils/src/map/bitmap.rs b/crates/utils/src/map/bitmap.rs index c4079237..5eb9a0d6 100644 --- a/crates/utils/src/map/bitmap.rs +++ b/crates/utils/src/map/bitmap.rs @@ -260,29 +260,3 @@ impl Default for Bitmap { } } } - -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct ShortId(pub u8); - -impl BitmapItem for ShortId { - fn max() -> u64 { - u8::MAX as u64 - } - - fn is_valid(&self) -> bool { - true - } -} - -impl From for ShortId { - fn from(value: u64) -> Self { - ShortId(value as u8) - } -} - -impl From for u64 { - fn from(value: ShortId) -> Self { - value.0 as u64 - } -} diff --git a/crates/utils/src/map/vec_map.rs b/crates/utils/src/map/vec_map.rs index 4aa5b239..cfc1ff5e 100644 --- a/crates/utils/src/map/vec_map.rs +++ b/crates/utils/src/map/vec_map.rs @@ -4,10 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{borrow::Borrow, cmp::Ordering, fmt, hash::Hash}; - use rkyv::Archive; use serde::{Deserialize, Serialize, de::DeserializeOwned, ser::SerializeMap}; +use std::{borrow::Borrow, cmp::Ordering, fmt, hash::Hash}; // A map implemented using vectors // used for small datasets of less than 20 items diff --git a/tests/Cargo.toml b/tests/Cargo.toml index e8364048..8824c75f 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -27,6 +27,7 @@ jmap = { path = "../crates/jmap", features = ["test_mode", "enterprise"] } jmap_proto = { path = "../crates/jmap-proto" } imap = { path = "../crates/imap", features = ["test_mode"] } imap_proto = { path = "../crates/imap-proto" } +types = { path = "../crates/types" } dav = { path = "../crates/dav", features = ["test_mode"] } dav-proto = { path = "../crates/dav-proto", features = ["test_mode"] } calcard = { version = "0.1.3", features = ["rkyv"] } diff --git a/tests/resources/jmap/email_set/headers.eml b/tests/resources/jmap/email_set/headers.eml index 0880e29b..d4a6866e 100644 --- a/tests/resources/jmap/email_set/headers.eml +++ b/tests/resources/jmap/email_set/headers.eml @@ -23,8 +23,8 @@ To: "Greg Vaudreuil" , X-AddressesGroup: "A Group": "Ed Jones" , , "John" ; X-AddressesGroup: "List 1": , - ;"List 2": , - ;, + ; "List 2": , + ; , ; X-References: <1234@local.machine.example> <3456@example.net> X-References: <789@local.machine.example> @@ -40,13 +40,16 @@ Content-Language: en Content-Type: text/plain; charset="us-ascii" X-Header: just a value X-Text: more text -Content-Transfer-Encoding: 7bit +Content-Transfer-Encoding: quoted-printable -I have the most brilliant plan. Let me tell you all about it. What we do is, we +I have the most brilliant plan. Let me tell you all about it. What we do i= +s, we --boundary_0 Content-Location: https://example.com/html-body.html Content-Type: text/html; charset="utf-8"; name="html-body.html" -Content-Transfer-Encoding: 7bit +Content-Transfer-Encoding: quoted-printable -
I have the most brilliant plan. Let me tell you all about it. What we do is, we
+
I have the most brilliant plan. = +Let me tell you all about it. What we do is, we
--boundary_0-- diff --git a/tests/resources/jmap/email_set/headers.jmap b/tests/resources/jmap/email_set/headers.jmap index 6287d497..7d3df3fe 100644 --- a/tests/resources/jmap/email_set/headers.jmap +++ b/tests/resources/jmap/email_set/headers.jmap @@ -144,7 +144,7 @@ }, { "name": "X-AddressesGroup", - "value": " \"List 1\": , \r\n\t;\"List 2\": , \r\n\t;, \r\n\t;" + "value": " \"List 1\": , \r\n\t; \"List 2\": , \r\n\t; , \r\n\t;" }, { "name": "X-References", @@ -196,7 +196,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", @@ -220,7 +220,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "name": "html-body.html", @@ -266,7 +266,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", @@ -292,7 +292,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "name": "html-body.html", diff --git a/tests/resources/jmap/email_set/mixed.eml b/tests/resources/jmap/email_set/mixed.eml index 13e41872..1e3bcdd7 100644 --- a/tests/resources/jmap/email_set/mixed.eml +++ b/tests/resources/jmap/email_set/mixed.eml @@ -2,7 +2,7 @@ Date: Sat, 20 Nov 2021 22:22:01 +0000 From: "Art Vandelay (Vandelay Industries)" Message-ID: Subject: =?utf-8?Q?Why_not_both_importing_AND_exporting=3F_=E2=98=BA?= -To: "Colleagues": "James Smythe" ; +To: "Colleagues": "James Smythe" ; "Friends": , "=?utf-8?Q?John_Sm=C3=AEth?=" ; MIME-Version: 1.0 @@ -27,9 +27,11 @@ but then I thought, why not do both? =E2=98=BA --boundary_1 Content-Language: en_US Content-Type: text/html -Content-Transfer-Encoding: 7bit +Content-Transfer-Encoding: quoted-printable -

I was thinking about quitting the “exporting” to focus just on the “importing”,

but then I thought, why not do both? ☺

+

I was thinking about quitting the “exporting” to focus = +just on the “importing”,

but then I thought, why not do bo= +th? ☺

--boundary_1-- --boundary_0 diff --git a/tests/resources/jmap/email_set/mixed.jmap b/tests/resources/jmap/email_set/mixed.jmap index 23ee2597..dee4f230 100644 --- a/tests/resources/jmap/email_set/mixed.jmap +++ b/tests/resources/jmap/email_set/mixed.jmap @@ -53,7 +53,7 @@ }, { "name": "To", - "value": " \"Colleagues\": \"James Smythe\" ;\r\n\t\"Friends\": , \r\n\t\"=?utf-8?Q?John_Sm=C3=AEth?=\" ;" + "value": " \"Colleagues\": \"James Smythe\" ; \r\n\t\"Friends\": , \r\n\t\"=?utf-8?Q?John_Sm=C3=AEth?=\" ;" }, { "name": "MIME-Version", @@ -114,7 +114,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/html", @@ -245,7 +245,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/html", diff --git a/tests/resources/jmap/email_set/rfc8621_1.eml b/tests/resources/jmap/email_set/rfc8621_1.eml index 2061a2ec..a112499c 100644 --- a/tests/resources/jmap/email_set/rfc8621_1.eml +++ b/tests/resources/jmap/email_set/rfc8621_1.eml @@ -5,6 +5,7 @@ Subject: World domination MIME-Version: 1.0 Content-Language: en Content-Type: text/plain; charset="utf-8" -Content-Transfer-Encoding: 7bit +Content-Transfer-Encoding: quoted-printable -I have the most brilliant plan. Let me tell you all about it. What we do is, we \ No newline at end of file +I have the most brilliant plan. Let me tell you all about it. What we do i= +s, we \ No newline at end of file diff --git a/tests/resources/jmap/email_set/rfc8621_1.jmap b/tests/resources/jmap/email_set/rfc8621_1.jmap index adda9a19..dab70a4a 100644 --- a/tests/resources/jmap/email_set/rfc8621_1.jmap +++ b/tests/resources/jmap/email_set/rfc8621_1.jmap @@ -53,7 +53,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", @@ -105,7 +105,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", @@ -151,7 +151,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", diff --git a/tests/resources/jmap/email_set/rfc8621_2.eml b/tests/resources/jmap/email_set/rfc8621_2.eml index 2482b4c9..dbacc203 100644 --- a/tests/resources/jmap/email_set/rfc8621_2.eml +++ b/tests/resources/jmap/email_set/rfc8621_2.eml @@ -11,13 +11,16 @@ Content-Type: multipart/alternative; --boundary_0 Content-Language: en Content-Type: text/html; charset="utf-8" -Content-Transfer-Encoding: 7bit +Content-Transfer-Encoding: quoted-printable -
I have the most brilliant plan. Let me tell you all about it. What we do is, we
+
I have the most brilliant plan. = +Let me tell you all about it. What we do is, we
--boundary_0 Content-Language: en Content-Type: text/plain; charset="utf-8" -Content-Transfer-Encoding: 7bit +Content-Transfer-Encoding: quoted-printable -I have the most brilliant plan. Let me tell you all about it. What we do is, we +I have the most brilliant plan. Let me tell you all about it. What we do i= +s, we --boundary_0-- diff --git a/tests/resources/jmap/email_set/rfc8621_2.jmap b/tests/resources/jmap/email_set/rfc8621_2.jmap index 9851e418..bae76d53 100644 --- a/tests/resources/jmap/email_set/rfc8621_2.jmap +++ b/tests/resources/jmap/email_set/rfc8621_2.jmap @@ -72,7 +72,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/html", @@ -96,7 +96,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", @@ -135,7 +135,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/plain", @@ -161,7 +161,7 @@ }, { "name": "Content-Transfer-Encoding", - "value": " 7bit" + "value": " quoted-printable" } ], "type": "text/html", diff --git a/tests/src/cluster/broadcast.rs b/tests/src/cluster/broadcast.rs index d8d97061..bccc0073 100644 --- a/tests/src/cluster/broadcast.rs +++ b/tests/src/cluster/broadcast.rs @@ -11,8 +11,8 @@ use directory::backend::internal::{ manage::{ManageDirectory, UpdatePrincipal}, }; use groupware::cache::GroupwareCache; -use jmap_proto::types::collection::SyncCollection; use std::net::IpAddr; +use types::collection::SyncCollection; pub async fn test(cluster: &ClusterTest) { println!("Running cluster broadcast tests..."); diff --git a/tests/src/cluster/stress.rs b/tests/src/cluster/stress.rs index d77f6be7..c05f798a 100644 --- a/tests/src/cluster/stress.rs +++ b/tests/src/cluster/stress.rs @@ -17,12 +17,12 @@ use jmap_client::{ core::set::{SetErrorType, SetObject}, mailbox::{self, Mailbox, Role}, }; -use jmap_proto::types::{collection::Collection, id::Id}; use std::{sync::Arc, time::Duration}; use store::{ rand::{self, Rng}, roaring::RoaringBitmap, }; +use types::{collection::Collection, id::Id}; const TEST_USER_ID: u32 = 1; const NUM_PASSES: usize = 1; diff --git a/tests/src/directory/internal.rs b/tests/src/directory/internal.rs index 70c3ac8f..279d5d7d 100644 --- a/tests/src/directory/internal.rs +++ b/tests/src/directory/internal.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::directory::{DirectoryTest, IntoTestPrincipal, TestPrincipal}; use ahash::AHashSet; use directory::{ Permission, QueryBy, QueryParams, Type, @@ -16,15 +17,13 @@ use directory::{ }, }, }; -use jmap_proto::types::collection::Collection; use mail_send::Credentials; use store::{ BitmapKey, Store, ValueKey, roaring::RoaringBitmap, write::{BatchBuilder, BitmapClass, ValueClass}, }; - -use crate::directory::{DirectoryTest, IntoTestPrincipal, TestPrincipal}; +use types::collection::Collection; #[tokio::test] async fn internal_directory() { diff --git a/tests/src/jmap/auth_acl.rs b/tests/src/jmap/auth_acl.rs index b525d01a..4e0551d9 100644 --- a/tests/src/jmap/auth_acl.rs +++ b/tests/src/jmap/auth_acl.rs @@ -4,6 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::{ + directory::internal::TestInternalDirectory, + jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, test_account_login}, +}; use ::email::mailbox::{INBOX_ID, TRASH_ID}; use jmap_client::{ core::{ @@ -14,14 +18,9 @@ use jmap_client::{ mailbox::{self, Role}, principal::ACL, }; -use jmap_proto::types::id::Id; use std::fmt::Debug; use store::ahash::AHashMap; - -use crate::{ - directory::internal::TestInternalDirectory, - jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, test_account_login}, -}; +use types::id::Id; use super::JMAPTest; diff --git a/tests/src/jmap/auth_limits.rs b/tests/src/jmap/auth_limits.rs index ae98b4a0..e69d6d88 100644 --- a/tests/src/jmap/auth_limits.rs +++ b/tests/src/jmap/auth_limits.rs @@ -10,6 +10,11 @@ use std::{ time::Duration, }; +use crate::{ + directory::internal::TestInternalDirectory, + imap::{ImapConnection, Type}, + jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}, +}; use common::listener::blocked::BLOCKED_IP_KEY; use imap_proto::ResponseType; use jmap_client::{ @@ -17,14 +22,8 @@ use jmap_client::{ core::set::{SetError, SetErrorType}, mailbox::{self}, }; -use jmap_proto::types::id::Id; use store::write::now; - -use crate::{ - directory::internal::TestInternalDirectory, - imap::{ImapConnection, Type}, - jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}, -}; +use types::id::Id; use super::JMAPTest; diff --git a/tests/src/jmap/auth_oauth.rs b/tests/src/jmap/auth_oauth.rs index fdeb9adf..82d8dc46 100644 --- a/tests/src/jmap/auth_oauth.rs +++ b/tests/src/jmap/auth_oauth.rs @@ -4,30 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use base64::{Engine, engine::general_purpose}; -use biscuit::{JWT, SingleOrMultiple, jwk::JWKSet}; -use bytes::Bytes; -use common::auth::oauth::{ - introspect::OAuthIntrospect, - oidc::StandardClaims, - registration::{ClientRegistrationRequest, ClientRegistrationResponse}, -}; - -use http::auth::oauth::{ - DeviceAuthResponse, ErrorType, OAuthCodeRequest, TokenResponse, auth::OAuthMetadata, - openid::OpenIdMetadata, -}; -use imap_proto::ResponseType; -use jmap_client::{ - client::{Client, Credentials}, - mailbox::query::Filter, -}; -use jmap_proto::types::id::Id; -use serde::{Serialize, de::DeserializeOwned}; -use store::ahash::AHashMap; - +use super::JMAPTest; use crate::{ directory::internal::TestInternalDirectory, imap::{ @@ -38,8 +15,27 @@ use crate::{ ManagementApi, assert_is_empty, delivery::SmtpConnection, mailbox::destroy_all_mailboxes, }, }; - -use super::JMAPTest; +use base64::{Engine, engine::general_purpose}; +use biscuit::{JWT, SingleOrMultiple, jwk::JWKSet}; +use bytes::Bytes; +use common::auth::oauth::{ + introspect::OAuthIntrospect, + oidc::StandardClaims, + registration::{ClientRegistrationRequest, ClientRegistrationResponse}, +}; +use http::auth::oauth::{ + DeviceAuthResponse, ErrorType, OAuthCodeRequest, TokenResponse, auth::OAuthMetadata, + openid::OpenIdMetadata, +}; +use imap_proto::ResponseType; +use jmap_client::{ + client::{Client, Credentials}, + mailbox::query::Filter, +}; +use serde::{Serialize, de::DeserializeOwned}; +use std::time::{Duration, Instant}; +use store::ahash::AHashMap; +use types::id::Id; #[derive(serde::Deserialize, Debug)] #[allow(dead_code)] diff --git a/tests/src/jmap/blob.rs b/tests/src/jmap/blob.rs index a94b8eb6..0254945c 100644 --- a/tests/src/jmap/blob.rs +++ b/tests/src/jmap/blob.rs @@ -4,16 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use email::mailbox::INBOX_ID; -use jmap_proto::types::id::Id; -use serde_json::Value; - +use super::JMAPTest; use crate::{ directory::internal::TestInternalDirectory, jmap::{assert_is_empty, jmap_json_request, mailbox::destroy_all_mailboxes}, }; - -use super::JMAPTest; +use email::mailbox::INBOX_ID; +use serde_json::Value; +use types::id::Id; pub async fn test(params: &mut JMAPTest) { println!("Running blob tests..."); diff --git a/tests/src/jmap/crypto.rs b/tests/src/jmap/crypto.rs index 117ed7c2..21bd3d01 100644 --- a/tests/src/jmap/crypto.rs +++ b/tests/src/jmap/crypto.rs @@ -4,24 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::path::PathBuf; - -use email::message::crypto::{ - Algorithm, EncryptMessage, EncryptionMethod, EncryptionParams, EncryptionType, try_parse_certs, -}; -use jmap_proto::types::id::Id; -use mail_parser::{MessageParser, MimeHeaders}; -use store::{ - Deserialize, Serialize, - write::{Archive, Archiver}, -}; - +use super::JMAPTest; use crate::{ directory::internal::TestInternalDirectory, jmap::{ManagementApi, delivery::SmtpConnection}, }; - -use super::JMAPTest; +use email::message::crypto::{ + Algorithm, EncryptMessage, EncryptionMethod, EncryptionParams, EncryptionType, try_parse_certs, +}; +use mail_parser::{MessageParser, MimeHeaders}; +use std::path::PathBuf; +use store::{ + Deserialize, Serialize, + write::{Archive, Archiver}, +}; +use types::id::Id; pub async fn test(params: &mut JMAPTest) { println!("Running Encryption-at-rest tests..."); diff --git a/tests/src/jmap/delivery.rs b/tests/src/jmap/delivery.rs index 0b0d542d..6c02b866 100644 --- a/tests/src/jmap/delivery.rs +++ b/tests/src/jmap/delivery.rs @@ -4,22 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use email::{ - cache::{MessageCacheFetch, email::MessageCacheAccess}, - mailbox::{INBOX_ID, JUNK_ID}, -}; -use jmap_proto::types::{collection::Collection, id::Id}; -use std::time::Duration; - use super::JMAPTest; use crate::{ directory::internal::TestInternalDirectory, jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}, }; +use email::{ + cache::{MessageCacheFetch, email::MessageCacheAccess}, + mailbox::{INBOX_ID, JUNK_ID}, +}; +use std::time::Duration; use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf}, net::TcpStream, }; +use types::{collection::Collection, id::Id}; pub async fn test(params: &mut JMAPTest) { println!("Running message delivery tests..."); diff --git a/tests/src/jmap/email_changes.rs b/tests/src/jmap/email_changes.rs index c85bda49..2192e402 100644 --- a/tests/src/jmap/email_changes.rs +++ b/tests/src/jmap/email_changes.rs @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::JMAPTest; +use crate::jmap::assert_is_empty; use jmap_proto::{ parser::{JsonObjectParser, json::Parser}, - types::{ - collection::{Collection, SyncCollection}, - id::Id, - state::State, - }, + types::state::State, }; use store::{ahash::AHashSet, write::BatchBuilder}; - -use crate::jmap::assert_is_empty; - -use super::JMAPTest; +use types::{ + collection::{Collection, SyncCollection}, + id::Id, +}; pub async fn test(params: &mut JMAPTest) { println!("Running Email Changes tests..."); diff --git a/tests/src/jmap/email_copy.rs b/tests/src/jmap/email_copy.rs index fb9438e7..1cdaeb0a 100644 --- a/tests/src/jmap/email_copy.rs +++ b/tests/src/jmap/email_copy.rs @@ -4,12 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use jmap_client::mailbox::Role; -use jmap_proto::types::id::Id; - -use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}; - use super::JMAPTest; +use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}; +use jmap_client::mailbox::Role; +use types::id::Id; pub async fn test(params: &mut JMAPTest) { println!("Running Email Copy tests..."); diff --git a/tests/src/jmap/email_get.rs b/tests/src/jmap/email_get.rs index 98abdec2..c8b3b6e6 100644 --- a/tests/src/jmap/email_get.rs +++ b/tests/src/jmap/email_get.rs @@ -4,16 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{fs, path::PathBuf}; - +use super::JMAPTest; +use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, replace_blob_ids}; use ::email::mailbox::INBOX_ID; use jmap_client::email::{self, Header, HeaderForm, import::EmailImportResponse}; -use jmap_proto::types::id::Id; use mail_parser::HeaderName; - -use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, replace_blob_ids}; - -use super::JMAPTest; +use std::{fs, path::PathBuf}; +use types::id::Id; pub async fn test(params: &mut JMAPTest) { println!("Running Email Get tests..."); diff --git a/tests/src/jmap/email_parse.rs b/tests/src/jmap/email_parse.rs index ae2412e0..32913235 100644 --- a/tests/src/jmap/email_parse.rs +++ b/tests/src/jmap/email_parse.rs @@ -4,19 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{fs, path::PathBuf}; - +use super::JMAPTest; +use crate::jmap::{ + assert_is_empty, email_get::all_headers, mailbox::destroy_all_mailboxes, replace_blob_ids, +}; use jmap_client::{ email::{self, Header, HeaderForm}, mailbox::Role, }; -use jmap_proto::types::id::Id; - -use crate::jmap::{ - assert_is_empty, email_get::all_headers, mailbox::destroy_all_mailboxes, replace_blob_ids, -}; - -use super::JMAPTest; +use std::{fs, path::PathBuf}; +use types::id::Id; pub async fn test(params: &mut JMAPTest) { println!("Running Email Parse tests..."); diff --git a/tests/src/jmap/email_query.rs b/tests/src/jmap/email_query.rs index 9f6f8a4a..f9dc081a 100644 --- a/tests/src/jmap/email_query.rs +++ b/tests/src/jmap/email_query.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{collections::hash_map::Entry, time::Instant}; - use super::JMAPTest; use crate::{ jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, wait_for_index}, @@ -19,12 +17,13 @@ use jmap_client::{ core::query::{Comparator, Filter}, email, }; -use jmap_proto::types::{collection::Collection, id::Id}; use mail_parser::{DateTime, HeaderName}; +use std::{collections::hash_map::Entry, time::Instant}; use store::{ ahash::AHashMap, write::{BatchBuilder, now}, }; +use types::{collection::Collection, id::Id}; const MAX_THREADS: usize = 100; const MAX_MESSAGES: usize = 1000; diff --git a/tests/src/jmap/email_query_changes.rs b/tests/src/jmap/email_query_changes.rs index 446092fc..d234b2e7 100644 --- a/tests/src/jmap/email_query_changes.rs +++ b/tests/src/jmap/email_query_changes.rs @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::JMAPTest; +use crate::jmap::{ + assert_is_empty, + email_changes::{LogAction, ParseState}, + mailbox::destroy_all_mailboxes, +}; use ::email::message::metadata::MessageData; use common::storage::index::ObjectIndexBuilder; use jmap_client::{ @@ -11,25 +17,16 @@ use jmap_client::{ email, mailbox::Role, }; -use jmap_proto::types::{ - collection::{Collection, SyncCollection}, - id::Id, - state::State, -}; - +use jmap_proto::types::state::State; use store::{ ahash::{AHashMap, AHashSet}, write::BatchBuilder, }; - -use crate::jmap::{ - assert_is_empty, - email_changes::{LogAction, ParseState}, - mailbox::destroy_all_mailboxes, +use types::{ + collection::{Collection, SyncCollection}, + id::Id, }; -use super::JMAPTest; - pub async fn test(params: &mut JMAPTest) { println!("Running Email QueryChanges tests..."); let server = params.server.clone(); diff --git a/tests/src/jmap/email_search_snippet.rs b/tests/src/jmap/email_search_snippet.rs index f1e9ee8c..3206065e 100644 --- a/tests/src/jmap/email_search_snippet.rs +++ b/tests/src/jmap/email_search_snippet.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{fs, path::PathBuf}; - use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, wait_for_index}; - use email::mailbox::INBOX_ID; use jmap_client::{core::query, email::query::Filter}; -use jmap_proto::types::id::Id; +use std::{fs, path::PathBuf}; use store::ahash::AHashMap; +use types::id::Id; use super::JMAPTest; diff --git a/tests/src/jmap/email_set.rs b/tests/src/jmap/email_set.rs index 813ec31b..9208a461 100644 --- a/tests/src/jmap/email_set.rs +++ b/tests/src/jmap/email_set.rs @@ -7,9 +7,8 @@ use std::{fs, path::PathBuf}; use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}; -use ahash::AHashSet; - use ::email::mailbox::INBOX_ID; +use ahash::AHashSet; use jmap_client::{ Error, Set, client::Client, @@ -17,7 +16,7 @@ use jmap_client::{ email::{self, Email}, mailbox::Role, }; -use jmap_proto::types::id::Id; +use types::id::Id; use super::{JMAPTest, find_values, replace_blob_ids, replace_boundaries, replace_values}; diff --git a/tests/src/jmap/email_submission.rs b/tests/src/jmap/email_submission.rs index c7841d19..aaa25477 100644 --- a/tests/src/jmap/email_submission.rs +++ b/tests/src/jmap/email_submission.rs @@ -11,7 +11,7 @@ use jmap_client::{ email_submission::{Address, Delivered, DeliveryStatus, Displayed, UndoStatus, query::Filter}, mailbox::Role, }; -use jmap_proto::types::id::Id; +use types::id::Id; use mail_parser::DateTime; use std::{ sync::Arc, diff --git a/tests/src/jmap/event_source.rs b/tests/src/jmap/event_source.rs index add0bb94..9b20ccb7 100644 --- a/tests/src/jmap/event_source.rs +++ b/tests/src/jmap/event_source.rs @@ -17,7 +17,7 @@ use email::mailbox::INBOX_ID; use futures::StreamExt; use jmap_client::{TypeState, event_source::Changes, mailbox::Role}; -use jmap_proto::types::id::Id; +use types::id::Id; use store::ahash::AHashSet; use tokio::sync::mpsc; diff --git a/tests/src/jmap/mailbox.rs b/tests/src/jmap/mailbox.rs index a333fa9c..e4de0e8d 100644 --- a/tests/src/jmap/mailbox.rs +++ b/tests/src/jmap/mailbox.rs @@ -15,9 +15,10 @@ use jmap_client::{ }, mailbox::{self, Mailbox, Role}, }; -use jmap_proto::types::{id::Id, state::State}; +use jmap_proto::types::state::State; use serde::{Deserialize, Serialize}; use store::ahash::AHashMap; +use types::id::Id; use crate::jmap::assert_is_empty; diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index 96a31d45..ad087276 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -30,7 +30,7 @@ use http::HttpSessionManager; use hyper::{Method, header::AUTHORIZATION}; use imap::core::ImapSessionManager; use jmap_client::client::{Client, Credentials}; -use jmap_proto::{error::request::RequestError, types::id::Id}; +use jmap_proto::error::request::RequestError; use managesieve::core::ManageSieveSessionManager; use pop3::Pop3SessionManager; use reqwest::header; @@ -44,7 +44,8 @@ use store::{ write::{AnyKey, TaskQueueClass, ValueClass, key::DeserializeBigEndian}, }; use tokio::sync::watch; -use utils::{BlobHash, config::Config}; +use types::{blob_hash::BlobHash, id::Id}; +use utils::config::Config; use webhooks::{MockWebhookEndpoint, spawn_mock_webhook_endpoint}; pub mod auth_acl; @@ -99,7 +100,7 @@ async fn jmap_tests() { thread_merge::test(&mut params).await; mailbox::test(&mut params).await; delivery::test(&mut params).await; - auth_acl::test(&mut params).await;*/ + auth_acl::test(&mut params).await; auth_limits::test(&mut params).await; auth_oauth::test(&mut params).await; event_source::test(&mut params).await; @@ -110,7 +111,7 @@ async fn jmap_tests() { websocket::test(&mut params).await; quota::test(&mut params).await; crypto::test(&mut params).await; - blob::test(&mut params).await; + blob::test(&mut params).await;*/ permissions::test(¶ms).await; purge::test(&mut params).await; enterprise::test(&mut params).await; diff --git a/tests/src/jmap/permissions.rs b/tests/src/jmap/permissions.rs index cf4e6cdc..860beced 100644 --- a/tests/src/jmap/permissions.rs +++ b/tests/src/jmap/permissions.rs @@ -4,21 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::sync::Arc; - +use super::{JMAPTest, ManagementApi, enterprise::List}; +use crate::jmap::assert_is_empty; use ahash::AHashSet; use common::auth::{AccessToken, TenantInfo}; - use directory::{ Permission, Type, backend::internal::{PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue}, }; use email::message::delivery::{IngestMessage, LocalDeliveryStatus, MailDelivery}; -use utils::BlobHash; - -use crate::jmap::assert_is_empty; - -use super::{JMAPTest, ManagementApi, enterprise::List}; +use std::sync::Arc; +use types::blob_hash::BlobHash; pub async fn test(params: &JMAPTest) { println!("Running permissions tests..."); diff --git a/tests/src/jmap/purge.rs b/tests/src/jmap/purge.rs index c5956975..d522a093 100644 --- a/tests/src/jmap/purge.rs +++ b/tests/src/jmap/purge.rs @@ -19,8 +19,8 @@ use email::{ message::delete::EmailDeletion, }; use imap_proto::ResponseType; -use jmap_proto::types::{collection::Collection, id::Id}; use store::{IterateParams, LogKey, U32_LEN, U64_LEN, write::key::DeserializeBigEndian}; +use types::{collection::Collection, id::Id}; pub async fn test(params: &mut JMAPTest) { println!("Running purge tests..."); diff --git a/tests/src/jmap/push_subscription.rs b/tests/src/jmap/push_subscription.rs index 3c771c92..fa04f060 100644 --- a/tests/src/jmap/push_subscription.rs +++ b/tests/src/jmap/push_subscription.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, - time::Duration, +use super::JMAPTest; +use crate::{ + AssertConfig, add_test_certs, + directory::internal::TestInternalDirectory, + jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, test_account_login}, }; - use base64::{Engine, engine::general_purpose}; use common::{Caches, Core, Data, Inner, config::server::Listeners, listener::SessionData}; use ece::EcKeyComponents; @@ -19,23 +17,19 @@ use http_proto::{HtmlResponse, ToHttpResponse, request::fetch_body}; use hyper::{StatusCode, body, header::CONTENT_ENCODING, server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; use jmap_client::{mailbox::Role, push_subscription::Keys}; -use jmap_proto::{ - response::status::StateChangeResponse, - types::{id::Id, type_state::DataType}, -}; +use jmap_proto::response::status::StateChangeResponse; use services::state_manager::ece::ece_encrypt; -use store::ahash::AHashSet; - -use tokio::sync::mpsc; -use utils::config::Config; - -use crate::{ - AssertConfig, add_test_certs, - directory::internal::TestInternalDirectory, - jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, test_account_login}, +use std::{ + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Duration, }; - -use super::JMAPTest; +use store::ahash::AHashSet; +use tokio::sync::mpsc; +use types::{id::Id, type_state::DataType}; +use utils::config::Config; const SERVER: &str = r#" [server] diff --git a/tests/src/jmap/quota.rs b/tests/src/jmap/quota.rs index 1094bcc6..b6c7b77b 100644 --- a/tests/src/jmap/quota.rs +++ b/tests/src/jmap/quota.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::JMAPTest; use crate::{ directory::internal::TestInternalDirectory, jmap::{ @@ -19,10 +20,8 @@ use jmap_client::{ core::set::{SetErrorType, SetObject}, email::EmailBodyPart, }; -use jmap_proto::types::{collection::Collection, id::Id}; use smtp::queue::spool::SmtpSpool; - -use super::JMAPTest; +use types::{collection::Collection, id::Id}; pub async fn test(params: &mut JMAPTest) { println!("Running quota tests..."); diff --git a/tests/src/jmap/sieve_script.rs b/tests/src/jmap/sieve_script.rs index 5793fbf2..dd39dd29 100644 --- a/tests/src/jmap/sieve_script.rs +++ b/tests/src/jmap/sieve_script.rs @@ -10,7 +10,7 @@ use jmap_client::{ email, mailbox, sieve::query::{Comparator, Filter}, }; -use jmap_proto::types::id::Id; +use types::id::Id; use std::{ fs, path::PathBuf, diff --git a/tests/src/jmap/thread_get.rs b/tests/src/jmap/thread_get.rs index e837e4b2..5d5eb30d 100644 --- a/tests/src/jmap/thread_get.rs +++ b/tests/src/jmap/thread_get.rs @@ -6,7 +6,7 @@ use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}; use jmap_client::mailbox::Role; -use jmap_proto::types::id::Id; +use types::id::Id; use super::JMAPTest; diff --git a/tests/src/jmap/thread_merge.rs b/tests/src/jmap/thread_merge.rs index 2b984bf1..7f04f8b4 100644 --- a/tests/src/jmap/thread_merge.rs +++ b/tests/src/jmap/thread_merge.rs @@ -4,24 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{io::Cursor, time::Duration}; - +use super::JMAPTest; use crate::{ jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}, store::deflate_test_resource, }; -use common::auth::AccessToken; - use ::email::message::ingest::{EmailIngest, IngestEmail, IngestSource}; +use common::auth::AccessToken; use jmap_client::{email, mailbox::Role}; -use jmap_proto::types::{collection::Collection, id::Id}; use mail_parser::{MessageParser, mailbox::mbox::MessageIterator}; +use std::{io::Cursor, time::Duration}; use store::{ ahash::{AHashMap, AHashSet}, rand::{self, Rng}, }; - -use super::JMAPTest; +use types::{collection::Collection, id::Id}; pub async fn test(params: &mut JMAPTest) { test_single_thread(params).await; diff --git a/tests/src/jmap/vacation_response.rs b/tests/src/jmap/vacation_response.rs index 94e06354..08dae085 100644 --- a/tests/src/jmap/vacation_response.rs +++ b/tests/src/jmap/vacation_response.rs @@ -6,8 +6,8 @@ use chrono::{TimeDelta, Utc}; -use jmap_proto::types::id::Id; use std::time::Instant; +use types::id::Id; use crate::{ directory::internal::TestInternalDirectory, diff --git a/tests/src/jmap/websocket.rs b/tests/src/jmap/websocket.rs index d34e3d7f..7a8f8574 100644 --- a/tests/src/jmap/websocket.rs +++ b/tests/src/jmap/websocket.rs @@ -14,8 +14,8 @@ use jmap_client::{ set::SetObject, }, }; -use jmap_proto::types::id::Id; use std::time::Duration; +use types::id::Id; use tokio::sync::mpsc; diff --git a/tests/src/smtp/queue/dsn.rs b/tests/src/smtp/queue/dsn.rs index 46fe1ba3..0055bb67 100644 --- a/tests/src/smtp/queue/dsn.rs +++ b/tests/src/smtp/queue/dsn.rs @@ -4,23 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::smtp::{QueueReceiver, TestSMTP, inbound::sign::SIGNATURES}; +use common::config::smtp::queue::{QueueExpiry, QueueName}; +use smtp::queue::{ + Error, ErrorDetails, HostResponse, Message, MessageWrapper, Recipient, Schedule, Status, + UnexpectedResponse, dsn::SendDsn, +}; +use smtp_proto::{RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_SUCCESS, Response}; use std::{ fs, net::{IpAddr, Ipv4Addr}, path::PathBuf, time::SystemTime, }; - -use common::config::smtp::queue::{QueueExpiry, QueueName}; -use smtp_proto::{RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_SUCCESS, Response}; use store::write::now; -use utils::BlobHash; - -use crate::smtp::{QueueReceiver, TestSMTP, inbound::sign::SIGNATURES}; -use smtp::queue::{ - Error, ErrorDetails, HostResponse, Message, MessageWrapper, Recipient, Schedule, Status, - UnexpectedResponse, dsn::SendDsn, -}; +use types::blob_hash::BlobHash; const CONFIG: &str = r#" [report] diff --git a/tests/src/store/blob.rs b/tests/src/store/blob.rs index 400d3f9e..3b232a10 100644 --- a/tests/src/store/blob.rs +++ b/tests/src/store/blob.rs @@ -6,10 +6,11 @@ use ahash::AHashMap; use store::{ - BlobClass, BlobStore, SerializeInfallible, Stores, + BlobStore, SerializeInfallible, Stores, write::{BatchBuilder, BlobOp, blob::BlobQuota, now}, }; -use utils::{BlobHash, config::Config}; +use types::{blob::BlobClass, blob_hash::BlobHash, collection::Collection}; +use utils::config::Config; use crate::store::{CONFIG, TempDir}; @@ -176,7 +177,7 @@ pub async fn blob_tests() { .write( BatchBuilder::new() .with_account_id(if document_id > 0 { 0 } else { 1 }) - .with_collection(0) + .with_collection(Collection::Email) .update_document(document_id as u32) .set(blob_op, blob_value) .set(BlobOp::Commit { hash: hash.clone() }, vec![]) @@ -289,7 +290,7 @@ pub async fn blob_tests() { .write( BatchBuilder::new() .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(2) .clear(BlobOp::Link { hash: BlobHash::generate(b"789".as_slice()), diff --git a/tests/src/store/import_export.rs b/tests/src/store/import_export.rs index 6be030bc..9e75a3a6 100644 --- a/tests/src/store/import_export.rs +++ b/tests/src/store/import_export.rs @@ -7,10 +7,6 @@ use crate::store::TempDir; use ahash::AHashSet; use common::{Core, manager::backup::BackupParams}; -use jmap_proto::types::{ - collection::{Collection, SyncCollection}, - property::Property, -}; use store::{ rand, write::{ @@ -19,7 +15,11 @@ use store::{ }, *, }; -use utils::BlobHash; +use types::{ + blob_hash::BlobHash, + collection::{Collection, SyncCollection}, + field::{Field, MailboxField}, +}; pub async fn test(db: Store) { let mut core = Core::default(); @@ -55,20 +55,25 @@ pub async fn test(db: Store) { batch.with_account_id(account_id); // Create properties of different sizes - for collection in [0, 1, 2, 3] { + for collection in [ + Collection::Email, + Collection::Mailbox, + Collection::Thread, + Collection::Identity, + ] { batch.with_collection(collection); for document_id in [0, 10, 20, 30, 40] { batch.create_document(document_id); - if collection == u8::from(Collection::Mailbox) { + if collection == Collection::Mailbox { batch .set( - ValueClass::Property(Property::Value.into()), + ValueClass::Property(Field::ARCHIVE.into()), random_bytes(10), ) .add( - ValueClass::Property(Property::EmailIds.into()), + ValueClass::Property(MailboxField::UidCounter.into()), rand::random(), ); } diff --git a/tests/src/store/ops.rs b/tests/src/store/ops.rs index eb7c5f96..4695942c 100644 --- a/tests/src/store/ops.rs +++ b/tests/src/store/ops.rs @@ -4,15 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::collections::HashSet; - use ahash::AHashSet; -use jmap_proto::types::collection::SyncCollection; +use std::collections::HashSet; use store::{ Store, ValueKey, rand::{self, Rng}, write::{AlignedBytes, Archive, Archiver, BatchBuilder, DirectoryClass, ValueClass}, }; +use types::collection::{Collection, SyncCollection}; // FDB max value const MAX_VALUE_SIZE: usize = 100000; @@ -20,13 +19,15 @@ const MAX_VALUE_SIZE: usize = 100000; pub async fn test(db: Store) { #[cfg(feature = "foundationdb")] if matches!(db, Store::FoundationDb(_)) && std::env::var("SLOW_FDB_TRX").is_ok() { + use types::collection::Collection; + println!("Running slow FoundationDB transaction tests..."); // Create 900000 keys let mut batch = BatchBuilder::new(); batch .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0); for n in 0..900000 { batch.set( @@ -39,7 +40,7 @@ pub async fn test(db: Store) { batch = BatchBuilder::new(); batch .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0); } } @@ -81,7 +82,7 @@ pub async fn test(db: Store) { let mut batch = BatchBuilder::new(); batch .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0); for n in 0..900000 { batch.clear(ValueClass::Config(format!("key{n:10}").into_bytes())); @@ -91,7 +92,7 @@ pub async fn test(db: Store) { batch = BatchBuilder::new(); batch .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0); } } @@ -108,7 +109,7 @@ pub async fn test(db: Store) { let mut builder = BatchBuilder::new(); builder .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0) .merge(ValueClass::Property(3), |bytes| { if let Some(bytes) = bytes { @@ -152,7 +153,7 @@ pub async fn test(db: Store) { let mut builder = BatchBuilder::new(); builder .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0) .add_and_get(ValueClass::Directory(DirectoryClass::UsedQuota(0)), 1); db.write(builder.build_all()) @@ -206,7 +207,7 @@ pub async fn test(db: Store) { builder .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(document_id) .set_versioned(ValueClass::Property(5), archived_value, offset) .log_container_insert(SyncCollection::Email); @@ -271,7 +272,7 @@ pub async fn test(db: Store) { db.write( BatchBuilder::new() .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0) .set(ValueClass::Property(1), value.as_slice()) .set(ValueClass::Property(0), "check1".as_bytes()) @@ -300,7 +301,7 @@ pub async fn test(db: Store) { db.write( BatchBuilder::new() .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .update_document(0) .clear(ValueClass::Property(1)) .build_all(), @@ -343,7 +344,7 @@ pub async fn test(db: Store) { let mut batch = BatchBuilder::new(); batch .with_account_id(0) - .with_collection(0) + .with_collection(Collection::Email) .with_account_id(0) .update_document(0) .clear(ValueClass::Property(0)) diff --git a/tests/src/store/query.rs b/tests/src/store/query.rs index 4a79d3f3..77132f87 100644 --- a/tests/src/store/query.rs +++ b/tests/src/store/query.rs @@ -4,30 +4,27 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::store::deflate_test_resource; +use nlp::language::Language; use std::{ fmt::Display, io::Write, sync::{Arc, Mutex}, time::Instant, }; - -use jmap_proto::types::keyword::Keyword; -use nlp::language::Language; use store::{ FtsStore, SerializeInfallible, ahash::AHashMap, fts::{Field, FtsFilter, index::FtsDocument}, query::sort::Pagination, - write::{TagValue, ValueClass}, + write::{BitmapClass, Operation, TagValue, ValueClass}, }; - use store::{ Store, ValueKey, query::{Comparator, Filter}, write::BatchBuilder, }; - -use crate::store::deflate_test_resource; +use types::collection::Collection; pub const FIELDS: [&str; 20] = [ "id", @@ -52,7 +49,7 @@ pub const FIELDS: [&str; 20] = [ "url", ]; -const COLLECTION_ID: u8 = 0; +const COLLECTION_ID: Collection = Collection::Email; enum FieldType { Keyword, @@ -145,10 +142,16 @@ pub async fn test(db: Store, fts_store: FtsStore, do_insert: bool) { match FIELDS_OPTIONS[pos] { FieldType::Text => { if !field.is_empty() { - builder.index(field_id, field.to_lowercase()).set( - ValueClass::Property(field_id), - field.to_lowercase().into_bytes(), - ); + builder + .any_op(Operation::Index { + field: field_id, + key: field.to_lowercase().into_bytes(), + set: true, + }) + .set( + ValueClass::Property(field_id), + field.to_lowercase().into_bytes(), + ); } } FieldType::FullText => { @@ -159,14 +162,22 @@ pub async fn test(db: Store, fts_store: FtsStore, do_insert: bool) { Language::English, ); if field_id == 7 { - builder.index(field_id, field.to_lowercase()); + builder.any_op(Operation::Index { + field: field_id, + key: field.to_lowercase().into_bytes(), + set: true, + }); } } } FieldType::Integer => { let field = field.parse::().unwrap_or(0); builder - .index(field_id, field.serialize()) + .any_op(Operation::Index { + field: field_id, + key: field.serialize(), + set: true, + }) .set(ValueClass::Property(field_id), field.serialize()); } FieldType::Keyword => { @@ -176,11 +187,20 @@ pub async fn test(db: Store, fts_store: FtsStore, do_insert: bool) { ValueClass::Property(field_id), field.to_lowercase().into_bytes(), ) - .tag( - field_id, - TagValue::Text(field.to_lowercase().into_bytes()), - ) - .index(field_id, field.to_lowercase()); + .any_op(Operation::Bitmap { + class: BitmapClass::Tag { + field: field_id, + value: TagValue::Text( + field.to_lowercase().into_bytes(), + ), + }, + set: true, + }) + .any_op(Operation::Index { + field: field_id, + key: field.to_lowercase().into_bytes(), + set: true, + }); } } } @@ -327,7 +347,7 @@ pub async fn test_filter(db: Store, fts: FtsStore) { ( vec![ Filter::contains(fields_u8["artist"], "kunst, mauro"), - Filter::is_in_bitmap(fields_u8["artistRole"], Keyword::Other("artist".into())), + Filter::is_in_bitmap(fields_u8["artistRole"], TagValue::Text("artist".into())), Filter::Or, Filter::eq(fields_u8["year"], 1969u32.serialize()), Filter::eq(fields_u8["year"], 1971u32.serialize()), @@ -448,7 +468,7 @@ pub async fn test_filter(db: Store, fts: FtsStore) { results.push( db.get_value::(ValueKey { account_id: 0, - collection: COLLECTION_ID, + collection: COLLECTION_ID.into(), document_id: document_id as u32, class: ValueClass::Property(fields_u8["accession_number"]), }) @@ -538,7 +558,7 @@ pub async fn test_sort(db: Store) { results.push( db.get_value::(ValueKey { account_id: 0, - collection: COLLECTION_ID, + collection: COLLECTION_ID.into(), document_id: document_id as u32, class: ValueClass::Property(fields["accession_number"]), }) diff --git a/tests/src/webdav/cal_scheduling.rs b/tests/src/webdav/cal_scheduling.rs index 380e239c..a09bcf7b 100644 --- a/tests/src/webdav/cal_scheduling.rs +++ b/tests/src/webdav/cal_scheduling.rs @@ -26,11 +26,11 @@ use groupware::{ }, }; use hyper::StatusCode; -use jmap_proto::types::collection::SyncCollection; use mail_parser::{DateTime, MessageParser}; use services::task_manager::{Task, TaskAction, imip::build_itip_template}; use std::str::FromStr; use store::write::now; +use types::collection::SyncCollection; pub async fn test(test: &WebDavTest) { println!("Running calendar scheduling tests..."); diff --git a/tests/src/webdav/mod.rs b/tests/src/webdav/mod.rs index dc1fb250..e47aef71 100644 --- a/tests/src/webdav/mod.rs +++ b/tests/src/webdav/mod.rs @@ -31,7 +31,6 @@ use groupware::{DavResourceName, cache::GroupwareCache}; use http::HttpSessionManager; use hyper::{HeaderMap, Method, StatusCode, header::AUTHORIZATION}; use imap::core::ImapSessionManager; -use jmap_proto::types::{collection::Collection, property::Property}; use pop3::Pop3SessionManager; use quick_xml::Reader; use quick_xml::events::Event; @@ -44,6 +43,7 @@ use std::{ }; use store::rand::{Rng, distr::Alphanumeric, rng}; use tokio::sync::watch; +use types::{collection::Collection, field::EmailField}; use utils::config::Config; pub mod acl; @@ -1045,7 +1045,7 @@ impl WebDavTest { account_id, Collection::Email, document_id, - Property::BodyStructure, + EmailField::Metadata.into(), ) .await .unwrap() diff --git a/tests/src/webdav/put_get.rs b/tests/src/webdav/put_get.rs index f771d1f1..3e7115a9 100644 --- a/tests/src/webdav/put_get.rs +++ b/tests/src/webdav/put_get.rs @@ -4,6 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use types::collection::Collection; + use super::WebDavTest; use crate::webdav::*;