JMAP protocol layer refactoring (passing tests)

This commit is contained in:
mdecimus 2025-09-29 18:41:16 +02:00
parent c7c996d738
commit c8e0a1b2cb
24 changed files with 528 additions and 553 deletions

778
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -147,7 +147,7 @@ pub async fn fetch_mailboxes(
mailbox::Property::ParentId,
mailbox::Property::Role,
mailbox::Property::SortOrder,
mailbox::Property::ACL,
mailbox::Property::ShareWith,
]);
let mut response = request

View file

@ -527,9 +527,9 @@ async fn import_mailboxes(
if mailbox.sort_order() > 0 {
create_request.sort_order(mailbox.sort_order());
}
if let Some(acls) = mailbox.acl() {
/*if let Some(acls) = mailbox.acl() {
create_request.acls(acls.clone().into_iter());
}
}*/
if mailbox.is_subscribed() {
create_request.is_subscribed(true);
}

View file

@ -9,7 +9,7 @@ store = { path = "../store" }
utils = { path = "../utils" }
types = { path = "../types" }
trc = { path = "../trc" }
jmap-tools = { path = "/Users/me/code/jmap-tool" }
jmap-tools = { version = "0.1" }
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
serde = { version = "1.0", features = ["derive"]}
ahash = { version = "0.8.2", features = ["serde"] }

View file

@ -54,7 +54,7 @@ impl<'de> DeserializeArguments<'de> for ChangesRequest {
b"accountId" => {
self.account_id = map.next_value()?;
},
b"sinceQueryState" => {
b"sinceState" => {
self.since_state = map.next_value()?;
},
b"maxChanges" => {

View file

@ -6,6 +6,7 @@
use crate::{
error::set::SetError,
method::JmapDict,
object::{
AnyId,
email::{EmailProperty, EmailValue},
@ -93,13 +94,13 @@ impl<'de> DeserializeArguments<'de> for ImportEmail {
self.blob_id = map.next_value()?;
},
b"keywords" => {
self.keywords = map.next_value()?;
self.keywords = map.next_value::<JmapDict<Keyword>>()?.0;
},
b"receivedAt" => {
self.received_at = map.next_value()?;
},
b"mailboxIds" => {
self.mailbox_ids = MaybeResultReference::Value(map.next_value::<Vec<MaybeIdReference<Id>>>()?);
self.mailbox_ids = MaybeResultReference::Value(map.next_value::<JmapDict<MaybeIdReference<Id>>>()?.0);
},
b"#mailboxIds" => {
self.mailbox_ids = MaybeResultReference::Reference(map.next_value::<ResultReference>()?);

View file

@ -6,6 +6,11 @@
use ahash::AHashMap;
use jmap_tools::Property;
use serde::{
Deserialize, Deserializer,
de::{self, MapAccess, Visitor},
};
use std::{borrow::Cow, fmt, str::FromStr};
pub mod changes;
pub mod copy;
@ -37,3 +42,44 @@ impl<T: Property + serde::Serialize> From<T> for PropertyWrapper<T> {
Self(value)
}
}
pub(crate) struct JmapDict<T: FromStr>(pub Vec<T>);
struct JmapDictVisitor<'de, T: FromStr> {
marker: std::marker::PhantomData<&'de T>,
}
impl<'de, T: FromStr> Visitor<'de> for JmapDictVisitor<'de, T> {
type Value = JmapDict<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut vec = Vec::with_capacity(3);
while let Some(key) = access.next_key::<Cow<'de, str>>()? {
let key = T::from_str(&key).map_err(|_| de::Error::custom("invalid dictionary key"))?;
if access.next_value::<Option<bool>>()?.unwrap_or(false) {
vec.push(key);
}
}
Ok(JmapDict(vec))
}
}
impl<'de, T: FromStr + 'static> Deserialize<'de> for JmapDict<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(JmapDictVisitor {
marker: std::marker::PhantomData,
})
}
}

View file

