),
+}
+
+#[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 {
+ 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(key: &Key<'_, Self::Property>, value: &str) -> Option {
+ 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 {
+ 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,
+ pub on_success_set_is_default: Option,
+}
+
+impl<'de> DeserializeArguments<'de> for AddressBookSetArguments {
+ fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
+ where
+ A: serde::de::MapAccess<'de>,
+ {
+ hashify::fnc_map!(key.as_bytes(),
+ b"onDestroyRemoveContents" => {
+ self.on_destroy_remove_contents = map.next_value()?;
+ },
+ b"onSuccessSetIsDefault" => {
+ self.on_success_set_is_default = map.next_value()?;
+ },
+ _ => {
+ let _ = map.next_value::()?;
+ }
+ );
+
+ Ok(())
+ }
+}
+
+impl FromStr for AddressBookProperty {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result {
+ 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 for AddressBookProperty {
+ fn from(id: Id) -> Self {
+ AddressBookProperty::IdValue(id)
+ }
+}
+
+impl TryFrom for Id {
+ type Error = ();
+
+ fn try_from(value: AddressBookProperty) -> Result {
+ if let AddressBookProperty::IdValue(id) = value {
+ Ok(id)
+ } else {
+ Err(())
+ }
+ }
+}
+
+impl TryFrom for AddressBookRight {
+ type Error = ();
+
+ fn try_from(value: AddressBookProperty) -> Result {
+ if let AddressBookProperty::Rights(right) = value {
+ Ok(right)
+ } else {
+ Err(())
+ }
+ }
+}
+
+impl From for AddressBookValue {
+ fn from(id: Id) -> Self {
+ AddressBookValue::Id(id)
+ }
+}
+
+impl JmapObjectId for AddressBookValue {
+ fn as_id(&self) -> Option {
+ if let AddressBookValue::Id(id) = self {
+ Some(*id)
+ } else {
+ None
+ }
+ }
+
+ fn as_any_id(&self) -> Option {
+ 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
+ }
+ }
+}
+
+impl TryFrom for AddressBookValue {
+ type Error = ();
+
+ fn try_from(value: AnyId) -> Result {
+ if let AnyId::Id(id) = value {
+ Ok(AddressBookValue::Id(id))
+ } else {
+ Err(())
+ }
+ }
+}
+
+impl JmapRight for AddressBookRight {
+ fn from_acl(acl: Acl) -> &'static [Self] {
+ match acl {
+ Acl::ReadItems => &[AddressBookRight::MayRead],
+ Acl::RemoveItems => &[AddressBookRight::MayDelete],
+ Acl::ModifyItems => &[AddressBookRight::MayWrite],
+ Acl::Delete => &[AddressBookRight::MayDelete],
+ Acl::Administer => &[AddressBookRight::MayShare],
+ _ => &[],
+ }
+ }
+
+ fn to_acl(&self) -> &'static [Acl] {
+ match self {
+ AddressBookRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
+ AddressBookRight::MayShare => &[Acl::Administer],
+ 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 for AddressBookProperty {
+ fn from(right: AddressBookRight) -> Self {
+ AddressBookProperty::Rights(right)
+ }
+}
diff --git a/crates/jmap-proto/src/object/blob.rs b/crates/jmap-proto/src/object/blob.rs
index 6336a5a9..05ed7ab0 100644
--- a/crates/jmap-proto/src/object/blob.rs
+++ b/crates/jmap-proto/src/object/blob.rs
@@ -183,6 +183,8 @@ impl JmapObject for Blob {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = BlobProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/contact.rs b/crates/jmap-proto/src/object/contact.rs
new file mode 100644
index 00000000..b3927f7d
--- /dev/null
+++ b/crates/jmap-proto/src/object/contact.rs
@@ -0,0 +1,230 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
+ */
+
+use crate::{
+ object::{AnyId, JmapObject, JmapObjectId},
+ request::deserialize::DeserializeArguments,
+ types::date::UTCDate,
+};
+use calcard::jscontact::{JSContactProperty, JSContactValue};
+use std::borrow::Cow;
+use types::{blob::BlobId, id::Id};
+
+#[derive(Debug, Clone, Default)]
+pub struct ContactCard;
+
+impl JmapObject for ContactCard {
+ type Property = JSContactProperty;
+
+ type Element = JSContactValue;
+
+ type Id = Id;
+
+ type Filter = ContactFilter;
+
+ type Comparator = ContactComparator;
+
+ type GetArguments = ();
+
+ type SetArguments<'de> = ();
+
+ type QueryArguments = ();
+
+ type CopyArguments = ();
+
+ type ParseArguments = ();
+
+ const ID_PROPERTY: Self::Property = JSContactProperty::Id;
+}
+
+impl JmapObjectId for JSContactValue {
+ fn as_id(&self) -> Option {
+ if let JSContactValue::Id(id) = self {
+ Some(*id)
+ } else {
+ None
+ }
+ }
+
+ fn as_any_id(&self) -> Option {
+ 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> {
+ None
+ }
+}
+
+impl TryFrom for JSContactValue {
+ type Error = ();
+
+ fn try_from(value: AnyId) -> Result {
+ match value {
+ AnyId::Id(id) => Ok(JSContactValue::Id(id)),
+ AnyId::BlobId(id) => Ok(JSContactValue::BlobId(id)),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ContactFilter {
+ InAddressBook(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 ContactComparator {
+ Created,
+ Updated,
+ NameGiven,
+ NameSurname,
+ NameSurname2,
+ _T(String),
+}
+
+impl<'de> DeserializeArguments<'de> for ContactFilter {
+ fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
+ where
+ A: serde::de::MapAccess<'de>,
+ {
+ hashify::fnc_map!(key.as_bytes(),
+ b"inAddressBook" => {
+ *self = ContactFilter::InAddressBook(map.next_value()?);
+ },
+ b"uid" => {
+ *self = ContactFilter::Uid(map.next_value()?);
+ },
+ b"hasMember" => {
+ *self = ContactFilter::HasMember(map.next_value()?);
+ },
+ b"kind" => {
+ *self = ContactFilter::Kind(map.next_value()?);
+ },
+ b"createdBefore" => {
+ *self = ContactFilter::CreatedBefore(map.next_value()?);
+ },
+ b"createdAfter" => {
+ *self = ContactFilter::CreatedAfter(map.next_value()?);
+ },
+ b"updatedBefore" => {
+ *self = ContactFilter::UpdatedBefore(map.next_value()?);
+ },
+ b"updatedAfter" => {
+ *self = ContactFilter::UpdatedAfter(map.next_value()?);
+ },
+ b"text" => {
+ *self = ContactFilter::Text(map.next_value()?);
+ },
+ b"name" => {
+ *self = ContactFilter::Name(map.next_value()?);
+ },
+ b"name/given" => {
+ *self = ContactFilter::NameGiven(map.next_value()?);
+ },
+ b"name/surname" => {
+ *self = ContactFilter::NameSurname(map.next_value()?);
+ },
+ b"name/surname2" => {
+ *self = ContactFilter::NameSurname2(map.next_value()?);
+ },
+ b"nickname" => {
+ *self = ContactFilter::Nickname(map.next_value()?);
+ },
+ b"organization" => {
+ *self = ContactFilter::Organization(map.next_value()?);
+ },
+ b"email" => {
+ *self = ContactFilter::Email(map.next_value()?);
+ },
+ b"phone" => {
+ *self = ContactFilter::Phone(map.next_value()?);
+ },
+ b"onlineService" => {
+ *self = ContactFilter::OnlineService(map.next_value()?);
+ },
+ b"address" => {
+ *self = ContactFilter::Address(map.next_value()?);
+ },
+ b"note" => {
+ *self = ContactFilter::Note(map.next_value()?);
+ },
+ _ => {
+ *self = ContactFilter::_T(key.to_string());
+ let _ = map.next_value::()?;
+ }
+ );
+ Ok(())
+ }
+}
+
+impl<'de> DeserializeArguments<'de> for ContactComparator {
+ fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
+ where
+ A: serde::de::MapAccess<'de>,
+ {
+ if key == "property" {
+ let value = map.next_value::>()?;
+ hashify::fnc_map!(value.as_bytes(),
+ b"created" => {
+ *self = ContactComparator::Created;
+ },
+ b"updated" => {
+ *self = ContactComparator::Updated;
+ },
+ b"name/given" => {
+ *self = ContactComparator::NameGiven;
+ },
+ b"name/surname" => {
+ *self = ContactComparator::NameSurname;
+ },
+ b"name/surname2" => {
+ *self = ContactComparator::NameSurname2;
+ },
+ _ => {
+ *self = ContactComparator::_T(value.to_string());
+ }
+ );
+ } else {
+ let _ = map.next_value::()?;
+ }
+ Ok(())
+ }
+}
+
+impl Default for ContactFilter {
+ fn default() -> Self {
+ ContactFilter::_T(String::new())
+ }
+}
+
+impl Default for ContactComparator {
+ fn default() -> Self {
+ ContactComparator::_T(String::new())
+ }
+}
diff --git a/crates/jmap-proto/src/object/email.rs b/crates/jmap-proto/src/object/email.rs
index ab284973..7ff2b680 100644
--- a/crates/jmap-proto/src/object/email.rs
+++ b/crates/jmap-proto/src/object/email.rs
@@ -396,6 +396,15 @@ pub struct EmailQueryArguments {
pub collapse_threads: Option,
}
+#[derive(Debug, Clone, Default)]
+pub struct EmailParseArguments {
+ pub body_properties: Option>>,
+ pub fetch_text_body_values: Option,
+ pub fetch_html_body_values: Option,
+ pub fetch_all_body_values: Option,
+ pub max_body_value_bytes: Option,
+}
+
impl<'de> DeserializeArguments<'de> for EmailGetArguments {
fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
where
@@ -441,6 +450,36 @@ impl<'de> DeserializeArguments<'de> for EmailQueryArguments {
}
}
+impl<'de> DeserializeArguments<'de> for EmailParseArguments {
+ fn deserialize_argument(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
+ where
+ A: serde::de::MapAccess<'de>,
+ {
+ hashify::fnc_map!(key.as_bytes(),
+ b"bodyProperties" => {
+ self.body_properties = map.next_value()?;
+ },
+ b"fetchTextBodyValues" => {
+ self.fetch_text_body_values = map.next_value()?;
+ },
+ b"fetchHTMLBodyValues" => {
+ self.fetch_html_body_values = map.next_value()?;
+ },
+ b"fetchAllBodyValues" => {
+ self.fetch_all_body_values = map.next_value()?;
+ },
+ b"maxBodyValueBytes" => {
+ self.max_body_value_bytes = map.next_value()?;
+ },
+ _ => {
+ let _ = map.next_value::()?;
+ }
+ );
+
+ Ok(())
+ }
+}
+
impl serde::Serialize for EmailProperty {
fn serialize(&self, serializer: S) -> Result
where
@@ -469,6 +508,8 @@ impl JmapObject for Email {
type CopyArguments = ();
+ type ParseArguments = EmailParseArguments;
+
const ID_PROPERTY: Self::Property = EmailProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/email_submission.rs b/crates/jmap-proto/src/object/email_submission.rs
index 1677ebc6..407708f3 100644
--- a/crates/jmap-proto/src/object/email_submission.rs
+++ b/crates/jmap-proto/src/object/email_submission.rs
@@ -330,6 +330,8 @@ impl JmapObject for EmailSubmission {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = EmailSubmissionProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/identity.rs b/crates/jmap-proto/src/object/identity.rs
index 6a4bd4ad..72840796 100644
--- a/crates/jmap-proto/src/object/identity.rs
+++ b/crates/jmap-proto/src/object/identity.rs
@@ -4,12 +4,11 @@
* 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;
-use crate::object::{AnyId, JmapObject, JmapObjectId};
-
#[derive(Debug, Clone, Default)]
pub struct Identity;
@@ -143,6 +142,8 @@ impl JmapObject for Identity {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = IdentityProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/mailbox.rs b/crates/jmap-proto/src/object/mailbox.rs
index fa20594c..92084a79 100644
--- a/crates/jmap-proto/src/object/mailbox.rs
+++ b/crates/jmap-proto/src/object/mailbox.rs
@@ -4,17 +4,15 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-use std::{borrow::Cow, str::FromStr};
-
-use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
-use types::{acl::Acl, id::Id, special_use::SpecialUse};
-
use crate::{
object::{
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
},
request::deserialize::DeserializeArguments,
};
+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;
@@ -272,6 +270,8 @@ impl JmapObject for Mailbox {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = MailboxProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/mod.rs b/crates/jmap-proto/src/object/mod.rs
index b304a7b5..5d828824 100644
--- a/crates/jmap-proto/src/object/mod.rs
+++ b/crates/jmap-proto/src/object/mod.rs
@@ -10,7 +10,9 @@ 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 contact;
pub mod email;
pub mod email_submission;
pub mod identity;
@@ -24,7 +26,7 @@ pub mod thread;
pub mod vacation_response;
pub trait JmapObject: std::fmt::Debug {
- type Property: Property + FromStr + Serialize + Debug + Sync + Send;
+ type Property: Property + FromStr + Debug + Sync + Send;
type Element: Element
+ From
+ JmapObjectId
@@ -40,6 +42,7 @@ pub trait JmapObject: std::fmt::Debug {
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;
}
@@ -156,6 +159,7 @@ impl JmapObject for NullObject {
type SetArguments<'de> = ();
type QueryArguments = ();
type CopyArguments = ();
+ type ParseArguments = ();
const ID_PROPERTY: Self::Property = Null;
}
diff --git a/crates/jmap-proto/src/object/principal.rs b/crates/jmap-proto/src/object/principal.rs
index 49566553..4bdc0297 100644
--- a/crates/jmap-proto/src/object/principal.rs
+++ b/crates/jmap-proto/src/object/principal.rs
@@ -156,6 +156,8 @@ impl JmapObject for Principal {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = PrincipalProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/push_subscription.rs b/crates/jmap-proto/src/object/push_subscription.rs
index a52a2f18..542ab2a1 100644
--- a/crates/jmap-proto/src/object/push_subscription.rs
+++ b/crates/jmap-proto/src/object/push_subscription.rs
@@ -162,6 +162,8 @@ impl JmapObject for PushSubscription {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = PushSubscriptionProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/quota.rs b/crates/jmap-proto/src/object/quota.rs
index 3cbfd8f7..8b09d038 100644
--- a/crates/jmap-proto/src/object/quota.rs
+++ b/crates/jmap-proto/src/object/quota.rs
@@ -133,6 +133,8 @@ impl JmapObject for Quota {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = QuotaProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/sieve.rs b/crates/jmap-proto/src/object/sieve.rs
index d267ec99..89271bdf 100644
--- a/crates/jmap-proto/src/object/sieve.rs
+++ b/crates/jmap-proto/src/object/sieve.rs
@@ -152,6 +152,8 @@ impl JmapObject for Sieve {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = SieveProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/thread.rs b/crates/jmap-proto/src/object/thread.rs
index c1a08d1b..bef0783c 100644
--- a/crates/jmap-proto/src/object/thread.rs
+++ b/crates/jmap-proto/src/object/thread.rs
@@ -101,6 +101,8 @@ impl JmapObject for Thread {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = ThreadProperty::Id;
}
diff --git a/crates/jmap-proto/src/object/vacation_response.rs b/crates/jmap-proto/src/object/vacation_response.rs
index 628ddf8c..a451f5d5 100644
--- a/crates/jmap-proto/src/object/vacation_response.rs
+++ b/crates/jmap-proto/src/object/vacation_response.rs
@@ -130,6 +130,8 @@ impl JmapObject for VacationResponse {
type CopyArguments = ();
+ type ParseArguments = ();
+
const ID_PROPERTY: Self::Property = VacationResponseProperty::Id;
}
diff --git a/crates/jmap-proto/src/references/eval.rs b/crates/jmap-proto/src/references/eval.rs
index f5c41d9a..85f2dbf4 100644
--- a/crates/jmap-proto/src/references/eval.rs
+++ b/crates/jmap-proto/src/references/eval.rs
@@ -57,6 +57,12 @@ impl Response<'_> {
response.eval_jptr(path, &mut results)
}
GetResponseMethod::Blob(response) => response.eval_jptr(path, &mut results),
+ GetResponseMethod::AddressBook(response) => {
+ response.eval_jptr(path, &mut results)
+ }
+ GetResponseMethod::ContactCard(response) => {
+ response.eval_jptr(path, &mut results)
+ }
},
ResponseMethod::Changes(response) => match response {
ChangesResponseMethod::Email(response) => {
@@ -77,6 +83,12 @@ impl Response<'_> {
ChangesResponseMethod::Quota(response) => {
response.eval_jptr(path, &mut results)
}
+ ChangesResponseMethod::AddressBook(response) => {
+ response.eval_jptr(path, &mut results)
+ }
+ ChangesResponseMethod::ContactCard(response) => {
+ response.eval_jptr(path, &mut results)
+ }
},
ResponseMethod::Query(response) => response.eval_jptr(path, &mut results),
ResponseMethod::QueryChanges(response) => {
diff --git a/crates/jmap-proto/src/references/jsptr.rs b/crates/jmap-proto/src/references/jsptr.rs
index b376e24b..b66b7c78 100644
--- a/crates/jmap-proto/src/references/jsptr.rs
+++ b/crates/jmap-proto/src/references/jsptr.rs
@@ -136,7 +136,7 @@ impl ResponsePtr for BlobId {
}
}
-impl ResponsePtr for PropertyWrapper {
+impl ResponsePtr for PropertyWrapper {
fn eval_jptr(&self, _: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
results.0.push(EvalResult::Property(self.0.to_cow()));
true
diff --git a/crates/jmap-proto/src/references/resolve.rs b/crates/jmap-proto/src/references/resolve.rs
index e8d48ff6..f20abbcf 100644
--- a/crates/jmap-proto/src/references/resolve.rs
+++ b/crates/jmap-proto/src/references/resolve.rs
@@ -42,6 +42,8 @@ impl Response<'_> {
GetRequestMethod::Principal(request) => request.resolve_references(self)?,
GetRequestMethod::Quota(request) => request.resolve_references(self)?,
GetRequestMethod::Blob(request) => request.resolve_references(self)?,
+ GetRequestMethod::AddressBook(request) => request.resolve_references(self)?,
+ GetRequestMethod::ContactCard(request) => request.resolve_references(self)?,
},
RequestMethod::Set(request) => match request {
SetRequestMethod::Email(request) => request.resolve_references(self)?,
@@ -51,9 +53,12 @@ impl Response<'_> {
SetRequestMethod::PushSubscription(request) => request.resolve_references(self)?,
SetRequestMethod::Sieve(request) => request.resolve_references(self)?,
SetRequestMethod::VacationResponse(request) => request.resolve_references(self)?,
+ SetRequestMethod::AddressBook(request) => request.resolve_references(self)?,
+ SetRequestMethod::ContactCard(request) => request.resolve_references(self)?,
},
RequestMethod::Copy(request) => match request {
CopyRequestMethod::Email(request) => request.resolve_references(self)?,
+ CopyRequestMethod::ContactCard(request) => request.resolve_references(self)?,
CopyRequestMethod::Blob(_) => (),
},
RequestMethod::ImportEmail(request) => request.resolve_references(self)?,
diff --git a/crates/jmap-proto/src/request/capability.rs b/crates/jmap-proto/src/request/capability.rs
index 3f0404ab..8a79b430 100644
--- a/crates/jmap-proto/src/request/capability.rs
+++ b/crates/jmap-proto/src/request/capability.rs
@@ -70,6 +70,10 @@ pub enum Capability {
Blob = 1 << 8,
#[serde(rename(serialize = "urn:ietf:params:jmap:quota"))]
Quota = 1 << 9,
+ #[serde(rename(serialize = "urn:ietf:params:jmap:principals"))]
+ Principals = 1 << 10,
+ #[serde(rename(serialize = "urn:ietf:params:jmap:principals:owner"))]
+ PrincipalsOwner = 1 << 11,
}
#[derive(Debug, Clone, Copy, Default)]
@@ -87,6 +91,9 @@ pub enum Capabilities {
SieveAccount(SieveAccountCapabilities),
SieveSession(SieveSessionCapabilities),
Blob(BlobCapabilities),
+ Contacts(ContactsCapabilities),
+ Principals(PrincipalsCapabilities),
+ PrincipalsOwner(PrincipalsOwnerCapabilities),
Empty(EmptyCapabilities),
}
@@ -178,6 +185,29 @@ pub struct BlobCapabilities {
pub supported_digest_algorithms: Vec<&'static str>,
}
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ContactsCapabilities {
+ #[serde(rename(serialize = "maxAddressBooksPerCard"))]
+ pub max_address_books_per_card: Option,
+ #[serde(rename(serialize = "mayCreateAddressBook"))]
+ pub may_create_address_book: bool,
+}
+
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct PrincipalsCapabilities {
+ #[serde(rename(serialize = "currentUserPrincipalId"))]
+ pub current_user_principal_id: Option,
+}
+
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct PrincipalsOwnerCapabilities {
+ #[serde(rename(serialize = "accountIdForPrincipal"))]
+ pub account_id_for_principal: Id,
+
+ #[serde(rename(serialize = "principalId"))]
+ pub principal_id: Id,
+}
+
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct EmptyCapabilities {}
@@ -332,6 +362,8 @@ impl Capability {
"urn:ietf:params:jmap:sieve" => Capability::Sieve,
"urn:ietf:params:jmap:blob" => Capability::Blob,
"urn:ietf:params:jmap:quota" => Capability::Quota,
+ "urn:ietf:params:jmap:principals" => Capability::Principals,
+ "urn:ietf:params:jmap:principals:owner" => Capability::PrincipalsOwner
)
}
}
diff --git a/crates/jmap-proto/src/request/method.rs b/crates/jmap-proto/src/request/method.rs
index b1c32866..284bbf0c 100644
--- a/crates/jmap-proto/src/request/method.rs
+++ b/crates/jmap-proto/src/request/method.rs
@@ -27,6 +27,8 @@ pub enum MethodObject {
SieveScript,
Principal,
Quota,
+ AddressBook,
+ ContactCard,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -122,6 +124,18 @@ impl MethodName {
(MethodFunction::Lookup, MethodObject::Blob) => "Blob/lookup",
(MethodFunction::Upload, MethodObject::Blob) => "Blob/upload",
+ (MethodFunction::Get, MethodObject::AddressBook) => "AddressBook/get",
+ (MethodFunction::Changes, MethodObject::AddressBook) => "AddressBook/changes",
+ (MethodFunction::Set, MethodObject::AddressBook) => "AddressBook/set",
+
+ (MethodFunction::Get, MethodObject::ContactCard) => "ContactCard/get",
+ (MethodFunction::Changes, MethodObject::ContactCard) => "ContactCard/changes",
+ (MethodFunction::Query, MethodObject::ContactCard) => "ContactCard/query",
+ (MethodFunction::QueryChanges, MethodObject::ContactCard) => "ContactCard/queryChanges",
+ (MethodFunction::Set, MethodObject::ContactCard) => "ContactCard/set",
+ (MethodFunction::Copy, MethodObject::ContactCard) => "ContactCard/copy",
+ (MethodFunction::Parse, MethodObject::ContactCard) => "ContactCard/parse",
+
(MethodFunction::Echo, MethodObject::Core) => "Core/echo",
_ => "error",
}
@@ -184,6 +198,18 @@ impl MethodName {
"Blob/lookup" => (MethodObject::Blob, MethodFunction::Lookup),
"Blob/upload" => (MethodObject::Blob, MethodFunction::Upload),
+ "AddressBook/get" => (MethodObject::AddressBook, MethodFunction::Get),
+ "AddressBook/changes" => (MethodObject::AddressBook, MethodFunction::Changes),
+ "AddressBook/set" => (MethodObject::AddressBook, MethodFunction::Set),
+
+ "ContactCard/get" => (MethodObject::ContactCard, MethodFunction::Get),
+ "ContactCard/changes" => (MethodObject::ContactCard, MethodFunction::Changes),
+ "ContactCard/query" => (MethodObject::ContactCard, MethodFunction::Query),
+ "ContactCard/queryChanges" => (MethodObject::ContactCard, MethodFunction::QueryChanges),
+ "ContactCard/set" => (MethodObject::ContactCard, MethodFunction::Set),
+ "ContactCard/copy" => (MethodObject::ContactCard, MethodFunction::Copy),
+ "ContactCard/parse" => (MethodObject::ContactCard, MethodFunction::Parse),
+
"Core/echo" => (MethodObject::Core, MethodFunction::Echo),
).map(|(obj, fnc)| MethodName { obj, fnc })
@@ -207,6 +233,8 @@ impl Display for MethodObject {
MethodObject::Thread => "Thread",
MethodObject::Email => "Email",
MethodObject::Quota => "Quota",
+ MethodObject::AddressBook => "AddressBook",
+ MethodObject::ContactCard => "ContactCard",
})
}
}
diff --git a/crates/jmap-proto/src/request/mod.rs b/crates/jmap-proto/src/request/mod.rs
index fe2d791a..23344fac 100644
--- a/crates/jmap-proto/src/request/mod.rs
+++ b/crates/jmap-proto/src/request/mod.rs
@@ -19,7 +19,7 @@ use crate::{
get::GetRequest,
import::ImportEmailRequest,
lookup::BlobLookupRequest,
- parse::ParseEmailRequest,
+ parse::ParseRequest,
query::QueryRequest,
query_changes::QueryChangesRequest,
search_snippet::GetSearchSnippetRequest,
@@ -28,9 +28,10 @@ use crate::{
validate::ValidateSieveScriptRequest,
},
object::{
- AnyId, blob::Blob, email::Email, email_submission::EmailSubmission, identity::Identity,
- mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, quota::Quota,
- sieve::Sieve, thread::Thread, vacation_response::VacationResponse,
+ AnyId, addressbook::AddressBook, blob::Blob, contact::ContactCard, email::Email,
+ email_submission::EmailSubmission, identity::Identity, mailbox::Mailbox,
+ principal::Principal, push_subscription::PushSubscription, quota::Quota, sieve::Sieve,
+ thread::Thread, vacation_response::VacationResponse,
},
request::{capability::CapabilityIds, reference::MaybeIdReference},
};
@@ -59,7 +60,7 @@ pub enum RequestMethod<'x> {
Changes(ChangesRequest),
Copy(CopyRequestMethod<'x>),
ImportEmail(ImportEmailRequest),
- ParseEmail(ParseEmailRequest),
+ Parse(ParseRequestMethod),
Query(QueryRequestMethod),
QueryChanges(QueryChangesRequestMethod),
SearchSnippet(GetSearchSnippetRequest),
@@ -83,6 +84,8 @@ pub enum GetRequestMethod {
Principal(GetRequest),
Quota(GetRequest),
Blob(GetRequest),
+ AddressBook(GetRequest),
+ ContactCard(GetRequest),
}
#[derive(Debug)]
@@ -94,11 +97,14 @@ pub enum SetRequestMethod<'x> {
PushSubscription(SetRequest<'x, PushSubscription>),
Sieve(SetRequest<'x, Sieve>),
VacationResponse(SetRequest<'x, VacationResponse>),
+ AddressBook(SetRequest<'x, AddressBook>),
+ ContactCard(SetRequest<'x, ContactCard>),
}
#[derive(Debug)]
pub enum CopyRequestMethod<'x> {
Email(CopyRequest<'x, Email>),
+ ContactCard(CopyRequest<'x, ContactCard>),
Blob(CopyBlobRequest),
}
@@ -110,6 +116,7 @@ pub enum QueryRequestMethod {
Sieve(QueryRequest),
Principal(QueryRequest),
Quota(QueryRequest),
+ ContactCard(QueryRequest),
}
#[derive(Debug)]
@@ -120,6 +127,13 @@ pub enum QueryChangesRequestMethod {
Sieve(QueryChangesRequest),
Principal(QueryChangesRequest),
Quota(QueryChangesRequest),
+ ContactCard(QueryChangesRequest),
+}
+
+#[derive(Debug)]
+pub enum ParseRequestMethod {
+ Email(ParseRequest),
+ ContactCard(ParseRequest),
}
#[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/crates/jmap-proto/src/request/parser.rs b/crates/jmap-proto/src/request/parser.rs
index f96a6933..8502afa4 100644
--- a/crates/jmap-proto/src/request/parser.rs
+++ b/crates/jmap-proto/src/request/parser.rs
@@ -9,8 +9,8 @@ use super::{
method::{MethodFunction, MethodName, MethodObject},
};
use crate::request::{
- CopyRequestMethod, GetRequestMethod, QueryChangesRequestMethod, QueryRequestMethod,
- SetRequestMethod,
+ CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod,
+ QueryRequestMethod, SetRequestMethod,
deserialize::{DeserializeArguments, deserialize_request},
};
use serde::{
@@ -181,6 +181,20 @@ impl<'de> Visitor<'de> for CallVisitor {
return Err(de::Error::invalid_length(1, &self));
}
},
+ (MethodFunction::Get, MethodObject::AddressBook) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AddressBook(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
+ (MethodFunction::Get, MethodObject::ContactCard) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ContactCard(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
(MethodFunction::Get, MethodObject::SearchSnippet) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::SearchSnippet(value),
Err(err) => RequestMethod::invalid(err),
@@ -237,6 +251,20 @@ impl<'de> Visitor<'de> for CallVisitor {
return Err(de::Error::invalid_length(1, &self));
}
},
+ (MethodFunction::Set, MethodObject::AddressBook) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AddressBook(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
+ (MethodFunction::Set, MethodObject::ContactCard) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ContactCard(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
(MethodFunction::Query, MethodObject::Email) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Email(value)),
Err(err) => RequestMethod::invalid(err),
@@ -279,6 +307,13 @@ impl<'de> Visitor<'de> for CallVisitor {
return Err(de::Error::invalid_length(1, &self));
}
},
+ (MethodFunction::Query, MethodObject::ContactCard) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::ContactCard(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
(MethodFunction::QueryChanges, MethodObject::Email) => match seq.next_element() {
Ok(Some(value)) => {
RequestMethod::QueryChanges(QueryChangesRequestMethod::Email(value))
@@ -335,6 +370,15 @@ impl<'de> Visitor<'de> for CallVisitor {
return Err(de::Error::invalid_length(1, &self));
}
},
+ (MethodFunction::QueryChanges, MethodObject::ContactCard) => match seq.next_element() {
+ Ok(Some(value)) => {
+ RequestMethod::QueryChanges(QueryChangesRequestMethod::ContactCard(value))
+ }
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
(MethodFunction::Changes, _) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Changes(value),
Err(err) => RequestMethod::invalid(err),
@@ -356,6 +400,13 @@ impl<'de> Visitor<'de> for CallVisitor {
return Err(de::Error::invalid_length(1, &self));
}
},
+ (MethodFunction::Copy, MethodObject::ContactCard) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::ContactCard(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
(MethodFunction::Lookup, MethodObject::Blob) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::LookupBlob(value),
Err(err) => RequestMethod::invalid(err),
@@ -378,7 +429,14 @@ impl<'de> Visitor<'de> for CallVisitor {
}
},
(MethodFunction::Parse, MethodObject::Email) => match seq.next_element() {
- Ok(Some(value)) => RequestMethod::ParseEmail(value),
+ Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::Email(value)),
+ Err(err) => RequestMethod::invalid(err),
+ Ok(None) => {
+ return Err(de::Error::invalid_length(1, &self));
+ }
+ },
+ (MethodFunction::Parse, MethodObject::ContactCard) => match seq.next_element() {
+ Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::ContactCard(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));
diff --git a/crates/jmap-proto/src/response/mod.rs b/crates/jmap-proto/src/response/mod.rs
index a52c9bff..b73dfd71 100644
--- a/crates/jmap-proto/src/response/mod.rs
+++ b/crates/jmap-proto/src/response/mod.rs
@@ -16,7 +16,7 @@ use crate::{
get::GetResponse,
import::ImportEmailResponse,
lookup::BlobLookupResponse,
- parse::ParseEmailResponse,
+ parse::ParseResponse,
query::QueryResponse,
query_changes::QueryChangesResponse,
search_snippet::GetSearchSnippetResponse,
@@ -25,9 +25,10 @@ use crate::{
validate::ValidateSieveScriptResponse,
},
object::{
- AnyId, blob::Blob, email::Email, email_submission::EmailSubmission, identity::Identity,
- mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, quota::Quota,
- sieve::Sieve, thread::Thread, vacation_response::VacationResponse,
+ AnyId, addressbook::AddressBook, blob::Blob, contact::ContactCard, email::Email,
+ email_submission::EmailSubmission, identity::Identity, mailbox::Mailbox,
+ principal::Principal, push_subscription::PushSubscription, quota::Quota, sieve::Sieve,
+ thread::Thread, vacation_response::VacationResponse,
},
request::{Call, method::MethodName},
};
@@ -42,7 +43,7 @@ pub enum ResponseMethod<'x> {
Changes(ChangesResponseMethod),
Copy(CopyResponseMethod),
ImportEmail(ImportEmailResponse),
- ParseEmail(ParseEmailResponse),
+ Parse(ParseResponseMethod),
QueryChanges(QueryChangesResponse),
Query(QueryResponse),
SearchSnippet(GetSearchSnippetResponse),
@@ -67,6 +68,8 @@ pub enum GetResponseMethod {
Principal(GetResponse),
Quota(GetResponse),
Blob(GetResponse),
+ AddressBook(GetResponse),
+ ContactCard(GetResponse),
}
#[derive(Debug, serde::Serialize)]
@@ -79,6 +82,8 @@ pub enum SetResponseMethod {
PushSubscription(SetResponse),
Sieve(SetResponse),
VacationResponse(SetResponse),
+ AddressBook(SetResponse),
+ ContactCard(SetResponse),
}
#[derive(Debug, serde::Serialize)]
@@ -90,15 +95,25 @@ pub enum ChangesResponseMethod {
Identity(ChangesResponse),
EmailSubmission(ChangesResponse),
Quota(ChangesResponse),
+ AddressBook(ChangesResponse),
+ ContactCard(ChangesResponse),
}
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum CopyResponseMethod {
Email(CopyResponse),
+ ContactCard(CopyResponse),
Blob(CopyBlobResponse),
}
+#[derive(Debug, serde::Serialize)]
+#[serde(untagged)]
+pub enum ParseResponseMethod {
+ Email(ParseResponse),
+ ContactCard(ParseResponse),
+}
+
#[derive(Debug, serde::Serialize)]
pub struct Response<'x> {
#[serde(rename = "methodResponses")]
@@ -229,7 +244,18 @@ impl<'x> From> for ResponseMethod<'x> {
}
}
-// Direct SetResponse conversions to ResponseMethod
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: GetResponse) -> Self {
+ ResponseMethod::Get(GetResponseMethod::ContactCard(value))
+ }
+}
+
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: GetResponse) -> Self {
+ ResponseMethod::Get(GetResponseMethod::AddressBook(value))
+ }
+}
+
impl<'x> From> for ResponseMethod<'x> {
fn from(value: SetResponse) -> Self {
ResponseMethod::Set(SetResponseMethod::Email(value))
@@ -272,7 +298,18 @@ impl<'x> From> for ResponseMethod<'x> {
}
}
-// Direct ChangesResponse conversions to ResponseMethod
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: SetResponse) -> Self {
+ ResponseMethod::Set(SetResponseMethod::AddressBook(value))
+ }
+}
+
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: SetResponse) -> Self {
+ ResponseMethod::Set(SetResponseMethod::ContactCard(value))
+ }
+}
+
impl<'x> From> for ResponseMethod<'x> {
fn from(value: ChangesResponse) -> Self {
ResponseMethod::Changes(ChangesResponseMethod::Email(value))
@@ -309,7 +346,12 @@ impl<'x> From> for ResponseMethod<'x> {
}
}
-// Direct CopyResponse conversions to ResponseMethod
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: ChangesResponse) -> Self {
+ ResponseMethod::Changes(ChangesResponseMethod::AddressBook(value))
+ }
+}
+
impl<'x> From> for ResponseMethod<'x> {
fn from(value: CopyResponse) -> Self {
ResponseMethod::Copy(CopyResponseMethod::Email(value))
@@ -322,16 +364,27 @@ impl<'x> From for ResponseMethod<'x> {
}
}
-// Other direct conversions
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: CopyResponse) -> Self {
+ ResponseMethod::Copy(CopyResponseMethod::ContactCard(value))
+ }
+}
+
impl<'x> From for ResponseMethod<'x> {
fn from(value: ImportEmailResponse) -> Self {
ResponseMethod::ImportEmail(value)
}
}
-impl<'x> From for ResponseMethod<'x> {
- fn from(value: ParseEmailResponse) -> Self {
- ResponseMethod::ParseEmail(value)
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: ParseResponse) -> Self {
+ ResponseMethod::Parse(ParseResponseMethod::Email(value))
+ }
+}
+
+impl<'x> From> for ResponseMethod<'x> {
+ fn from(value: ParseResponse) -> Self {
+ ResponseMethod::Parse(ParseResponseMethod::ContactCard(value))
}
}
diff --git a/crates/jmap/src/addressbook/get.rs b/crates/jmap/src/addressbook/get.rs
new file mode 100644
index 00000000..42f25fec
--- /dev/null
+++ b/crates/jmap/src/addressbook/get.rs
@@ -0,0 +1,29 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
+ */
+
+use common::{Server, auth::AccessToken};
+use jmap_proto::{
+ method::get::{GetRequest, GetResponse},
+ object::addressbook::AddressBook,
+};
+
+pub trait AddressBookGet: Sync + Send {
+ fn address_book_get(
+ &self,
+ request: GetRequest,
+ access_token: &AccessToken,
+ ) -> impl Future