Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "types"
|
||||
version = "0.16.22"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
utils = { path = "../utils" }
|
||||
trc = { path = "../trc" }
|
||||
jmap-tools = { version = "0.1" }
|
||||
hashify = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"]}
|
||||
rkyv = { version = "0.8.18", features = ["little_endian"] }
|
||||
compact_str = { version = "0.10.0", features = ["rkyv", "serde"] }
|
||||
blake3 = "1.8.7"
|
||||
|
||||
|
||||
[features]
|
||||
test_mode = []
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* 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,
|
||||
Share = 8,
|
||||
Submit = 9,
|
||||
SchedulingReadFreeBusy = 10,
|
||||
SchedulingInvite = 11,
|
||||
SchedulingReply = 12,
|
||||
ModifyItemsOwn = 13,
|
||||
ModifyPrivateProperties = 14,
|
||||
ModifyRSVP = 15,
|
||||
None = 16,
|
||||
}
|
||||
|
||||
#[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<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::Share => "share",
|
||||
Acl::Submit => "submit",
|
||||
Acl::ModifyItemsOwn => "modifyItemsOwn",
|
||||
Acl::ModifyPrivateProperties => "modifyPrivateProperties",
|
||||
Acl::None => "",
|
||||
Acl::SchedulingReadFreeBusy => "schedulingReadFreeBusy",
|
||||
Acl::SchedulingInvite => "schedulingInvite",
|
||||
Acl::SchedulingReply => "schedulingReply",
|
||||
Acl::ModifyRSVP => "modifyRSVP",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<Acl> for u64 {
|
||||
fn from(value: Acl) -> Self {
|
||||
value as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> 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::Share,
|
||||
9 => Acl::Submit,
|
||||
10 => Acl::SchedulingReadFreeBusy,
|
||||
11 => Acl::SchedulingInvite,
|
||||
12 => Acl::SchedulingReply,
|
||||
13 => Acl::ModifyItemsOwn,
|
||||
14 => Acl::ModifyPrivateProperties,
|
||||
15 => Acl::ModifyRSVP,
|
||||
_ => Acl::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedAclGrant> for AclGrant {
|
||||
fn from(value: &ArchivedAclGrant) -> Self {
|
||||
Self {
|
||||
account_id: u32::from(value.account_id),
|
||||
grants: (&value.grants).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use jmap_tools::{Element, Property, Value};
|
||||
use std::{borrow::Borrow, str::FromStr, 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, PartialOrd, Ord)]
|
||||
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: u32::MAX,
|
||||
expires: u64::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<BlobClass> 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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_superuser(&self) -> bool {
|
||||
matches!(self, BlobClass::Reserved { account_id, expires } if *account_id == u32::MAX && *expires == u64::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct BlobId {
|
||||
pub hash: BlobHash,
|
||||
pub class: BlobClass,
|
||||
pub section: Option<BlobSection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct BlobSection {
|
||||
pub offset_start: usize,
|
||||
pub size: usize,
|
||||
pub encoding: u8,
|
||||
}
|
||||
|
||||
impl FromStr for BlobId {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
BlobId::from_base32(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
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<u8>,
|
||||
) -> 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
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_base32(value: impl AsRef<[u8]>) -> Option<Self> {
|
||||
BlobId::from_iter(&mut Base32Reader::new(value.as_ref()))
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_iter<T, U>(it: &mut T) -> Option<Self>
|
||||
where
|
||||
T: Iterator<Item = U> + Leb128Iterator<U>,
|
||||
U: Borrow<u8>,
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.hash.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for BlobId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for BlobId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
BlobId::from_str(<&str>::deserialize(deserializer)?)
|
||||
.map_err(|_| serde::de::Error::custom("invalid BlobId"))
|
||||
}
|
||||
}
|
||||
|
||||
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::<BlobId>() * 2);
|
||||
self.serialize_as(&mut writer);
|
||||
f.write_str(&writer.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, P: Property, E: Element + From<BlobId>> From<BlobId> for Value<'x, P, E> {
|
||||
fn from(id: BlobId) -> Self {
|
||||
Value::Element(E::from(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* 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,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
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<BlobHash, std::array::TryFromSliceError> {
|
||||
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
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0 == [0; BLOB_HASH_LEN]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedBlobHash> for BlobHash {
|
||||
fn from(value: &ArchivedBlobHash) -> Self {
|
||||
BlobHash(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<BlobHash> for BlobHash {
|
||||
fn as_ref(&self) -> &BlobHash {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlobHash> for Vec<u8> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::type_state::DataType;
|
||||
use compact_str::CompactString;
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
str::FromStr,
|
||||
};
|
||||
use utils::map::bitmap::BitmapItem;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Default,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Collection {
|
||||
Email = 0,
|
||||
Mailbox = 1,
|
||||
Thread = 2,
|
||||
Identity = 3,
|
||||
EmailSubmission = 4,
|
||||
SieveScript = 5,
|
||||
PushSubscription = 6,
|
||||
Principal = 7,
|
||||
Calendar = 8,
|
||||
CalendarEvent = 9,
|
||||
AddressBook = 10,
|
||||
ContactCard = 11,
|
||||
FileNode = 12,
|
||||
CalendarEventNotification = 13,
|
||||
#[default]
|
||||
None = 14,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
|
||||
#[repr(u8)]
|
||||
pub enum SyncCollection {
|
||||
Email = 0,
|
||||
Thread = 1,
|
||||
Calendar = 2,
|
||||
AddressBook = 3,
|
||||
FileNode = 4,
|
||||
Identity = 5,
|
||||
EmailSubmission = 6,
|
||||
SieveScript = 7,
|
||||
CalendarEventNotification = 8,
|
||||
ShareNotification = 9,
|
||||
#[default]
|
||||
None = 10,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum VanishedCollection {
|
||||
Email = 251,
|
||||
Calendar = 252,
|
||||
AddressBook = 253,
|
||||
FileNode = 254,
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub const MAX: usize = Collection::None as usize;
|
||||
|
||||
pub fn main_collection(&self) -> Collection {
|
||||
match self {
|
||||
Collection::Email => Collection::Mailbox,
|
||||
Collection::CalendarEvent => Collection::Calendar,
|
||||
Collection::ContactCard => Collection::AddressBook,
|
||||
_ => *self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent_collection(&self) -> Option<Collection> {
|
||||
match self {
|
||||
Collection::Email => Some(Collection::Mailbox),
|
||||
Collection::CalendarEvent => Some(Collection::Calendar),
|
||||
Collection::ContactCard => Some(Collection::AddressBook),
|
||||
Collection::FileNode => Some(Collection::FileNode),
|
||||
Collection::CalendarEventNotification => Some(Collection::CalendarEventNotification),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_collection(&self) -> Option<Collection> {
|
||||
match self {
|
||||
Collection::Mailbox => Some(Collection::Email),
|
||||
Collection::Calendar => Some(Collection::CalendarEvent),
|
||||
Collection::AddressBook => Some(Collection::ContactCard),
|
||||
Collection::FileNode => Some(Collection::FileNode),
|
||||
Collection::CalendarEventNotification => Some(Collection::CalendarEventNotification),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncCollection {
|
||||
pub fn collection(&self, is_container: bool) -> Collection {
|
||||
match self {
|
||||
SyncCollection::Email => {
|
||||
if is_container {
|
||||
Collection::Mailbox
|
||||
} else {
|
||||
Collection::Email
|
||||
}
|
||||
}
|
||||
SyncCollection::Thread => Collection::Thread,
|
||||
SyncCollection::Calendar => {
|
||||
if is_container {
|
||||
Collection::Calendar
|
||||
} else {
|
||||
Collection::CalendarEvent
|
||||
}
|
||||
}
|
||||
SyncCollection::AddressBook => {
|
||||
if is_container {
|
||||
Collection::AddressBook
|
||||
} else {
|
||||
Collection::ContactCard
|
||||
}
|
||||
}
|
||||
SyncCollection::FileNode => Collection::FileNode,
|
||||
SyncCollection::Identity => Collection::Identity,
|
||||
SyncCollection::EmailSubmission => Collection::EmailSubmission,
|
||||
SyncCollection::SieveScript => Collection::SieveScript,
|
||||
SyncCollection::CalendarEventNotification => Collection::CalendarEventNotification,
|
||||
SyncCollection::ShareNotification | SyncCollection::None => Collection::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vanished_collection(&self) -> Option<VanishedCollection> {
|
||||
match self {
|
||||
SyncCollection::Email => Some(VanishedCollection::Email),
|
||||
SyncCollection::Calendar => Some(VanishedCollection::Calendar),
|
||||
SyncCollection::AddressBook => Some(VanishedCollection::AddressBook),
|
||||
SyncCollection::FileNode => Some(VanishedCollection::FileNode),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Collection> for SyncCollection {
|
||||
fn from(v: Collection) -> Self {
|
||||
match v {
|
||||
Collection::Email => SyncCollection::Email,
|
||||
Collection::Mailbox => SyncCollection::Email,
|
||||
Collection::Thread => SyncCollection::Thread,
|
||||
Collection::Identity => SyncCollection::Identity,
|
||||
Collection::EmailSubmission => SyncCollection::EmailSubmission,
|
||||
Collection::SieveScript => SyncCollection::SieveScript,
|
||||
Collection::PushSubscription => SyncCollection::None,
|
||||
Collection::Principal => SyncCollection::None,
|
||||
Collection::Calendar => SyncCollection::Calendar,
|
||||
Collection::CalendarEvent => SyncCollection::Calendar,
|
||||
Collection::CalendarEventNotification => SyncCollection::CalendarEventNotification,
|
||||
Collection::AddressBook => SyncCollection::AddressBook,
|
||||
Collection::ContactCard => SyncCollection::AddressBook,
|
||||
Collection::FileNode => SyncCollection::FileNode,
|
||||
_ => SyncCollection::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Collection {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
0 => Collection::Email,
|
||||
1 => Collection::Mailbox,
|
||||
2 => Collection::Thread,
|
||||
3 => Collection::Identity,
|
||||
4 => Collection::EmailSubmission,
|
||||
5 => Collection::SieveScript,
|
||||
6 => Collection::PushSubscription,
|
||||
7 => Collection::Principal,
|
||||
8 => Collection::Calendar,
|
||||
9 => Collection::CalendarEvent,
|
||||
10 => Collection::AddressBook,
|
||||
11 => Collection::ContactCard,
|
||||
12 => Collection::FileNode,
|
||||
13 => Collection::CalendarEventNotification,
|
||||
_ => Collection::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for SyncCollection {
|
||||
fn from(v: u8) -> 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::CalendarEventNotification,
|
||||
9 => SyncCollection::ShareNotification,
|
||||
_ => SyncCollection::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> 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::CalendarEventNotification,
|
||||
9 => SyncCollection::ShareNotification,
|
||||
_ => SyncCollection::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Collection {
|
||||
fn from(v: u64) -> Self {
|
||||
match v {
|
||||
0 => Collection::Email,
|
||||
1 => Collection::Mailbox,
|
||||
2 => Collection::Thread,
|
||||
3 => Collection::Identity,
|
||||
4 => Collection::EmailSubmission,
|
||||
5 => Collection::SieveScript,
|
||||
6 => Collection::PushSubscription,
|
||||
7 => Collection::Principal,
|
||||
8 => Collection::Calendar,
|
||||
9 => Collection::CalendarEvent,
|
||||
10 => Collection::AddressBook,
|
||||
11 => Collection::ContactCard,
|
||||
12 => Collection::FileNode,
|
||||
13 => Collection::CalendarEventNotification,
|
||||
_ => Collection::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Collection> for u8 {
|
||||
fn from(v: Collection) -> Self {
|
||||
v as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyncCollection> for u8 {
|
||||
fn from(v: SyncCollection) -> Self {
|
||||
v as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyncCollection> for u64 {
|
||||
fn from(v: SyncCollection) -> Self {
|
||||
v as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VanishedCollection> for u8 {
|
||||
fn from(v: VanishedCollection) -> Self {
|
||||
v as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Collection> for u64 {
|
||||
fn from(collection: Collection) -> u64 {
|
||||
collection as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Collection> for DataType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Collection) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Collection::Email => Ok(DataType::Email),
|
||||
Collection::Mailbox => Ok(DataType::Mailbox),
|
||||
Collection::Thread => Ok(DataType::Thread),
|
||||
Collection::Identity => Ok(DataType::Identity),
|
||||
Collection::EmailSubmission => Ok(DataType::EmailSubmission),
|
||||
Collection::SieveScript => Ok(DataType::SieveScript),
|
||||
Collection::PushSubscription => Ok(DataType::PushSubscription),
|
||||
Collection::Principal => Ok(DataType::Principal),
|
||||
Collection::Calendar => Ok(DataType::Calendar),
|
||||
Collection::CalendarEvent => Ok(DataType::CalendarEvent),
|
||||
Collection::AddressBook => Ok(DataType::AddressBook),
|
||||
Collection::ContactCard => Ok(DataType::ContactCard),
|
||||
Collection::FileNode => Ok(DataType::FileNode),
|
||||
Collection::CalendarEventNotification => Ok(DataType::CalendarEventNotification),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DataType> for Collection {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: DataType) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
DataType::Email => Ok(Collection::Email),
|
||||
DataType::Mailbox => Ok(Collection::Mailbox),
|
||||
DataType::Thread => Ok(Collection::Thread),
|
||||
DataType::Identity => Ok(Collection::Identity),
|
||||
DataType::EmailSubmission => Ok(Collection::EmailSubmission),
|
||||
DataType::SieveScript => Ok(Collection::SieveScript),
|
||||
DataType::PushSubscription => Ok(Collection::PushSubscription),
|
||||
DataType::Principal => Ok(Collection::Principal),
|
||||
DataType::Calendar => Ok(Collection::Calendar),
|
||||
DataType::CalendarEvent => Ok(Collection::CalendarEvent),
|
||||
DataType::AddressBook => Ok(Collection::AddressBook),
|
||||
DataType::ContactCard => Ok(Collection::ContactCard),
|
||||
DataType::FileNode => Ok(Collection::FileNode),
|
||||
DataType::CalendarEventNotification => Ok(Collection::CalendarEventNotification),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Collection {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Collection::PushSubscription => "pushSubscription",
|
||||
Collection::Email => "email",
|
||||
Collection::Mailbox => "mailbox",
|
||||
Collection::Thread => "thread",
|
||||
Collection::Identity => "identity",
|
||||
Collection::EmailSubmission => "emailSubmission",
|
||||
Collection::SieveScript => "sieveScript",
|
||||
Collection::Principal => "principal",
|
||||
Collection::Calendar => "calendar",
|
||||
Collection::CalendarEvent => "calendarEvent",
|
||||
Collection::AddressBook => "addressBook",
|
||||
Collection::ContactCard => "contactCard",
|
||||
Collection::FileNode => "fileNode",
|
||||
Collection::CalendarEventNotification => "calendarEventNotification",
|
||||
Collection::None => "",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_config_case(&self) -> &'static str {
|
||||
match self {
|
||||
Collection::PushSubscription => "push-subscription",
|
||||
Collection::Email => "email",
|
||||
Collection::Mailbox => "mailbox",
|
||||
Collection::Thread => "thread",
|
||||
Collection::Identity => "identity",
|
||||
Collection::EmailSubmission => "email-submission",
|
||||
Collection::SieveScript => "sieve-script",
|
||||
Collection::Principal => "principal",
|
||||
Collection::Calendar => "calendar",
|
||||
Collection::CalendarEvent => "calendar-event",
|
||||
Collection::AddressBook => "address-book",
|
||||
Collection::ContactCard => "contact-card",
|
||||
Collection::FileNode => "file-node",
|
||||
Collection::CalendarEventNotification => "calendar-event-notification",
|
||||
Collection::None => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Collection {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
hashify::tiny_map!(s.as_bytes(),
|
||||
"pushSubscription" => Collection::PushSubscription,
|
||||
"email" => Collection::Email,
|
||||
"mailbox" => Collection::Mailbox,
|
||||
"thread" => Collection::Thread,
|
||||
"identity" => Collection::Identity,
|
||||
"emailSubmission" => Collection::EmailSubmission,
|
||||
"sieveScript" => Collection::SieveScript,
|
||||
"principal" => Collection::Principal,
|
||||
"calendar" => Collection::Calendar,
|
||||
"calendarEvent" => Collection::CalendarEvent,
|
||||
"addressBook" => Collection::AddressBook,
|
||||
"contactCard" => Collection::ContactCard,
|
||||
"fileNode" => Collection::FileNode,
|
||||
"calendarEventNotification" => Collection::CalendarEventNotification,
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Collection> for trc::Value {
|
||||
fn from(value: Collection) -> Self {
|
||||
trc::Value::String(CompactString::const_new(value.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl BitmapItem for Collection {
|
||||
fn max() -> u64 {
|
||||
Collection::None as u64
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
!matches!(self, Collection::None)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
SyncCollection::Email => "email",
|
||||
SyncCollection::Thread => "thread",
|
||||
SyncCollection::Calendar => "calendar",
|
||||
SyncCollection::AddressBook => "addressBook",
|
||||
SyncCollection::FileNode => "fileNode",
|
||||
SyncCollection::Identity => "identity",
|
||||
SyncCollection::EmailSubmission => "emailSubmission",
|
||||
SyncCollection::SieveScript => "sieveScript",
|
||||
SyncCollection::CalendarEventNotification => "calendarEventNotification",
|
||||
SyncCollection::ShareNotification => "shareNotification",
|
||||
SyncCollection::None => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "test_mode", serde(tag = "type", content = "data"))]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum DeadPropertyTag {
|
||||
ElementStart(DeadElementTag),
|
||||
ElementEnd,
|
||||
Text(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub struct DeadElementTag {
|
||||
pub name: String,
|
||||
pub attrs: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "test_mode", serde(transparent))]
|
||||
#[rkyv(derive(Debug))]
|
||||
#[repr(transparent)]
|
||||
pub struct DeadProperty(pub Vec<DeadPropertyTag>);
|
||||
|
||||
impl From<&ArchivedDeadProperty> for DeadProperty {
|
||||
fn from(value: &ArchivedDeadProperty) -> Self {
|
||||
DeadProperty(value.0.iter().map(|tag| tag.into()).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedDeadPropertyTag> for DeadPropertyTag {
|
||||
fn from(tag: &ArchivedDeadPropertyTag) -> Self {
|
||||
match tag {
|
||||
ArchivedDeadPropertyTag::ElementStart(tag) => DeadPropertyTag::ElementStart(tag.into()),
|
||||
ArchivedDeadPropertyTag::ElementEnd => DeadPropertyTag::ElementEnd,
|
||||
ArchivedDeadPropertyTag::Text(tag) => DeadPropertyTag::Text(tag.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedDeadElementTag> for DeadElementTag {
|
||||
fn from(tag: &ArchivedDeadElementTag) -> Self {
|
||||
DeadElementTag {
|
||||
name: tag.name.to_string(),
|
||||
attrs: tag.attrs.as_ref().map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedDeadProperty {
|
||||
pub fn find_tag(&self, needle: &str) -> Option<DeadProperty> {
|
||||
let mut depth: u32 = 0;
|
||||
let mut tags = Vec::new();
|
||||
let mut found_tag = false;
|
||||
|
||||
for tag in self.0.iter() {
|
||||
match tag {
|
||||
ArchivedDeadPropertyTag::ElementStart(start) => {
|
||||
if depth == 0 && start.name == needle {
|
||||
found_tag = true;
|
||||
} else if found_tag {
|
||||
tags.push(tag.into());
|
||||
}
|
||||
|
||||
depth += 1;
|
||||
}
|
||||
ArchivedDeadPropertyTag::ElementEnd => {
|
||||
if found_tag {
|
||||
if depth == 1 {
|
||||
break;
|
||||
} else {
|
||||
tags.push(tag.into());
|
||||
}
|
||||
}
|
||||
depth = depth.saturating_sub(1);
|
||||
}
|
||||
ArchivedDeadPropertyTag::Text(_) => {
|
||||
if found_tag {
|
||||
tags.push(tag.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found_tag {
|
||||
Some(DeadProperty(tags))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeadProperty {
|
||||
pub fn remove_element(&mut self, element: &DeadElementTag) {
|
||||
let mut depth = 0;
|
||||
let mut remove = false;
|
||||
self.0.retain(|item| match item {
|
||||
DeadPropertyTag::ElementStart(tag) => {
|
||||
if depth == 0 && !remove && tag.name == element.name {
|
||||
remove = true;
|
||||
}
|
||||
depth += 1;
|
||||
|
||||
!remove
|
||||
}
|
||||
DeadPropertyTag::ElementEnd => {
|
||||
depth -= 1;
|
||||
if remove && depth == 0 {
|
||||
remove = false;
|
||||
false
|
||||
} else {
|
||||
!remove
|
||||
}
|
||||
}
|
||||
_ => !remove,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_element(&mut self, element: DeadElementTag, values: Vec<DeadPropertyTag>) {
|
||||
self.0.push(DeadPropertyTag::ElementStart(element));
|
||||
self.0.extend(values);
|
||||
self.0.push(DeadPropertyTag::ElementEnd);
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
let mut size = 0;
|
||||
for item in &self.0 {
|
||||
match item {
|
||||
DeadPropertyTag::ElementStart(tag) => {
|
||||
size += tag.size();
|
||||
}
|
||||
DeadPropertyTag::ElementEnd => {
|
||||
size += 1;
|
||||
}
|
||||
DeadPropertyTag::Text(text) => {
|
||||
size += text.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedDeadProperty {
|
||||
pub fn size(&self) -> usize {
|
||||
let mut size = 0;
|
||||
for item in self.0.iter() {
|
||||
match item {
|
||||
ArchivedDeadPropertyTag::ElementStart(tag) => {
|
||||
size += tag.size();
|
||||
}
|
||||
ArchivedDeadPropertyTag::ElementEnd => {
|
||||
size += 1;
|
||||
}
|
||||
ArchivedDeadPropertyTag::Text(text) => {
|
||||
size += text.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl DeadElementTag {
|
||||
pub fn new(name: String, attrs: Option<String>) -> Self {
|
||||
DeadElementTag { name, attrs }
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.name.len() + self.attrs.as_ref().map_or(0, |attrs| attrs.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedDeadElementTag {
|
||||
pub fn size(&self) -> usize {
|
||||
self.name.len() + self.attrs.as_ref().map_or(0, |attrs| attrs.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DeadProperty {
|
||||
fn default() -> Self {
|
||||
DeadProperty(Vec::with_capacity(4))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
const ARCHIVE_FIELD: u8 = 50;
|
||||
|
||||
pub trait FieldType: Into<u8> + 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,
|
||||
CreatedToUpdated,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum CalendarEventField {
|
||||
Uid,
|
||||
Archive,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum CalendarNotificationField {
|
||||
CreatedToId,
|
||||
Archive,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum EmailField {
|
||||
Archive,
|
||||
Metadata,
|
||||
Threading,
|
||||
DeletedAt,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum MailboxField {
|
||||
UidCounter = 84,
|
||||
Archive = ARCHIVE_FIELD,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum SieveField {
|
||||
Name,
|
||||
Ids,
|
||||
Archive,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum EmailSubmissionField {
|
||||
Archive,
|
||||
Metadata,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum IdentityField {
|
||||
Archive,
|
||||
DocumentId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum PrincipalField {
|
||||
Archive = ARCHIVE_FIELD,
|
||||
ParticipantIdentities = 45,
|
||||
IdentityAddresses = 110,
|
||||
DefaultCalendarId = 47,
|
||||
DefaultAddressBookId = 48,
|
||||
ActiveScriptId = 49,
|
||||
PushSubscriptions = 44,
|
||||
}
|
||||
|
||||
impl From<ContactField> for u8 {
|
||||
fn from(value: ContactField) -> Self {
|
||||
match value {
|
||||
ContactField::Uid => 0,
|
||||
ContactField::Email => 1,
|
||||
ContactField::CreatedToUpdated => 2,
|
||||
ContactField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarEventField> for u8 {
|
||||
fn from(value: CalendarEventField) -> Self {
|
||||
match value {
|
||||
CalendarEventField::Uid => 0,
|
||||
CalendarEventField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarNotificationField> for u8 {
|
||||
fn from(value: CalendarNotificationField) -> Self {
|
||||
match value {
|
||||
CalendarNotificationField::CreatedToId => 0,
|
||||
CalendarNotificationField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmailField> for u8 {
|
||||
fn from(value: EmailField) -> Self {
|
||||
match value {
|
||||
EmailField::Metadata => 71,
|
||||
EmailField::Threading => 90,
|
||||
EmailField::DeletedAt => 91,
|
||||
EmailField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MailboxField> for u8 {
|
||||
fn from(value: MailboxField) -> Self {
|
||||
match value {
|
||||
MailboxField::UidCounter => 84,
|
||||
MailboxField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SieveField> for u8 {
|
||||
fn from(value: SieveField) -> Self {
|
||||
match value {
|
||||
SieveField::Name => 13,
|
||||
SieveField::Ids => 84,
|
||||
SieveField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmailSubmissionField> for u8 {
|
||||
fn from(value: EmailSubmissionField) -> Self {
|
||||
match value {
|
||||
EmailSubmissionField::Metadata => 49,
|
||||
EmailSubmissionField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PrincipalField> for u8 {
|
||||
fn from(value: PrincipalField) -> Self {
|
||||
match value {
|
||||
PrincipalField::ParticipantIdentities => 45,
|
||||
PrincipalField::IdentityAddresses => 110,
|
||||
PrincipalField::DefaultCalendarId => 47,
|
||||
PrincipalField::DefaultAddressBookId => 48,
|
||||
PrincipalField::ActiveScriptId => 49,
|
||||
PrincipalField::PushSubscriptions => 44,
|
||||
PrincipalField::Archive => ARCHIVE_FIELD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentityField> for u8 {
|
||||
fn from(value: IdentityField) -> Self {
|
||||
match value {
|
||||
IdentityField::Archive => ARCHIVE_FIELD,
|
||||
IdentityField::DocumentId => 51,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Field> for u8 {
|
||||
fn from(value: Field) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ContactField> for Field {
|
||||
fn from(value: ContactField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarEventField> for Field {
|
||||
fn from(value: CalendarEventField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarNotificationField> for Field {
|
||||
fn from(value: CalendarNotificationField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmailField> for Field {
|
||||
fn from(value: EmailField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MailboxField> for Field {
|
||||
fn from(value: MailboxField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PrincipalField> for Field {
|
||||
fn from(value: PrincipalField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SieveField> for Field {
|
||||
fn from(value: SieveField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmailSubmissionField> for Field {
|
||||
fn from(value: EmailSubmissionField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentityField> for Field {
|
||||
fn from(value: IdentityField) -> Self {
|
||||
Field(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Field {
|
||||
pub const ARCHIVE: Field = Field(ARCHIVE_FIELD);
|
||||
|
||||
pub fn new(value: u8) -> Self {
|
||||
Field(value)
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldType for Field {}
|
||||
impl FieldType for ContactField {}
|
||||
impl FieldType for CalendarEventField {}
|
||||
impl FieldType for CalendarNotificationField {}
|
||||
impl FieldType for EmailField {}
|
||||
impl FieldType for MailboxField {}
|
||||
impl FieldType for PrincipalField {}
|
||||
impl FieldType for SieveField {}
|
||||
impl FieldType for EmailSubmissionField {}
|
||||
impl FieldType for IdentityField {}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::DocumentId;
|
||||
use jmap_tools::{Element, Property, Value};
|
||||
use std::{ops::Deref, str::FromStr};
|
||||
use utils::codec::base32_custom::{BASE32_ALPHABET, BASE32_INVERSE};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive,
|
||||
rkyv::Serialize,
|
||||
rkyv::Deserialize,
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Copy,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[rkyv(derive(Debug), compare(PartialEq))]
|
||||
#[repr(transparent)]
|
||||
pub struct Id(u64);
|
||||
|
||||
impl Default for Id {
|
||||
fn default() -> Self {
|
||||
Id(u64::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Id {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut id = 0;
|
||||
|
||||
for &ch in s.as_bytes() {
|
||||
let i = BASE32_INVERSE[ch as usize];
|
||||
if i != u8::MAX {
|
||||
id = (id << 5) | i as u64;
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Id(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedId> for Id {
|
||||
fn from(value: &ArchivedId) -> Self {
|
||||
Id(value.0.to_native())
|
||||
}
|
||||
}
|
||||
|
||||
impl Id {
|
||||
pub fn new(id: u64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
pub fn singleton() -> Self {
|
||||
Self::new(20080258862541)
|
||||
}
|
||||
|
||||
// From https://github.com/archer884/crockford by J/A <[email protected]>
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_parts(prefix_id: DocumentId, doc_id: DocumentId) -> Id {
|
||||
Id(((prefix_id as u64) << 32) | doc_id as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn document_id(&self) -> DocumentId {
|
||||
self.0 as DocumentId
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn prefix_id(&self) -> DocumentId {
|
||||
(self.0 >> 32) as DocumentId
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_singleton(&self) -> bool {
|
||||
self.0 == 20080258862541
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.0 != u64::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Id {
|
||||
fn from(id: u64) -> Self {
|
||||
Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Id {
|
||||
fn from(id: u32) -> Self {
|
||||
Id(id as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> 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<u64> for Id {
|
||||
fn as_ref(&self) -> &u64 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for u32 {
|
||||
fn from(id: Id) -> Self {
|
||||
id.document_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for String {
|
||||
fn from(id: Id) -> Self {
|
||||
id.as_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Id {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Id {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Id::from_str(<&str>::deserialize(deserializer)?)
|
||||
.map_err(|_| 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())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, P: Property, E: Element + From<Id>> From<Id> for Value<'x, P, E> {
|
||||
fn from(id: Id) -> Self {
|
||||
Value::Element(E::from(id))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::id::Id;
|
||||
|
||||
#[test]
|
||||
fn parse_jmap_id() {
|
||||
for number in [
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
1000,
|
||||
Id::singleton().id(),
|
||||
u64::MAX / 2,
|
||||
u64::MAX - 1,
|
||||
u64::MAX,
|
||||
] {
|
||||
let id = Id::from(number);
|
||||
assert_eq!(Id::from_str(&id.to_string()).unwrap(), id);
|
||||
}
|
||||
|
||||
Id::from_str("p333333333333p333333333333").unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use jmap_tools::{Element, Property, Value};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
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 AUTOSENT: usize = 12;
|
||||
pub const CANUNSUBSCRIBE: usize = 13;
|
||||
pub const FOLLOWED: usize = 14;
|
||||
pub const HASATTACHMENT: usize = 15;
|
||||
pub const HASMEMO: usize = 16;
|
||||
pub const HASNOATTACHMENT: usize = 17;
|
||||
pub const IMPORTED: usize = 18;
|
||||
pub const ISTRUSTED: usize = 19;
|
||||
pub const MAILFLAGBIT0: usize = 20;
|
||||
pub const MAILFLAGBIT1: usize = 21;
|
||||
pub const MAILFLAGBIT2: usize = 22;
|
||||
pub const MASKEDEMAIL: usize = 23;
|
||||
pub const MEMO: usize = 24;
|
||||
pub const MUTED: usize = 25;
|
||||
pub const NEW: usize = 26;
|
||||
pub const NOTIFY: usize = 27;
|
||||
pub const UNSUBSCRIBED: usize = 28;
|
||||
pub const OTHER: usize = 29;
|
||||
|
||||
#[derive(
|
||||
rkyv::Serialize,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Archive,
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Default,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
serde::Serialize,
|
||||
)]
|
||||
#[serde(untagged)]
|
||||
#[rkyv(derive(PartialEq), compare(PartialEq))]
|
||||
pub enum Keyword {
|
||||
Other(Box<str>),
|
||||
#[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,
|
||||
#[serde(rename(serialize = "$autosent"))]
|
||||
Autosent,
|
||||
#[serde(rename(serialize = "$canunsubscribe"))]
|
||||
CanUnsubscribe,
|
||||
#[serde(rename(serialize = "$followed"))]
|
||||
Followed,
|
||||
#[serde(rename(serialize = "$hasattachment"))]
|
||||
HasAttachment,
|
||||
#[serde(rename(serialize = "$hasmemo"))]
|
||||
HasMemo,
|
||||
#[serde(rename(serialize = "$hasnoattachment"))]
|
||||
HasNoAttachment,
|
||||
#[serde(rename(serialize = "$imported"))]
|
||||
Imported,
|
||||
#[serde(rename(serialize = "$istrusted"))]
|
||||
IsTrusted,
|
||||
#[serde(rename(serialize = "$MailFlagBit0"))]
|
||||
MailFlagBit0,
|
||||
#[serde(rename(serialize = "$MailFlagBit1"))]
|
||||
MailFlagBit1,
|
||||
#[serde(rename(serialize = "$MailFlagBit2"))]
|
||||
MailFlagBit2,
|
||||
#[serde(rename(serialize = "$maskedemail"))]
|
||||
MaskedEmail,
|
||||
#[serde(rename(serialize = "$memo"))]
|
||||
Memo,
|
||||
#[serde(rename(serialize = "$muted"))]
|
||||
Muted,
|
||||
#[serde(rename(serialize = "$new"))]
|
||||
New,
|
||||
#[serde(rename(serialize = "$notify"))]
|
||||
Notify,
|
||||
#[serde(rename(serialize = "$unsubscribed"))]
|
||||
Unsubscribed,
|
||||
}
|
||||
|
||||
impl Keyword {
|
||||
pub const MAX_LENGTH: usize = 128;
|
||||
|
||||
pub fn parse(value: &str) -> Self {
|
||||
Self::try_parse(value)
|
||||
.unwrap_or_else(|| Keyword::Other(value.chars().take(Keyword::MAX_LENGTH).collect()))
|
||||
}
|
||||
|
||||
pub fn from_other(value: String) -> Self {
|
||||
if value.len() <= Keyword::MAX_LENGTH {
|
||||
Keyword::Other(value.into_boxed_str())
|
||||
} else {
|
||||
Keyword::Other(value.chars().take(Keyword::MAX_LENGTH).collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_boxed_other(value: Box<str>) -> Self {
|
||||
if value.len() <= Keyword::MAX_LENGTH {
|
||||
Keyword::Other(value)
|
||||
} else {
|
||||
Keyword::Other(value.chars().take(Keyword::MAX_LENGTH).collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_parse(value: &str) -> Option<Self> {
|
||||
value
|
||||
.split_at_checked(1)
|
||||
.filter(|(prefix, _)| matches!(*prefix, "$" | "\\"))
|
||||
.and_then(|(_, rest)| {
|
||||
hashify::tiny_map_ignore_case!(rest.as_bytes(),
|
||||
"seen" => Keyword::Seen,
|
||||
"draft" => Keyword::Draft,
|
||||
"flagged" => Keyword::Flagged,
|
||||
"answered" => Keyword::Answered,
|
||||
"recent" => Keyword::Recent,
|
||||
"important" => Keyword::Important,
|
||||
"phishing" => Keyword::Phishing,
|
||||
"junk" => Keyword::Junk,
|
||||
"notjunk" => Keyword::NotJunk,
|
||||
"deleted" => Keyword::Deleted,
|
||||
"forwarded" => Keyword::Forwarded,
|
||||
"mdnsent" => Keyword::MdnSent,
|
||||
"autosent" => Keyword::Autosent,
|
||||
"canunsubscribe" => Keyword::CanUnsubscribe,
|
||||
"followed" => Keyword::Followed,
|
||||
"hasattachment" => Keyword::HasAttachment,
|
||||
"hasmemo" => Keyword::HasMemo,
|
||||
"hasnoattachment" => Keyword::HasNoAttachment,
|
||||
"imported" => Keyword::Imported,
|
||||
"istrusted" => Keyword::IsTrusted,
|
||||
"mailflagbit0" => Keyword::MailFlagBit0,
|
||||
"mailflagbit1" => Keyword::MailFlagBit1,
|
||||
"mailflagbit2" => Keyword::MailFlagBit2,
|
||||
"maskedemail" => Keyword::MaskedEmail,
|
||||
"memo" => Keyword::Memo,
|
||||
"muted" => Keyword::Muted,
|
||||
"new" => Keyword::New,
|
||||
"notify" => Keyword::Notify,
|
||||
"unsubscribed" => Keyword::Unsubscribed,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Result<u32, &str> {
|
||||
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::Autosent => Ok(AUTOSENT as u32),
|
||||
Keyword::CanUnsubscribe => Ok(CANUNSUBSCRIBE as u32),
|
||||
Keyword::Followed => Ok(FOLLOWED as u32),
|
||||
Keyword::HasAttachment => Ok(HASATTACHMENT as u32),
|
||||
Keyword::HasMemo => Ok(HASMEMO as u32),
|
||||
Keyword::HasNoAttachment => Ok(HASNOATTACHMENT as u32),
|
||||
Keyword::Imported => Ok(IMPORTED as u32),
|
||||
Keyword::IsTrusted => Ok(ISTRUSTED as u32),
|
||||
Keyword::MailFlagBit0 => Ok(MAILFLAGBIT0 as u32),
|
||||
Keyword::MailFlagBit1 => Ok(MAILFLAGBIT1 as u32),
|
||||
Keyword::MailFlagBit2 => Ok(MAILFLAGBIT2 as u32),
|
||||
Keyword::MaskedEmail => Ok(MASKEDEMAIL as u32),
|
||||
Keyword::Memo => Ok(MEMO as u32),
|
||||
Keyword::Muted => Ok(MUTED as u32),
|
||||
Keyword::New => Ok(NEW as u32),
|
||||
Keyword::Notify => Ok(NOTIFY as u32),
|
||||
Keyword::Unsubscribed => Ok(UNSUBSCRIBED as u32),
|
||||
Keyword::Other(string) => Err(string.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_id(self) -> Result<u32, Box<str>> {
|
||||
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::Autosent => Ok(AUTOSENT as u32),
|
||||
Keyword::CanUnsubscribe => Ok(CANUNSUBSCRIBE as u32),
|
||||
Keyword::Followed => Ok(FOLLOWED as u32),
|
||||
Keyword::HasAttachment => Ok(HASATTACHMENT as u32),
|
||||
Keyword::HasMemo => Ok(HASMEMO as u32),
|
||||
Keyword::HasNoAttachment => Ok(HASNOATTACHMENT as u32),
|
||||
Keyword::Imported => Ok(IMPORTED as u32),
|
||||
Keyword::IsTrusted => Ok(ISTRUSTED as u32),
|
||||
Keyword::MailFlagBit0 => Ok(MAILFLAGBIT0 as u32),
|
||||
Keyword::MailFlagBit1 => Ok(MAILFLAGBIT1 as u32),
|
||||
Keyword::MailFlagBit2 => Ok(MAILFLAGBIT2 as u32),
|
||||
Keyword::MaskedEmail => Ok(MASKEDEMAIL as u32),
|
||||
Keyword::Memo => Ok(MEMO as u32),
|
||||
Keyword::Muted => Ok(MUTED as u32),
|
||||
Keyword::New => Ok(NEW as u32),
|
||||
Keyword::Notify => Ok(NOTIFY as u32),
|
||||
Keyword::Unsubscribed => Ok(UNSUBSCRIBED as u32),
|
||||
Keyword::Other(string) => Err(string),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_id(id: usize) -> Result<Self, usize> {
|
||||
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),
|
||||
AUTOSENT => Ok(Keyword::Autosent),
|
||||
CANUNSUBSCRIBE => Ok(Keyword::CanUnsubscribe),
|
||||
FOLLOWED => Ok(Keyword::Followed),
|
||||
HASATTACHMENT => Ok(Keyword::HasAttachment),
|
||||
HASMEMO => Ok(Keyword::HasMemo),
|
||||
HASNOATTACHMENT => Ok(Keyword::HasNoAttachment),
|
||||
IMPORTED => Ok(Keyword::Imported),
|
||||
ISTRUSTED => Ok(Keyword::IsTrusted),
|
||||
MAILFLAGBIT0 => Ok(Keyword::MailFlagBit0),
|
||||
MAILFLAGBIT1 => Ok(Keyword::MailFlagBit1),
|
||||
MAILFLAGBIT2 => Ok(Keyword::MailFlagBit2),
|
||||
MASKEDEMAIL => Ok(Keyword::MaskedEmail),
|
||||
MEMO => Ok(Keyword::Memo),
|
||||
MUTED => Ok(Keyword::Muted),
|
||||
NEW => Ok(Keyword::New),
|
||||
NOTIFY => Ok(Keyword::Notify),
|
||||
UNSUBSCRIBED => Ok(Keyword::Unsubscribed),
|
||||
_ => Err(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Keyword {
|
||||
fn from(value: String) -> Self {
|
||||
Keyword::try_parse(&value).unwrap_or_else(|| Keyword::from_other(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::Autosent => write!(f, "$autosent"),
|
||||
Keyword::CanUnsubscribe => write!(f, "$canunsubscribe"),
|
||||
Keyword::Followed => write!(f, "$followed"),
|
||||
Keyword::HasAttachment => write!(f, "$hasattachment"),
|
||||
Keyword::HasMemo => write!(f, "$hasmemo"),
|
||||
Keyword::HasNoAttachment => write!(f, "$hasnoattachment"),
|
||||
Keyword::Imported => write!(f, "$imported"),
|
||||
Keyword::IsTrusted => write!(f, "$istrusted"),
|
||||
Keyword::MailFlagBit0 => write!(f, "$MailFlagBit0"),
|
||||
Keyword::MailFlagBit1 => write!(f, "$MailFlagBit1"),
|
||||
Keyword::MailFlagBit2 => write!(f, "$MailFlagBit2"),
|
||||
Keyword::MaskedEmail => write!(f, "$maskedemail"),
|
||||
Keyword::Memo => write!(f, "$memo"),
|
||||
Keyword::Muted => write!(f, "$muted"),
|
||||
Keyword::New => write!(f, "$new"),
|
||||
Keyword::Notify => write!(f, "$notify"),
|
||||
Keyword::Unsubscribed => write!(f, "$unsubscribed"),
|
||||
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::Autosent => write!(f, "$autosent"),
|
||||
ArchivedKeyword::CanUnsubscribe => write!(f, "$canunsubscribe"),
|
||||
ArchivedKeyword::Followed => write!(f, "$followed"),
|
||||
ArchivedKeyword::HasAttachment => write!(f, "$hasattachment"),
|
||||
ArchivedKeyword::HasMemo => write!(f, "$hasmemo"),
|
||||
ArchivedKeyword::HasNoAttachment => write!(f, "$hasnoattachment"),
|
||||
ArchivedKeyword::Imported => write!(f, "$imported"),
|
||||
ArchivedKeyword::IsTrusted => write!(f, "$istrusted"),
|
||||
ArchivedKeyword::MailFlagBit0 => write!(f, "$MailFlagBit0"),
|
||||
ArchivedKeyword::MailFlagBit1 => write!(f, "$MailFlagBit1"),
|
||||
ArchivedKeyword::MailFlagBit2 => write!(f, "$MailFlagBit2"),
|
||||
ArchivedKeyword::MaskedEmail => write!(f, "$maskedemail"),
|
||||
ArchivedKeyword::Memo => write!(f, "$memo"),
|
||||
ArchivedKeyword::Muted => write!(f, "$muted"),
|
||||
ArchivedKeyword::New => write!(f, "$new"),
|
||||
ArchivedKeyword::Notify => write!(f, "$notify"),
|
||||
ArchivedKeyword::Unsubscribed => write!(f, "$unsubscribed"),
|
||||
ArchivedKeyword::Other(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyword> for Vec<u8> {
|
||||
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::Autosent => vec![AUTOSENT as u8],
|
||||
Keyword::CanUnsubscribe => vec![CANUNSUBSCRIBE as u8],
|
||||
Keyword::Followed => vec![FOLLOWED as u8],
|
||||
Keyword::HasAttachment => vec![HASATTACHMENT as u8],
|
||||
Keyword::HasMemo => vec![HASMEMO as u8],
|
||||
Keyword::HasNoAttachment => vec![HASNOATTACHMENT as u8],
|
||||
Keyword::Imported => vec![IMPORTED as u8],
|
||||
Keyword::IsTrusted => vec![ISTRUSTED as u8],
|
||||
Keyword::MailFlagBit0 => vec![MAILFLAGBIT0 as u8],
|
||||
Keyword::MailFlagBit1 => vec![MAILFLAGBIT1 as u8],
|
||||
Keyword::MailFlagBit2 => vec![MAILFLAGBIT2 as u8],
|
||||
Keyword::MaskedEmail => vec![MASKEDEMAIL as u8],
|
||||
Keyword::Memo => vec![MEMO as u8],
|
||||
Keyword::Muted => vec![MUTED as u8],
|
||||
Keyword::New => vec![NEW as u8],
|
||||
Keyword::Notify => vec![NOTIFY as u8],
|
||||
Keyword::Unsubscribed => vec![UNSUBSCRIBED as u8],
|
||||
Keyword::Other(string) => string.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Keyword {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Keyword::parse(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedKeyword {
|
||||
pub fn id(&self) -> Result<u32, &str> {
|
||||
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::Autosent => Ok(AUTOSENT as u32),
|
||||
ArchivedKeyword::CanUnsubscribe => Ok(CANUNSUBSCRIBE as u32),
|
||||
ArchivedKeyword::Followed => Ok(FOLLOWED as u32),
|
||||
ArchivedKeyword::HasAttachment => Ok(HASATTACHMENT as u32),
|
||||
ArchivedKeyword::HasMemo => Ok(HASMEMO as u32),
|
||||
ArchivedKeyword::HasNoAttachment => Ok(HASNOATTACHMENT as u32),
|
||||
ArchivedKeyword::Imported => Ok(IMPORTED as u32),
|
||||
ArchivedKeyword::IsTrusted => Ok(ISTRUSTED as u32),
|
||||
ArchivedKeyword::MailFlagBit0 => Ok(MAILFLAGBIT0 as u32),
|
||||
ArchivedKeyword::MailFlagBit1 => Ok(MAILFLAGBIT1 as u32),
|
||||
ArchivedKeyword::MailFlagBit2 => Ok(MAILFLAGBIT2 as u32),
|
||||
ArchivedKeyword::MaskedEmail => Ok(MASKEDEMAIL as u32),
|
||||
ArchivedKeyword::Memo => Ok(MEMO as u32),
|
||||
ArchivedKeyword::Muted => Ok(MUTED as u32),
|
||||
ArchivedKeyword::New => Ok(NEW as u32),
|
||||
ArchivedKeyword::Notify => Ok(NOTIFY as u32),
|
||||
ArchivedKeyword::Unsubscribed => Ok(UNSUBSCRIBED as u32),
|
||||
ArchivedKeyword::Other(string) => Err(string.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_native(&self) -> Keyword {
|
||||
match self {
|
||||
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::Autosent => Keyword::Autosent,
|
||||
ArchivedKeyword::CanUnsubscribe => Keyword::CanUnsubscribe,
|
||||
ArchivedKeyword::Followed => Keyword::Followed,
|
||||
ArchivedKeyword::HasAttachment => Keyword::HasAttachment,
|
||||
ArchivedKeyword::HasMemo => Keyword::HasMemo,
|
||||
ArchivedKeyword::HasNoAttachment => Keyword::HasNoAttachment,
|
||||
ArchivedKeyword::Imported => Keyword::Imported,
|
||||
ArchivedKeyword::IsTrusted => Keyword::IsTrusted,
|
||||
ArchivedKeyword::MailFlagBit0 => Keyword::MailFlagBit0,
|
||||
ArchivedKeyword::MailFlagBit1 => Keyword::MailFlagBit1,
|
||||
ArchivedKeyword::MailFlagBit2 => Keyword::MailFlagBit2,
|
||||
ArchivedKeyword::MaskedEmail => Keyword::MaskedEmail,
|
||||
ArchivedKeyword::Memo => Keyword::Memo,
|
||||
ArchivedKeyword::Muted => Keyword::Muted,
|
||||
ArchivedKeyword::New => Keyword::New,
|
||||
ArchivedKeyword::Notify => Keyword::Notify,
|
||||
ArchivedKeyword::Unsubscribed => Keyword::Unsubscribed,
|
||||
ArchivedKeyword::Other(other) => Keyword::Other(other.as_ref().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::Autosent => Keyword::Autosent,
|
||||
ArchivedKeyword::CanUnsubscribe => Keyword::CanUnsubscribe,
|
||||
ArchivedKeyword::Followed => Keyword::Followed,
|
||||
ArchivedKeyword::HasAttachment => Keyword::HasAttachment,
|
||||
ArchivedKeyword::HasMemo => Keyword::HasMemo,
|
||||
ArchivedKeyword::HasNoAttachment => Keyword::HasNoAttachment,
|
||||
ArchivedKeyword::Imported => Keyword::Imported,
|
||||
ArchivedKeyword::IsTrusted => Keyword::IsTrusted,
|
||||
ArchivedKeyword::MailFlagBit0 => Keyword::MailFlagBit0,
|
||||
ArchivedKeyword::MailFlagBit1 => Keyword::MailFlagBit1,
|
||||
ArchivedKeyword::MailFlagBit2 => Keyword::MailFlagBit2,
|
||||
ArchivedKeyword::MaskedEmail => Keyword::MaskedEmail,
|
||||
ArchivedKeyword::Memo => Keyword::Memo,
|
||||
ArchivedKeyword::Muted => Keyword::Muted,
|
||||
ArchivedKeyword::New => Keyword::New,
|
||||
ArchivedKeyword::Notify => Keyword::Notify,
|
||||
ArchivedKeyword::Unsubscribed => Keyword::Unsubscribed,
|
||||
ArchivedKeyword::Other(string) => Keyword::Other(string.as_ref().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Keyword {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Ok(Keyword::parse(
|
||||
<std::borrow::Cow<'de, str>>::deserialize(deserializer)?.as_ref(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, P: Property, E: Element + From<Keyword>> From<Keyword> for Value<'x, P, E> {
|
||||
fn from(id: Keyword) -> Self {
|
||||
Value::Element(E::from(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* 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 dead_property;
|
||||
pub mod field;
|
||||
pub mod id;
|
||||
pub mod keyword;
|
||||
pub mod semver;
|
||||
pub mod special_use;
|
||||
pub mod type_state;
|
||||
|
||||
pub type DocumentId = u32;
|
||||
pub type ChangeId = u64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "test_mode", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TimeRange {
|
||||
pub start: i64,
|
||||
pub end: i64,
|
||||
}
|
||||
|
||||
impl TimeRange {
|
||||
pub fn new(start: i64, end: i64) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub fn is_in_range(&self, match_overlap: bool, start: i64, end: i64) -> bool {
|
||||
if !match_overlap {
|
||||
// RFC4791#9.9: (start < DTEND AND end > DTSTART)
|
||||
self.start < end && self.end > start
|
||||
} else {
|
||||
// RFC4791#9.9: ((start < DUE) OR (start <= DTSTART)) AND ((end > DTSTART) OR (end >= DUE))
|
||||
((start < self.end) || (start <= self.start)) && (end > self.start || end >= self.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TimeRange {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start: i64::MIN,
|
||||
end: i64::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* 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<u64> for Semver {
|
||||
fn as_ref(&self) -> &u64 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Semver {
|
||||
fn from(value: u64) -> Self {
|
||||
Semver(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Semver {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
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}")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use jmap_tools::{Element, Property, Value};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Debug,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum SpecialUse {
|
||||
Inbox,
|
||||
Trash,
|
||||
Junk,
|
||||
Drafts,
|
||||
Archive,
|
||||
Sent,
|
||||
Shared,
|
||||
Important,
|
||||
None,
|
||||
Memos,
|
||||
Scheduled,
|
||||
Snoozed,
|
||||
}
|
||||
|
||||
impl SpecialUse {
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
hashify::tiny_map_ignore_case!(s.as_bytes(),
|
||||
b"inbox" => SpecialUse::Inbox,
|
||||
b"trash" => SpecialUse::Trash,
|
||||
b"junk" => SpecialUse::Junk,
|
||||
b"drafts" => SpecialUse::Drafts,
|
||||
b"archive" => SpecialUse::Archive,
|
||||
b"sent" => SpecialUse::Sent,
|
||||
b"shared" => SpecialUse::Shared,
|
||||
b"important" => SpecialUse::Important,
|
||||
b"memos" => SpecialUse::Memos,
|
||||
b"scheduled" => SpecialUse::Scheduled,
|
||||
b"snoozed" => SpecialUse::Snoozed,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn parse_use_attr(s: &str) -> Option<Self> {
|
||||
Self::parse(s.strip_prefix('\\').unwrap_or(s))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
SpecialUse::Inbox => Some("inbox"),
|
||||
SpecialUse::Trash => Some("trash"),
|
||||
SpecialUse::Junk => Some("junk"),
|
||||
SpecialUse::Drafts => Some("drafts"),
|
||||
SpecialUse::Archive => Some("archive"),
|
||||
SpecialUse::Sent => Some("sent"),
|
||||
SpecialUse::Shared => Some("shared"),
|
||||
SpecialUse::Important => Some("important"),
|
||||
SpecialUse::Memos => Some("memos"),
|
||||
SpecialUse::Scheduled => Some("scheduled"),
|
||||
SpecialUse::Snoozed => Some("snoozed"),
|
||||
SpecialUse::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedSpecialUse {
|
||||
pub fn as_str(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
ArchivedSpecialUse::Inbox => Some("inbox"),
|
||||
ArchivedSpecialUse::Trash => Some("trash"),
|
||||
ArchivedSpecialUse::Junk => Some("junk"),
|
||||
ArchivedSpecialUse::Drafts => Some("drafts"),
|
||||
ArchivedSpecialUse::Archive => Some("archive"),
|
||||
ArchivedSpecialUse::Sent => Some("sent"),
|
||||
ArchivedSpecialUse::Shared => Some("shared"),
|
||||
ArchivedSpecialUse::Important => Some("important"),
|
||||
ArchivedSpecialUse::Memos => Some("memos"),
|
||||
ArchivedSpecialUse::Scheduled => Some("scheduled"),
|
||||
ArchivedSpecialUse::Snoozed => Some("snoozed"),
|
||||
ArchivedSpecialUse::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedSpecialUse> for SpecialUse {
|
||||
fn from(value: &ArchivedSpecialUse) -> Self {
|
||||
match value {
|
||||
ArchivedSpecialUse::Inbox => SpecialUse::Inbox,
|
||||
ArchivedSpecialUse::Trash => SpecialUse::Trash,
|
||||
ArchivedSpecialUse::Junk => SpecialUse::Junk,
|
||||
ArchivedSpecialUse::Drafts => SpecialUse::Drafts,
|
||||
ArchivedSpecialUse::Archive => SpecialUse::Archive,
|
||||
ArchivedSpecialUse::Sent => SpecialUse::Sent,
|
||||
ArchivedSpecialUse::Shared => SpecialUse::Shared,
|
||||
ArchivedSpecialUse::Important => SpecialUse::Important,
|
||||
ArchivedSpecialUse::Memos => SpecialUse::Memos,
|
||||
ArchivedSpecialUse::Scheduled => SpecialUse::Scheduled,
|
||||
ArchivedSpecialUse::Snoozed => SpecialUse::Snoozed,
|
||||
ArchivedSpecialUse::None => SpecialUse::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, P: Property, E: Element + From<SpecialUse>> From<SpecialUse> for Value<'x, P, E> {
|
||||
fn from(id: SpecialUse) -> Self {
|
||||
Value::Element(E::from(id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::collection::SyncCollection;
|
||||
use jmap_tools::{Element, Property, Value};
|
||||
use serde::Serialize;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use utils::map::bitmap::{Bitmap, BitmapItem};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, PartialOrd, Ord)]
|
||||
#[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,
|
||||
#[serde(rename = "Principal")]
|
||||
Principal = 19,
|
||||
#[serde(rename = "ShareNotification")]
|
||||
ShareNotification = 20,
|
||||
#[serde(rename = "ParticipantIdentity")]
|
||||
ParticipantIdentity = 21,
|
||||
#[serde(rename = "CalendarAlert")]
|
||||
CalendarAlert = 22,
|
||||
None = 23,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct StateChange {
|
||||
pub account_id: u32,
|
||||
pub change_id: u64,
|
||||
pub types: Bitmap<DataType>,
|
||||
}
|
||||
|
||||
impl StateChange {
|
||||
pub fn new(account_id: u32) -> Self {
|
||||
Self {
|
||||
account_id,
|
||||
change_id: 0,
|
||||
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 with_change_id(mut self, change_id: u64) -> Self {
|
||||
self.change_id = change_id;
|
||||
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<u64> 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,
|
||||
19 => DataType::Principal,
|
||||
20 => DataType::ShareNotification,
|
||||
21 => DataType::ParticipantIdentity,
|
||||
22 => DataType::CalendarAlert,
|
||||
_ => {
|
||||
debug_assert!(false, "Invalid type_state value: {}", value);
|
||||
DataType::None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DataType> for u64 {
|
||||
fn from(type_state: DataType) -> u64 {
|
||||
type_state as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl DataType {
|
||||
pub fn try_from_sync(value: SyncCollection, is_container: bool) -> Option<Self> {
|
||||
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 parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"Email" => DataType::Email,
|
||||
b"EmailDelivery" => DataType::EmailDelivery,
|
||||
b"EmailSubmission" => DataType::EmailSubmission,
|
||||
b"Mailbox" => DataType::Mailbox,
|
||||
b"Thread" => DataType::Thread,
|
||||
b"Identity" => DataType::Identity,
|
||||
b"Core" => DataType::Core,
|
||||
b"PushSubscription" => DataType::PushSubscription,
|
||||
b"SearchSnippet" => DataType::SearchSnippet,
|
||||
b"VacationResponse" => DataType::VacationResponse,
|
||||
b"MDN" => DataType::Mdn,
|
||||
b"Quota" => DataType::Quota,
|
||||
b"SieveScript" => DataType::SieveScript,
|
||||
b"Calendar" => DataType::Calendar,
|
||||
b"CalendarEvent" => DataType::CalendarEvent,
|
||||
b"CalendarEventNotification" => DataType::CalendarEventNotification,
|
||||
b"AddressBook" => DataType::AddressBook,
|
||||
b"ContactCard" => DataType::ContactCard,
|
||||
b"FileNode" => DataType::FileNode,
|
||||
b"Principal" => DataType::Principal,
|
||||
b"ShareNotification" => DataType::ShareNotification,
|
||||
b"ParticipantIdentity" => DataType::ParticipantIdentity,
|
||||
b"CalendarAlert" => DataType::CalendarAlert,
|
||||
)
|
||||
}
|
||||
|
||||
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::Principal => "Principal",
|
||||
DataType::ShareNotification => "ShareNotification",
|
||||
DataType::ParticipantIdentity => "ParticipantIdentity",
|
||||
DataType::CalendarAlert => "CalendarAlert",
|
||||
DataType::None => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DataType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
DataType::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DataType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for DataType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
DataType::parse(<&str>::deserialize(deserializer)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP data type"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x, P: Property, E: Element + From<DataType>> From<DataType> for Value<'x, P, E> {
|
||||
fn from(id: DataType) -> Self {
|
||||
Value::Element(E::from(id))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user