@ -112,6 +112,7 @@ impl Property for EmailProperty {
if let Some(Key::Property(key)) = key {
match key.patch_or_prop() {
EmailProperty::Keywords => EmailProperty::Keyword(Keyword::parse(value)).into(),
EmailProperty::MailboxIds => Id::from_str(value).ok().map(EmailProperty::IdValue),
_ => EmailProperty::parse(value, allow_patch),
}
} else {

View file

@ -114,13 +114,14 @@ impl Element for EmailSubmissionValue {
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 => {
match parse_ref(value) {
MaybeReference::Value(v) => Some(EmailSubmissionValue::Id(v)),
MaybeReference::Reference(v) => Some(EmailSubmissionValue::IdReference(v)),
MaybeReference::ParseError => None,
}
}
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)),

View file

@ -51,6 +51,7 @@ pub enum MailboxRight {
MayRename,
MaySubmit,
MayDelete,
MayShare,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -62,10 +63,16 @@ pub enum MailboxValue {
impl Property for MailboxProperty {
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
if let Some(Key::Property(MailboxProperty::ShareWith)) = key {
Id::from_str(value).ok().map(MailboxProperty::IdValue)
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, key.is_none())
MailboxProperty::parse(value, allow_patch)
}
}
@ -103,6 +110,7 @@ impl MailboxRight {
MailboxRight::MayRename => "mayRename",
MailboxRight::MaySubmit => "maySubmit",
MailboxRight::MayDelete => "mayDelete",
MailboxRight::MayShare => "mayShare",
}
}
}
@ -158,6 +166,7 @@ impl MailboxProperty {
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(|| {
@ -460,21 +469,23 @@ impl JmapRight for MailboxRight {
Acl::Modify => &[MailboxRight::MayRename],
Acl::Submit => &[MailboxRight::MaySubmit],
Acl::Delete => &[MailboxRight::MayDelete],
Acl::Administer => &[MailboxRight::MayShare],
_ => &[],
}
}
fn to_acl(&self) -> Acl {
fn to_acl(&self) -> &'static [Acl] {
match self {
MailboxRight::MayReadItems => 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::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::Administer],
}
}
@ -489,6 +500,7 @@ impl JmapRight for MailboxRight {
MailboxRight::MayRename,
MailboxRight::MaySubmit,
MailboxRight::MayDelete,
MailboxRight::MayShare,
]
}
}

View file

@ -45,7 +45,7 @@ pub trait JmapObject: std::fmt::Debug {
}
pub trait JmapSharedObject: JmapObject {
type Right: JmapRight + Into<Self::Property> + Debug + Sync + Send;
type Right: JmapRight + Into<Self::Property> + Debug + Clone + Copy + Sync + Send;
const SHARE_WITH_PROPERTY: Self::Property;
}
@ -53,7 +53,7 @@ pub trait JmapSharedObject: JmapObject {
pub trait JmapRight: Clone + Copy + Sized + 'static {
fn from_acl(acl: Acl) -> &'static [Self];
fn all_rights() -> &'static [Self];
fn to_acl(&self) -> Acl;
fn to_acl(&self) -> &'static [Acl];
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@ -169,7 +169,7 @@ impl JmapRight for Null {
unreachable!()
}
fn to_acl(&self) -> Acl {
fn to_acl(&self) -> &'static [Acl] {
unreachable!()
}
}

View file

@ -23,7 +23,7 @@ use crate::{
response::Response,
};
use compact_str::format_compact;
use jmap_tools::{Element, Property, Value};
use jmap_tools::{Element, Key, Property, Value};
use std::collections::HashMap;
use types::id::Id;
@ -310,6 +310,8 @@ where
fn get_created_id(&self, id_ref: &str) -> Option<AnyId> {
self.created
.get(id_ref)
.and_then(|v| v.as_object())
.and_then(|v| v.get(&Key::Property(T::ID_PROPERTY)))
.and_then(|v| v.as_element())
.and_then(|v| v.as_any_id())
}

View file

