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:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+354
View File
@@ -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())
}
}
+233
View File
@@ -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
}
}
+475
View File
@@ -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())
}
}
+334
View File
@@ -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
}
}
+967
View File
@@ -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
}
}
+826
View File
@@ -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())
}
}
+190
View File
@@ -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
}
}
+516
View File
@@ -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
}
}
}
+211
View File
@@ -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)
}
}
+354
View File
@@ -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
}
}
+267
View File
@@ -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
}
}
+200
View File
@@ -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())
}
}
+284
View File
@@ -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
}
}
+147
View File
@@ -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
}
}