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,354 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
|
||||
},
|
||||
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{acl::Acl, id::Id, special_use::SpecialUse};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AddressBook;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBookProperty {
|
||||
Id,
|
||||
Name,
|
||||
Description,
|
||||
SortOrder,
|
||||
IsDefault,
|
||||
IsSubscribed,
|
||||
ShareWith,
|
||||
MyRights,
|
||||
|
||||
// Other
|
||||
IdValue(Id),
|
||||
Rights(AddressBookRight),
|
||||
Pointer(JsonPointer<AddressBookProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBookRight {
|
||||
MayRead,
|
||||
MayWrite,
|
||||
MayShare,
|
||||
MayDelete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBookValue {
|
||||
Id(Id),
|
||||
IdReference(String),
|
||||
Role(SpecialUse),
|
||||
}
|
||||
|
||||
impl Property for AddressBookProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
let allow_patch = key.is_none();
|
||||
if let Some(Key::Property(key)) = key {
|
||||
match key.patch_or_prop() {
|
||||
AddressBookProperty::ShareWith => {
|
||||
Id::from_str(value).ok().map(AddressBookProperty::IdValue)
|
||||
}
|
||||
_ => AddressBookProperty::parse(value, allow_patch),
|
||||
}
|
||||
} else {
|
||||
AddressBookProperty::parse(value, allow_patch)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
AddressBookProperty::Id => "id",
|
||||
AddressBookProperty::Name => "name",
|
||||
AddressBookProperty::Description => "description",
|
||||
AddressBookProperty::SortOrder => "sortOrder",
|
||||
AddressBookProperty::IsDefault => "isDefault",
|
||||
AddressBookProperty::IsSubscribed => "isSubscribed",
|
||||
AddressBookProperty::ShareWith => "shareWith",
|
||||
AddressBookProperty::MyRights => "myRights",
|
||||
AddressBookProperty::Rights(addressbook_right) => addressbook_right.as_str(),
|
||||
AddressBookProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
AddressBookProperty::IdValue(id) => return id.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressBookRight {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
AddressBookRight::MayRead => "mayRead",
|
||||
AddressBookRight::MayWrite => "mayWrite",
|
||||
AddressBookRight::MayShare => "mayShare",
|
||||
AddressBookRight::MayDelete => "mayDelete",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for AddressBookValue {
|
||||
type Property = AddressBookProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
AddressBookProperty::Id => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(AddressBookValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(AddressBookValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
AddressBookValue::Id(id) => id.to_string().into(),
|
||||
AddressBookValue::IdReference(r) => format!("#{r}").into(),
|
||||
AddressBookValue::Role(special_use) => special_use.as_str().unwrap_or_default().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressBookProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => AddressBookProperty::Id,
|
||||
b"name" => AddressBookProperty::Name,
|
||||
b"description" => AddressBookProperty::Description,
|
||||
b"sortOrder" => AddressBookProperty::SortOrder,
|
||||
b"isDefault" => AddressBookProperty::IsDefault,
|
||||
b"isSubscribed" => AddressBookProperty::IsSubscribed,
|
||||
b"shareWith" => AddressBookProperty::ShareWith,
|
||||
b"myRights" => AddressBookProperty::MyRights,
|
||||
b"mayRead" => AddressBookProperty::Rights(AddressBookRight::MayRead),
|
||||
b"mayWrite" => AddressBookProperty::Rights(AddressBookRight::MayWrite),
|
||||
b"mayShare" => AddressBookProperty::Rights(AddressBookRight::MayShare),
|
||||
b"mayDelete" => AddressBookProperty::Rights(AddressBookRight::MayDelete)
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
AddressBookProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &AddressBookProperty {
|
||||
if let AddressBookProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AddressBookSetArguments {
|
||||
pub on_destroy_remove_contents: Option<bool>,
|
||||
pub on_success_set_is_default: Option<MaybeIdReference<Id>>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for AddressBookSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onDestroyRemoveContents" => {
|
||||
self.on_destroy_remove_contents = map.next_value()?;
|
||||
},
|
||||
b"onSuccessSetIsDefault" => {
|
||||
self.on_success_set_is_default = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AddressBookProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
AddressBookProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for AddressBook {
|
||||
type Property = AddressBookProperty;
|
||||
|
||||
type Element = AddressBookValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = AddressBookSetArguments;
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = AddressBookProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapSharedObject for AddressBook {
|
||||
type Right = AddressBookRight;
|
||||
|
||||
const SHARE_WITH_PROPERTY: Self::Property = AddressBookProperty::ShareWith;
|
||||
}
|
||||
|
||||
impl From<Id> for AddressBookProperty {
|
||||
fn from(id: Id) -> Self {
|
||||
AddressBookProperty::IdValue(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AddressBookProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AddressBookProperty) -> Result<Self, Self::Error> {
|
||||
if let AddressBookProperty::IdValue(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AddressBookProperty> for AddressBookRight {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AddressBookProperty) -> Result<Self, Self::Error> {
|
||||
if let AddressBookProperty::Rights(right) = value {
|
||||
Ok(right)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for AddressBookValue {
|
||||
fn from(id: Id) -> Self {
|
||||
AddressBookValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for AddressBookValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let AddressBookValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let AddressBookValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let AddressBookValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(new_id) = new_id {
|
||||
*self = AddressBookValue::Id(new_id);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapRight for AddressBookRight {
|
||||
fn to_acl(&self) -> &'static [Acl] {
|
||||
match self {
|
||||
AddressBookRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
|
||||
AddressBookRight::MayShare => &[Acl::Share],
|
||||
AddressBookRight::MayRead => &[Acl::Read, Acl::ReadItems],
|
||||
AddressBookRight::MayWrite => &[Acl::Modify, Acl::AddItems, Acl::ModifyItems],
|
||||
}
|
||||
}
|
||||
|
||||
fn all_rights() -> &'static [Self] {
|
||||
&[
|
||||
AddressBookRight::MayRead,
|
||||
AddressBookRight::MayWrite,
|
||||
AddressBookRight::MayDelete,
|
||||
AddressBookRight::MayShare,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddressBookRight> for AddressBookProperty {
|
||||
fn from(right: AddressBookRight) -> Self {
|
||||
AddressBookProperty::Rights(right)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for AddressBookProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let AddressBookProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let AddressBookProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(new_id) = new_id {
|
||||
*self = AddressBookProperty::IdValue(new_id);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AddressBookProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_cow())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId, MaybeReference, parse_ref},
|
||||
request::deserialize::DeserializeArguments,
|
||||
};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Blob;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum BlobProperty {
|
||||
Id,
|
||||
BlobId,
|
||||
Type,
|
||||
Size,
|
||||
Digest(DigestProperty),
|
||||
Data(DataProperty),
|
||||
IsEncodingProblem,
|
||||
IsTruncated,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DigestProperty {
|
||||
Sha,
|
||||
Sha256,
|
||||
Sha512,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DataProperty {
|
||||
AsText,
|
||||
AsBase64,
|
||||
Default,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum BlobValue {
|
||||
BlobId(BlobId),
|
||||
IdReference(String),
|
||||
}
|
||||
|
||||
impl Property for BlobProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
BlobProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
BlobProperty::BlobId => "blobId",
|
||||
BlobProperty::Id => "id",
|
||||
BlobProperty::Size => "size",
|
||||
BlobProperty::Type => "type",
|
||||
BlobProperty::IsEncodingProblem => "isEncodingProblem",
|
||||
BlobProperty::IsTruncated => "isTruncated",
|
||||
BlobProperty::Data(data) => match data {
|
||||
DataProperty::AsText => "data:asText",
|
||||
DataProperty::AsBase64 => "data:asBase64",
|
||||
DataProperty::Default => "data",
|
||||
},
|
||||
BlobProperty::Digest(digest) => match digest {
|
||||
DigestProperty::Sha => "digest:sha",
|
||||
DigestProperty::Sha256 => "digest:sha-256",
|
||||
DigestProperty::Sha512 => "digest:sha-512",
|
||||
},
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for BlobValue {
|
||||
type Property = BlobProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
BlobProperty::BlobId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(BlobValue::BlobId(v)),
|
||||
MaybeReference::Reference(v) => Some(BlobValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
BlobValue::BlobId(blob_id) => blob_id.to_string().into(),
|
||||
BlobValue::IdReference(r) => format!("#{r}").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlobProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"blobId" => BlobProperty::BlobId,
|
||||
b"id" => BlobProperty::Id,
|
||||
b"size" => BlobProperty::Size,
|
||||
b"type" => BlobProperty::Type,
|
||||
b"isEncodingProblem" => BlobProperty::IsEncodingProblem,
|
||||
b"isTruncated" => BlobProperty::IsTruncated,
|
||||
b"data:asText" => BlobProperty::Data(DataProperty::AsText),
|
||||
b"data:asBase64" => BlobProperty::Data(DataProperty::AsBase64),
|
||||
b"data" => BlobProperty::Data(DataProperty::Default),
|
||||
b"digest:sha" => BlobProperty::Digest(DigestProperty::Sha),
|
||||
b"digest:sha-256" => BlobProperty::Digest(DigestProperty::Sha256),
|
||||
b"digest:sha-512" => BlobProperty::Digest(DigestProperty::Sha512),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BlobProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
BlobProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct BlobGetArguments {
|
||||
pub offset: Option<usize>,
|
||||
pub length: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for BlobGetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"offset" => {
|
||||
self.offset = map.next_value()?;
|
||||
},
|
||||
b"length" => {
|
||||
self.length = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Blob {
|
||||
type Property = BlobProperty;
|
||||
|
||||
type Element = BlobValue;
|
||||
|
||||
type Id = BlobId;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = BlobGetArguments;
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = BlobProperty::Id;
|
||||
}
|
||||
|
||||
impl From<BlobId> for BlobValue {
|
||||
fn from(id: BlobId) -> Self {
|
||||
BlobValue::BlobId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for BlobValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
BlobValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let BlobValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::BlobId(id) = new_id {
|
||||
*self = BlobValue::BlobId(id);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for BlobProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
|
||||
},
|
||||
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use calcard::{
|
||||
common::{IanaParse, timezone::Tz},
|
||||
icalendar::ICalendarDuration,
|
||||
jscalendar::{JSCalendarAlertAction, JSCalendarRelativeTo, JSCalendarType},
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::{acl::Acl, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Calendar;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum CalendarProperty {
|
||||
Id,
|
||||
Name,
|
||||
Description,
|
||||
Color,
|
||||
SortOrder,
|
||||
IsSubscribed,
|
||||
IsVisible,
|
||||
IsDefault,
|
||||
IncludeInAvailability,
|
||||
DefaultAlertsWithTime,
|
||||
DefaultAlertsWithoutTime,
|
||||
TimeZone,
|
||||
ShareWith,
|
||||
MyRights,
|
||||
|
||||
// Alert object properties
|
||||
When,
|
||||
Trigger,
|
||||
Offset,
|
||||
RelativeTo,
|
||||
Action,
|
||||
Type,
|
||||
|
||||
// Other
|
||||
IdValue(Id),
|
||||
Rights(CalendarRight),
|
||||
Pointer(JsonPointer<CalendarProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum CalendarRight {
|
||||
MayReadFreeBusy,
|
||||
MayReadItems,
|
||||
MayWriteAll,
|
||||
MayWriteOwn,
|
||||
MayUpdatePrivate,
|
||||
MayRSVP,
|
||||
MayShare,
|
||||
MayDelete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum CalendarValue {
|
||||
Id(Id),
|
||||
IdReference(String),
|
||||
IncludeInAvailability(IncludeInAvailability),
|
||||
Date(UTCDate),
|
||||
Timezone(Tz),
|
||||
Action(JSCalendarAlertAction),
|
||||
RelativeTo(JSCalendarRelativeTo),
|
||||
Type(JSCalendarType),
|
||||
Duration(ICalendarDuration),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum IncludeInAvailability {
|
||||
#[default]
|
||||
All,
|
||||
Attending,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Property for CalendarProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
let allow_patch = key.is_none();
|
||||
if let Some(Key::Property(key)) = key {
|
||||
match key.patch_or_prop() {
|
||||
CalendarProperty::ShareWith => {
|
||||
Id::from_str(value).ok().map(CalendarProperty::IdValue)
|
||||
}
|
||||
_ => CalendarProperty::parse(value, allow_patch),
|
||||
}
|
||||
} else {
|
||||
CalendarProperty::parse(value, allow_patch)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarProperty::Id => "id",
|
||||
CalendarProperty::Name => "name",
|
||||
CalendarProperty::Description => "description",
|
||||
CalendarProperty::Color => "color",
|
||||
CalendarProperty::SortOrder => "sortOrder",
|
||||
CalendarProperty::IsSubscribed => "isSubscribed",
|
||||
CalendarProperty::IsVisible => "isVisible",
|
||||
CalendarProperty::IsDefault => "isDefault",
|
||||
CalendarProperty::IncludeInAvailability => "includeInAvailability",
|
||||
CalendarProperty::DefaultAlertsWithTime => "defaultAlertsWithTime",
|
||||
CalendarProperty::DefaultAlertsWithoutTime => "defaultAlertsWithoutTime",
|
||||
CalendarProperty::TimeZone => "timeZone",
|
||||
CalendarProperty::ShareWith => "shareWith",
|
||||
CalendarProperty::MyRights => "myRights",
|
||||
CalendarProperty::When => "when",
|
||||
CalendarProperty::Trigger => "trigger",
|
||||
CalendarProperty::Offset => "offset",
|
||||
CalendarProperty::RelativeTo => "relativeTo",
|
||||
CalendarProperty::Action => "action",
|
||||
CalendarProperty::Type => "@type",
|
||||
CalendarProperty::Rights(calendar_right) => calendar_right.as_str(),
|
||||
CalendarProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
CalendarProperty::IdValue(id) => return id.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarRight {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
CalendarRight::MayReadFreeBusy => "mayReadFreeBusy",
|
||||
CalendarRight::MayReadItems => "mayReadItems",
|
||||
CalendarRight::MayWriteAll => "mayWriteAll",
|
||||
CalendarRight::MayWriteOwn => "mayWriteOwn",
|
||||
CalendarRight::MayUpdatePrivate => "mayUpdatePrivate",
|
||||
CalendarRight::MayRSVP => "mayRSVP",
|
||||
CalendarRight::MayShare => "mayShare",
|
||||
CalendarRight::MayDelete => "mayDelete",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeInAvailability {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"all" => IncludeInAvailability::All,
|
||||
b"attending" => IncludeInAvailability::Attending,
|
||||
b"none" => IncludeInAvailability::None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
IncludeInAvailability::All => "all",
|
||||
IncludeInAvailability::Attending => "attending",
|
||||
IncludeInAvailability::None => "none",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for CalendarValue {
|
||||
type Property = CalendarProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
CalendarProperty::Id => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(CalendarValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(CalendarValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
CalendarProperty::TimeZone => Tz::from_str(value).ok().map(CalendarValue::Timezone),
|
||||
CalendarProperty::IncludeInAvailability => {
|
||||
IncludeInAvailability::parse(value).map(CalendarValue::IncludeInAvailability)
|
||||
}
|
||||
CalendarProperty::Action => JSCalendarAlertAction::from_str(value)
|
||||
.ok()
|
||||
.map(CalendarValue::Action),
|
||||
CalendarProperty::RelativeTo => JSCalendarRelativeTo::from_str(value)
|
||||
.ok()
|
||||
.map(CalendarValue::RelativeTo),
|
||||
CalendarProperty::When => UTCDate::from_str(value).ok().map(CalendarValue::Date),
|
||||
CalendarProperty::Offset => {
|
||||
ICalendarDuration::parse(value.as_bytes()).map(CalendarValue::Duration)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarValue::Id(id) => id.to_string().into(),
|
||||
CalendarValue::IdReference(r) => format!("#{r}").into(),
|
||||
CalendarValue::IncludeInAvailability(include) => include.as_str().into(),
|
||||
CalendarValue::Date(date) => date.to_string().into(),
|
||||
CalendarValue::Action(action) => action.as_str().into(),
|
||||
CalendarValue::RelativeTo(relative) => relative.as_str().into(),
|
||||
CalendarValue::Type(typ) => typ.as_str().into(),
|
||||
CalendarValue::Duration(dur) => dur.to_string().into(),
|
||||
CalendarValue::Timezone(tz) => tz.name().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => CalendarProperty::Id,
|
||||
b"name" => CalendarProperty::Name,
|
||||
b"description" => CalendarProperty::Description,
|
||||
b"color" => CalendarProperty::Color,
|
||||
b"sortOrder" => CalendarProperty::SortOrder,
|
||||
b"isSubscribed" => CalendarProperty::IsSubscribed,
|
||||
b"isVisible" => CalendarProperty::IsVisible,
|
||||
b"isDefault" => CalendarProperty::IsDefault,
|
||||
b"includeInAvailability" => CalendarProperty::IncludeInAvailability,
|
||||
b"defaultAlertsWithTime" => CalendarProperty::DefaultAlertsWithTime,
|
||||
b"defaultAlertsWithoutTime" => CalendarProperty::DefaultAlertsWithoutTime,
|
||||
b"timeZone" => CalendarProperty::TimeZone,
|
||||
b"shareWith" => CalendarProperty::ShareWith,
|
||||
b"myRights" => CalendarProperty::MyRights,
|
||||
b"mayReadFreeBusy" => CalendarProperty::Rights(CalendarRight::MayReadFreeBusy),
|
||||
b"mayReadItems" => CalendarProperty::Rights(CalendarRight::MayReadItems),
|
||||
b"mayWriteAll" => CalendarProperty::Rights(CalendarRight::MayWriteAll),
|
||||
b"mayWriteOwn" => CalendarProperty::Rights(CalendarRight::MayWriteOwn),
|
||||
b"mayUpdatePrivate" => CalendarProperty::Rights(CalendarRight::MayUpdatePrivate),
|
||||
b"mayRSVP" => CalendarProperty::Rights(CalendarRight::MayRSVP),
|
||||
b"mayShare" => CalendarProperty::Rights(CalendarRight::MayShare),
|
||||
b"mayDelete" => CalendarProperty::Rights(CalendarRight::MayDelete),
|
||||
b"@type" => CalendarProperty::Type,
|
||||
b"when" => CalendarProperty::When,
|
||||
b"trigger" => CalendarProperty::Trigger,
|
||||
b"offset" => CalendarProperty::Offset,
|
||||
b"relativeTo" => CalendarProperty::RelativeTo,
|
||||
b"action" => CalendarProperty::Action,
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
CalendarProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &CalendarProperty {
|
||||
if let CalendarProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarSetArguments {
|
||||
pub on_destroy_remove_events: Option<bool>,
|
||||
pub on_success_set_is_default: Option<MaybeIdReference<Id>>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onDestroyRemoveEvents" => {
|
||||
self.on_destroy_remove_events = map.next_value()?;
|
||||
},
|
||||
b"onSuccessSetIsDefault" => {
|
||||
self.on_success_set_is_default = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CalendarProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
CalendarProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Calendar {
|
||||
type Property = CalendarProperty;
|
||||
|
||||
type Element = CalendarValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = CalendarSetArguments;
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = CalendarProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapSharedObject for Calendar {
|
||||
type Right = CalendarRight;
|
||||
|
||||
const SHARE_WITH_PROPERTY: Self::Property = CalendarProperty::ShareWith;
|
||||
}
|
||||
|
||||
impl From<Id> for CalendarProperty {
|
||||
fn from(id: Id) -> Self {
|
||||
CalendarProperty::IdValue(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CalendarProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: CalendarProperty) -> Result<Self, Self::Error> {
|
||||
if let CalendarProperty::IdValue(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CalendarProperty> for CalendarRight {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: CalendarProperty) -> Result<Self, Self::Error> {
|
||||
if let CalendarProperty::Rights(right) = value {
|
||||
Ok(right)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for CalendarValue {
|
||||
fn from(id: Id) -> Self {
|
||||
CalendarValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for CalendarValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let CalendarValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let CalendarValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let CalendarValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(new_id) = new_id {
|
||||
*self = CalendarValue::Id(new_id);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapRight for CalendarRight {
|
||||
fn to_acl(&self) -> &'static [Acl] {
|
||||
match self {
|
||||
CalendarRight::MayReadFreeBusy => &[Acl::SchedulingReadFreeBusy],
|
||||
CalendarRight::MayReadItems => &[Acl::Read, Acl::ReadItems],
|
||||
CalendarRight::MayWriteAll => &[
|
||||
Acl::Modify,
|
||||
Acl::AddItems,
|
||||
Acl::ModifyItems,
|
||||
Acl::RemoveItems,
|
||||
],
|
||||
CalendarRight::MayWriteOwn => &[Acl::ModifyItemsOwn],
|
||||
CalendarRight::MayUpdatePrivate => &[Acl::ModifyPrivateProperties],
|
||||
CalendarRight::MayRSVP => &[Acl::ModifyRSVP],
|
||||
CalendarRight::MayShare => &[Acl::Share],
|
||||
CalendarRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
|
||||
}
|
||||
}
|
||||
|
||||
fn all_rights() -> &'static [Self] {
|
||||
&[
|
||||
CalendarRight::MayReadFreeBusy,
|
||||
CalendarRight::MayReadItems,
|
||||
CalendarRight::MayWriteAll,
|
||||
CalendarRight::MayWriteOwn,
|
||||
CalendarRight::MayUpdatePrivate,
|
||||
CalendarRight::MayRSVP,
|
||||
CalendarRight::MayShare,
|
||||
CalendarRight::MayDelete,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarRight> for CalendarProperty {
|
||||
fn from(right: CalendarRight) -> Self {
|
||||
CalendarProperty::Rights(right)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for CalendarProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let CalendarProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let CalendarProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(new_id) = new_id {
|
||||
*self = CalendarProperty::IdValue(new_id);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CalendarProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_cow())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::{MaybeInvalid, deserialize::DeserializeArguments},
|
||||
};
|
||||
use calcard::{
|
||||
common::timezone::Tz,
|
||||
jscalendar::{JSCalendarDateTime, JSCalendarProperty, JSCalendarValue},
|
||||
};
|
||||
use jmap_tools::{JsonPointerItem, Key};
|
||||
use mail_parser::DateTime;
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarEvent;
|
||||
|
||||
impl JmapObject for CalendarEvent {
|
||||
type Property = JSCalendarProperty<Id>;
|
||||
|
||||
type Element = JSCalendarValue<Id, BlobId>;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = CalendarEventFilter;
|
||||
|
||||
type Comparator = CalendarEventComparator;
|
||||
|
||||
type GetArguments = CalendarEventGetArguments;
|
||||
|
||||
type SetArguments<'de> = CalendarEventSetArguments;
|
||||
|
||||
type QueryArguments = CalendarEventQueryArguments;
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = JSCalendarProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapObjectId for JSCalendarValue<Id, BlobId> {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let JSCalendarValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
JSCalendarValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
JSCalendarValue::BlobId(blob_id) => Some(AnyId::BlobId(blob_id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
match self {
|
||||
JSCalendarValue::IdReference(r) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = JSCalendarValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CalendarEventFilter {
|
||||
InCalendar(MaybeInvalid<Id>),
|
||||
After(JSCalendarDateTime),
|
||||
Before(JSCalendarDateTime),
|
||||
Text(String),
|
||||
Title(String),
|
||||
Description(String),
|
||||
Location(String),
|
||||
Owner(String),
|
||||
Attendee(String),
|
||||
Uid(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CalendarEventComparator {
|
||||
Start,
|
||||
Uid,
|
||||
RecurrenceId,
|
||||
Created,
|
||||
Updated,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarEventGetArguments {
|
||||
pub recurrence_overrides_before: Option<JSCalendarDateTime>,
|
||||
pub recurrence_overrides_after: Option<JSCalendarDateTime>,
|
||||
pub reduce_participants: Option<bool>,
|
||||
pub time_zone: Option<Tz>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarEventSetArguments {
|
||||
pub send_scheduling_messages: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarEventQueryArguments {
|
||||
pub expand_recurrences: Option<bool>,
|
||||
pub time_zone: Option<Tz>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"inCalendar" => {
|
||||
*self = CalendarEventFilter::InCalendar(map.next_value()?);
|
||||
},
|
||||
b"after" => {
|
||||
*self = CalendarEventFilter::After(map.next_value::<LocalTime>()?.0);
|
||||
},
|
||||
b"before" => {
|
||||
*self = CalendarEventFilter::Before(map.next_value::<LocalTime>()?.0);
|
||||
},
|
||||
b"text" => {
|
||||
*self = CalendarEventFilter::Text(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"title" => {
|
||||
*self = CalendarEventFilter::Title(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"description" => {
|
||||
*self = CalendarEventFilter::Description(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"location" => {
|
||||
*self = CalendarEventFilter::Location(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"owner" => {
|
||||
*self = CalendarEventFilter::Owner(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"attendee" => {
|
||||
*self = CalendarEventFilter::Attendee(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"uid" => {
|
||||
*self = CalendarEventFilter::Uid(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = CalendarEventFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"start" => {
|
||||
*self = CalendarEventComparator::Start;
|
||||
},
|
||||
b"uid" => {
|
||||
*self = CalendarEventComparator::Uid;
|
||||
},
|
||||
b"recurrenceId" => {
|
||||
*self = CalendarEventComparator::RecurrenceId;
|
||||
},
|
||||
b"created" => {
|
||||
*self = CalendarEventComparator::Created;
|
||||
},
|
||||
b"updated" => {
|
||||
*self = CalendarEventComparator::Updated;
|
||||
},
|
||||
_ => {
|
||||
*self = CalendarEventComparator::_T(value.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventGetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"recurrenceOverridesBefore" => {
|
||||
self.recurrence_overrides_before = map.next_value::<Option<LocalTime>>()?.map(|lt| lt.0)
|
||||
},
|
||||
b"recurrenceOverridesAfter" => {
|
||||
self.recurrence_overrides_after = map.next_value::<Option<LocalTime>>()?.map(|lt| lt.0);
|
||||
},
|
||||
b"reduceParticipants" => {
|
||||
self.reduce_participants = map.next_value()?;
|
||||
},
|
||||
b"timeZone" => {
|
||||
self.time_zone = map.next_value::<Option<&str>>()?.and_then(|s| Tz::from_str(s).ok());
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"sendSchedulingMessages" => {
|
||||
self.send_scheduling_messages = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventQueryArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"expandRecurrences" => {
|
||||
self.expand_recurrences = map.next_value()?;
|
||||
},
|
||||
b"timeZone" => {
|
||||
self.time_zone = map.next_value::<Option<&str>>()?.and_then(|s| Tz::from_str(s).ok());
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventFilter {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarEventFilter::InCalendar(_) => "inCalendar",
|
||||
CalendarEventFilter::After(_) => "after",
|
||||
CalendarEventFilter::Before(_) => "before",
|
||||
CalendarEventFilter::Text(_) => "text",
|
||||
CalendarEventFilter::Title(_) => "title",
|
||||
CalendarEventFilter::Description(_) => "description",
|
||||
CalendarEventFilter::Location(_) => "location",
|
||||
CalendarEventFilter::Owner(_) => "owner",
|
||||
CalendarEventFilter::Attendee(_) => "attendee",
|
||||
CalendarEventFilter::Uid(_) => "uid",
|
||||
CalendarEventFilter::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventComparator {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarEventComparator::Start => "start",
|
||||
CalendarEventComparator::Uid => "uid",
|
||||
CalendarEventComparator::RecurrenceId => "recurrenceId",
|
||||
CalendarEventComparator::Created => "created",
|
||||
CalendarEventComparator::Updated => "updated",
|
||||
CalendarEventComparator::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarEventFilter {
|
||||
fn default() -> Self {
|
||||
CalendarEventFilter::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarEventComparator {
|
||||
fn default() -> Self {
|
||||
CalendarEventComparator::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalTime(JSCalendarDateTime);
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for LocalTime {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = <&str>::deserialize(deserializer)?;
|
||||
|
||||
if let Some(dt) = DateTime::parse_rfc3339(value) {
|
||||
Ok(LocalTime(JSCalendarDateTime {
|
||||
timestamp: dt.to_timestamp_local(),
|
||||
is_local: true,
|
||||
}))
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"Invalid datetime: {}",
|
||||
value
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for JSCalendarProperty<Id> {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let JSCalendarProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let JSCalendarProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
match self {
|
||||
JSCalendarProperty::IdReference(r) => Some(r),
|
||||
JSCalendarProperty::Pointer(value) => {
|
||||
let value = value.as_slice();
|
||||
match (value.first(), value.get(1)) {
|
||||
(
|
||||
Some(JsonPointerItem::Key(Key::Property(JSCalendarProperty::CalendarIds))),
|
||||
Some(JsonPointerItem::Key(Key::Property(JSCalendarProperty::IdReference(
|
||||
r,
|
||||
)))),
|
||||
) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
if let JSCalendarProperty::Pointer(value) = self {
|
||||
let value = value.as_mut_slice();
|
||||
if let Some(value) = value.get_mut(1) {
|
||||
*value = JsonPointerItem::Key(Key::Property(JSCalendarProperty::IdValue(id)));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
*self = JSCalendarProperty::IdValue(id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::{MaybeInvalid, deserialize::DeserializeArguments},
|
||||
types::{date::UTCDate, state::State},
|
||||
};
|
||||
use calcard::jscalendar::JSCalendar;
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use serde::Serialize;
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarEventNotification;
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CalendarEventNotificationObject {
|
||||
pub id: Id,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub created: Option<UTCDate>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub changed_by: Option<PersonObject>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "type")]
|
||||
pub notification_type: Option<CalendarEventNotificationType>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub calendar_event_id: Option<Id>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_draft: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub event: Option<JSCalendar<'static, Id, BlobId>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub event_patch: Option<JSCalendar<'static, Id, BlobId>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonObject {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub email: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub principal_id: Option<Id>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub calendar_address: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct CalendarEventNotificationGetResponse {
|
||||
#[serde(rename = "accountId")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub account_id: Option<Id>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<State>,
|
||||
|
||||
pub list: Vec<CalendarEventNotificationObject>,
|
||||
|
||||
#[serde(rename = "notFound")]
|
||||
pub not_found: Vec<crate::request::MaybeInvalid<Id>>,
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationGetResponse {
|
||||
pub fn push_not_found(&mut self, id: Id) {
|
||||
self.not_found.push(crate::request::MaybeInvalid::Value(id));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum CalendarEventNotificationProperty {
|
||||
Id,
|
||||
Created,
|
||||
ChangedBy,
|
||||
Comment,
|
||||
Type,
|
||||
CalendarEventId,
|
||||
IsDraft,
|
||||
Event,
|
||||
EventPatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum CalendarEventNotificationValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
Type(CalendarEventNotificationType),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum CalendarEventNotificationType {
|
||||
Created,
|
||||
Updated,
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
impl Property for CalendarEventNotificationProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
CalendarEventNotificationProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarEventNotificationProperty::Id => "id",
|
||||
CalendarEventNotificationProperty::Created => "created",
|
||||
CalendarEventNotificationProperty::ChangedBy => "changedBy",
|
||||
CalendarEventNotificationProperty::Comment => "comment",
|
||||
CalendarEventNotificationProperty::Type => "type",
|
||||
CalendarEventNotificationProperty::CalendarEventId => "calendarEventId",
|
||||
CalendarEventNotificationProperty::IsDraft => "isDraft",
|
||||
CalendarEventNotificationProperty::Event => "event",
|
||||
CalendarEventNotificationProperty::EventPatch => "eventPatch",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for CalendarEventNotificationValue {
|
||||
type Property = CalendarEventNotificationProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
CalendarEventNotificationProperty::Id
|
||||
| CalendarEventNotificationProperty::CalendarEventId => Id::from_str(value)
|
||||
.ok()
|
||||
.map(CalendarEventNotificationValue::Id),
|
||||
CalendarEventNotificationProperty::Created => UTCDate::from_str(value)
|
||||
.ok()
|
||||
.map(CalendarEventNotificationValue::Date),
|
||||
CalendarEventNotificationProperty::Type => {
|
||||
CalendarEventNotificationType::parse(value)
|
||||
.map(CalendarEventNotificationValue::Type)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarEventNotificationValue::Id(id) => id.to_string().into(),
|
||||
CalendarEventNotificationValue::Date(date) => date.to_string().into(),
|
||||
CalendarEventNotificationValue::Type(t) => t.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationType {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"created" => CalendarEventNotificationType::Created,
|
||||
b"updated" => CalendarEventNotificationType::Updated,
|
||||
b"destroyed" => CalendarEventNotificationType::Destroyed,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
CalendarEventNotificationType::Created => "created",
|
||||
CalendarEventNotificationType::Updated => "updated",
|
||||
CalendarEventNotificationType::Destroyed => "destroyed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => CalendarEventNotificationProperty::Id,
|
||||
b"created" => CalendarEventNotificationProperty::Created,
|
||||
b"changedBy" => CalendarEventNotificationProperty::ChangedBy,
|
||||
b"comment" => CalendarEventNotificationProperty::Comment,
|
||||
b"type" => CalendarEventNotificationProperty::Type,
|
||||
b"calendarEventId" => CalendarEventNotificationProperty::CalendarEventId,
|
||||
b"isDraft" => CalendarEventNotificationProperty::IsDraft,
|
||||
b"event" => CalendarEventNotificationProperty::Event,
|
||||
b"eventPatch" => CalendarEventNotificationProperty::EventPatch
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CalendarEventNotificationProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
CalendarEventNotificationProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for CalendarEventNotification {
|
||||
type Property = CalendarEventNotificationProperty;
|
||||
|
||||
type Element = CalendarEventNotificationValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = CalendarEventNotificationFilter;
|
||||
|
||||
type Comparator = CalendarEventNotificationComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = CalendarEventNotificationProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CalendarEventNotificationFilter {
|
||||
After(UTCDate),
|
||||
Before(UTCDate),
|
||||
Type(CalendarEventNotificationType),
|
||||
CalendarEventIds(Vec<MaybeInvalid<Id>>),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CalendarEventNotificationComparator {
|
||||
Created,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventNotificationFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"after" => {
|
||||
*self = CalendarEventNotificationFilter::After(map.next_value()?);
|
||||
},
|
||||
b"before" => {
|
||||
*self = CalendarEventNotificationFilter::Before(map.next_value()?);
|
||||
},
|
||||
b"type" => {
|
||||
*self = CalendarEventNotificationFilter::Type(map.next_value()?);
|
||||
},
|
||||
b"calendarEventIds" => {
|
||||
*self = CalendarEventNotificationFilter::CalendarEventIds(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = CalendarEventNotificationFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for CalendarEventNotificationComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"created" => {
|
||||
*self = CalendarEventNotificationComparator::Created;
|
||||
},
|
||||
_ => {
|
||||
*self = CalendarEventNotificationComparator::_T(value.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for CalendarEventNotificationType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
CalendarEventNotificationType::parse(<&str>::deserialize(deserializer)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid CalendarEventNotificationType"))
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationFilter {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarEventNotificationFilter::After(_) => "after",
|
||||
CalendarEventNotificationFilter::Before(_) => "before",
|
||||
CalendarEventNotificationFilter::Type(_) => "type",
|
||||
CalendarEventNotificationFilter::CalendarEventIds(_) => "calendarEventIds",
|
||||
CalendarEventNotificationFilter::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationComparator {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
CalendarEventNotificationComparator::Created => "created",
|
||||
CalendarEventNotificationComparator::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarEventNotificationFilter {
|
||||
fn default() -> Self {
|
||||
CalendarEventNotificationFilter::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarEventNotificationComparator {
|
||||
fn default() -> Self {
|
||||
CalendarEventNotificationComparator::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CalendarEventNotificationProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(_: CalendarEventNotificationProperty) -> Result<Self, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for CalendarEventNotificationValue {
|
||||
fn from(id: Id) -> Self {
|
||||
CalendarEventNotificationValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for CalendarEventNotificationValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let CalendarEventNotificationValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let CalendarEventNotificationValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for CalendarEventNotificationProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for CalendarEventNotificationType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CalendarEventNotificationProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_cow())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::{MaybeInvalid, deserialize::DeserializeArguments},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use calcard::jscontact::{JSContactProperty, JSContactValue};
|
||||
use jmap_tools::{JsonPointerItem, Key};
|
||||
use std::borrow::Cow;
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ContactCard;
|
||||
|
||||
impl JmapObject for ContactCard {
|
||||
type Property = JSContactProperty<Id>;
|
||||
|
||||
type Element = JSContactValue<Id, BlobId>;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ContactCardFilter;
|
||||
|
||||
type Comparator = ContactCardComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = JSContactProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapObjectId for JSContactValue<Id, BlobId> {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let JSContactValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
JSContactValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
JSContactValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
match self {
|
||||
JSContactValue::IdReference(r) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
match new_id {
|
||||
AnyId::Id(id) => {
|
||||
*self = JSContactValue::Id(id);
|
||||
}
|
||||
AnyId::BlobId(id) => {
|
||||
*self = JSContactValue::BlobId(id);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ContactCardFilter {
|
||||
InAddressBook(MaybeInvalid<Id>),
|
||||
Uid(String),
|
||||
HasMember(String),
|
||||
Kind(String),
|
||||
CreatedBefore(UTCDate),
|
||||
CreatedAfter(UTCDate),
|
||||
UpdatedBefore(UTCDate),
|
||||
UpdatedAfter(UTCDate),
|
||||
Text(String),
|
||||
Name(String),
|
||||
NameGiven(String),
|
||||
NameSurname(String),
|
||||
NameSurname2(String),
|
||||
Nickname(String),
|
||||
Organization(String),
|
||||
Email(String),
|
||||
Phone(String),
|
||||
OnlineService(String),
|
||||
Address(String),
|
||||
Note(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ContactCardComparator {
|
||||
Created,
|
||||
Updated,
|
||||
NameGiven,
|
||||
NameSurname,
|
||||
NameSurname2,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ContactCardFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"inAddressBook" => {
|
||||
*self = ContactCardFilter::InAddressBook(map.next_value()?);
|
||||
},
|
||||
b"uid" => {
|
||||
*self = ContactCardFilter::Uid(map.next_value()?);
|
||||
},
|
||||
b"hasMember" => {
|
||||
*self = ContactCardFilter::HasMember(map.next_value()?);
|
||||
},
|
||||
b"kind" => {
|
||||
*self = ContactCardFilter::Kind(map.next_value()?);
|
||||
},
|
||||
b"createdBefore" => {
|
||||
*self = ContactCardFilter::CreatedBefore(map.next_value()?);
|
||||
},
|
||||
b"createdAfter" => {
|
||||
*self = ContactCardFilter::CreatedAfter(map.next_value()?);
|
||||
},
|
||||
b"updatedBefore" => {
|
||||
*self = ContactCardFilter::UpdatedBefore(map.next_value()?);
|
||||
},
|
||||
b"updatedAfter" => {
|
||||
*self = ContactCardFilter::UpdatedAfter(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = ContactCardFilter::Text(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"name" => {
|
||||
*self = ContactCardFilter::Name(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"name/given" => {
|
||||
*self = ContactCardFilter::NameGiven(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"name/surname" => {
|
||||
*self = ContactCardFilter::NameSurname(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"name/surname2" => {
|
||||
*self = ContactCardFilter::NameSurname2(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"nickname" => {
|
||||
*self = ContactCardFilter::Nickname(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"organization" => {
|
||||
*self = ContactCardFilter::Organization(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"email" => {
|
||||
*self = ContactCardFilter::Email(map.next_value()?);
|
||||
},
|
||||
b"phone" => {
|
||||
*self = ContactCardFilter::Phone(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"onlineService" => {
|
||||
*self = ContactCardFilter::OnlineService(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"address" => {
|
||||
*self = ContactCardFilter::Address(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
b"note" => {
|
||||
*self = ContactCardFilter::Note(map.next_value::<Cow<str>>()?.to_lowercase());
|
||||
},
|
||||
_ => {
|
||||
*self = ContactCardFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ContactCardComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"created" => {
|
||||
*self = ContactCardComparator::Created;
|
||||
},
|
||||
b"updated" => {
|
||||
*self = ContactCardComparator::Updated;
|
||||
},
|
||||
b"name/given" => {
|
||||
*self = ContactCardComparator::NameGiven;
|
||||
},
|
||||
b"name/surname" => {
|
||||
*self = ContactCardComparator::NameSurname;
|
||||
},
|
||||
b"name/surname2" => {
|
||||
*self = ContactCardComparator::NameSurname2;
|
||||
},
|
||||
_ => {
|
||||
*self = ContactCardComparator::_T(value.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ContactCardFilter {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ContactCardFilter::InAddressBook(_) => "inAddressBook",
|
||||
ContactCardFilter::Uid(_) => "uid",
|
||||
ContactCardFilter::HasMember(_) => "hasMember",
|
||||
ContactCardFilter::Kind(_) => "kind",
|
||||
ContactCardFilter::CreatedBefore(_) => "createdBefore",
|
||||
ContactCardFilter::CreatedAfter(_) => "createdAfter",
|
||||
ContactCardFilter::UpdatedBefore(_) => "updatedBefore",
|
||||
ContactCardFilter::UpdatedAfter(_) => "updatedAfter",
|
||||
ContactCardFilter::Text(_) => "text",
|
||||
ContactCardFilter::Name(_) => "name",
|
||||
ContactCardFilter::NameGiven(_) => "name/given",
|
||||
ContactCardFilter::NameSurname(_) => "name/surname",
|
||||
ContactCardFilter::NameSurname2(_) => "name/surname2",
|
||||
ContactCardFilter::Nickname(_) => "nickname",
|
||||
ContactCardFilter::Organization(_) => "organization",
|
||||
ContactCardFilter::Email(_) => "email",
|
||||
ContactCardFilter::Phone(_) => "phone",
|
||||
ContactCardFilter::OnlineService(_) => "onlineService",
|
||||
ContactCardFilter::Address(_) => "address",
|
||||
ContactCardFilter::Note(_) => "note",
|
||||
ContactCardFilter::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ContactCardComparator {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ContactCardComparator::Created => "created",
|
||||
ContactCardComparator::Updated => "updated",
|
||||
ContactCardComparator::NameGiven => "name/given",
|
||||
ContactCardComparator::NameSurname => "name/surname",
|
||||
ContactCardComparator::NameSurname2 => "name/surname2",
|
||||
ContactCardComparator::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContactCardFilter {
|
||||
fn default() -> Self {
|
||||
ContactCardFilter::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContactCardComparator {
|
||||
fn default() -> Self {
|
||||
ContactCardComparator::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for JSContactProperty<Id> {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let JSContactProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let JSContactProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
match self {
|
||||
JSContactProperty::IdReference(r) => Some(r),
|
||||
JSContactProperty::Pointer(value) => {
|
||||
let value = value.as_slice();
|
||||
match (value.first(), value.get(1)) {
|
||||
(
|
||||
Some(JsonPointerItem::Key(Key::Property(
|
||||
JSContactProperty::AddressBookIds,
|
||||
))),
|
||||
Some(JsonPointerItem::Key(Key::Property(JSContactProperty::IdReference(
|
||||
r,
|
||||
)))),
|
||||
) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
if let JSContactProperty::Pointer(value) = self {
|
||||
let value = value.as_mut_slice();
|
||||
if let Some(value) = value.get_mut(1) {
|
||||
*value = JsonPointerItem::Key(Key::Property(JSContactProperty::IdValue(id)));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
*self = JSContactProperty::IdValue(id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
method::query::{Comparator, Filter},
|
||||
object::{AnyId, JmapObject, JmapObjectId, MaybeReference, parse_ref},
|
||||
request::{MaybeInvalid, deserialize::DeserializeArguments},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use mail_parser::HeaderName;
|
||||
use serde::Serialize;
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id, keyword::Keyword};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Email;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EmailProperty {
|
||||
// Metadata
|
||||
Id,
|
||||
BlobId,
|
||||
ThreadId,
|
||||
MailboxIds,
|
||||
Keywords,
|
||||
Size,
|
||||
ReceivedAt,
|
||||
|
||||
// Address
|
||||
Name,
|
||||
Email,
|
||||
|
||||
// GroupedAddresses
|
||||
Addresses,
|
||||
|
||||
// Header Fields Properties
|
||||
Value,
|
||||
Header(HeaderProperty),
|
||||
|
||||
// Convenience properties
|
||||
MessageId,
|
||||
InReplyTo,
|
||||
References,
|
||||
Sender,
|
||||
From,
|
||||
To,
|
||||
Cc,
|
||||
Bcc,
|
||||
ReplyTo,
|
||||
Subject,
|
||||
SentAt,
|
||||
|
||||
// Body Parts
|
||||
TextBody,
|
||||
HtmlBody,
|
||||
Attachments,
|
||||
PartId,
|
||||
Headers,
|
||||
Type,
|
||||
Charset,
|
||||
Disposition,
|
||||
Cid,
|
||||
Language,
|
||||
Location,
|
||||
SubParts,
|
||||
BodyStructure,
|
||||
BodyValues,
|
||||
IsEncodingProblem,
|
||||
IsTruncated,
|
||||
HasAttachment,
|
||||
Preview,
|
||||
|
||||
// Other
|
||||
Keyword(Keyword),
|
||||
IdValue(Id),
|
||||
IdReference(String),
|
||||
Pointer(JsonPointer<EmailProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct HeaderProperty {
|
||||
pub form: HeaderForm,
|
||||
pub header: String,
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum HeaderForm {
|
||||
Raw,
|
||||
Text,
|
||||
Addresses,
|
||||
GroupedAddresses,
|
||||
MessageIds,
|
||||
Date,
|
||||
URLs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EmailValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
BlobId(BlobId),
|
||||
IdReference(String),
|
||||
}
|
||||
|
||||
impl Property for EmailProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
let allow_patch = key.is_none();
|
||||
if let Some(Key::Property(key)) = key {
|
||||
match key.patch_or_prop() {
|
||||
EmailProperty::Keywords => EmailProperty::Keyword(Keyword::parse(value)).into(),
|
||||
EmailProperty::MailboxIds => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(EmailProperty::IdValue(v)),
|
||||
MaybeReference::Reference(v) => Some(EmailProperty::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
_ => EmailProperty::parse(value, allow_patch),
|
||||
}
|
||||
} else {
|
||||
EmailProperty::parse(value, allow_patch)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
EmailProperty::Attachments => "attachments",
|
||||
EmailProperty::Bcc => "bcc",
|
||||
EmailProperty::BlobId => "blobId",
|
||||
EmailProperty::BodyStructure => "bodyStructure",
|
||||
EmailProperty::BodyValues => "bodyValues",
|
||||
EmailProperty::Cc => "cc",
|
||||
EmailProperty::Charset => "charset",
|
||||
EmailProperty::Cid => "cid",
|
||||
EmailProperty::Disposition => "disposition",
|
||||
EmailProperty::Email => "email",
|
||||
EmailProperty::From => "from",
|
||||
EmailProperty::HasAttachment => "hasAttachment",
|
||||
EmailProperty::Headers => "headers",
|
||||
EmailProperty::HtmlBody => "htmlBody",
|
||||
EmailProperty::Id => "id",
|
||||
EmailProperty::InReplyTo => "inReplyTo",
|
||||
EmailProperty::Keywords => "keywords",
|
||||
EmailProperty::Language => "language",
|
||||
EmailProperty::Location => "location",
|
||||
EmailProperty::MailboxIds => "mailboxIds",
|
||||
EmailProperty::MessageId => "messageId",
|
||||
EmailProperty::Name => "name",
|
||||
EmailProperty::PartId => "partId",
|
||||
EmailProperty::Preview => "preview",
|
||||
EmailProperty::ReceivedAt => "receivedAt",
|
||||
EmailProperty::References => "references",
|
||||
EmailProperty::ReplyTo => "replyTo",
|
||||
EmailProperty::Sender => "sender",
|
||||
EmailProperty::SentAt => "sentAt",
|
||||
EmailProperty::Size => "size",
|
||||
EmailProperty::Subject => "subject",
|
||||
EmailProperty::SubParts => "subParts",
|
||||
EmailProperty::TextBody => "textBody",
|
||||
EmailProperty::ThreadId => "threadId",
|
||||
EmailProperty::To => "to",
|
||||
EmailProperty::Type => "type",
|
||||
EmailProperty::Addresses => "addresses",
|
||||
EmailProperty::Value => "value",
|
||||
EmailProperty::IsEncodingProblem => "isEncodingProblem",
|
||||
EmailProperty::IsTruncated => "isTruncated",
|
||||
EmailProperty::Header(header) => return header.to_string().into(),
|
||||
EmailProperty::Keyword(keyword) => return keyword.to_string().into(),
|
||||
EmailProperty::IdValue(id) => return id.to_string().into(),
|
||||
EmailProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
EmailProperty::IdReference(r) => return format!("#{r}").into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for EmailValue {
|
||||
type Property = EmailProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
EmailProperty::Id | EmailProperty::ThreadId | EmailProperty::MailboxIds => {
|
||||
match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(EmailValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(EmailValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
}
|
||||
}
|
||||
EmailProperty::BlobId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(EmailValue::BlobId(v)),
|
||||
MaybeReference::Reference(v) => Some(EmailValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
EmailProperty::Header(HeaderProperty {
|
||||
form: HeaderForm::Date,
|
||||
..
|
||||
})
|
||||
| EmailProperty::ReceivedAt
|
||||
| EmailProperty::SentAt => UTCDate::from_str(value).ok().map(EmailValue::Date),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
EmailValue::Id(id) => id.to_string().into(),
|
||||
EmailValue::Date(utcdate) => utcdate.to_string().into(),
|
||||
EmailValue::BlobId(blob_id) => blob_id.to_string().into(),
|
||||
EmailValue::IdReference(r) => format!("#{r}").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
"id" => EmailProperty::Id,
|
||||
"blobId" => EmailProperty::BlobId,
|
||||
"threadId" => EmailProperty::ThreadId,
|
||||
"mailboxIds" => EmailProperty::MailboxIds,
|
||||
"keywords" => EmailProperty::Keywords,
|
||||
"size" => EmailProperty::Size,
|
||||
"receivedAt" => EmailProperty::ReceivedAt,
|
||||
"name" => EmailProperty::Name,
|
||||
"email" => EmailProperty::Email,
|
||||
"addresses" => EmailProperty::Addresses,
|
||||
"value" => EmailProperty::Value,
|
||||
"messageId" => EmailProperty::MessageId,
|
||||
"inReplyTo" => EmailProperty::InReplyTo,
|
||||
"references" => EmailProperty::References,
|
||||
"sender" => EmailProperty::Sender,
|
||||
"from" => EmailProperty::From,
|
||||
"to" => EmailProperty::To,
|
||||
"cc" => EmailProperty::Cc,
|
||||
"bcc" => EmailProperty::Bcc,
|
||||
"replyTo" => EmailProperty::ReplyTo,
|
||||
"subject" => EmailProperty::Subject,
|
||||
"sentAt" => EmailProperty::SentAt,
|
||||
"textBody" => EmailProperty::TextBody,
|
||||
"htmlBody" => EmailProperty::HtmlBody,
|
||||
"attachments" => EmailProperty::Attachments,
|
||||
"partId" => EmailProperty::PartId,
|
||||
"headers" => EmailProperty::Headers,
|
||||
"type" => EmailProperty::Type,
|
||||
"charset" => EmailProperty::Charset,
|
||||
"disposition" => EmailProperty::Disposition,
|
||||
"cid" => EmailProperty::Cid,
|
||||
"language" => EmailProperty::Language,
|
||||
"location" => EmailProperty::Location,
|
||||
"subParts" => EmailProperty::SubParts,
|
||||
"bodyStructure" => EmailProperty::BodyStructure,
|
||||
"bodyValues" => EmailProperty::BodyValues,
|
||||
"isEncodingProblem" => EmailProperty::IsEncodingProblem,
|
||||
"isTruncated" => EmailProperty::IsTruncated,
|
||||
"hasAttachment" => EmailProperty::HasAttachment,
|
||||
"preview" => EmailProperty::Preview
|
||||
)
|
||||
.or_else(|| {
|
||||
if let Some(header) = value.strip_prefix("header:") {
|
||||
HeaderProperty::parse(header).map(EmailProperty::Header)
|
||||
} else if allow_patch && value.contains('/') {
|
||||
EmailProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &EmailProperty {
|
||||
if let EmailProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_rfc_header(&self) -> HeaderName<'static> {
|
||||
match self {
|
||||
EmailProperty::MessageId => HeaderName::MessageId,
|
||||
EmailProperty::InReplyTo => HeaderName::InReplyTo,
|
||||
EmailProperty::References => HeaderName::References,
|
||||
EmailProperty::Sender => HeaderName::Sender,
|
||||
EmailProperty::From => HeaderName::From,
|
||||
EmailProperty::To => HeaderName::To,
|
||||
EmailProperty::Cc => HeaderName::Cc,
|
||||
EmailProperty::Bcc => HeaderName::Bcc,
|
||||
EmailProperty::ReplyTo => HeaderName::ReplyTo,
|
||||
EmailProperty::Subject => HeaderName::Subject,
|
||||
EmailProperty::SentAt => HeaderName::Date,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_into_id(self) -> Option<Id> {
|
||||
match self {
|
||||
EmailProperty::IdValue(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_into_keyword(self) -> Option<Keyword> {
|
||||
match self {
|
||||
EmailProperty::Keyword(keyword) => Some(keyword),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
let mut result = HeaderProperty {
|
||||
form: HeaderForm::Raw,
|
||||
header: String::new(),
|
||||
all: false,
|
||||
};
|
||||
|
||||
for (pos, value) in value.split(':').enumerate() {
|
||||
match pos {
|
||||
0 => {
|
||||
result.header = value.to_string();
|
||||
}
|
||||
1 => {
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"asText" => { result.form = HeaderForm::Text;},
|
||||
b"asAddresses" => { result.form = HeaderForm::Addresses;},
|
||||
b"asGroupedAddresses" => { result.form = HeaderForm::GroupedAddresses;},
|
||||
b"asMessageIds" => { result.form = HeaderForm::MessageIds;},
|
||||
b"asDate" => { result.form = HeaderForm::Date;},
|
||||
b"asURLs" => { result.form = HeaderForm::URLs;},
|
||||
b"asRaw" => { result.form = HeaderForm::Raw; },
|
||||
b"all" => { result.all = true; },
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
);
|
||||
}
|
||||
2 if value == "all" && !result.all => {
|
||||
result.all = true;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
if !result.header.is_empty() {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HeaderProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "header:{}", self.header)?;
|
||||
self.form.fmt(f)?;
|
||||
if self.all { write!(f, ":all") } else { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HeaderForm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
HeaderForm::Raw => Ok(()),
|
||||
HeaderForm::Text => write!(f, ":asText"),
|
||||
HeaderForm::Addresses => write!(f, ":asAddresses"),
|
||||
HeaderForm::GroupedAddresses => write!(f, ":asGroupedAddresses"),
|
||||
HeaderForm::MessageIds => write!(f, ":asMessageIds"),
|
||||
HeaderForm::Date => write!(f, ":asDate"),
|
||||
HeaderForm::URLs => write!(f, ":asURLs"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EmailProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
EmailProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EmailGetArguments {
|
||||
pub body_properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
|
||||
pub fetch_text_body_values: Option<bool>,
|
||||
pub fetch_html_body_values: Option<bool>,
|
||||
pub fetch_all_body_values: Option<bool>,
|
||||
pub max_body_value_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EmailQueryArguments {
|
||||
pub collapse_threads: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EmailParseArguments {
|
||||
pub body_properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
|
||||
pub fetch_text_body_values: Option<bool>,
|
||||
pub fetch_html_body_values: Option<bool>,
|
||||
pub fetch_all_body_values: Option<bool>,
|
||||
pub max_body_value_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailGetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"bodyProperties" => {
|
||||
self.body_properties = map.next_value()?;
|
||||
},
|
||||
b"fetchTextBodyValues" => {
|
||||
self.fetch_text_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchHTMLBodyValues" => {
|
||||
self.fetch_html_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchAllBodyValues" => {
|
||||
self.fetch_all_body_values = map.next_value()?;
|
||||
},
|
||||
b"maxBodyValueBytes" => {
|
||||
self.max_body_value_bytes = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailQueryArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "collapseThreads" {
|
||||
self.collapse_threads = map.next_value()?;
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailParseArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"bodyProperties" => {
|
||||
self.body_properties = map.next_value()?;
|
||||
},
|
||||
b"fetchTextBodyValues" => {
|
||||
self.fetch_text_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchHTMLBodyValues" => {
|
||||
self.fetch_html_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchAllBodyValues" => {
|
||||
self.fetch_all_body_values = map.next_value()?;
|
||||
},
|
||||
b"maxBodyValueBytes" => {
|
||||
self.max_body_value_bytes = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Email {
|
||||
type Property = EmailProperty;
|
||||
|
||||
type Element = EmailValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = EmailFilter;
|
||||
|
||||
type Comparator = EmailComparator;
|
||||
|
||||
type GetArguments = EmailGetArguments;
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = EmailQueryArguments;
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = EmailParseArguments;
|
||||
|
||||
const ID_PROPERTY: Self::Property = EmailProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailFilter {
|
||||
InMailbox(Id),
|
||||
InMailboxOtherThan(Vec<Id>),
|
||||
Before(UTCDate),
|
||||
After(UTCDate),
|
||||
MinSize(u32),
|
||||
MaxSize(u32),
|
||||
AllInThreadHaveKeyword(Keyword),
|
||||
SomeInThreadHaveKeyword(Keyword),
|
||||
NoneInThreadHaveKeyword(Keyword),
|
||||
HasKeyword(Keyword),
|
||||
NotKeyword(Keyword),
|
||||
HasAttachment(bool),
|
||||
From(String),
|
||||
To(String),
|
||||
Cc(String),
|
||||
Bcc(String),
|
||||
Subject(String),
|
||||
Body(String),
|
||||
Header(Vec<String>),
|
||||
Text(String),
|
||||
SentBefore(UTCDate),
|
||||
SentAfter(UTCDate),
|
||||
InThread(Id),
|
||||
Id(Vec<Id>),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailComparator {
|
||||
ReceivedAt,
|
||||
Size,
|
||||
From,
|
||||
To,
|
||||
Subject,
|
||||
Cc,
|
||||
SentAt,
|
||||
ThreadId,
|
||||
HasKeyword(Keyword),
|
||||
AllInThreadHaveKeyword(Keyword),
|
||||
SomeInThreadHaveKeyword(Keyword),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"inMailbox" => {
|
||||
*self = EmailFilter::InMailbox(map.next_value()?);
|
||||
},
|
||||
b"inMailboxOtherThan" => {
|
||||
*self = EmailFilter::InMailboxOtherThan(map.next_value()?);
|
||||
},
|
||||
b"before" => {
|
||||
*self = EmailFilter::Before(map.next_value()?);
|
||||
},
|
||||
b"after" => {
|
||||
*self = EmailFilter::After(map.next_value()?);
|
||||
},
|
||||
b"minSize" => {
|
||||
*self = EmailFilter::MinSize(map.next_value()?);
|
||||
},
|
||||
b"maxSize" => {
|
||||
*self = EmailFilter::MaxSize(map.next_value()?);
|
||||
},
|
||||
b"allInThreadHaveKeyword" => {
|
||||
*self = EmailFilter::AllInThreadHaveKeyword(map.next_value()?);
|
||||
},
|
||||
b"someInThreadHaveKeyword" => {
|
||||
*self = EmailFilter::SomeInThreadHaveKeyword(map.next_value()?);
|
||||
},
|
||||
b"noneInThreadHaveKeyword" => {
|
||||
*self = EmailFilter::NoneInThreadHaveKeyword(map.next_value()?);
|
||||
},
|
||||
b"hasKeyword" => {
|
||||
*self = EmailFilter::HasKeyword(map.next_value()?);
|
||||
},
|
||||
b"notKeyword" => {
|
||||
*self = EmailFilter::NotKeyword(map.next_value()?);
|
||||
},
|
||||
b"hasAttachment" => {
|
||||
*self = EmailFilter::HasAttachment(map.next_value()?);
|
||||
},
|
||||
b"from" => {
|
||||
*self = EmailFilter::From(map.next_value()?);
|
||||
},
|
||||
b"to" => {
|
||||
*self = EmailFilter::To(map.next_value()?);
|
||||
},
|
||||
b"cc" => {
|
||||
*self = EmailFilter::Cc(map.next_value()?);
|
||||
},
|
||||
b"bcc" => {
|
||||
*self = EmailFilter::Bcc(map.next_value()?);
|
||||
},
|
||||
b"subject" => {
|
||||
*self = EmailFilter::Subject(map.next_value()?);
|
||||
},
|
||||
b"body" => {
|
||||
*self = EmailFilter::Body(map.next_value()?);
|
||||
},
|
||||
b"header" => {
|
||||
*self = EmailFilter::Header(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = EmailFilter::Text(map.next_value()?);
|
||||
},
|
||||
b"sentBefore" => {
|
||||
*self = EmailFilter::SentBefore(map.next_value()?);
|
||||
},
|
||||
b"sentAfter" => {
|
||||
*self = EmailFilter::SentAfter(map.next_value()?);
|
||||
},
|
||||
b"inThread" => {
|
||||
*self = EmailFilter::InThread(map.next_value()?);
|
||||
},
|
||||
b"id" => {
|
||||
*self = EmailFilter::Id(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = EmailFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"receivedAt" => {
|
||||
*self = EmailComparator::ReceivedAt;
|
||||
},
|
||||
b"size" => {
|
||||
*self = EmailComparator::Size;
|
||||
},
|
||||
b"from" => {
|
||||
*self = EmailComparator::From;
|
||||
},
|
||||
b"to" => {
|
||||
*self = EmailComparator::To;
|
||||
},
|
||||
b"cc" => {
|
||||
*self = EmailComparator::Cc;
|
||||
},
|
||||
b"subject" => {
|
||||
*self = EmailComparator::Subject;
|
||||
},
|
||||
b"sentAt" => {
|
||||
*self = EmailComparator::SentAt;
|
||||
},
|
||||
b"threadId" => {
|
||||
*self = EmailComparator::ThreadId;
|
||||
},
|
||||
b"hasKeyword" => {
|
||||
*self = EmailComparator::HasKeyword(self.take_keyword());
|
||||
},
|
||||
b"allInThreadHaveKeyword" => {
|
||||
*self = EmailComparator::AllInThreadHaveKeyword(self.take_keyword());
|
||||
},
|
||||
b"someInThreadHaveKeyword" => {
|
||||
*self = EmailComparator::SomeInThreadHaveKeyword(self.take_keyword());
|
||||
},
|
||||
_ => {
|
||||
*self = EmailComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else if key == "keyword" {
|
||||
let keyword: Keyword = map.next_value()?;
|
||||
match self {
|
||||
EmailComparator::HasKeyword(_) => *self = EmailComparator::HasKeyword(keyword),
|
||||
EmailComparator::AllInThreadHaveKeyword(_) => {
|
||||
*self = EmailComparator::AllInThreadHaveKeyword(keyword)
|
||||
}
|
||||
EmailComparator::SomeInThreadHaveKeyword(_) => {
|
||||
*self = EmailComparator::SomeInThreadHaveKeyword(keyword)
|
||||
}
|
||||
_ => {
|
||||
*self = EmailComparator::HasKeyword(keyword);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailFilter {
|
||||
fn default() -> Self {
|
||||
EmailFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailComparator {
|
||||
fn default() -> Self {
|
||||
EmailComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailComparator {
|
||||
fn take_keyword(&mut self) -> Keyword {
|
||||
match self {
|
||||
EmailComparator::HasKeyword(k) => {
|
||||
std::mem::replace(k, Keyword::Other(Default::default()))
|
||||
}
|
||||
EmailComparator::AllInThreadHaveKeyword(k) => {
|
||||
std::mem::replace(k, Keyword::Other(Default::default()))
|
||||
}
|
||||
EmailComparator::SomeInThreadHaveKeyword(k) => {
|
||||
std::mem::replace(k, Keyword::Other(Default::default()))
|
||||
}
|
||||
_ => Keyword::Other(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EmailFilter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
EmailFilter::InMailbox(_) => "inMailbox",
|
||||
EmailFilter::InMailboxOtherThan(_) => "inMailboxOtherThan",
|
||||
EmailFilter::Before(_) => "before",
|
||||
EmailFilter::After(_) => "after",
|
||||
EmailFilter::MinSize(_) => "minSize",
|
||||
EmailFilter::MaxSize(_) => "maxSize",
|
||||
EmailFilter::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
|
||||
EmailFilter::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
|
||||
EmailFilter::NoneInThreadHaveKeyword(_) => "noneInThreadHaveKeyword",
|
||||
EmailFilter::HasKeyword(_) => "hasKeyword",
|
||||
EmailFilter::NotKeyword(_) => "notKeyword",
|
||||
EmailFilter::HasAttachment(_) => "hasAttachment",
|
||||
EmailFilter::From(_) => "from",
|
||||
EmailFilter::To(_) => "to",
|
||||
EmailFilter::Cc(_) => "cc",
|
||||
EmailFilter::Bcc(_) => "bcc",
|
||||
EmailFilter::Subject(_) => "subject",
|
||||
EmailFilter::Body(_) => "body",
|
||||
EmailFilter::Header(_) => "header",
|
||||
EmailFilter::Text(_) => "text",
|
||||
EmailFilter::SentBefore(_) => "sentBefore",
|
||||
EmailFilter::SentAfter(_) => "sentAfter",
|
||||
EmailFilter::InThread(_) => "inThread",
|
||||
EmailFilter::Id(_) => "id",
|
||||
EmailFilter::_T(v) => v.as_str(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EmailComparator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailComparator {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
EmailComparator::ReceivedAt => "receivedAt",
|
||||
EmailComparator::Size => "size",
|
||||
EmailComparator::From => "from",
|
||||
EmailComparator::To => "to",
|
||||
EmailComparator::Subject => "subject",
|
||||
EmailComparator::Cc => "cc",
|
||||
EmailComparator::SentAt => "sentAt",
|
||||
EmailComparator::ThreadId => "threadId",
|
||||
EmailComparator::HasKeyword(_) => "hasKeyword",
|
||||
EmailComparator::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword",
|
||||
EmailComparator::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword",
|
||||
EmailComparator::_T(v) => v.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for EmailComparator {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter<EmailFilter> {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
match self {
|
||||
Filter::Property(f) => f.is_immutable(),
|
||||
Filter::And | Filter::Or | Filter::Not | Filter::Close => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailFilter {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
EmailFilter::Before(_)
|
||||
| EmailFilter::After(_)
|
||||
| EmailFilter::MinSize(_)
|
||||
| EmailFilter::MaxSize(_)
|
||||
| EmailFilter::HasAttachment(_)
|
||||
| EmailFilter::From(_)
|
||||
| EmailFilter::To(_)
|
||||
| EmailFilter::Cc(_)
|
||||
| EmailFilter::Bcc(_)
|
||||
| EmailFilter::Subject(_)
|
||||
| EmailFilter::Body(_)
|
||||
| EmailFilter::Header(_)
|
||||
| EmailFilter::Text(_)
|
||||
| EmailFilter::Id(_)
|
||||
| EmailFilter::SentBefore(_)
|
||||
| EmailFilter::SentAfter(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparator<EmailComparator> {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
self.property.is_immutable()
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailComparator {
|
||||
pub fn is_immutable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
EmailComparator::ReceivedAt
|
||||
| EmailComparator::Size
|
||||
| EmailComparator::From
|
||||
| EmailComparator::To
|
||||
| EmailComparator::Subject
|
||||
| EmailComparator::Cc
|
||||
| EmailComparator::SentAt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for EmailValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let EmailValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
EmailValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
EmailValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let EmailValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
match new_id {
|
||||
AnyId::Id(id) => {
|
||||
*self = EmailValue::Id(id);
|
||||
}
|
||||
AnyId::BlobId(id) => {
|
||||
*self = EmailValue::BlobId(id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for EmailValue {
|
||||
fn from(id: Id) -> Self {
|
||||
EmailValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlobId> for EmailValue {
|
||||
fn from(id: BlobId) -> Self {
|
||||
EmailValue::BlobId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UTCDate> for EmailValue {
|
||||
fn from(date: UTCDate) -> Self {
|
||||
EmailValue::Date(date)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for EmailProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let EmailProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let EmailProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
match self {
|
||||
EmailProperty::IdReference(r) => Some(r),
|
||||
EmailProperty::Pointer(value) => {
|
||||
let value = value.as_slice();
|
||||
match (value.first(), value.get(1)) {
|
||||
(
|
||||
Some(JsonPointerItem::Key(Key::Property(EmailProperty::MailboxIds))),
|
||||
Some(JsonPointerItem::Key(Key::Property(EmailProperty::IdReference(r)))),
|
||||
) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
if let EmailProperty::Pointer(value) = self {
|
||||
let value = value.as_mut_slice();
|
||||
if let Some(value) = value.get_mut(1) {
|
||||
*value = JsonPointerItem::Key(Key::Property(EmailProperty::IdValue(id)));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
*self = EmailProperty::IdValue(id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, MaybeReference,
|
||||
email::{EmailProperty, EmailValue},
|
||||
parse_ref,
|
||||
},
|
||||
request::{MaybeInvalid, deserialize::DeserializeArguments, reference::MaybeIdReference},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property, Value};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EmailSubmission;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EmailSubmissionProperty {
|
||||
Id,
|
||||
IdentityId,
|
||||
ThreadId,
|
||||
EmailId,
|
||||
Envelope,
|
||||
MailFrom,
|
||||
RcptTo,
|
||||
Email,
|
||||
Parameters,
|
||||
SendAt,
|
||||
UndoStatus,
|
||||
DeliveryStatus,
|
||||
SmtpReply,
|
||||
Delivered,
|
||||
Displayed,
|
||||
DsnBlobIds,
|
||||
MdnBlobIds,
|
||||
|
||||
Pointer(JsonPointer<EmailSubmissionProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EmailSubmissionValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
BlobId(BlobId),
|
||||
UndoStatus(UndoStatus),
|
||||
Delivered(Delivered),
|
||||
Displayed(Displayed),
|
||||
IdReference(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum UndoStatus {
|
||||
Pending,
|
||||
Final,
|
||||
Canceled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Delivered {
|
||||
Queued,
|
||||
Yes,
|
||||
No,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Displayed {
|
||||
Yes,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Property for EmailSubmissionProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
EmailSubmissionProperty::parse(value, key.is_none())
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
EmailSubmissionProperty::DeliveryStatus => "deliveryStatus",
|
||||
EmailSubmissionProperty::DsnBlobIds => "dsnBlobIds",
|
||||
EmailSubmissionProperty::Email => "email",
|
||||
EmailSubmissionProperty::Envelope => "envelope",
|
||||
EmailSubmissionProperty::Id => "id",
|
||||
EmailSubmissionProperty::IdentityId => "identityId",
|
||||
EmailSubmissionProperty::MdnBlobIds => "mdnBlobIds",
|
||||
EmailSubmissionProperty::SendAt => "sendAt",
|
||||
EmailSubmissionProperty::ThreadId => "threadId",
|
||||
EmailSubmissionProperty::UndoStatus => "undoStatus",
|
||||
EmailSubmissionProperty::Parameters => "parameters",
|
||||
EmailSubmissionProperty::SmtpReply => "smtpReply",
|
||||
EmailSubmissionProperty::Delivered => "delivered",
|
||||
EmailSubmissionProperty::Displayed => "displayed",
|
||||
EmailSubmissionProperty::MailFrom => "mailFrom",
|
||||
EmailSubmissionProperty::RcptTo => "rcptTo",
|
||||
EmailSubmissionProperty::EmailId => "emailId",
|
||||
EmailSubmissionProperty::Pointer(json_pointer) => {
|
||||
return json_pointer.to_string().into();
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for EmailSubmissionValue {
|
||||
type Property = EmailSubmissionProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
EmailSubmissionProperty::Id
|
||||
| EmailSubmissionProperty::ThreadId
|
||||
| EmailSubmissionProperty::IdentityId
|
||||
| EmailSubmissionProperty::EmailId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(EmailSubmissionValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(EmailSubmissionValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
EmailSubmissionProperty::MdnBlobIds | EmailSubmissionProperty::DsnBlobIds => {
|
||||
match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(EmailSubmissionValue::BlobId(v)),
|
||||
MaybeReference::Reference(v) => Some(EmailSubmissionValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
}
|
||||
}
|
||||
EmailSubmissionProperty::SendAt => UTCDate::from_str(value)
|
||||
.ok()
|
||||
.map(EmailSubmissionValue::Date),
|
||||
EmailSubmissionProperty::UndoStatus => {
|
||||
UndoStatus::parse(value).map(EmailSubmissionValue::UndoStatus)
|
||||
}
|
||||
EmailSubmissionProperty::Delivered => {
|
||||
Delivered::parse(value).map(EmailSubmissionValue::Delivered)
|
||||
}
|
||||
EmailSubmissionProperty::Displayed => {
|
||||
Displayed::parse(value).map(EmailSubmissionValue::Displayed)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
EmailSubmissionValue::Id(id) => id.to_string().into(),
|
||||
EmailSubmissionValue::Date(utcdate) => utcdate.to_string().into(),
|
||||
EmailSubmissionValue::BlobId(blob_id) => blob_id.to_string().into(),
|
||||
EmailSubmissionValue::IdReference(r) => format!("#{r}").into(),
|
||||
EmailSubmissionValue::UndoStatus(undo_status) => undo_status.as_str().into(),
|
||||
EmailSubmissionValue::Delivered(delivered) => delivered.as_str().into(),
|
||||
EmailSubmissionValue::Displayed(displayed) => displayed.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailSubmissionProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
"id" => EmailSubmissionProperty::Id,
|
||||
"identityId" => EmailSubmissionProperty::IdentityId,
|
||||
"threadId" => EmailSubmissionProperty::ThreadId,
|
||||
"emailId" => EmailSubmissionProperty::EmailId,
|
||||
"envelope" => EmailSubmissionProperty::Envelope,
|
||||
"mailFrom" => EmailSubmissionProperty::MailFrom,
|
||||
"rcptTo" => EmailSubmissionProperty::RcptTo,
|
||||
"email" => EmailSubmissionProperty::Email,
|
||||
"parameters" => EmailSubmissionProperty::Parameters,
|
||||
"sendAt" => EmailSubmissionProperty::SendAt,
|
||||
"undoStatus" => EmailSubmissionProperty::UndoStatus,
|
||||
"deliveryStatus" => EmailSubmissionProperty::DeliveryStatus,
|
||||
"smtpReply" => EmailSubmissionProperty::SmtpReply,
|
||||
"delivered" => EmailSubmissionProperty::Delivered,
|
||||
"displayed" => EmailSubmissionProperty::Displayed,
|
||||
"dsnBlobIds" => EmailSubmissionProperty::DsnBlobIds,
|
||||
"mdnBlobIds" => EmailSubmissionProperty::MdnBlobIds,
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
EmailSubmissionProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &EmailSubmissionProperty {
|
||||
if let EmailSubmissionProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UndoStatus {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"pending" => UndoStatus::Pending,
|
||||
b"final" => UndoStatus::Final,
|
||||
b"canceled" => UndoStatus::Canceled,
|
||||
)
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
UndoStatus::Pending => "pending",
|
||||
UndoStatus::Final => "final",
|
||||
UndoStatus::Canceled => "canceled",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Delivered {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"queued" => Delivered::Queued,
|
||||
b"yes" => Delivered::Yes,
|
||||
b"no" => Delivered::No,
|
||||
b"unknown" => Delivered::Unknown,
|
||||
)
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Delivered::Queued => "queued",
|
||||
Delivered::Yes => "yes",
|
||||
Delivered::No => "no",
|
||||
Delivered::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Displayed {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"yes" => Displayed::Yes,
|
||||
b"unknown" => Displayed::Unknown,
|
||||
)
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Displayed::Yes => "yes",
|
||||
Displayed::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EmailSubmissionSetArguments<'x> {
|
||||
pub on_success_update_email:
|
||||
Option<VecMap<MaybeIdReference<Id>, Value<'x, EmailProperty, EmailValue>>>,
|
||||
pub on_success_destroy_email: Option<Vec<MaybeIdReference<Id>>>,
|
||||
}
|
||||
|
||||
impl<'x> DeserializeArguments<'x> for EmailSubmissionSetArguments<'x> {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'x>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onSuccessUpdateEmail" => {
|
||||
self.on_success_update_email = map.next_value()?;
|
||||
},
|
||||
b"onSuccessDestroyEmail" => {
|
||||
self.on_success_destroy_email = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EmailSubmissionProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
EmailSubmissionProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for UndoStatus {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
UndoStatus::parse(<&str>::deserialize(deserializer)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP UndoStatus"))
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for EmailSubmission {
|
||||
type Property = EmailSubmissionProperty;
|
||||
|
||||
type Element = EmailSubmissionValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = EmailSubmissionFilter;
|
||||
|
||||
type Comparator = EmailSubmissionComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = EmailSubmissionSetArguments<'de>;
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = EmailSubmissionProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailSubmissionFilter {
|
||||
IdentityIds(Vec<MaybeInvalid<Id>>),
|
||||
EmailIds(Vec<MaybeInvalid<Id>>),
|
||||
ThreadIds(Vec<MaybeInvalid<Id>>),
|
||||
Before(UTCDate),
|
||||
After(UTCDate),
|
||||
UndoStatus(UndoStatus),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailSubmissionComparator {
|
||||
EmailId,
|
||||
ThreadId,
|
||||
SentAt,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailSubmissionFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"identityIds" => {
|
||||
*self = EmailSubmissionFilter::IdentityIds(map.next_value()?);
|
||||
},
|
||||
b"emailIds" => {
|
||||
*self = EmailSubmissionFilter::EmailIds(map.next_value()?);
|
||||
},
|
||||
b"threadIds" => {
|
||||
*self = EmailSubmissionFilter::ThreadIds(map.next_value()?);
|
||||
},
|
||||
b"before" => {
|
||||
*self = EmailSubmissionFilter::Before(map.next_value()?);
|
||||
},
|
||||
b"after" => {
|
||||
*self = EmailSubmissionFilter::After(map.next_value()?);
|
||||
},
|
||||
b"undoStatus" => {
|
||||
*self = EmailSubmissionFilter::UndoStatus(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = EmailSubmissionFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailSubmissionComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
|
||||
b"emailId" => {
|
||||
*self = EmailSubmissionComparator::EmailId;
|
||||
},
|
||||
b"threadId" => {
|
||||
*self = EmailSubmissionComparator::ThreadId;
|
||||
},
|
||||
b"sentAt" => {
|
||||
*self = EmailSubmissionComparator::SentAt;
|
||||
},
|
||||
_ => {
|
||||
*self = EmailSubmissionComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailSubmissionFilter {
|
||||
fn default() -> Self {
|
||||
EmailSubmissionFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmailSubmissionComparator {
|
||||
fn default() -> Self {
|
||||
EmailSubmissionComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for EmailSubmissionValue {
|
||||
fn from(id: Id) -> Self {
|
||||
EmailSubmissionValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for EmailSubmissionValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
EmailSubmissionValue::Id(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
EmailSubmissionValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
EmailSubmissionValue::BlobId(blob_id) => Some(AnyId::BlobId(blob_id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let EmailSubmissionValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
match new_id {
|
||||
AnyId::Id(id) => {
|
||||
*self = EmailSubmissionValue::Id(id);
|
||||
}
|
||||
AnyId::BlobId(blob_id) => {
|
||||
*self = EmailSubmissionValue::BlobId(blob_id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for EmailSubmissionProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,826 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
|
||||
},
|
||||
request::{MaybeInvalid, deserialize::DeserializeArguments},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::{acl::Acl, blob::BlobId, id::Id};
|
||||
use utils::glob::GlobPattern;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FileNode;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FileNodeProperty {
|
||||
Id,
|
||||
ParentId,
|
||||
BlobId,
|
||||
Size,
|
||||
Name,
|
||||
Type,
|
||||
NodeType,
|
||||
Target,
|
||||
Created,
|
||||
Modified,
|
||||
Accessed,
|
||||
Changed,
|
||||
Executable,
|
||||
Role,
|
||||
MyRights,
|
||||
ShareWith,
|
||||
IsSubscribed,
|
||||
|
||||
IdValue(Id),
|
||||
Rights(FileNodeRight),
|
||||
Pointer(JsonPointer<FileNodeProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FileNodeRight {
|
||||
MayRead,
|
||||
MayAddChildren,
|
||||
MayRename,
|
||||
MayDelete,
|
||||
MayModifyContent,
|
||||
MayShare,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FileNodeNodeType {
|
||||
File,
|
||||
Directory,
|
||||
Symlink,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FileNodeRole {
|
||||
Root,
|
||||
Home,
|
||||
Temp,
|
||||
Trash,
|
||||
Documents,
|
||||
Downloads,
|
||||
Music,
|
||||
Pictures,
|
||||
Videos,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FileNodeValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
BlobId(BlobId),
|
||||
IdReference(String),
|
||||
}
|
||||
|
||||
impl Property for FileNodeProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
let allow_patch = key.is_none();
|
||||
if let Some(Key::Property(key)) = key {
|
||||
match key.patch_or_prop() {
|
||||
FileNodeProperty::ShareWith => {
|
||||
Id::from_str(value).ok().map(FileNodeProperty::IdValue)
|
||||
}
|
||||
_ => FileNodeProperty::parse(value, allow_patch),
|
||||
}
|
||||
} else {
|
||||
FileNodeProperty::parse(value, allow_patch)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
FileNodeProperty::Id => "id",
|
||||
FileNodeProperty::ParentId => "parentId",
|
||||
FileNodeProperty::BlobId => "blobId",
|
||||
FileNodeProperty::Size => "size",
|
||||
FileNodeProperty::Name => "name",
|
||||
FileNodeProperty::Type => "type",
|
||||
FileNodeProperty::NodeType => "nodeType",
|
||||
FileNodeProperty::Target => "target",
|
||||
FileNodeProperty::Created => "created",
|
||||
FileNodeProperty::Modified => "modified",
|
||||
FileNodeProperty::Accessed => "accessed",
|
||||
FileNodeProperty::Changed => "changed",
|
||||
FileNodeProperty::Executable => "executable",
|
||||
FileNodeProperty::Role => "role",
|
||||
FileNodeProperty::MyRights => "myRights",
|
||||
FileNodeProperty::ShareWith => "shareWith",
|
||||
FileNodeProperty::IsSubscribed => "isSubscribed",
|
||||
FileNodeProperty::Rights(file_right) => file_right.as_str(),
|
||||
FileNodeProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
FileNodeProperty::IdValue(id) => return id.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNodeRight {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
FileNodeRight::MayRead => "mayRead",
|
||||
FileNodeRight::MayAddChildren => "mayAddChildren",
|
||||
FileNodeRight::MayRename => "mayRename",
|
||||
FileNodeRight::MayDelete => "mayDelete",
|
||||
FileNodeRight::MayModifyContent => "mayModifyContent",
|
||||
FileNodeRight::MayShare => "mayShare",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNodeNodeType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
FileNodeNodeType::File => "file",
|
||||
FileNodeNodeType::Directory => "directory",
|
||||
FileNodeNodeType::Symlink => "symlink",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"file" => FileNodeNodeType::File,
|
||||
b"directory" => FileNodeNodeType::Directory,
|
||||
b"symlink" => FileNodeNodeType::Symlink,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileNodeNodeType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
FileNodeNodeType::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileNodeNodeType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNodeRole {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
FileNodeRole::Root => "root",
|
||||
FileNodeRole::Home => "home",
|
||||
FileNodeRole::Temp => "temp",
|
||||
FileNodeRole::Trash => "trash",
|
||||
FileNodeRole::Documents => "documents",
|
||||
FileNodeRole::Downloads => "downloads",
|
||||
FileNodeRole::Music => "music",
|
||||
FileNodeRole::Pictures => "pictures",
|
||||
FileNodeRole::Videos => "videos",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"root" => FileNodeRole::Root,
|
||||
b"home" => FileNodeRole::Home,
|
||||
b"temp" => FileNodeRole::Temp,
|
||||
b"trash" => FileNodeRole::Trash,
|
||||
b"documents" => FileNodeRole::Documents,
|
||||
b"downloads" => FileNodeRole::Downloads,
|
||||
b"music" => FileNodeRole::Music,
|
||||
b"pictures" => FileNodeRole::Pictures,
|
||||
b"videos" => FileNodeRole::Videos,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileNodeRole {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
FileNodeRole::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileNodeRole {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for FileNodeValue {
|
||||
type Property = FileNodeProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
FileNodeProperty::Id | FileNodeProperty::ParentId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(FileNodeValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(FileNodeValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
FileNodeProperty::BlobId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(FileNodeValue::BlobId(v)),
|
||||
MaybeReference::Reference(v) => Some(FileNodeValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
FileNodeProperty::Created
|
||||
| FileNodeProperty::Modified
|
||||
| FileNodeProperty::Accessed
|
||||
| FileNodeProperty::Changed => {
|
||||
UTCDate::from_str(value).ok().map(FileNodeValue::Date)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
FileNodeValue::Id(id) => id.to_string().into(),
|
||||
FileNodeValue::Date(utcdate) => utcdate.to_string().into(),
|
||||
FileNodeValue::BlobId(blob_id) => blob_id.to_string().into(),
|
||||
FileNodeValue::IdReference(r) => format!("#{r}").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNodeProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => FileNodeProperty::Id,
|
||||
b"parentId" => FileNodeProperty::ParentId,
|
||||
b"blobId" => FileNodeProperty::BlobId,
|
||||
b"size" => FileNodeProperty::Size,
|
||||
b"name" => FileNodeProperty::Name,
|
||||
b"type" => FileNodeProperty::Type,
|
||||
b"nodeType" => FileNodeProperty::NodeType,
|
||||
b"target" => FileNodeProperty::Target,
|
||||
b"created" => FileNodeProperty::Created,
|
||||
b"modified" => FileNodeProperty::Modified,
|
||||
b"accessed" => FileNodeProperty::Accessed,
|
||||
b"changed" => FileNodeProperty::Changed,
|
||||
b"executable" => FileNodeProperty::Executable,
|
||||
b"role" => FileNodeProperty::Role,
|
||||
b"myRights" => FileNodeProperty::MyRights,
|
||||
b"shareWith" => FileNodeProperty::ShareWith,
|
||||
b"isSubscribed" => FileNodeProperty::IsSubscribed,
|
||||
b"mayRead" => FileNodeProperty::Rights(FileNodeRight::MayRead),
|
||||
b"mayAddChildren" => FileNodeProperty::Rights(FileNodeRight::MayAddChildren),
|
||||
b"mayRename" => FileNodeProperty::Rights(FileNodeRight::MayRename),
|
||||
b"mayDelete" => FileNodeProperty::Rights(FileNodeRight::MayDelete),
|
||||
b"mayModifyContent" => FileNodeProperty::Rights(FileNodeRight::MayModifyContent),
|
||||
b"mayShare" => FileNodeProperty::Rights(FileNodeRight::MayShare),
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
FileNodeProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &FileNodeProperty {
|
||||
if let FileNodeProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FileNodeSetArguments {
|
||||
pub on_destroy_remove_children: Option<bool>,
|
||||
pub on_exists: OnExists,
|
||||
pub compare_case_insensitively: Option<bool>,
|
||||
}
|
||||
|
||||
pub type FileNodeCopyArguments = FileNodeSetArguments;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum OnExists {
|
||||
#[default]
|
||||
Reject,
|
||||
Replace,
|
||||
Rename,
|
||||
Newest,
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for OnExists {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value: Option<Cow<'_, str>> = Option::deserialize(deserializer)?;
|
||||
match value.as_deref() {
|
||||
Some("replace") => Ok(OnExists::Replace),
|
||||
Some("rename") => Ok(OnExists::Rename),
|
||||
Some("newest") => Ok(OnExists::Newest),
|
||||
None | Some("") => Ok(OnExists::Reject),
|
||||
Some(other) => Err(serde::de::Error::custom(format!(
|
||||
"Invalid onExists value: {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> DeserializeArguments<'x> for FileNodeSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'x>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onDestroyRemoveChildren" => {
|
||||
self.on_destroy_remove_children = map.next_value()?;
|
||||
},
|
||||
b"onExists" => {
|
||||
self.on_exists = map.next_value()?;
|
||||
},
|
||||
b"compareCaseInsensitively" => {
|
||||
self.compare_case_insensitively = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FileNodeGetArguments {
|
||||
pub fetch_parents: Option<bool>,
|
||||
}
|
||||
|
||||
impl<'x> DeserializeArguments<'x> for FileNodeGetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'x>,
|
||||
{
|
||||
if key == "fetchParents" {
|
||||
self.fetch_parents = map.next_value()?;
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FileNodeQueryArguments {
|
||||
pub depth: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'x> DeserializeArguments<'x> for FileNodeQueryArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'x>,
|
||||
{
|
||||
if key == "depth" {
|
||||
self.depth = map.next_value()?;
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileNodeProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
FileNodeProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for FileNode {
|
||||
type Property = FileNodeProperty;
|
||||
|
||||
type Element = FileNodeValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = FileNodeFilter;
|
||||
|
||||
type Comparator = FileNodeComparator;
|
||||
|
||||
type GetArguments = FileNodeGetArguments;
|
||||
|
||||
type SetArguments<'de> = FileNodeSetArguments;
|
||||
|
||||
type QueryArguments = FileNodeQueryArguments;
|
||||
|
||||
type CopyArguments = FileNodeCopyArguments;
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = FileNodeProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapSharedObject for FileNode {
|
||||
type Right = FileNodeRight;
|
||||
|
||||
const SHARE_WITH_PROPERTY: Self::Property = FileNodeProperty::ShareWith;
|
||||
}
|
||||
|
||||
impl From<Id> for FileNodeProperty {
|
||||
fn from(id: Id) -> Self {
|
||||
FileNodeProperty::IdValue(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapRight for FileNodeRight {
|
||||
fn to_acl(&self) -> &'static [Acl] {
|
||||
match self {
|
||||
FileNodeRight::MayRead => &[Acl::Read, Acl::ReadItems],
|
||||
FileNodeRight::MayAddChildren => &[Acl::AddItems],
|
||||
FileNodeRight::MayRename => &[Acl::Modify],
|
||||
FileNodeRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
|
||||
FileNodeRight::MayModifyContent => &[Acl::ModifyItems],
|
||||
FileNodeRight::MayShare => &[Acl::Share],
|
||||
}
|
||||
}
|
||||
|
||||
fn all_rights() -> &'static [Self] {
|
||||
&[
|
||||
FileNodeRight::MayRead,
|
||||
FileNodeRight::MayAddChildren,
|
||||
FileNodeRight::MayRename,
|
||||
FileNodeRight::MayDelete,
|
||||
FileNodeRight::MayModifyContent,
|
||||
FileNodeRight::MayShare,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileNodeRight> for FileNodeProperty {
|
||||
fn from(right: FileNodeRight) -> Self {
|
||||
FileNodeProperty::Rights(right)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FileNodeFilter {
|
||||
IsTopLevel(bool),
|
||||
ParentId(MaybeInvalid<Id>),
|
||||
AncestorId(MaybeInvalid<Id>),
|
||||
DescendantId(MaybeInvalid<Id>),
|
||||
NodeType(String),
|
||||
Role(String),
|
||||
HasAnyRole(bool),
|
||||
BlobId(MaybeInvalid<BlobId>),
|
||||
IsExecutable(bool),
|
||||
CreatedBefore(UTCDate),
|
||||
CreatedAfter(UTCDate),
|
||||
ModifiedBefore(UTCDate),
|
||||
ModifiedAfter(UTCDate),
|
||||
AccessedBefore(UTCDate),
|
||||
AccessedAfter(UTCDate),
|
||||
MinSize(u64),
|
||||
MaxSize(u64),
|
||||
Name(String),
|
||||
NameMatch(GlobPattern),
|
||||
Type(String),
|
||||
TypeMatch(GlobPattern),
|
||||
Text(String),
|
||||
Body(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FileNodeComparator {
|
||||
Name,
|
||||
Size,
|
||||
Created,
|
||||
Modified,
|
||||
Type,
|
||||
NodeType,
|
||||
Tree,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for FileNodeFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"isTopLevel" => {
|
||||
*self = FileNodeFilter::IsTopLevel(map.next_value()?);
|
||||
},
|
||||
b"parentId" => {
|
||||
*self = FileNodeFilter::ParentId(map.next_value()?);
|
||||
},
|
||||
b"ancestorId" => {
|
||||
*self = FileNodeFilter::AncestorId(map.next_value()?);
|
||||
},
|
||||
b"descendantId" => {
|
||||
*self = FileNodeFilter::DescendantId(map.next_value()?);
|
||||
},
|
||||
b"nodeType" => {
|
||||
*self = FileNodeFilter::NodeType(map.next_value()?);
|
||||
},
|
||||
b"role" => {
|
||||
*self = FileNodeFilter::Role(map.next_value()?);
|
||||
},
|
||||
b"hasAnyRole" => {
|
||||
*self = FileNodeFilter::HasAnyRole(map.next_value()?);
|
||||
},
|
||||
b"blobId" => {
|
||||
*self = FileNodeFilter::BlobId(map.next_value()?);
|
||||
},
|
||||
b"isExecutable" => {
|
||||
*self = FileNodeFilter::IsExecutable(map.next_value()?);
|
||||
},
|
||||
b"createdBefore" => {
|
||||
*self = FileNodeFilter::CreatedBefore(map.next_value()?);
|
||||
},
|
||||
b"createdAfter" => {
|
||||
*self = FileNodeFilter::CreatedAfter(map.next_value()?);
|
||||
},
|
||||
b"modifiedBefore" => {
|
||||
*self = FileNodeFilter::ModifiedBefore(map.next_value()?);
|
||||
},
|
||||
b"modifiedAfter" => {
|
||||
*self = FileNodeFilter::ModifiedAfter(map.next_value()?);
|
||||
},
|
||||
b"accessedBefore" => {
|
||||
*self = FileNodeFilter::AccessedBefore(map.next_value()?);
|
||||
},
|
||||
b"accessedAfter" => {
|
||||
*self = FileNodeFilter::AccessedAfter(map.next_value()?);
|
||||
},
|
||||
b"minSize" => {
|
||||
*self = FileNodeFilter::MinSize(map.next_value()?);
|
||||
},
|
||||
b"maxSize" => {
|
||||
*self = FileNodeFilter::MaxSize(map.next_value()?);
|
||||
},
|
||||
b"name" => {
|
||||
*self = FileNodeFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"nameMatch" => {
|
||||
*self = FileNodeFilter::NameMatch(map.next_value()?);
|
||||
},
|
||||
b"type" => {
|
||||
*self = FileNodeFilter::Type(map.next_value()?);
|
||||
},
|
||||
b"typeMatch" => {
|
||||
*self = FileNodeFilter::TypeMatch(map.next_value()?);
|
||||
},
|
||||
b"body" => {
|
||||
*self = FileNodeFilter::Body(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = FileNodeFilter::Text(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = FileNodeFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for FileNodeComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = FileNodeComparator::Name;
|
||||
},
|
||||
b"size" => {
|
||||
*self = FileNodeComparator::Size;
|
||||
},
|
||||
b"created" => {
|
||||
*self = FileNodeComparator::Created;
|
||||
},
|
||||
b"modified" => {
|
||||
*self = FileNodeComparator::Modified;
|
||||
},
|
||||
b"type" => {
|
||||
*self = FileNodeComparator::Type;
|
||||
},
|
||||
b"nodeType" => {
|
||||
*self = FileNodeComparator::NodeType;
|
||||
},
|
||||
b"tree" => {
|
||||
*self = FileNodeComparator::Tree;
|
||||
},
|
||||
_ => {
|
||||
*self = FileNodeComparator::_T(value.into_owned());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileNodeFilter {
|
||||
fn default() -> Self {
|
||||
FileNodeFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileNodeComparator {
|
||||
fn default() -> Self {
|
||||
FileNodeComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for FileNodeValue {
|
||||
fn from(id: Id) -> Self {
|
||||
FileNodeValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for FileNodeValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
FileNodeValue::Id(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
FileNodeValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
FileNodeValue::BlobId(blob_id) => Some(AnyId::BlobId(blob_id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let FileNodeValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
match new_id {
|
||||
AnyId::Id(id) => {
|
||||
*self = FileNodeValue::Id(id);
|
||||
}
|
||||
AnyId::BlobId(blob_id) => {
|
||||
*self = FileNodeValue::BlobId(blob_id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNodeFilter {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
FileNodeFilter::IsTopLevel(_) => "isTopLevel",
|
||||
FileNodeFilter::ParentId(_) => "parentId",
|
||||
FileNodeFilter::AncestorId(_) => "ancestorId",
|
||||
FileNodeFilter::DescendantId(_) => "descendantId",
|
||||
FileNodeFilter::NodeType(_) => "nodeType",
|
||||
FileNodeFilter::Role(_) => "role",
|
||||
FileNodeFilter::HasAnyRole(_) => "hasAnyRole",
|
||||
FileNodeFilter::BlobId(_) => "blobId",
|
||||
FileNodeFilter::IsExecutable(_) => "isExecutable",
|
||||
FileNodeFilter::CreatedBefore(_) => "createdBefore",
|
||||
FileNodeFilter::CreatedAfter(_) => "createdAfter",
|
||||
FileNodeFilter::ModifiedBefore(_) => "modifiedBefore",
|
||||
FileNodeFilter::ModifiedAfter(_) => "modifiedAfter",
|
||||
FileNodeFilter::AccessedBefore(_) => "accessedBefore",
|
||||
FileNodeFilter::AccessedAfter(_) => "accessedAfter",
|
||||
FileNodeFilter::MinSize(_) => "minSize",
|
||||
FileNodeFilter::MaxSize(_) => "maxSize",
|
||||
FileNodeFilter::Name(_) => "name",
|
||||
FileNodeFilter::NameMatch(_) => "nameMatch",
|
||||
FileNodeFilter::Type(_) => "type",
|
||||
FileNodeFilter::TypeMatch(_) => "typeMatch",
|
||||
FileNodeFilter::Text(_) => "text",
|
||||
FileNodeFilter::Body(_) => "body",
|
||||
FileNodeFilter::_T(s) => return s.into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileNodeComparator {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
FileNodeComparator::Name => "name",
|
||||
FileNodeComparator::Size => "size",
|
||||
FileNodeComparator::Created => "created",
|
||||
FileNodeComparator::Modified => "modified",
|
||||
FileNodeComparator::Type => "type",
|
||||
FileNodeComparator::NodeType => "nodeType",
|
||||
FileNodeComparator::Tree => "tree",
|
||||
FileNodeComparator::_T(s) => s.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
FileNodeComparator::Name => "name",
|
||||
FileNodeComparator::Size => "size",
|
||||
FileNodeComparator::Created => "created",
|
||||
FileNodeComparator::Modified => "modified",
|
||||
FileNodeComparator::Type => "type",
|
||||
FileNodeComparator::NodeType => "nodeType",
|
||||
FileNodeComparator::Tree => "tree",
|
||||
FileNodeComparator::_T(s) => return s.into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for FileNodeComparator {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<FileNodeProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: FileNodeProperty) -> Result<Self, Self::Error> {
|
||||
if let FileNodeProperty::IdValue(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<FileNodeProperty> for FileNodeRight {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: FileNodeProperty) -> Result<Self, Self::Error> {
|
||||
if let FileNodeProperty::Rights(right) = value {
|
||||
Ok(right)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for FileNodeProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let FileNodeProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let FileNodeProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = FileNodeProperty::IdValue(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileNodeProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_cow())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::object::{AnyId, JmapObject, JmapObjectId};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Identity;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum IdentityProperty {
|
||||
Id,
|
||||
Name,
|
||||
Email,
|
||||
ReplyTo,
|
||||
Bcc,
|
||||
TextSignature,
|
||||
HtmlSignature,
|
||||
MayDelete,
|
||||
|
||||
// Other
|
||||
Pointer(JsonPointer<IdentityProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum IdentityValue {
|
||||
Id(Id),
|
||||
}
|
||||
|
||||
impl Property for IdentityProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
IdentityProperty::parse(value, key.is_none())
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
IdentityProperty::Bcc => "bcc",
|
||||
IdentityProperty::Email => "email",
|
||||
IdentityProperty::HtmlSignature => "htmlSignature",
|
||||
IdentityProperty::Id => "id",
|
||||
IdentityProperty::MayDelete => "mayDelete",
|
||||
IdentityProperty::Name => "name",
|
||||
IdentityProperty::ReplyTo => "replyTo",
|
||||
IdentityProperty::TextSignature => "textSignature",
|
||||
IdentityProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for IdentityValue {
|
||||
type Property = IdentityProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
IdentityProperty::Id => Id::from_str(value).ok().map(IdentityValue::Id),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
IdentityValue::Id(id) => id.to_string().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IdentityProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => IdentityProperty::Id,
|
||||
b"name" => IdentityProperty::Name,
|
||||
b"email" => IdentityProperty::Email,
|
||||
b"replyTo" => IdentityProperty::ReplyTo,
|
||||
b"bcc" => IdentityProperty::Bcc,
|
||||
b"textSignature" => IdentityProperty::TextSignature,
|
||||
b"htmlSignature" => IdentityProperty::HtmlSignature,
|
||||
b"mayDelete" => IdentityProperty::MayDelete,
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
IdentityProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &IdentityProperty {
|
||||
if let IdentityProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IdentityProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
IdentityProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Identity {
|
||||
type Property = IdentityProperty;
|
||||
|
||||
type Element = IdentityValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = IdentityProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for IdentityValue {
|
||||
fn from(id: Id) -> Self {
|
||||
IdentityValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for IdentityValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
IdentityValue::Id(id) => Some(*id),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
IdentityValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = IdentityValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for IdentityProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
|
||||
},
|
||||
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{acl::Acl, id::Id, special_use::SpecialUse};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Mailbox;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum MailboxProperty {
|
||||
Id,
|
||||
Name,
|
||||
ParentId,
|
||||
Role,
|
||||
SortOrder,
|
||||
TotalEmails,
|
||||
UnreadEmails,
|
||||
TotalThreads,
|
||||
UnreadThreads,
|
||||
ShareWith,
|
||||
MyRights,
|
||||
IsSubscribed,
|
||||
|
||||
// Other
|
||||
IdValue(Id),
|
||||
Rights(MailboxRight),
|
||||
Pointer(JsonPointer<MailboxProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum MailboxRight {
|
||||
MayReadItems,
|
||||
MayAddItems,
|
||||
MayRemoveItems,
|
||||
MaySetSeen,
|
||||
MaySetKeywords,
|
||||
MayCreateChild,
|
||||
MayRename,
|
||||
MaySubmit,
|
||||
MayDelete,
|
||||
MayShare,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum MailboxValue {
|
||||
Id(Id),
|
||||
IdReference(String),
|
||||
Role(SpecialUse),
|
||||
}
|
||||
|
||||
impl Property for MailboxProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
let allow_patch = key.is_none();
|
||||
if let Some(Key::Property(key)) = key {
|
||||
match key.patch_or_prop() {
|
||||
MailboxProperty::ShareWith => {
|
||||
Id::from_str(value).ok().map(MailboxProperty::IdValue)
|
||||
}
|
||||
_ => MailboxProperty::parse(value, allow_patch),
|
||||
}
|
||||
} else {
|
||||
MailboxProperty::parse(value, allow_patch)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
MailboxProperty::Id => "id",
|
||||
MailboxProperty::IsSubscribed => "isSubscribed",
|
||||
MailboxProperty::MyRights => "myRights",
|
||||
MailboxProperty::Name => "name",
|
||||
MailboxProperty::ParentId => "parentId",
|
||||
MailboxProperty::Role => "role",
|
||||
MailboxProperty::SortOrder => "sortOrder",
|
||||
MailboxProperty::TotalEmails => "totalEmails",
|
||||
MailboxProperty::TotalThreads => "totalThreads",
|
||||
MailboxProperty::UnreadEmails => "unreadEmails",
|
||||
MailboxProperty::UnreadThreads => "unreadThreads",
|
||||
MailboxProperty::ShareWith => "shareWith",
|
||||
MailboxProperty::Rights(mailbox_right) => mailbox_right.as_str(),
|
||||
MailboxProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
MailboxProperty::IdValue(id) => return id.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl MailboxRight {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
MailboxRight::MayReadItems => "mayReadItems",
|
||||
MailboxRight::MayAddItems => "mayAddItems",
|
||||
MailboxRight::MayRemoveItems => "mayRemoveItems",
|
||||
MailboxRight::MaySetSeen => "maySetSeen",
|
||||
MailboxRight::MaySetKeywords => "maySetKeywords",
|
||||
MailboxRight::MayCreateChild => "mayCreateChild",
|
||||
MailboxRight::MayRename => "mayRename",
|
||||
MailboxRight::MaySubmit => "maySubmit",
|
||||
MailboxRight::MayDelete => "mayDelete",
|
||||
MailboxRight::MayShare => "mayShare",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for MailboxValue {
|
||||
type Property = MailboxProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
MailboxProperty::Id | MailboxProperty::ParentId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(MailboxValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(MailboxValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
MailboxProperty::Role => SpecialUse::parse(value).map(MailboxValue::Role),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
MailboxValue::Id(id) => id.to_string().into(),
|
||||
MailboxValue::IdReference(r) => format!("#{r}").into(),
|
||||
MailboxValue::Role(special_use) => special_use.as_str().unwrap_or_default().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MailboxProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => MailboxProperty::Id,
|
||||
b"name" => MailboxProperty::Name,
|
||||
b"parentId" => MailboxProperty::ParentId,
|
||||
b"role" => MailboxProperty::Role,
|
||||
b"sortOrder" => MailboxProperty::SortOrder,
|
||||
b"totalEmails" => MailboxProperty::TotalEmails,
|
||||
b"unreadEmails" => MailboxProperty::UnreadEmails,
|
||||
b"totalThreads" => MailboxProperty::TotalThreads,
|
||||
b"unreadThreads" => MailboxProperty::UnreadThreads,
|
||||
b"shareWith" => MailboxProperty::ShareWith,
|
||||
b"myRights" => MailboxProperty::MyRights,
|
||||
b"mayReadItems" => MailboxProperty::Rights(MailboxRight::MayReadItems),
|
||||
b"mayAddItems" => MailboxProperty::Rights(MailboxRight::MayAddItems),
|
||||
b"mayRemoveItems" => MailboxProperty::Rights(MailboxRight::MayRemoveItems),
|
||||
b"maySetSeen" => MailboxProperty::Rights(MailboxRight::MaySetSeen),
|
||||
b"maySetKeywords" => MailboxProperty::Rights(MailboxRight::MaySetKeywords),
|
||||
b"mayCreateChild" => MailboxProperty::Rights(MailboxRight::MayCreateChild),
|
||||
b"mayRename" => MailboxProperty::Rights(MailboxRight::MayRename),
|
||||
b"maySubmit" => MailboxProperty::Rights(MailboxRight::MaySubmit),
|
||||
b"mayDelete" => MailboxProperty::Rights(MailboxRight::MayDelete),
|
||||
b"mayShare" => MailboxProperty::Rights(MailboxRight::MayShare),
|
||||
b"isSubscribed" => MailboxProperty::IsSubscribed,
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
MailboxProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &MailboxProperty {
|
||||
if let MailboxProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MailboxSetArguments {
|
||||
pub on_destroy_remove_emails: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MailboxQueryArguments {
|
||||
pub sort_as_tree: Option<bool>,
|
||||
pub filter_as_tree: Option<bool>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for MailboxSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "onDestroyRemoveEmails" {
|
||||
self.on_destroy_remove_emails = map.next_value()?;
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for MailboxQueryArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"sortAsTree" => {
|
||||
self.sort_as_tree = map.next_value()?;
|
||||
},
|
||||
b"filterAsTree" => {
|
||||
self.filter_as_tree = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MailboxProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
MailboxProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Mailbox {
|
||||
type Property = MailboxProperty;
|
||||
|
||||
type Element = MailboxValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = MailboxFilter;
|
||||
|
||||
type Comparator = MailboxComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = MailboxSetArguments;
|
||||
|
||||
type QueryArguments = MailboxQueryArguments;
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = MailboxProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapSharedObject for Mailbox {
|
||||
type Right = MailboxRight;
|
||||
|
||||
const SHARE_WITH_PROPERTY: Self::Property = MailboxProperty::ShareWith;
|
||||
}
|
||||
|
||||
impl From<Id> for MailboxProperty {
|
||||
fn from(id: Id) -> Self {
|
||||
MailboxProperty::IdValue(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MailboxProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: MailboxProperty) -> Result<Self, Self::Error> {
|
||||
if let MailboxProperty::IdValue(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MailboxProperty> for MailboxRight {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: MailboxProperty) -> Result<Self, Self::Error> {
|
||||
if let MailboxProperty::Rights(right) = value {
|
||||
Ok(right)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MailboxFilter {
|
||||
Name(String),
|
||||
ParentId(Option<MaybeIdReference<Id>>),
|
||||
Role(Option<SpecialUse>),
|
||||
HasAnyRole(bool),
|
||||
IsSubscribed(bool),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MailboxComparator {
|
||||
SortOrder,
|
||||
Name,
|
||||
ParentId,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for MailboxFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"name" => {
|
||||
*self = MailboxFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"parentId" => {
|
||||
*self = MailboxFilter::ParentId(map.next_value()?);
|
||||
},
|
||||
b"role" => {
|
||||
*self = MailboxFilter::Role(map.next_value::<Option<RoleWrapper>>()?.map(|r| r.0));
|
||||
},
|
||||
b"hasAnyRole" => {
|
||||
*self = MailboxFilter::HasAnyRole(map.next_value()?);
|
||||
},
|
||||
b"isSubscribed" => {
|
||||
*self = MailboxFilter::IsSubscribed(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = MailboxFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for MailboxComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"sortOrder" => {
|
||||
*self = MailboxComparator::SortOrder;
|
||||
},
|
||||
b"name" => {
|
||||
*self = MailboxComparator::Name;
|
||||
},
|
||||
b"parentId" => {
|
||||
*self = MailboxComparator::ParentId;
|
||||
},
|
||||
_ => {
|
||||
*self = MailboxComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MailboxFilter {
|
||||
fn default() -> Self {
|
||||
MailboxFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MailboxComparator {
|
||||
fn default() -> Self {
|
||||
MailboxComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
struct RoleWrapper(SpecialUse);
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for RoleWrapper {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
SpecialUse::parse(<&str>::deserialize(deserializer)?)
|
||||
.map(RoleWrapper)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP role"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for MailboxValue {
|
||||
fn from(id: Id) -> Self {
|
||||
MailboxValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for MailboxValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let MailboxValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let MailboxValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let MailboxValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = MailboxValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapRight for MailboxRight {
|
||||
fn to_acl(&self) -> &'static [Acl] {
|
||||
match self {
|
||||
MailboxRight::MayReadItems => &[Acl::Read, Acl::ReadItems],
|
||||
MailboxRight::MayAddItems => &[Acl::AddItems],
|
||||
MailboxRight::MayRemoveItems => &[Acl::RemoveItems],
|
||||
MailboxRight::MaySetSeen => &[Acl::ModifyItems],
|
||||
MailboxRight::MaySetKeywords => &[Acl::ModifyItems],
|
||||
MailboxRight::MayCreateChild => &[Acl::CreateChild],
|
||||
MailboxRight::MayRename => &[Acl::Modify],
|
||||
MailboxRight::MaySubmit => &[Acl::Submit],
|
||||
MailboxRight::MayDelete => &[Acl::Delete],
|
||||
MailboxRight::MayShare => &[Acl::Share],
|
||||
}
|
||||
}
|
||||
|
||||
fn all_rights() -> &'static [Self] {
|
||||
&[
|
||||
MailboxRight::MayReadItems,
|
||||
MailboxRight::MayAddItems,
|
||||
MailboxRight::MayRemoveItems,
|
||||
MailboxRight::MaySetSeen,
|
||||
MailboxRight::MaySetKeywords,
|
||||
MailboxRight::MayCreateChild,
|
||||
MailboxRight::MayRename,
|
||||
MailboxRight::MaySubmit,
|
||||
MailboxRight::MayDelete,
|
||||
MailboxRight::MayShare,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MailboxRight> for MailboxProperty {
|
||||
fn from(right: MailboxRight) -> Self {
|
||||
MailboxProperty::Rights(right)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for MailboxProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let MailboxProperty::IdValue(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let MailboxProperty::IdValue(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = MailboxProperty::IdValue(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::request::deserialize::DeserializeArguments;
|
||||
use jmap_tools::{Element, Null, Property};
|
||||
use serde::Serialize;
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
use types::{acl::Acl, blob::BlobId, id::Id};
|
||||
|
||||
pub mod addressbook;
|
||||
pub mod blob;
|
||||
pub mod calendar;
|
||||
pub mod calendar_event;
|
||||
pub mod calendar_event_notification;
|
||||
pub mod contact;
|
||||
pub mod email;
|
||||
pub mod email_submission;
|
||||
pub mod file_node;
|
||||
pub mod identity;
|
||||
pub mod mailbox;
|
||||
pub mod participant_identity;
|
||||
pub mod principal;
|
||||
pub mod push_subscription;
|
||||
pub mod quota;
|
||||
pub mod registry;
|
||||
pub mod search_snippet;
|
||||
pub mod share_notification;
|
||||
pub mod sieve;
|
||||
pub mod thread;
|
||||
pub mod vacation_response;
|
||||
|
||||
pub trait JmapObject: std::fmt::Debug {
|
||||
type Property: Property + JmapObjectId + FromStr + Debug + Sync + Send;
|
||||
type Element: Element<Property = Self::Property> + JmapObjectId + Debug + Sync + Send;
|
||||
type Id: FromStr + TryFrom<AnyId> + Into<Self::Element> + Serialize + Debug + Sync + Send;
|
||||
|
||||
type Filter: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type Comparator: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
|
||||
type GetArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type SetArguments<'de>: Default + DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type QueryArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type CopyArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type ParseArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
|
||||
const ID_PROPERTY: Self::Property;
|
||||
}
|
||||
|
||||
pub trait JmapSharedObject: JmapObject {
|
||||
type Right: JmapRight + Into<Self::Property> + Debug + Clone + Copy + Sync + Send;
|
||||
|
||||
const SHARE_WITH_PROPERTY: Self::Property;
|
||||
}
|
||||
|
||||
pub trait JmapRight: Clone + Copy + Sized + 'static {
|
||||
fn all_rights() -> &'static [Self];
|
||||
fn to_acl(&self) -> &'static [Acl];
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum AnyId {
|
||||
Id(Id),
|
||||
BlobId(BlobId),
|
||||
}
|
||||
|
||||
pub trait JmapObjectId {
|
||||
fn as_id(&self) -> Option<Id>;
|
||||
fn as_any_id(&self) -> Option<AnyId>;
|
||||
fn as_id_ref(&self) -> Option<&str>;
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum MaybeReference<T: FromStr> {
|
||||
Value(T),
|
||||
Reference(String),
|
||||
ParseError,
|
||||
}
|
||||
|
||||
fn parse_ref<T: FromStr>(value: &str) -> MaybeReference<T> {
|
||||
if let Some(reference) = value.strip_prefix('#') {
|
||||
MaybeReference::Reference(reference.to_string())
|
||||
} else {
|
||||
T::from_str(value)
|
||||
.map(MaybeReference::Value)
|
||||
.unwrap_or(MaybeReference::ParseError)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for AnyId {
|
||||
fn from(value: Id) -> Self {
|
||||
AnyId::Id(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlobId> for AnyId {
|
||||
fn from(value: BlobId) -> Self {
|
||||
AnyId::BlobId(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyId> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AnyId) -> Result<Self, Self::Error> {
|
||||
if let AnyId::Id(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyId> for BlobId {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AnyId) -> Result<Self, Self::Error> {
|
||||
if let AnyId::BlobId(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for AnyId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = <&str>::deserialize(deserializer)?;
|
||||
if let Some(blob_id) = BlobId::from_base32(value) {
|
||||
Ok(AnyId::BlobId(blob_id))
|
||||
} else if let Ok(id) = Id::from_str(value) {
|
||||
Ok(AnyId::Id(id))
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"Invalid AnyId: {}",
|
||||
value
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||
pub struct NullObject;
|
||||
|
||||
impl JmapObject for NullObject {
|
||||
type Property = Null;
|
||||
type Element = Null;
|
||||
type Id = Null;
|
||||
|
||||
type Filter = ();
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
type SetArguments<'de> = ();
|
||||
type QueryArguments = ();
|
||||
type CopyArguments = ();
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = Null;
|
||||
}
|
||||
|
||||
impl JmapRight for Null {
|
||||
fn all_rights() -> &'static [Self] {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn to_acl(&self) -> &'static [Acl] {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for NullObject {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(_: &str) -> Result<Self, Self::Err> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for Null {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyId> for Null {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(_: AnyId) -> Result<Self, Self::Error> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::{deserialize::DeserializeArguments, reference::MaybeIdReference},
|
||||
};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ParticipantIdentity;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ParticipantIdentityProperty {
|
||||
Id,
|
||||
Name,
|
||||
CalendarAddress,
|
||||
IsDefault,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ParticipantIdentityValue {
|
||||
Id(Id),
|
||||
}
|
||||
|
||||
impl Property for ParticipantIdentityProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
ParticipantIdentityProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ParticipantIdentityProperty::Id => "id",
|
||||
ParticipantIdentityProperty::Name => "name",
|
||||
ParticipantIdentityProperty::CalendarAddress => "calendarAddress",
|
||||
ParticipantIdentityProperty::IsDefault => "isDefault",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ParticipantIdentityValue {
|
||||
type Property = ParticipantIdentityProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
ParticipantIdentityProperty::Id => {
|
||||
Id::from_str(value).ok().map(ParticipantIdentityValue::Id)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ParticipantIdentityValue::Id(id) => id.to_string().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParticipantIdentityProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => ParticipantIdentityProperty::Id,
|
||||
b"name" => ParticipantIdentityProperty::Name,
|
||||
b"calendarAddress" => ParticipantIdentityProperty::CalendarAddress,
|
||||
b"isDefault" => ParticipantIdentityProperty::IsDefault
|
||||
)
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ParticipantIdentityProperty::Id => "id",
|
||||
ParticipantIdentityProperty::Name => "name",
|
||||
ParticipantIdentityProperty::CalendarAddress => "calendarAddress",
|
||||
ParticipantIdentityProperty::IsDefault => "isDefault",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ParticipantIdentitySetArguments {
|
||||
pub on_success_set_is_default: Option<MaybeIdReference<Id>>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ParticipantIdentitySetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onSuccessSetIsDefault" => {
|
||||
self.on_success_set_is_default = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ParticipantIdentityProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
ParticipantIdentityProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for ParticipantIdentity {
|
||||
type Property = ParticipantIdentityProperty;
|
||||
|
||||
type Element = ParticipantIdentityValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ParticipantIdentitySetArguments;
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = ParticipantIdentityProperty::Id;
|
||||
}
|
||||
|
||||
impl TryFrom<ParticipantIdentityProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(_: ParticipantIdentityProperty) -> Result<Self, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for ParticipantIdentityValue {
|
||||
fn from(id: Id) -> Self {
|
||||
ParticipantIdentityValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for ParticipantIdentityValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
let ParticipantIdentityValue::Id(id) = self;
|
||||
Some(*id)
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
let ParticipantIdentityValue::Id(id) = self;
|
||||
Some(AnyId::Id(*id))
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(new_id) = new_id {
|
||||
*self = ParticipantIdentityValue::Id(new_id);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for ParticipantIdentityProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParticipantIdentityProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::{capability::Capability, deserialize::DeserializeArguments},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Principal;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PrincipalProperty {
|
||||
Id,
|
||||
Type,
|
||||
Name,
|
||||
Description,
|
||||
Email,
|
||||
Timezone,
|
||||
Capabilities,
|
||||
Accounts,
|
||||
IdValue(Id),
|
||||
Capability(Capability),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PrincipalValue {
|
||||
Id(Id),
|
||||
Type(PrincipalType),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PrincipalType {
|
||||
Individual,
|
||||
Group,
|
||||
Resource,
|
||||
Location,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl Property for PrincipalProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
PrincipalProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
PrincipalProperty::Capabilities => "capabilities",
|
||||
PrincipalProperty::Description => "description",
|
||||
PrincipalProperty::Email => "email",
|
||||
PrincipalProperty::Id => "id",
|
||||
PrincipalProperty::Name => "name",
|
||||
PrincipalProperty::Timezone => "timezone",
|
||||
PrincipalProperty::Type => "type",
|
||||
PrincipalProperty::Accounts => "accounts",
|
||||
PrincipalProperty::Capability(cap) => cap.as_str(),
|
||||
PrincipalProperty::IdValue(id) => return id.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for PrincipalValue {
|
||||
type Property = PrincipalProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
PrincipalProperty::Id => Id::from_str(value).ok().map(PrincipalValue::Id),
|
||||
PrincipalProperty::Type => PrincipalType::parse(value).map(PrincipalValue::Type),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
PrincipalValue::Id(id) => id.to_string().into(),
|
||||
PrincipalValue::Type(t) => t.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrincipalProperty {
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => PrincipalProperty::Id,
|
||||
b"type" => PrincipalProperty::Type,
|
||||
b"name" => PrincipalProperty::Name,
|
||||
b"description" => PrincipalProperty::Description,
|
||||
b"email" => PrincipalProperty::Email,
|
||||
b"timeZone" => PrincipalProperty::Timezone,
|
||||
b"capabilities" => PrincipalProperty::Capabilities,
|
||||
b"accounts" => PrincipalProperty::Accounts,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
PrincipalProperty::Id => "id",
|
||||
PrincipalProperty::Type => "type",
|
||||
PrincipalProperty::Name => "name",
|
||||
PrincipalProperty::Description => "description",
|
||||
PrincipalProperty::Email => "email",
|
||||
PrincipalProperty::Timezone => "timeZone",
|
||||
PrincipalProperty::Capabilities => "capabilities",
|
||||
PrincipalProperty::Accounts => "accounts",
|
||||
PrincipalProperty::Capability(cap) => cap.as_str(),
|
||||
PrincipalProperty::IdValue(_) => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrincipalType {
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(s.as_bytes(),
|
||||
b"individual" => PrincipalType::Individual,
|
||||
b"group" => PrincipalType::Group,
|
||||
b"resource" => PrincipalType::Resource,
|
||||
b"location" => PrincipalType::Location,
|
||||
b"other" => PrincipalType::Other,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
PrincipalType::Individual => "individual",
|
||||
PrincipalType::Group => "group",
|
||||
PrincipalType::Resource => "resource",
|
||||
PrincipalType::Location => "location",
|
||||
PrincipalType::Other => "other",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrincipalProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
PrincipalProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Principal {
|
||||
type Property = PrincipalProperty;
|
||||
|
||||
type Element = PrincipalValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = PrincipalFilter;
|
||||
|
||||
type Comparator = PrincipalComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = PrincipalProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PrincipalFilter {
|
||||
AccountIds(Vec<Id>),
|
||||
Email(String),
|
||||
Name(String),
|
||||
Text(String),
|
||||
Type(PrincipalType),
|
||||
Timezone(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PrincipalComparator {
|
||||
Name,
|
||||
Email,
|
||||
Type,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for PrincipalFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"accountIds" => {
|
||||
*self = PrincipalFilter::AccountIds(map.next_value()?);
|
||||
},
|
||||
b"email" => {
|
||||
*self = PrincipalFilter::Email(map.next_value()?);
|
||||
},
|
||||
b"name" => {
|
||||
*self = PrincipalFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = PrincipalFilter::Text(map.next_value()?);
|
||||
},
|
||||
b"type" => {
|
||||
*self = PrincipalFilter::Type(map.next_value()?);
|
||||
},
|
||||
b"timeZone" => {
|
||||
*self = PrincipalFilter::Timezone(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = PrincipalFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for PrincipalComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = PrincipalComparator::Name;
|
||||
},
|
||||
b"email" => {
|
||||
*self = PrincipalComparator::Email;
|
||||
},
|
||||
b"type" => {
|
||||
*self = PrincipalComparator::Type;
|
||||
},
|
||||
_ => {
|
||||
*self = PrincipalComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PrincipalFilter {
|
||||
fn default() -> Self {
|
||||
PrincipalFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PrincipalComparator {
|
||||
fn default() -> Self {
|
||||
PrincipalComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for PrincipalType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
PrincipalType::parse(<&str>::deserialize(deserializer)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid JMAP PrincipalType"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for PrincipalValue {
|
||||
fn from(id: Id) -> Self {
|
||||
PrincipalValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for PrincipalValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let PrincipalValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let PrincipalValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = PrincipalValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PrincipalFilter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
PrincipalFilter::AccountIds(_) => "accountIds",
|
||||
PrincipalFilter::Email(_) => "email",
|
||||
PrincipalFilter::Name(_) => "name",
|
||||
PrincipalFilter::Text(_) => "text",
|
||||
PrincipalFilter::Type(_) => "type",
|
||||
PrincipalFilter::Timezone(_) => "timezone",
|
||||
PrincipalFilter::_T(other) => other,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for PrincipalProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PrincipalProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::object::email::{EmailProperty, HeaderForm, HeaderProperty};
|
||||
use crate::object::{AnyId, JmapObject, JmapObjectId};
|
||||
use crate::types::date::UTCDate;
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem};
|
||||
use jmap_tools::{Key, Property};
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use types::{id::Id, type_state::DataType};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PushSubscription;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PushSubscriptionProperty {
|
||||
Id,
|
||||
DeviceClientId,
|
||||
Url,
|
||||
Keys,
|
||||
P256dh,
|
||||
Auth,
|
||||
VerificationCode,
|
||||
Expires,
|
||||
Types,
|
||||
EmailPush,
|
||||
|
||||
// Other
|
||||
Pointer(JsonPointer<PushSubscriptionProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PushSubscriptionValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
Types(DataType),
|
||||
}
|
||||
|
||||
impl Property for PushSubscriptionProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
PushSubscriptionProperty::parse(value, key.is_none())
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
PushSubscriptionProperty::DeviceClientId => "deviceClientId",
|
||||
PushSubscriptionProperty::Expires => "expires",
|
||||
PushSubscriptionProperty::Id => "id",
|
||||
PushSubscriptionProperty::Keys => "keys",
|
||||
PushSubscriptionProperty::Types => "types",
|
||||
PushSubscriptionProperty::Url => "url",
|
||||
PushSubscriptionProperty::EmailPush => "emailPush",
|
||||
PushSubscriptionProperty::VerificationCode => "verificationCode",
|
||||
PushSubscriptionProperty::P256dh => "p256dh",
|
||||
PushSubscriptionProperty::Auth => "auth",
|
||||
PushSubscriptionProperty::Pointer(json_pointer) => {
|
||||
return json_pointer.to_string().into();
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSubscriptionProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => PushSubscriptionProperty::Id,
|
||||
b"deviceClientId" => PushSubscriptionProperty::DeviceClientId,
|
||||
b"url" => PushSubscriptionProperty::Url,
|
||||
b"keys" => PushSubscriptionProperty::Keys,
|
||||
b"p256dh" => PushSubscriptionProperty::P256dh,
|
||||
b"auth" => PushSubscriptionProperty::Auth,
|
||||
b"verificationCode" => PushSubscriptionProperty::VerificationCode,
|
||||
b"expires" => PushSubscriptionProperty::Expires,
|
||||
b"types" => PushSubscriptionProperty::Types,
|
||||
b"emailPush" => PushSubscriptionProperty::EmailPush,
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
PushSubscriptionProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &PushSubscriptionProperty {
|
||||
if let PushSubscriptionProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for PushSubscriptionValue {
|
||||
type Property = PushSubscriptionProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
PushSubscriptionProperty::Id => {
|
||||
Id::from_str(value).ok().map(PushSubscriptionValue::Id)
|
||||
}
|
||||
PushSubscriptionProperty::Types => {
|
||||
DataType::parse(value).map(PushSubscriptionValue::Types)
|
||||
}
|
||||
PushSubscriptionProperty::Expires => UTCDate::from_str(value)
|
||||
.ok()
|
||||
.map(PushSubscriptionValue::Date),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
PushSubscriptionValue::Id(id) => id.to_string().into(),
|
||||
PushSubscriptionValue::Date(utcdate) => utcdate.to_string().into(),
|
||||
PushSubscriptionValue::Types(data_type) => data_type.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PushSubscriptionProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
PushSubscriptionProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EmailPushProperty {
|
||||
Id,
|
||||
BlobId,
|
||||
ThreadId,
|
||||
MailboxIds,
|
||||
Keywords,
|
||||
Size,
|
||||
ReceivedAt,
|
||||
MessageId,
|
||||
InReplyTo,
|
||||
References,
|
||||
Sender,
|
||||
From,
|
||||
To,
|
||||
Cc,
|
||||
Bcc,
|
||||
ReplyTo,
|
||||
Subject,
|
||||
SentAt,
|
||||
Preview,
|
||||
HasAttachment,
|
||||
BodyStructure,
|
||||
BodyValues,
|
||||
TextBody,
|
||||
HtmlBody,
|
||||
Attachments,
|
||||
Headers,
|
||||
Header(EmailPushHeaderProperty),
|
||||
}
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EmailPushHeaderProperty {
|
||||
pub form: EmailPushHeaderForm,
|
||||
pub header: String,
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, Copy, PartialEq, Eq, Default,
|
||||
)]
|
||||
#[rkyv(compare(PartialEq), derive(Debug))]
|
||||
#[repr(u8)]
|
||||
pub enum EmailPushHeaderForm {
|
||||
#[default]
|
||||
Raw = 0,
|
||||
Text = 1,
|
||||
Addresses = 2,
|
||||
GroupedAddresses = 3,
|
||||
MessageIds = 4,
|
||||
Date = 5,
|
||||
Urls = 6,
|
||||
}
|
||||
|
||||
impl TryFrom<&EmailProperty> for EmailPushProperty {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &EmailProperty) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
EmailProperty::Id => EmailPushProperty::Id,
|
||||
EmailProperty::BlobId => EmailPushProperty::BlobId,
|
||||
EmailProperty::ThreadId => EmailPushProperty::ThreadId,
|
||||
EmailProperty::MailboxIds => EmailPushProperty::MailboxIds,
|
||||
EmailProperty::Keywords => EmailPushProperty::Keywords,
|
||||
EmailProperty::Size => EmailPushProperty::Size,
|
||||
EmailProperty::ReceivedAt => EmailPushProperty::ReceivedAt,
|
||||
EmailProperty::MessageId => EmailPushProperty::MessageId,
|
||||
EmailProperty::InReplyTo => EmailPushProperty::InReplyTo,
|
||||
EmailProperty::References => EmailPushProperty::References,
|
||||
EmailProperty::Sender => EmailPushProperty::Sender,
|
||||
EmailProperty::From => EmailPushProperty::From,
|
||||
EmailProperty::To => EmailPushProperty::To,
|
||||
EmailProperty::Cc => EmailPushProperty::Cc,
|
||||
EmailProperty::Bcc => EmailPushProperty::Bcc,
|
||||
EmailProperty::ReplyTo => EmailPushProperty::ReplyTo,
|
||||
EmailProperty::Subject => EmailPushProperty::Subject,
|
||||
EmailProperty::SentAt => EmailPushProperty::SentAt,
|
||||
EmailProperty::Preview => EmailPushProperty::Preview,
|
||||
EmailProperty::HasAttachment => EmailPushProperty::HasAttachment,
|
||||
EmailProperty::BodyStructure => EmailPushProperty::BodyStructure,
|
||||
EmailProperty::BodyValues => EmailPushProperty::BodyValues,
|
||||
EmailProperty::TextBody => EmailPushProperty::TextBody,
|
||||
EmailProperty::HtmlBody => EmailPushProperty::HtmlBody,
|
||||
EmailProperty::Attachments => EmailPushProperty::Attachments,
|
||||
EmailProperty::Headers => EmailPushProperty::Headers,
|
||||
EmailProperty::Header(header) => EmailPushProperty::Header(EmailPushHeaderProperty {
|
||||
form: (&header.form).into(),
|
||||
header: header.header.clone(),
|
||||
all: header.all,
|
||||
}),
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EmailPushProperty> for EmailProperty {
|
||||
fn from(value: &EmailPushProperty) -> Self {
|
||||
match value {
|
||||
EmailPushProperty::Id => EmailProperty::Id,
|
||||
EmailPushProperty::BlobId => EmailProperty::BlobId,
|
||||
EmailPushProperty::ThreadId => EmailProperty::ThreadId,
|
||||
EmailPushProperty::MailboxIds => EmailProperty::MailboxIds,
|
||||
EmailPushProperty::Keywords => EmailProperty::Keywords,
|
||||
EmailPushProperty::Size => EmailProperty::Size,
|
||||
EmailPushProperty::ReceivedAt => EmailProperty::ReceivedAt,
|
||||
EmailPushProperty::MessageId => EmailProperty::MessageId,
|
||||
EmailPushProperty::InReplyTo => EmailProperty::InReplyTo,
|
||||
EmailPushProperty::References => EmailProperty::References,
|
||||
EmailPushProperty::Sender => EmailProperty::Sender,
|
||||
EmailPushProperty::From => EmailProperty::From,
|
||||
EmailPushProperty::To => EmailProperty::To,
|
||||
EmailPushProperty::Cc => EmailProperty::Cc,
|
||||
EmailPushProperty::Bcc => EmailProperty::Bcc,
|
||||
EmailPushProperty::ReplyTo => EmailProperty::ReplyTo,
|
||||
EmailPushProperty::Subject => EmailProperty::Subject,
|
||||
EmailPushProperty::SentAt => EmailProperty::SentAt,
|
||||
EmailPushProperty::Preview => EmailProperty::Preview,
|
||||
EmailPushProperty::HasAttachment => EmailProperty::HasAttachment,
|
||||
EmailPushProperty::BodyStructure => EmailProperty::BodyStructure,
|
||||
EmailPushProperty::BodyValues => EmailProperty::BodyValues,
|
||||
EmailPushProperty::TextBody => EmailProperty::TextBody,
|
||||
EmailPushProperty::HtmlBody => EmailProperty::HtmlBody,
|
||||
EmailPushProperty::Attachments => EmailProperty::Attachments,
|
||||
EmailPushProperty::Headers => EmailProperty::Headers,
|
||||
EmailPushProperty::Header(header) => EmailProperty::Header(HeaderProperty {
|
||||
form: (&header.form).into(),
|
||||
header: header.header.clone(),
|
||||
all: header.all,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedEmailPushProperty> for EmailProperty {
|
||||
fn from(value: &ArchivedEmailPushProperty) -> Self {
|
||||
match value {
|
||||
ArchivedEmailPushProperty::Id => EmailProperty::Id,
|
||||
ArchivedEmailPushProperty::BlobId => EmailProperty::BlobId,
|
||||
ArchivedEmailPushProperty::ThreadId => EmailProperty::ThreadId,
|
||||
ArchivedEmailPushProperty::MailboxIds => EmailProperty::MailboxIds,
|
||||
ArchivedEmailPushProperty::Keywords => EmailProperty::Keywords,
|
||||
ArchivedEmailPushProperty::Size => EmailProperty::Size,
|
||||
ArchivedEmailPushProperty::ReceivedAt => EmailProperty::ReceivedAt,
|
||||
ArchivedEmailPushProperty::MessageId => EmailProperty::MessageId,
|
||||
ArchivedEmailPushProperty::InReplyTo => EmailProperty::InReplyTo,
|
||||
ArchivedEmailPushProperty::References => EmailProperty::References,
|
||||
ArchivedEmailPushProperty::Sender => EmailProperty::Sender,
|
||||
ArchivedEmailPushProperty::From => EmailProperty::From,
|
||||
ArchivedEmailPushProperty::To => EmailProperty::To,
|
||||
ArchivedEmailPushProperty::Cc => EmailProperty::Cc,
|
||||
ArchivedEmailPushProperty::Bcc => EmailProperty::Bcc,
|
||||
ArchivedEmailPushProperty::ReplyTo => EmailProperty::ReplyTo,
|
||||
ArchivedEmailPushProperty::Subject => EmailProperty::Subject,
|
||||
ArchivedEmailPushProperty::SentAt => EmailProperty::SentAt,
|
||||
ArchivedEmailPushProperty::Preview => EmailProperty::Preview,
|
||||
ArchivedEmailPushProperty::HasAttachment => EmailProperty::HasAttachment,
|
||||
ArchivedEmailPushProperty::BodyStructure => EmailProperty::BodyStructure,
|
||||
ArchivedEmailPushProperty::BodyValues => EmailProperty::BodyValues,
|
||||
ArchivedEmailPushProperty::TextBody => EmailProperty::TextBody,
|
||||
ArchivedEmailPushProperty::HtmlBody => EmailProperty::HtmlBody,
|
||||
ArchivedEmailPushProperty::Attachments => EmailProperty::Attachments,
|
||||
ArchivedEmailPushProperty::Headers => EmailProperty::Headers,
|
||||
ArchivedEmailPushProperty::Header(header) => EmailProperty::Header(HeaderProperty {
|
||||
form: (&header.form).into(),
|
||||
header: header.header.as_str().to_string(),
|
||||
all: header.all,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArchivedEmailPushHeaderForm> for HeaderForm {
|
||||
fn from(value: &ArchivedEmailPushHeaderForm) -> Self {
|
||||
match value {
|
||||
ArchivedEmailPushHeaderForm::Raw => HeaderForm::Raw,
|
||||
ArchivedEmailPushHeaderForm::Text => HeaderForm::Text,
|
||||
ArchivedEmailPushHeaderForm::Addresses => HeaderForm::Addresses,
|
||||
ArchivedEmailPushHeaderForm::GroupedAddresses => HeaderForm::GroupedAddresses,
|
||||
ArchivedEmailPushHeaderForm::MessageIds => HeaderForm::MessageIds,
|
||||
ArchivedEmailPushHeaderForm::Date => HeaderForm::Date,
|
||||
ArchivedEmailPushHeaderForm::Urls => HeaderForm::URLs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&HeaderForm> for EmailPushHeaderForm {
|
||||
fn from(value: &HeaderForm) -> Self {
|
||||
match value {
|
||||
HeaderForm::Raw => EmailPushHeaderForm::Raw,
|
||||
HeaderForm::Text => EmailPushHeaderForm::Text,
|
||||
HeaderForm::Addresses => EmailPushHeaderForm::Addresses,
|
||||
HeaderForm::GroupedAddresses => EmailPushHeaderForm::GroupedAddresses,
|
||||
HeaderForm::MessageIds => EmailPushHeaderForm::MessageIds,
|
||||
HeaderForm::Date => EmailPushHeaderForm::Date,
|
||||
HeaderForm::URLs => EmailPushHeaderForm::Urls,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EmailPushHeaderForm> for HeaderForm {
|
||||
fn from(value: &EmailPushHeaderForm) -> Self {
|
||||
match value {
|
||||
EmailPushHeaderForm::Raw => HeaderForm::Raw,
|
||||
EmailPushHeaderForm::Text => HeaderForm::Text,
|
||||
EmailPushHeaderForm::Addresses => HeaderForm::Addresses,
|
||||
EmailPushHeaderForm::GroupedAddresses => HeaderForm::GroupedAddresses,
|
||||
EmailPushHeaderForm::MessageIds => HeaderForm::MessageIds,
|
||||
EmailPushHeaderForm::Date => HeaderForm::Date,
|
||||
EmailPushHeaderForm::Urls => HeaderForm::URLs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for PushSubscription {
|
||||
type Property = PushSubscriptionProperty;
|
||||
|
||||
type Element = PushSubscriptionValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = PushSubscriptionProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for PushSubscriptionValue {
|
||||
fn from(id: Id) -> Self {
|
||||
PushSubscriptionValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for PushSubscriptionValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
PushSubscriptionValue::Id(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
PushSubscriptionValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = PushSubscriptionValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for PushSubscriptionProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::deserialize::DeserializeArguments,
|
||||
};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{id::Id, type_state::DataType};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Quota;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum QuotaProperty {
|
||||
Id,
|
||||
ResourceType,
|
||||
Used,
|
||||
Name,
|
||||
Scope,
|
||||
Types,
|
||||
HardLimit,
|
||||
WarnLimit,
|
||||
SoftLimit,
|
||||
Description,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum QuotaValue {
|
||||
Id(Id),
|
||||
Types(DataType),
|
||||
}
|
||||
|
||||
impl Property for QuotaProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
QuotaProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
QuotaProperty::Description => "description",
|
||||
QuotaProperty::Id => "id",
|
||||
QuotaProperty::Name => "name",
|
||||
QuotaProperty::Types => "types",
|
||||
QuotaProperty::ResourceType => "resourceType",
|
||||
QuotaProperty::Used => "used",
|
||||
QuotaProperty::HardLimit => "hardLimit",
|
||||
QuotaProperty::Scope => "scope",
|
||||
QuotaProperty::WarnLimit => "warnLimit",
|
||||
QuotaProperty::SoftLimit => "softLimit",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl QuotaProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => QuotaProperty::Id,
|
||||
b"resourceType" => QuotaProperty::ResourceType,
|
||||
b"used" => QuotaProperty::Used,
|
||||
b"name" => QuotaProperty::Name,
|
||||
b"scope" => QuotaProperty::Scope,
|
||||
b"types" => QuotaProperty::Types,
|
||||
b"hardLimit" => QuotaProperty::HardLimit,
|
||||
b"warnLimit" => QuotaProperty::WarnLimit,
|
||||
b"softLimit" => QuotaProperty::SoftLimit,
|
||||
b"description" => QuotaProperty::Description,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for QuotaValue {
|
||||
type Property = QuotaProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
QuotaProperty::Id => Id::from_str(value).ok().map(QuotaValue::Id),
|
||||
QuotaProperty::Types => DataType::parse(value).map(QuotaValue::Types),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
QuotaValue::Id(id) => id.to_string().into(),
|
||||
QuotaValue::Types(data_type) => data_type.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for QuotaProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
QuotaProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Quota {
|
||||
type Property = QuotaProperty;
|
||||
|
||||
type Element = QuotaValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = QuotaFilter;
|
||||
|
||||
type Comparator = QuotaComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = QuotaProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QuotaFilter {
|
||||
Name(String),
|
||||
Type(String),
|
||||
Scope(String),
|
||||
ResourceType(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QuotaComparator {
|
||||
Name,
|
||||
Type,
|
||||
Used,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for QuotaFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"name" => {
|
||||
*self = QuotaFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"type" => {
|
||||
*self = QuotaFilter::Type(map.next_value()?);
|
||||
},
|
||||
b"scope" => {
|
||||
*self = QuotaFilter::Scope(map.next_value()?);
|
||||
},
|
||||
b"resourceType" => {
|
||||
*self = QuotaFilter::ResourceType(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = QuotaFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for QuotaComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = QuotaComparator::Name;
|
||||
},
|
||||
b"type" => {
|
||||
*self = QuotaComparator::Type;
|
||||
},
|
||||
b"used" => {
|
||||
*self = QuotaComparator::Used;
|
||||
},
|
||||
_ => {
|
||||
*self = QuotaComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QuotaFilter {
|
||||
fn default() -> Self {
|
||||
QuotaFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QuotaComparator {
|
||||
fn default() -> Self {
|
||||
QuotaComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for QuotaValue {
|
||||
fn from(id: Id) -> Self {
|
||||
QuotaValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for QuotaValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let QuotaValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
self.as_id().map(AnyId::Id)
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = QuotaValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for QuotaProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::deserialize::DeserializeArguments,
|
||||
};
|
||||
use registry::{jmap::RegistryValue, schema::prelude::Property, types::EnumImpl};
|
||||
use std::borrow::Cow;
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Registry;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RegistryFilter {
|
||||
Property {
|
||||
property: Property,
|
||||
operator: RegistryFilterOperator,
|
||||
value: serde_json::Value,
|
||||
},
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RegistryFilterOperator {
|
||||
Equal,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RegistryComparator {
|
||||
Property(Property),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl JmapObject for Registry {
|
||||
type Property = Property;
|
||||
|
||||
type Element = RegistryValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = RegistryFilter;
|
||||
|
||||
type Comparator = RegistryComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = Property::Id;
|
||||
}
|
||||
|
||||
impl JmapObjectId for Property {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<super::AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: super::AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for RegistryValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let RegistryValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
RegistryValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
RegistryValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let RegistryValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
match new_id {
|
||||
AnyId::Id(id) => {
|
||||
*self = RegistryValue::Id(id);
|
||||
}
|
||||
AnyId::BlobId(id) => {
|
||||
*self = RegistryValue::BlobId(id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for RegistryFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if let Some(property) = Property::parse(key) {
|
||||
let value = map.next_value()?;
|
||||
*self = RegistryFilter::Property {
|
||||
property,
|
||||
operator: RegistryFilterOperator::Equal,
|
||||
value,
|
||||
};
|
||||
return Ok(());
|
||||
} else if let Some((property, operator)) = key.rsplit_once("Is")
|
||||
&& let (Some(property), Some(operator)) = (
|
||||
Property::parse(property),
|
||||
RegistryFilterOperator::parse(operator),
|
||||
)
|
||||
{
|
||||
let value = map.next_value()?;
|
||||
*self = RegistryFilter::Property {
|
||||
property,
|
||||
operator,
|
||||
value,
|
||||
};
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
*self = RegistryFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for RegistryComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
|
||||
if let Some(property) = Property::parse(value.as_ref()) {
|
||||
*self = RegistryComparator::Property(property);
|
||||
} else {
|
||||
*self = RegistryComparator::_T(value.into_owned());
|
||||
}
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryFilterOperator {
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"GreaterThan" => RegistryFilterOperator::GreaterThan,
|
||||
b"GreaterThanOrEqual" => RegistryFilterOperator::GreaterThanOrEqual,
|
||||
b"LessThan" => RegistryFilterOperator::LessThan,
|
||||
b"LessThanOrEqual" => RegistryFilterOperator::LessThanOrEqual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RegistryFilter {
|
||||
fn default() -> Self {
|
||||
RegistryFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RegistryComparator {
|
||||
fn default() -> Self {
|
||||
RegistryComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SearchSnippet;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum SearchSnippetProperty {
|
||||
EmailId,
|
||||
Subject,
|
||||
Preview,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum SearchSnippetValue {
|
||||
Id(Id),
|
||||
}
|
||||
|
||||
impl Property for SearchSnippetProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
SearchSnippetProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
SearchSnippetProperty::Preview => "preview",
|
||||
SearchSnippetProperty::Subject => "subject",
|
||||
SearchSnippetProperty::EmailId => "emailId",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SearchSnippetValue {
|
||||
type Property = SearchSnippetProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
SearchSnippetProperty::EmailId => {
|
||||
Id::from_str(value).ok().map(SearchSnippetValue::Id)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
SearchSnippetValue::Id(id) => id.to_string().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchSnippetProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"emailId" => SearchSnippetProperty::EmailId,
|
||||
b"subject" => SearchSnippetProperty::Subject,
|
||||
b"preview" => SearchSnippetProperty::Preview,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::deserialize::DeserializeArguments,
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, fmt::Display, str::FromStr};
|
||||
use types::{id::Id, type_state::DataType};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ShareNotification;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ShareNotificationProperty {
|
||||
Id,
|
||||
Created,
|
||||
ChangedBy,
|
||||
ChangedByName,
|
||||
ChangedByEmail,
|
||||
ChangedByPrincipalId,
|
||||
ObjectType,
|
||||
ObjectAccountId,
|
||||
ObjectId,
|
||||
OldRights,
|
||||
NewRights,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ShareNotificationValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
ObjectType(DataType),
|
||||
}
|
||||
|
||||
impl Property for ShareNotificationProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
ShareNotificationProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ShareNotificationProperty::Id => "id",
|
||||
ShareNotificationProperty::Created => "created",
|
||||
ShareNotificationProperty::ChangedBy => "changedBy",
|
||||
ShareNotificationProperty::ChangedByName => "name",
|
||||
ShareNotificationProperty::ChangedByEmail => "email",
|
||||
ShareNotificationProperty::ChangedByPrincipalId => "principalId",
|
||||
ShareNotificationProperty::ObjectType => "objectType",
|
||||
ShareNotificationProperty::ObjectAccountId => "objectAccountId",
|
||||
ShareNotificationProperty::ObjectId => "objectId",
|
||||
ShareNotificationProperty::OldRights => "oldRights",
|
||||
ShareNotificationProperty::NewRights => "newRights",
|
||||
ShareNotificationProperty::Name => "name",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ShareNotificationValue {
|
||||
type Property = ShareNotificationProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
ShareNotificationProperty::Id
|
||||
| ShareNotificationProperty::ChangedByPrincipalId
|
||||
| ShareNotificationProperty::ObjectAccountId
|
||||
| ShareNotificationProperty::ObjectId => {
|
||||
Id::from_str(value).ok().map(ShareNotificationValue::Id)
|
||||
}
|
||||
ShareNotificationProperty::Created => UTCDate::from_str(value)
|
||||
.ok()
|
||||
.map(ShareNotificationValue::Date),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ShareNotificationValue::Id(id) => id.to_string().into(),
|
||||
ShareNotificationValue::Date(date) => date.to_string().into(),
|
||||
ShareNotificationValue::ObjectType(ty) => ty.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShareNotificationProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => ShareNotificationProperty::Id,
|
||||
b"created" => ShareNotificationProperty::Created,
|
||||
b"changedBy" => ShareNotificationProperty::ChangedBy,
|
||||
b"name" => ShareNotificationProperty::ChangedByName,
|
||||
b"email" => ShareNotificationProperty::ChangedByEmail,
|
||||
b"principalId" => ShareNotificationProperty::ChangedByPrincipalId,
|
||||
b"objectType" => ShareNotificationProperty::ObjectType,
|
||||
b"objectAccountId" => ShareNotificationProperty::ObjectAccountId,
|
||||
b"objectId" => ShareNotificationProperty::ObjectId,
|
||||
b"oldRights" => ShareNotificationProperty::OldRights,
|
||||
b"newRights" => ShareNotificationProperty::NewRights
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ShareNotificationProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
ShareNotificationProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for ShareNotification {
|
||||
type Property = ShareNotificationProperty;
|
||||
|
||||
type Element = ShareNotificationValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ShareNotificationFilter;
|
||||
|
||||
type Comparator = ShareNotificationComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = ShareNotificationProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ShareNotificationFilter {
|
||||
After(UTCDate),
|
||||
Before(UTCDate),
|
||||
ObjectType(DataType),
|
||||
ObjectAccountId(Id),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ShareNotificationComparator {
|
||||
Created,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ShareNotificationFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"after" => {
|
||||
*self = ShareNotificationFilter::After(map.next_value()?);
|
||||
},
|
||||
b"before" => {
|
||||
*self = ShareNotificationFilter::Before(map.next_value()?);
|
||||
},
|
||||
b"objectType" => {
|
||||
*self = ShareNotificationFilter::ObjectType(map.next_value()?);
|
||||
},
|
||||
b"objectAccountId" => {
|
||||
*self = ShareNotificationFilter::ObjectAccountId(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = ShareNotificationFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ShareNotificationComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"created" => {
|
||||
*self = ShareNotificationComparator::Created;
|
||||
},
|
||||
_ => {
|
||||
*self = ShareNotificationComparator::_T(value.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ShareNotificationFilter {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ShareNotificationFilter::After(_) => "after",
|
||||
ShareNotificationFilter::Before(_) => "before",
|
||||
ShareNotificationFilter::ObjectType(_) => "objectType",
|
||||
ShareNotificationFilter::ObjectAccountId(_) => "objectAccountId",
|
||||
ShareNotificationFilter::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShareNotificationComparator {
|
||||
pub fn into_string(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ShareNotificationComparator::Created => "created",
|
||||
ShareNotificationComparator::_T(s) => return Cow::Owned(s),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ShareNotificationFilter {
|
||||
fn default() -> Self {
|
||||
ShareNotificationFilter::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ShareNotificationComparator {
|
||||
fn default() -> Self {
|
||||
ShareNotificationComparator::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ShareNotificationProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(_: ShareNotificationProperty) -> Result<Self, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for ShareNotificationValue {
|
||||
fn from(id: Id) -> Self {
|
||||
ShareNotificationValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for ShareNotificationValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let ShareNotificationValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let ShareNotificationValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for ShareNotificationProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ShareNotificationProperty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_cow())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, DeserializeArguments, JmapObject, JmapObjectId, MaybeReference, parse_ref},
|
||||
request::reference::MaybeIdReference,
|
||||
};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Sieve;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum SieveProperty {
|
||||
Id,
|
||||
Name,
|
||||
BlobId,
|
||||
IsActive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum SieveValue {
|
||||
Id(Id),
|
||||
BlobId(BlobId),
|
||||
IdReference(String),
|
||||
}
|
||||
|
||||
impl Property for SieveProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
SieveProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
SieveProperty::BlobId => "blobId",
|
||||
SieveProperty::Id => "id",
|
||||
SieveProperty::Name => "name",
|
||||
SieveProperty::IsActive => "isActive",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SieveValue {
|
||||
type Property = SieveProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
SieveProperty::Id => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(SieveValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(SieveValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
SieveProperty::BlobId => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(SieveValue::BlobId(v)),
|
||||
MaybeReference::Reference(v) => Some(SieveValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
SieveValue::Id(id) => id.to_string().into(),
|
||||
SieveValue::BlobId(blob_id) => blob_id.to_string().into(),
|
||||
SieveValue::IdReference(r) => format!("#{r}").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SieveProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => SieveProperty::Id,
|
||||
b"name" => SieveProperty::Name,
|
||||
b"blobId" => SieveProperty::BlobId,
|
||||
b"isActive" => SieveProperty::IsActive,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SieveSetArguments {
|
||||
pub on_success_activate_script: Option<MaybeIdReference<Id>>,
|
||||
pub on_success_deactivate_script: Option<bool>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for SieveSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onSuccessActivateScript" => {
|
||||
self.on_success_activate_script = map.next_value()?;
|
||||
},
|
||||
b"onSuccessDeactivateScript" => {
|
||||
self.on_success_deactivate_script = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SieveProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
SieveProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Sieve {
|
||||
type Property = SieveProperty;
|
||||
|
||||
type Element = SieveValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = SieveFilter;
|
||||
|
||||
type Comparator = SieveComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = SieveSetArguments;
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = SieveProperty::Id;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SieveFilter {
|
||||
Name(String),
|
||||
IsActive(bool),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SieveComparator {
|
||||
Name,
|
||||
IsActive,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for SieveFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"name" => {
|
||||
*self = SieveFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"isActive" => {
|
||||
*self = SieveFilter::IsActive(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = SieveFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for SieveComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"name" => {
|
||||
*self = SieveComparator::Name;
|
||||
},
|
||||
b"isActive" => {
|
||||
*self = SieveComparator::IsActive;
|
||||
},
|
||||
_ => {
|
||||
*self = SieveComparator::_T(key.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SieveFilter {
|
||||
fn default() -> Self {
|
||||
SieveFilter::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SieveComparator {
|
||||
fn default() -> Self {
|
||||
SieveComparator::_T("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for SieveValue {
|
||||
fn from(id: Id) -> Self {
|
||||
SieveValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for SieveValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
SieveValue::Id(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
SieveValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
SieveValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
|
||||
SieveValue::IdReference(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let SieveValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
match new_id {
|
||||
AnyId::Id(id) => {
|
||||
*self = SieveValue::Id(id);
|
||||
}
|
||||
AnyId::BlobId(id) => {
|
||||
*self = SieveValue::BlobId(id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for SieveProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
use crate::object::{AnyId, JmapObject, JmapObjectId};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Thread;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ThreadProperty {
|
||||
Id,
|
||||
EmailIds,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ThreadValue {
|
||||
Id(Id),
|
||||
}
|
||||
|
||||
impl Property for ThreadProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
ThreadProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ThreadProperty::Id => "id",
|
||||
ThreadProperty::EmailIds => "emailIds",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ThreadValue {
|
||||
type Property = ThreadProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(_) = key {
|
||||
Id::from_str(value).ok().map(ThreadValue::Id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ThreadValue::Id(id) => id.to_string().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => ThreadProperty::Id,
|
||||
b"emailIds" => ThreadProperty::EmailIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ThreadProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
ThreadProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for Thread {
|
||||
type Property = ThreadProperty;
|
||||
|
||||
type Element = ThreadValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = ThreadProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for ThreadValue {
|
||||
fn from(id: Id) -> Self {
|
||||
ThreadValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for ThreadValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
ThreadValue::Id(id) => Some(*id),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
self.as_id().map(AnyId::Id)
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = ThreadValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for ThreadProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use jmap_tools::{Element, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct VacationResponse;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum VacationResponseProperty {
|
||||
Id,
|
||||
IsEnabled,
|
||||
FromDate,
|
||||
ToDate,
|
||||
Subject,
|
||||
TextBody,
|
||||
HtmlBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum VacationResponseValue {
|
||||
Id(Id),
|
||||
Date(UTCDate),
|
||||
}
|
||||
|
||||
impl Property for VacationResponseProperty {
|
||||
fn try_parse(_: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
VacationResponseProperty::parse(value)
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
VacationResponseProperty::HtmlBody => "htmlBody",
|
||||
VacationResponseProperty::Id => "id",
|
||||
VacationResponseProperty::TextBody => "textBody",
|
||||
VacationResponseProperty::FromDate => "fromDate",
|
||||
VacationResponseProperty::IsEnabled => "isEnabled",
|
||||
VacationResponseProperty::ToDate => "toDate",
|
||||
VacationResponseProperty::Subject => "subject",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for VacationResponseValue {
|
||||
type Property = VacationResponseProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop {
|
||||
VacationResponseProperty::Id => {
|
||||
Id::from_str(value).ok().map(VacationResponseValue::Id)
|
||||
}
|
||||
VacationResponseProperty::FromDate | VacationResponseProperty::ToDate => {
|
||||
UTCDate::from_str(value)
|
||||
.ok()
|
||||
.map(VacationResponseValue::Date)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
VacationResponseValue::Id(id) => id.to_string().into(),
|
||||
VacationResponseValue::Date(utcdate) => utcdate.to_string().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VacationResponseProperty {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => VacationResponseProperty::Id,
|
||||
b"isEnabled" => VacationResponseProperty::IsEnabled,
|
||||
b"fromDate" => VacationResponseProperty::FromDate,
|
||||
b"toDate" => VacationResponseProperty::ToDate,
|
||||
b"textBody" => VacationResponseProperty::TextBody,
|
||||
b"htmlBody" => VacationResponseProperty::HtmlBody,
|
||||
b"subject" => VacationResponseProperty::Subject,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VacationResponseProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
VacationResponseProperty::parse(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for VacationResponse {
|
||||
type Property = VacationResponseProperty;
|
||||
|
||||
type Element = VacationResponseValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = VacationResponseProperty::Id;
|
||||
}
|
||||
|
||||
impl From<Id> for VacationResponseValue {
|
||||
fn from(id: Id) -> Self {
|
||||
VacationResponseValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for VacationResponseValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
match self {
|
||||
VacationResponseValue::Id(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
VacationResponseValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, new_id: AnyId) -> bool {
|
||||
if let AnyId::Id(id) = new_id {
|
||||
*self = VacationResponseValue::Id(id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for VacationResponseProperty {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_set_id(&mut self, _: AnyId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user