@ -175,7 +175,7 @@ impl<'de> Visitor<'de> for CallVisitor {
}
},
(MethodFunction::Get, MethodObject::Blob) => match seq.next_element() {
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Email(value)),
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::Blob(value)),
Err(err) => RequestMethod::invalid(err),
Ok(None) => {
return Err(de::Error::invalid_length(1, &self));

View file

@ -6,7 +6,7 @@
use super::method::MethodName;
use jmap_tools::{JsonPointer, Null};
use std::{fmt::Display, str::FromStr};
use std::{borrow::Cow, fmt::Display, str::FromStr};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ResultReference {
@ -54,17 +54,34 @@ impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeIdReference<V> {
where
D: serde::Deserializer<'de>,
{
let value = <&str>::deserialize(deserializer)?;
let value = <Cow<'de, str>>::deserialize(deserializer)?;
if let Some(reference) = value.strip_prefix('#') {
if reference.is_empty() {
return Ok(MaybeIdReference::Invalid(value.to_string()));
return Ok(MaybeIdReference::Invalid(value.into_owned()));
}
Ok(MaybeIdReference::Reference(reference.to_string()))
} else if let Ok(id) = V::from_str(value) {
} else if let Ok(id) = V::from_str(value.as_ref()) {
Ok(MaybeIdReference::Id(id))
} else {
Ok(MaybeIdReference::Invalid(value.to_string()))
Ok(MaybeIdReference::Invalid(value.into_owned()))
}
}
}
impl<V: FromStr> FromStr for MaybeIdReference<V> {
type Err = V::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(reference) = s.strip_prefix('#') {
if reference.is_empty() {
return Ok(MaybeIdReference::Invalid(s.to_string()));
}
Ok(MaybeIdReference::Reference(reference.to_string()))
} else if let Ok(id) = V::from_str(s) {
Ok(MaybeIdReference::Id(id))
} else {
Ok(MaybeIdReference::Invalid(s.to_string()))
}
}
}

View file

@ -168,7 +168,7 @@ impl<'de> Visitor<'de> for WebSocketMessageVisitor {
message_type = MessageType::parse(map.next_value()?);
},
b"dataTypes" => {
push_enable.data_types = map.next_value()?;
push_enable.data_types = map.next_value::<Option<Vec<DataType>>>()?.unwrap_or_default();
found_push_keys = true;
},
b"pushState" => {

View file

@ -24,7 +24,7 @@ mail-builder = { version = "0.4" }
mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] }
mail-auth = { version = "0.7.1", features = ["generate"] }
sieve-rs = { version = "0.7", features = ["rkyv"] }
jmap-tools = { path = "/Users/me/code/jmap-tool", features = ["rkyv"] }
jmap-tools = { version = "0.1", features = ["rkyv"] }
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
hyper = { version = "1.0.1", features = ["server", "http1", "http2"] }

View file

@ -96,15 +96,20 @@ impl JmapRights {
.ok_or_else(|| {
SetError::invalid_properties()
.with_property(T::SHARE_WITH_PROPERTY)
.with_description("Invalid permission.")
.with_description(format!(
"Invalid permission {:?}.",
right.to_cow().unwrap_or_default()
))
})?
.to_acl();
.to_acl()
.iter()
.copied();
if let Some(acl_item) = grants.iter_mut().find(|item| item.account_id == account_id) {
if is_set {
acl_item.grants.insert(acl);
acl_item.grants.insert_many(acl);
} else {
acl_item.grants.remove(acl);
acl_item.grants.insert_many(acl);
if acl_item.grants.is_empty() {
grants.retain(|item| item.account_id != account_id);
}
@ -112,7 +117,7 @@ impl JmapRights {
} else if is_set {
grants.push(AclGrant {
account_id,
grants: Bitmap::from_iter([acl]),
grants: Bitmap::from_iter(acl),
});
}
} else {
@ -145,15 +150,17 @@ impl JmapRights {
let mut acls = Bitmap::new();
for key in value.into_expanded_boolean_set() {
acls.insert(
key.try_into_property()
.and_then(|p| T::Right::try_from(p).ok())
acls.insert_many(
key.as_property()
.and_then(|p| T::Right::try_from(p.clone()).ok())
.ok_or_else(|| {
SetError::invalid_properties()
.with_property(T::SHARE_WITH_PROPERTY)
.with_description("Invalid permission.")
.with_description(format!("Invalid permission {:?}.", key.to_string()))
})?
.to_acl(),
.to_acl()
.iter()
.copied(),
);
}

View file

@ -177,7 +177,12 @@ impl IntoForm for HeaderValue<'_> {
(
HeaderValue::Address(mail_parser::Address::Group(grouplist)),
HeaderForm::Addresses,
) => from_mail_grouplist(grouplist),
) => Value::Array(
grouplist
.into_iter()
.flat_map(|group| group.addresses.into_iter().map(from_mail_addr))
.collect(),
),
(
HeaderValue::Address(mail_parser::Address::List(addrlist)),
HeaderForm::GroupedAddresses,
@ -190,7 +195,12 @@ impl IntoForm for HeaderValue<'_> {
(
HeaderValue::Address(mail_parser::Address::Group(grouplist)),
HeaderForm::GroupedAddresses,
) => from_mail_grouplist(grouplist),
) => Value::Array(
grouplist
.into_iter()
.map(from_mail_group)
.collect::<Vec<Value<'static, EmailProperty, EmailValue>>>(),
),
_ => Value::Null,
}
@ -513,12 +523,3 @@ fn from_mail_addrlist(addrlist: Vec<Addr<'_>>) -> Value<'static, EmailProperty,
.collect::<Vec<Value<'static, EmailProperty, EmailValue>>>(),
)
}
fn from_mail_grouplist(grouplist: Vec<Group<'_>>) -> Value<'static, EmailProperty, EmailValue> {
Value::Array(
grouplist
.into_iter()
.map(from_mail_group)
.collect::<Vec<Value<'static, EmailProperty, EmailValue>>>(),
)
}

View file

@ -7,7 +7,7 @@ resolver = "2"
[dependencies]
utils = { path = "../utils" }
trc = { path = "../trc" }
jmap-tools = { path = "/Users/me/code/jmap-tool" }
jmap-tools = { version = "0.1" }
hashify = "0.2"
serde = { version = "1.0", features = ["derive"]}
rkyv = { version = "0.8.10", features = ["little_endian"] }

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};
use jmap_tools::{Element, Property, Value};
@ -226,6 +226,14 @@ impl From<Keyword> for Vec<u8> {
}
}
impl FromStr for Keyword {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Keyword::parse(s))
}
}
impl ArchivedKeyword {
pub fn id(&self) -> Result<u32, &str> {
match self {

View file

@ -69,6 +69,12 @@ impl<T: BitmapItem> Bitmap<T> {
self.bitmap |= 1 << item.into();
}
pub fn insert_many(&mut self, items: impl IntoIterator<Item = T>) {
for item in items.into_iter() {
self.insert(item);
}
}
#[inline(always)]
pub fn with_item(mut self, item: T) -> Self {
self.insert(item);

View file

@ -172,7 +172,7 @@ pub async fn test(params: &mut JMAPTest) {
// Jane grants Inbox ReadItems access to John
jane_client
.mailbox_update_acl(&inbox_id, "jdoe@example.com", [ACL::ReadItems])
.mailbox_update_acl(&inbox_id, &john_id.to_string(), [ACL::ReadItems])
.await
.unwrap();
@ -256,15 +256,9 @@ pub async fn test(params: &mut JMAPTest) {
.await,
);
// John only has ReadItems access to Inbox but no Read access
assert_forbidden(
john_client
.set_default_account_id(jane_id.to_string())
.mailbox_get(&inbox_id, [mailbox::Property::MyRights].into())
.await,
);
// John only has ReadItems access to Inbox
jane_client
.mailbox_update_acl(&inbox_id, "jdoe@example.com", [ACL::Read, ACL::ReadItems])
.mailbox_update_acl(&inbox_id, &john_id.to_string(), [ACL::ReadItems])
.await
.unwrap();
assert_eq!(
@ -331,8 +325,8 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&inbox_id,
"jdoe@example.com",
[ACL::Read, ACL::ReadItems, ACL::AddItems],
&john_id.to_string(),
[ACL::ReadItems, ACL::AddItems],
)
.await
.unwrap();
@ -396,8 +390,8 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&inbox_id,
"jdoe@example.com",
[ACL::Read, ACL::ReadItems, ACL::AddItems, ACL::RemoveItems],
&john_id.to_string(),
[ACL::ReadItems, ACL::AddItems, ACL::RemoveItems],
)
.await
.unwrap();
@ -417,13 +411,12 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&inbox_id,
"jdoe@example.com",
&john_id.to_string(),
[
ACL::Read,
ACL::ReadItems,
ACL::AddItems,
ACL::RemoveItems,
ACL::ModifyItems,
ACL::SetKeywords,
],
)
.await
@ -449,13 +442,12 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&inbox_id,
"jdoe@example.com",
&john_id.to_string(),
[
ACL::Read,
ACL::ReadItems,
ACL::AddItems,
ACL::RemoveItems,
ACL::ModifyItems,
ACL::SetKeywords,
ACL::CreateChild,
],
)
@ -478,8 +470,8 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&mailbox_id,
"jdoe@example.com",
[ACL::Read, ACL::ReadItems, ACL::Modify],
&john_id.to_string(),
[ACL::ReadItems, ACL::Rename],
)
.await
.unwrap();
@ -499,8 +491,8 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&mailbox_id,
"jdoe@example.com",
[ACL::Read, ACL::ReadItems, ACL::Modify, ACL::AddItems],
&john_id.to_string(),
[ACL::ReadItems, ACL::Rename, ACL::AddItems],
)
.await
.unwrap();
@ -520,14 +512,8 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&mailbox_id,
"jdoe@example.com",
[
ACL::Read,
ACL::ReadItems,
ACL::Modify,
ACL::AddItems,
ACL::Delete,
],
&john_id.to_string(),
[ACL::ReadItems, ACL::Rename, ACL::AddItems, ACL::Delete],
)
.await
.unwrap();
@ -540,11 +526,10 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&mailbox_id,
"jdoe@example.com",
&john_id.to_string(),
[
ACL::Read,
ACL::ReadItems,
ACL::Modify,
ACL::Rename,
ACL::AddItems,
ACL::Delete,
ACL::RemoveItems,
@ -562,7 +547,7 @@ pub async fn test(params: &mut JMAPTest) {
assert_forbidden(
john_client
.set_default_account_id(jane_id.to_string())
.mailbox_update_acl(&inbox_id, "bill@example.com", [ACL::Read, ACL::ReadItems])
.mailbox_update_acl(&inbox_id, &bill_id.to_string(), [ACL::ReadItems])
.await,
);
assert_forbidden(
@ -574,15 +559,14 @@ pub async fn test(params: &mut JMAPTest) {
jane_client
.mailbox_update_acl(
&inbox_id,
"jdoe@example.com",
&john_id.to_string(),
[
ACL::Read,
ACL::ReadItems,
ACL::AddItems,
ACL::RemoveItems,
ACL::ModifyItems,
ACL::SetKeywords,
ACL::CreateChild,
ACL::Modify,
ACL::Rename,
ACL::Administer,
],
)
@ -602,14 +586,15 @@ pub async fn test(params: &mut JMAPTest) {
ACL::ReadItems,
ACL::AddItems,
ACL::RemoveItems,
ACL::ModifyItems,
ACL::SetSeen,
ACL::SetKeywords,
ACL::CreateChild,
ACL::Modify
ACL::Rename
]
);
john_client
.set_default_account_id(jane_id.to_string())
.mailbox_update_acl(&inbox_id, "bill@example.com", [ACL::Read, ACL::ReadItems])
.mailbox_update_acl(&inbox_id, &bill_id.to_string(), [ACL::ReadItems])
.await
.unwrap();
assert_eq!(
@ -630,7 +615,7 @@ pub async fn test(params: &mut JMAPTest) {
// Revoke all access to John
jane_client
.mailbox_update_acl(&inbox_id, "jdoe@example.com", [])
.mailbox_update_acl(&inbox_id, &john_id.to_string(), [])
.await
.unwrap();
assert_forbidden(

View file

@ -175,7 +175,8 @@ pub async fn test(params: &mut JMAPTest) {
})
.unwrap_or_default(),
expected,
"Pointer {pointer:?} Response: {response:?}",
"Pointer {pointer:?} Response: {}",
serde_json::to_string_pretty(&response).unwrap()
);
}

View file

@ -109,7 +109,7 @@ async fn jmap_tests() {
vacation_response::test(&mut params).await;
email_submission::test(&mut params).await;
websocket::test(&mut params).await;
quota::test(&mut params).await;
quota::test(&mut params).await;*
crypto::test(&mut params).await;
blob::test(&mut params).await;*/
permissions::test(&params).await;
@ -991,6 +991,7 @@ type = "console"
level = "{LEVEL}"
multiline = false
ansi = true
#disabled-events = ["network.*", "telemetry.webhook-error"]
disabled-events = ["network.*", "telemetry.webhook-error", "http.request-body"]
[webhook."test"]